3 * FusionForge Git plugin
5 * Copyright 2009, Roland Mas
6 * Copyright 2009, Mehdi Dogguy <mehdi@debian.org>
7 * Copyright 2013, Thorsten Glaser <t.glaser@tarent.de>
8 * Copyright 2012-2014,2016-2018, Franck Villaume - TrivialDev
9 * http://fusionforge.org
11 * This file is part of FusionForge.
13 * FusionForge is free software; you can redistribute it and/or modify
14 * it under the terms of the GNU General Public License as published
15 * by the Free Software Foundation; either version 2 of the License,
16 * or (at your option) any later version.
18 * FusionForge is distributed in the hope that it will be useful, but
19 * WITHOUT ANY WARRANTY; without even the implied warranty of
20 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
21 * General Public License for more details.
23 * You should have received a copy of the GNU General Public License along
24 * with this program; if not, write to the Free Software Foundation, Inc.,
25 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
28 require_once $gfcommon.'include/plugins_utils.php';
30 forge_define_config_item('default_server', 'scmgit', forge_get_config('scm_host'));
31 forge_define_config_item('repos_path', 'scmgit', forge_get_config('chroot').'/scmrepos/git');
32 forge_define_config_item('use_ssh', 'scmgit', false);
33 forge_set_config_item_bool('use_ssh', 'scmgit');
34 forge_define_config_item('use_ssl', 'scmgit', true);
35 forge_set_config_item_bool('use_ssl', 'scmgit');
36 forge_define_config_item('ssh_port', 'core', 22);
38 class GitPlugin extends SCMPlugin {
39 function __construct() {
40 parent::__construct();
41 $this->name = 'scmgit';
42 $this->text = _('Git');
44 _("This plugin contains the Git subsystem of FusionForge. It allows each
45 FusionForge project to have its own Git repository, and gives some
46 control over it to the project's administrator.");
47 $this->_addHook('scm_browser_page');
48 $this->_addHook('scm_update_repolist');
49 $this->_addHook('scm_generate_snapshots');
50 $this->_addHook('scm_gather_stats');
51 $this->_addHook('scm_admin_form');
52 $this->_addHook('scm_add_repo');
53 $this->_addHook('scm_delete_repo');
54 $this->_addHook('get_scm_repo_list');
55 $this->_addHook('get_scm_repo_info');
56 $this->_addHook('parse_scm_repo_activities');
57 $this->_addHook('widget_instance', 'myPageBox', false);
58 $this->_addHook('widgets', 'widgets', false);
59 $this->_addHook('activity');
60 $this->_addHook('weekly');
64 function getDefaultServer() {
65 return forge_get_config('default_server', 'scmgit');
68 function printShortStats($params) {
69 $project = $this->checkParams($params);
74 if (forge_check_perm('scm', $project->getID(), 'read')) {
75 $result = db_query_params('SELECT sum(updates) AS updates, sum(adds) AS adds FROM stats_cvs_group WHERE group_id=$1',
76 array($project->getID()));
77 $commit_num = db_result($result,0,'updates');
78 $add_num = db_result($result, 0, 'adds');
85 $params['result'] .= ' (Git: '.sprintf(_('<strong>%1$s</strong> updates, <strong>%2$s</strong> adds'), number_format($commit_num, 0), number_format($add_num, 0)).')';
90 return html_e('p', array(), sprintf(_('Documentation for %1$s is available at <a href="%2$s">%2$s</a>.'),
92 'http://git-scm.com/'));
95 function getInstructionsForAnon($project) {
96 $repo_list = $this->getRepositories($project);
97 $protocol = forge_get_config('use_ssl', 'scmgit')? 'https' : 'http';
98 $clone_commands = array();
99 foreach ($repo_list as $repo_name) {
100 if (forge_get_config('use_smarthttp', 'scmgit')) {
101 $clone_commands[] = 'git clone '.$protocol.'://'.$this->getBoxForProject($project).'/anonscm/git/'.$project->getUnixName().'/'.$repo_name.'.git';
105 $b = html_e('h2', array(), _('Anonymous Access'));
107 $b .= html_e('p', array(),
108 ngettext("This project's Git repository can be checked out through anonymous access with the following command.",
109 "This project's Git repositories can be checked out through anonymous access with the following commands.",
113 foreach ($clone_commands as $cmd) {
114 $htmlRepo .= html_e('kbd', array(), $cmd).html_e('br');;
116 $b .= html_e('p', array(), $htmlRepo);
118 $result = db_query_params('SELECT u.user_id, u.user_name, u.realname FROM scm_personal_repos p, users u WHERE p.group_id=$1 AND u.user_id=p.user_id AND u.unix_status=$2 AND plugin_id=$3',
119 array($project->getID(),
122 $rows = db_numrows($result);
125 $b .= html_e('h3', array(),
126 ngettext("Member repository",
127 "Members repositories",
129 $b .= html_e('p', array(),
130 ngettext("One of this project's members also has a personal Git repository that can be checked out anonymously.",
131 "Some of this project's members also have personal Git repositories that can be checked out anonymously.",
134 for ($i=0; $i<$rows; $i++) {
135 $user_id = db_result($result, $i, 'user_id');
136 $user_name = db_result($result, $i, 'user_name');
137 $real_name = db_result($result, $i, 'realname');
138 $htmlRepo .= html_e('kbd', array(), 'git clone '.$protocol.'://'.$this->getBoxForProject($project).'/anonscm/git/'.$project->getUnixName().'/users/'.$user_name.'.git')
139 . ' ('.util_make_link_u($user_name, $user_id, $real_name).')'
142 $b .= html_e('p', array(), $htmlRepo);
148 function getInstructionsForRW($project) {
150 $repo_list = $this->getRepositories($project);
152 $b = html_e('h2', array(), _('Developer Access'));
153 $b .= html_e('p', array(),
154 ngettext('Only project developers can access the Git repository via this method.',
155 'Only project developers can access the Git repositories via this method.',
157 $b .= '<div id="tabber-git">';
158 $liElements = array();
159 if (forge_get_config('use_ssh', 'scmgit')) {
160 $liElements[]['content'] = '<a href="#tabber-gitssh">'._('via SSH').'</a>';
163 if (forge_get_config('use_smarthttp', 'scmgit')) {
164 $liElements[]['content'] = '<a href="#tabber-gitsmarthttp">'._('via "smart HTTP"').'</a>';
167 $b .= $HTML->html_list($liElements);
168 if (!isset($configuration)) {
169 return $HTML->error_msg(_('Error')._(': ')._('No access protocol has been allowed for the Git plugin in scmgit.ini: use_ssh and use_smarthttp are disabled'));
172 if (forge_get_config('ssh_port') != 22) {
173 $ssh_port = ':'.forge_get_config('ssh_port');
175 if (session_loggedin()) {
176 $u = user_get_object(user_getid());
177 $d = $u->getUnixName();
178 if (forge_get_config('use_ssh', 'scmgit')) {
179 $b .= '<div id="tabber-gitssh" class="tabbertab" >';
180 $b .= html_e('p', array(), _('SSH must be installed on your client machine.'));
182 foreach ($repo_list as $repo_name) {
183 if (forge_get_config('use_shell_limited')) {
184 $htmlRepo .= html_e('kbd', array(), 'git clone '.$d.'@'.$this->getBoxForProject($project).$ssh_port.':'.$project->getUnixName().'/'.$repo_name.'.git').html_e('br');
186 $htmlRepo .= html_e('kbd', array(), 'git clone git+ssh://'.$d.'@'.$this->getBoxForProject($project).$ssh_port.forge_get_config('repos_path', 'scmgit').'/'.$project->getUnixName().'/'.$repo_name.'.git').html_e('br');
189 $b .= html_e('p', array(), $htmlRepo);
192 if (forge_get_config('use_smarthttp', 'scmgit')) {
193 $b .= '<div id="tabber-gitsmarthttp" class="tabbertab" >';
194 $b .= html_e('p', array(), _('Enter your site password when prompted.'));
196 $protocol = forge_get_config('use_ssl', 'scmgit') ? 'https' : 'http';
197 foreach ($repo_list as $repo_name) {
198 $htmlRepo .= html_e('kbd', array(), 'git clone '.$protocol.'://'.$d.'@'.$this->getBoxForProject($project).'/authscm/'.$d.'/git/'.$project->getUnixName() .'/'. $repo_name .'.git').html_e('br');
200 $b .= html_e('p', array(), $htmlRepo);
204 if (forge_get_config('use_ssh', 'scmgit')) {
205 $b .= '<div id="tabber-gitssh" class="tabbertab" >';
206 $b .= html_e('p', array(),
207 ngettext('Only project developers can access the Git repository via this method.',
208 'Only project developers can access the Git repositories via this method.',
210 ' '. _('SSH must be installed on your client machine.').
211 ' '. _('Substitute <em>developername</em> with the proper value.'));
213 foreach ($repo_list as $repo_name) {
214 if (forge_get_config('use_shell_limited')) {
215 $htmlRepo .= html_e('kbd', array(), 'git clone '.html_e('i', array(), _('developername'), true, false).'@'.$this->getBoxForProject($project).$ssh_port.':'.$project->getUnixName().'/'.$repo_name.'.git').html_e('br');
217 $htmlRepo .= html_e('kbd', array(), 'git clone git+ssh://'.html_e('i', array(), _('developername'), true, false).'@'.$this->getBoxForProject($project).$ssh_port.forge_get_config('repos_path', 'scmgit').'/'.$project->getUnixName().'/'.$repo_name.'.git').html_e('br');
220 $b .= html_e('p', array(), $htmlRepo);
223 if (forge_get_config('use_smarthttp', 'scmgit')) {
224 $protocol = forge_get_config('use_ssl', 'scmgit')? 'https' : 'http';
225 $b .= '<div id="tabber-gitsmarthttp" class="tabbertab" >';
226 $b .= html_e('p', array(),
227 ngettext('Only project developers can access the Git repository via this method.',
228 'Only project developers can access the Git repositories via this method.',
230 ' '. _('Enter your site password when prompted.'));
232 foreach ($repo_list as $repo_name) {
233 $htmlRepo .= '<tt>git clone '.$protocol.'://<i>'._('developername').'</i>@'.$this->getBoxForProject($project).'/authscm/<i>'._('developername').'</i>/git/'.$project->getUnixName() .'/'. $repo_name .'.git</tt><br />';
235 $b .= html_e('p', array(), $htmlRepo);
240 if (session_loggedin()) {
241 $u = user_get_object(user_getid());
242 if ($u->getUnixStatus() == 'A') {
243 $result = db_query_params('SELECT * FROM scm_personal_repos p WHERE p.group_id=$1 AND p.user_id=$2 AND plugin_id=$3',
244 array($project->getID(),
247 if ($result && db_numrows($result) > 0) {
248 $b .= html_e('h3', array(), _('Access to your personal repository'));
249 $b .= html_e('p', array(), _('You have a personal repository for this project, accessible through the following methods. Enter your site password when prompted.'));
250 if (forge_get_config('use_ssh', 'scmgit')) {
251 $b .= html_e('kbd', array(), 'git clone git+ssh://'.$u->getUnixName().'@'.$this->getBoxForProject($project).$ssh_port.forge_get_config('repos_path', 'scmgit').'/'.$project->getUnixName().'/users/'.$u->getUnixName().'.git').html_e('br');
253 if (forge_get_config('use_smarthttp', 'scmgit')) {
254 $b .= html_e('kbd', array(), 'git clone '.$protocol.'://'.$u->getUnixName().'@'.$this->getBoxForProject($project).'/authscm/'.$u->getUnixName().'/git/'.$project->getUnixName() .'/users/'. $u->getUnixName() .'.git').html_e('br');
257 $glist = $u->getGroups();
258 foreach ($glist as $g) {
259 if ($g->getID() == $project->getID()) {
260 $b .= html_e('h3', array(), _('Request a personal repository'));
261 $b .= html_e('p', array(), _("You can clone the project repository into a personal one into which you alone will be able to write. Other members of the project will only have read access. Access for non-members will follow the same rules as for the project's main repository. Note that the personal repository may take some time before it is created (less than an hour in most situations)."));
262 $b .= html_e('p', array(), util_make_link('/plugins/scmgit/index.php?func=request-personal-repo&group_id='.$project->getID(), _('Request a personal repository')));
271 function getSnapshotPara($project) {
273 $filename = $project->getUnixName().'-scm-latest.tar'.util_get_compressed_file_extension();
274 if (file_exists(forge_get_config('scm_snapshots_path').'/'.$filename)) {
275 $b .= html_e('p', array(), '['.util_make_link('/snapshots.php?group_id='.$project->getID(), _('Download the nightly snapshot')).']');
280 function printBrowserPage($params) {
281 if ($params['scm_plugin'] != $this->name) {
286 $project = $this->checkParams($params);
291 if ($this->browserDisplayable($project)) {
292 if ($params['user_id']) {
293 $repo_user = user_get_object($params['user_id']);
294 $repo = $project->getUnixName().'/users/'.$repo_user->getUnixName().'.git';
295 } elseif ($params['extra']) {
296 $repo = $project->getUnixName().'/'.$params['extra'].'.git';
298 $repo = $project->getUnixName().'/'.$project->getUnixName().'.git';
301 $protocol = forge_get_config('use_ssl', 'scmgit')? 'https' : 'http';
302 $box = $this->getBoxForProject($project);
303 if ($project->enableAnonSCM()) {
304 $iframesrc = "$protocol://$box/anonscm/gitweb/?p=$repo";
305 } elseif (session_loggedin()) {
306 $logged_user = user_get_object(user_getid())->getUnixName();
307 $iframesrc = "$protocol://$box/authscm/$logged_user/gitweb/?p=$repo";
309 if ($params['commit']) {
310 $iframesrc .= ';a=log;h='.$params['commit'];
312 htmlIframeResizer($iframesrc, array('id'=>'scmgit_iframe', 'absolute'=>true));
316 function getBrowserLinkBlock($project) {
317 $b = html_e('h2', array(), _('Git Repository Browser'));
318 $b .= html_e('p', array(), _("Browsing the Git tree gives you a view into the current status"
319 . " of this project's code. You may also view the complete"
320 . " history of any file in the repository."));
321 $b .= html_e('p', array(), '['.util_make_link('/scm/browser.php?group_id='.$project->getID().'&scm_plugin='.$this->name, _('Browse main git repository')).']');
324 $repo_list = $this->getRepositories($project, false);
325 foreach ($repo_list as $repo_name) {
326 $b .= '['.util_make_link('/scm/browser.php?group_id='.$project->getID().'&extra='.$repo_name.'&scm_plugin='.$this->name, _('Browse extra git repository')._(': ').$repo_name).']'.html_e('br');
329 $result = db_query_params('SELECT u.user_id, u.user_name FROM scm_personal_repos p, users u WHERE p.group_id=$1 AND u.user_id=p.user_id AND u.unix_status=$2 AND plugin_id=$3',
330 array($project->getID(),
333 $rows = db_numrows($result);
334 for ($i=0; $i<$rows; $i++) {
335 $user_id = db_result($result,$i,'user_id');
336 $user_name = db_result($result,$i,'user_name');
337 $b .= '['.util_make_link('/scm/browser.php?group_id='.$project->getID().'&user_id='.$user_id.'&scm_plugin='.$this->name, _('Browse personal git repository')._(': ').$user_name).']'.html_e('br');
343 function getStatsBlock($project) {
347 $result = db_query_params('SELECT u.realname, u.user_name, u.user_id, sum(updates) as updates, sum(adds) as adds, sum(adds+updates) as combined, reponame FROM stats_cvs_user s, users u WHERE group_id=$1 AND s.user_id=u.user_id AND (updates>0 OR adds >0) GROUP BY u.user_id, realname, user_name, u.user_id, reponame ORDER BY reponame, combined DESC, realname',
348 array($project->getID()));
350 if (db_numrows($result) > 0) {
351 $tableHeaders = array(
356 $b .= $HTML->listTableTop($tableHeaders, array(), '', 'repo-history');
359 $total = array('adds' => 0, 'updates' => 0);
362 while ($data = db_fetch_array($result)) {
363 if ($prevrepo != $data['reponame']) {
364 if ($prevrepo != '') {
366 $cells[] = array(html_e('strong', array(), _('Total')._(':')), 'class' => 'halfwidth');
367 $cells[] = array($total['adds'], 'class' => 'onequarterwidth align-right');
368 $cells[] = array($total['updates'], 'class' => 'onequarterwidth align-right');
369 $b .= $HTML->multiTableRow(array(), $cells);
371 $prevrepo = $data['reponame'];
372 $total = array('adds' => 0, 'updates' => 0);
374 $cells[] = array(html_e('strong', array(), $data['reponame'].' '._('statistics')), 'colspan' => 3);
375 $b .= $HTML->multiTableRow(array('class' => 'tableheading'), $cells);
378 $cells[] = array(util_display_user($data['user_name'], $data['user_id'], $data['realname']), 'class' => 'halfwidth');
379 $cells[] = array($data['adds'], 'class' => 'onequarterwidth align-right');
380 $cells[] = array($data['updates'], 'class' => 'onequarterwidth align-right');
381 $b .= $HTML->multiTableRow(array(), $cells);
382 $total['adds'] += $data['adds'];
383 $total['updates'] += $data['updates'];
387 $cells[] = array(html_e('strong', array(), _('Total')._(':')), 'class' => 'halfwidth');
388 $cells[] = array($total['adds'], 'class' => 'onequarterwidth align-right');
389 $cells[] = array($total['updates'], 'class' => 'onequarterwidth align-right');
390 $b .= $HTML->multiTableRow(array(), $cells);
391 $b .= $HTML->listTableBottom();
393 $b .= $HTML->warning_msg(_('No history yet.'));
400 * Create user repository under non-privileged uid
402 static function createUserRepo($params) {
403 $project = $params['project'];
404 $project_name = $project->getUnixName();
405 $user_name = $params['user_name'];
406 $main_repo = $params['main_repo'];
407 $root = $params['root'];
409 // dir was already created by root
410 $repodir = $root . '/users/' . $user_name . '.git';
411 chmod($repodir, 00755);
412 if (!is_file("$repodir/HEAD") && !is_dir("$repodir/objects") && !is_dir("$repodir/refs")) {
413 // 'cd $root' because git will abort if e.g. we're in a 0700 /root after setuid
414 system("cd $root; LC_ALL=C git clone --bare --quiet --no-hardlinks $main_repo $repodir 2>&1 >/dev/null | grep -v 'warning: You appear to have cloned an empty repository.' >&2");
415 system("GIT_DIR=\"$repodir\" git update-server-info");
416 system("GIT_DIR=\"$repodir\" git config http.receivepack true");
417 system("GIT_DIR=\"$repodir\" git config core.logAllRefUpdates true");
419 if (is_file("$repodir/hooks/post-update.sample")) {
420 rename("$repodir/hooks/post-update.sample",
421 "$repodir/hooks/post-update");
423 if (!is_file("$repodir/hooks/post-update")) {
424 $f = fopen("$repodir/hooks/post-update","x+");
425 fwrite($f, "exec git-update-server-info\n");
428 if (is_file("$repodir/hooks/post-update")) {
429 system("chmod +x $repodir/hooks/post-update");
431 system("echo \"Git repository for user $user_name in project $project_name\" > $repodir/description");
432 system("chmod -R go=rX $repodir");
436 function createOrUpdateRepo($params) {
439 $project = $this->checkParams($params);
440 if (!$project) return false;
441 if (!$project->isActive()) return false;
443 $project_name = $project->getUnixName();
444 $unix_group_ro = $project_name . '_scmro';
445 $unix_group_rw = $project_name . '_scmrw';
447 $root = forge_get_config('repos_path', 'scmgit') . '/' . $project_name;
448 if (!is_dir($root)) {
449 system("mkdir -p $root");
450 system("chgrp $unix_group_ro $root");
452 if ($project->enableAnonSCM()) {
453 system("chmod 2755 $root");
455 system("chmod 2750 $root");
458 // Create main repository
459 $main_repo = $root . '/' . $project_name . '.git';
460 if (!is_dir($main_repo) || (!is_file("$main_repo/HEAD") &&
461 !is_dir("$main_repo/objects") && !is_dir("$main_repo/refs"))) {
462 $tmp_repo = util_mkdtemp('.git', $project_name);
463 if ($tmp_repo == false) {
467 exec("GIT_DIR=\"$tmp_repo\" git init --bare --shared=group", $result);
468 $output .= join("<br />", $result);
470 exec("GIT_DIR=\"$tmp_repo\" git update-server-info", $result);
471 exec("GIT_DIR=\"$tmp_repo\" git config http.receivepack true", $result);
472 exec("GIT_DIR=\"$tmp_repo\" git config core.logAllRefUpdates true");
473 $output .= join("<br />", $result);
474 if (is_file("$tmp_repo/hooks/post-update.sample")) {
475 rename("$tmp_repo/hooks/post-update.sample",
476 "$tmp_repo/hooks/post-update");
478 if (!is_file("$tmp_repo/hooks/post-update")) {
479 $f = fopen("$tmp_repo/hooks/post-update", 'w');
480 fwrite($f, "exec git-update-server-info\n");
483 if (is_file("$tmp_repo/hooks/post-update")) {
484 system("chmod +x $tmp_repo/hooks/post-update");
486 system("echo \"Git repository for $project_name\" > $tmp_repo/description");
487 system("find $tmp_repo -type d | xargs chmod g+s");
488 system("chgrp -R $unix_group_rw $tmp_repo");
489 system("chmod -R g=rwX,o=rX $tmp_repo");
492 * $main_repo can already exist, for example if it’s
493 * not a directory or doesn’t contain a HEAD file or
494 * an objects or refs subdirectory… move it out of
495 * the way in these cases
497 system("if test -e $main_repo || test -h $main_repo; then d=\$(mktemp -d $main_repo.scmgit-moved.XXXXXXXXXX) && mv -f $main_repo \$d/; fi");
498 /* here’s still a TOCTOU but we check $ret below */
499 system("mv $tmp_repo $main_repo", $ret);
503 system("echo \"Git repository for $project_name\" > $main_repo/description");
506 // Create project-wide secondary repositories
507 $result = db_query_params('SELECT repo_name, description, clone_url FROM scm_secondary_repos WHERE group_id=$1 AND next_action = $2 AND plugin_id=$3',
508 array($project->getID(),
509 SCM_EXTRA_REPO_ACTION_UPDATE,
511 $rows = db_numrows($result);
512 for ($i=0; $i<$rows; $i++) {
513 $repo_name = db_result($result, $i, 'repo_name');
514 $description = db_result($result, $i, 'description');
515 $clone_url = db_result($result, $i, 'clone_url');
516 // Clone URLs need to be validated to prevent a potential arbitrary command execution
517 if (!preg_match('|^[-a-zA-Z0-9:./_]+$|', $clone_url)) {
520 $repodir = $root . '/' . $repo_name . '.git';
521 if (!is_file("$repodir/HEAD") && !is_dir("$repodir/objects") && !is_dir("$repodir/refs")) {
522 if ($clone_url != '') {
523 system("cd $root; LC_ALL=C git clone --quiet --bare $clone_url $repodir 2>&1 >/dev/null | grep -v 'warning: You appear to have cloned an empty repository.' >&2");
525 system("GIT_DIR=\"$repodir\" git init --quiet --bare --shared=group");
527 system("GIT_DIR=\"$repodir\" git update-server-info");
528 system("GIT_DIR=\"$repodir\" git config http.receivepack true");
529 system("GIT_DIR=\"$repodir\" git config core.logAllRefUpdates true");
530 if (is_file("$repodir/hooks/post-update.sample")) {
531 rename("$repodir/hooks/post-update.sample",
532 "$repodir/hooks/post-update");
534 if (!is_file("$repodir/hooks/post-update")) {
535 $f = fopen("$repodir/hooks/post-update", 'w');
536 fwrite($f, "exec git-update-server-info\n");
539 if (is_file("$repodir/hooks/post-update")) {
540 system("chmod +x $repodir/hooks/post-update");
542 $f = fopen("$repodir/description", "w");
543 fwrite($f, $description."\n");
545 system("chgrp -R $unix_group_rw $repodir");
546 system("chmod -R g=rwX,o=rX $repodir");
550 // Delete project-wide secondary repositories
551 $result = db_query_params ('SELECT repo_name FROM scm_secondary_repos WHERE group_id=$1 AND next_action = $2 AND plugin_id=$3',
552 array($project->getID(),
553 SCM_EXTRA_REPO_ACTION_DELETE,
555 $rows = db_numrows ($result);
556 for ($i=0; $i<$rows; $i++) {
557 $repo_name = db_result($result, $i, 'repo_name');
558 $repodir = $root . '/' . $repo_name . '.git';
559 if (util_is_valid_repository_name($repo_name)) {
560 system("rm -rf $repodir");
562 db_query_params ('DELETE FROM scm_secondary_repos WHERE group_id=$1 AND repo_name=$2 AND next_action = $3 AND plugin_id=$4',
563 array($project->getID(),
565 SCM_EXTRA_REPO_ACTION_DELETE,
569 // Create users' personal repositories
570 $result = db_query_params ('SELECT u.user_name FROM scm_personal_repos p, users u WHERE p.group_id=$1 AND u.user_id=p.user_id AND u.unix_status=$2 AND plugin_id=$3',
571 array($project->getID(),
574 $rows = db_numrows ($result);
575 if (!is_dir($root.'/users')) {
576 system("mkdir -p $root/users");
577 chgrp("$root/users", 'root'); // make it clear group members don't have write access
578 system("chmod 00755 $root/users");
580 for ($i=0; $i<$rows; $i++) {
581 $user_name = db_result($result, $i, 'user_name');
582 $repodir = $root . '/users/' . $user_name . '.git';
584 if (!is_dir($repodir) && mkdir ($repodir, 0700)) {
585 chown ($repodir, $user_name);
586 chgrp ($repodir, 'root'); // make it clear group members don't have write access
589 $params['project'] = $project;
590 $params['user_name'] = $user_name;
591 $params['root'] = $root;
592 $params['main_repo'] = $main_repo;
594 util_sudo_effective_user($user_name,
595 array("GitPlugin", "createUserRepo"),
599 $params['output'] = $output;
602 function updateRepositoryList($params) {
603 $config_dir = forge_get_config('config_path').'/plugins/scmgit';
604 if (!is_dir($config_dir)) {
605 mkdir($config_dir, 0755, true);
607 $fname = $config_dir . '/gitweb.conf';
608 $f = fopen($fname.'.new', 'w');
609 $rootdir = forge_get_config('repos_path', 'scmgit');
610 fwrite($f, "\$projectroot = '$rootdir';\n");
611 fwrite($f, "\$projects_list = '$config_dir/gitweb.list';\n");
612 fwrite($f, "\$anon_clone_url = '". util_make_url('/anonscm/git') . "';\n");
613 fwrite($f, "\$logo = '". util_make_url('/plugins/scmgit/git-logo.png') . "';\n");
614 fwrite($f, "\$favicon = '". util_make_url('/plugins/scmgit/git-favicon.png')."';\n");
615 fwrite($f, "\$stylesheet = '". util_make_url('/plugins/scmgit/gitweb.css')."';\n");
616 fwrite($f, "\$javascript = '". util_make_url('/plugins/scmgit/gitweb.js')."';\n");
617 fwrite($f, "\$site_html_head_string = '<script type=\"text/javascript\" src=\"". util_make_url('/scripts/iframe-resizer/iframeResizer.contentWindow.js'). "\" />';\n");
618 fwrite($f, "\$prevent_xss = 'true';\n");
619 fwrite($f, "\$site_footer = '".forge_get_config('source_path')."/plugins/scmgit/www/gitweb_footer.html';\n");
620 fwrite($f, "\$feature{'actions'}{'default'} = [('project home', '" .
621 util_make_url('/plugins/scmgit/?func=grouppage/%n') .
622 "', 'summary')];\n");
624 fwrite($f, "\$per_request_config = sub {\n");
626 fwrite($f, "push @git_base_url_list, qq,". util_make_url('/anonscm/git') .",;\n");
628 $protocol = forge_get_config('use_ssl', 'scmgit')? 'https' : 'http';
629 if (forge_get_config('use_smarthttp', 'scmgit')) {
630 fwrite($f, "if (defined \$ENV{ITKUID} && \$ENV{ITKUID} ne '".forge_get_config('apache_user')."') { push @git_base_url_list, qq,$protocol://\$ENV{ITKUID}\@".forge_get_config('scm_host')."/authscm/\$ENV{ITKUID}/git,; }\n");
633 if (forge_get_config('use_ssh', 'scmgit')) {
634 fwrite($f, "if (defined \$ENV{ITKUID} && \$ENV{ITKUID} ne '".forge_get_config('apache_user')."') { push @git_base_url_list, qq,git+ssh://\$ENV{ITKUID}\@".forge_get_config('scm_host').forge_get_config('repos_path', 'scmgit').",; }\n");
640 chmod($fname.'.new', 0644);
641 rename($fname.'.new', $fname);
643 # Optimized gitweb.list generation
644 # Useful to list all a project's repos: /gitweb?a=project_list;pf=project_name
645 $fname = $config_dir . '/gitweb.list';
646 $f = fopen($fname.'.new', 'w');
647 $res = db_query_params("SELECT unix_group_name FROM groups
648 JOIN group_plugin ON (groups.group_id=group_plugin.group_id)
649 WHERE groups.status=$1 AND group_plugin.plugin_id=$2
650 ORDER BY unix_group_name", array('A', $this->getID()));
651 while ($arr = db_fetch_array($res)) {
652 fwrite($f, $arr['unix_group_name'].'/'.$arr['unix_group_name'].".git\n");
654 $res = db_query_params("SELECT unix_group_name, repo_name
656 JOIN group_plugin ON (groups.group_id=group_plugin.group_id)
657 JOIN scm_secondary_repos ON (groups.group_id=scm_secondary_repos.group_id)
658 WHERE groups.status=$1 AND group_plugin.plugin_id=$2
659 ORDER BY unix_group_name, repo_name", array('A', $this->getID()));
660 while ($arr = db_fetch_array($res)) {
661 fwrite($f, $arr['unix_group_name'].'/'.$arr['repo_name'].".git\n");
663 $res = db_query_params("SELECT unix_group_name, user_name
665 JOIN group_plugin ON (groups.group_id=group_plugin.group_id)
666 JOIN scm_personal_repos ON (groups.group_id=scm_personal_repos.group_id)
667 JOIN users ON (scm_personal_repos.user_id=users.user_id)
668 WHERE groups.status=$1 AND group_plugin.plugin_id=$2 AND users.status=$3
669 ORDER BY unix_group_name, user_name", array('A', $this->getID(), 'A'));
670 while ($arr = db_fetch_array($res)) {
671 fwrite($f, $arr['unix_group_name'].'/users/'.$arr['user_name'].".git\n");
674 chmod($fname.'.new', 0644);
675 rename($fname.'.new', $fname);
678 function gatherStats($params) {
679 $project = $this->checkParams($params);
684 if ($params['mode'] == 'day') {
685 $year = $params['year'];
686 $month = $params['month'];
687 $day = $params['day'];
688 $month_string = sprintf("%04d%02d", $year, $month);
689 $start_time = gmmktime(0, 0, 0, $month, $day, $year);
690 $end_time = $start_time + 86400;
692 $repolist = $this->getRepositories($project);
693 foreach ($repolist as $repo_name) {
694 $this->gatherStatsRepo($project, $repo_name, $year, $month, $day);
699 function gatherStatsRepo($group, $project_reponame, $year, $month, $day) {
700 $month_string = sprintf("%04d%02d", $year, $month);
701 $start_time = gmmktime(0, 0, 0, $month, $day, $year);
702 $end_time = $start_time + 86400;
705 $usr_updates = array();
706 $usr_deletes = array();
707 $usr_commits = array();
714 $repo = forge_get_config('repos_path', 'scmgit') . '/' . $group->getUnixName() . '/' . $project_reponame . '.git';
715 if (!is_dir($repo) || !is_dir("$repo/refs")) {
716 // echo "No repository $repo\n";
720 # For each commit, get committer full name and e-mail (respecting git .mailmap file),
721 # and a list of files prefixed by their status (A/M/D)
722 $pipe = popen("GIT_DIR=\"$repo\" git log --since=@$start_time --until=@$end_time --all --pretty='format:%n%aN <%aE>' --name-status 2>/dev/null", 'r');
726 // cleaning stats_cvs_* table for the current day for the default repository
727 $res = db_query_params('DELETE FROM stats_cvs_group WHERE month = $1 AND day = $2 AND group_id = $3 and reponame = $4',
733 echo "Error while cleaning stats_cvs_group\n";
738 $res = db_query_params ('DELETE FROM stats_cvs_user WHERE month = $1 AND day = $2 AND group_id = $3 and reponame = $4',
744 echo "Error while cleaning stats_cvs_user\n";
750 while (!feof($pipe) && $data = fgets($pipe)) {
752 // Replace bad UTF-8 with '?' - it's quite hard to make git output non-UTF-8
753 // (e.g. with i18n.commitEncoding = unknown) - but some users do!
754 // and this makes PostgreSQL choke (SQL> ERROR: invalid byte sequence for encoding "UTF8": 0xf9)
755 $line = mb_convert_encoding($line, 'UTF-8', 'UTF-8');
756 if (strlen($line) > 0) {
757 $result = preg_match("/^(?P<name>.+) <(?P<mail>.+)>/", $line, $matches);
760 $last_user = $matches['name'];
761 $user2email[$last_user] = $matches['mail'];
762 if (!isset($usr_adds[$last_user])) {
763 $usr_adds[$last_user] = 0;
764 $usr_updates[$last_user] = 0;
765 $usr_deletes[$last_user] = 0;
766 $usr_commits[$last_user] = 0;
769 $usr_commits[$last_user]++;
771 // Short-commit stats line
772 $result = preg_match("/^(?P<mode>[AMD])\s+(?P<file>.+)$/", $line, $matches);
775 if ($last_user == "")
777 if (!isset($usr_adds[$last_user]))
778 $usr_adds[$last_user] = 0;
779 if (!isset($usr_updates[$last_user]))
780 $usr_updates[$last_user] = 0;
781 if (!isset($usr_deletes[$last_user]))
782 $usr_deletes[$last_user] = 0;
783 if ($matches['mode'] == 'A') {
784 $usr_adds[$last_user]++;
786 } elseif ($matches['mode'] == 'M') {
787 $usr_updates[$last_user]++;
789 } elseif ($matches['mode'] == 'D') {
790 $usr_deletes[$last_user]++;
797 // inserting group results in stats_cvs_groups
798 if ($updates > 0 || $adds > 0 || $deletes > 0 || $commits > 0) {
799 if (!db_query_params('INSERT INTO stats_cvs_group (month, day, group_id, checkouts, commits, adds, updates, deletes, reponame)
800 VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)',
809 $project_reponame))) {
810 echo "Error while inserting into stats_cvs_group\n";
816 // building the user list
817 $user_list = array_unique(array_merge(array_keys($usr_adds), array_keys($usr_updates), array_keys($usr_deletes), array_keys($usr_commits)));
819 foreach ($user_list as $user) {
820 // Trying to get user id from user name or email
821 $u = user_get_object_by_name($user);
823 $user_id = $u->getID();
825 $res = db_query_params('SELECT user_id FROM users WHERE lower(realname)=$1 OR lower(email)=$2',
826 array(strtolower($user), strtolower($user2email[$user])));
827 if ($res && db_numrows($res) > 0) {
828 $user_id = db_result($res, 0, 'user_id');
834 $uc = isset($usr_commits[$user]) ? $usr_commits[$user] : 0;
835 $uu = isset($usr_updates[$user]) ? $usr_updates[$user] : 0;
836 $ua = isset($usr_adds[$user]) ? $usr_adds[$user] : 0;
837 $ud = isset($usr_deletes[$user]) ? $usr_deletes[$user] : 0;
838 if ($uu > 0 || $ua > 0 || $uc > 0 || $ud > 0) {
839 if (!db_query_params('INSERT INTO stats_cvs_user (month, day, group_id, user_id, commits, adds, updates, deletes, reponame)
840 VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)',
849 $project_reponame))) {
850 echo "Error while inserting into stats_cvs_user\n";
859 function generateSnapshots($params) {
860 $us = forge_get_config('use_scm_snapshots') ;
861 $ut = forge_get_config('use_scm_tarballs') ;
866 $project = $this->checkParams($params);
871 $group_name = $project->getUnixName();
873 $snapshot = forge_get_config('scm_snapshots_path').'/'.$group_name.'-scm-latest.tar'.util_get_compressed_file_extension();
874 $tarball = forge_get_config('scm_tarballs_path').'/'.$group_name.'-scmroot.tar'.util_get_compressed_file_extension();
876 if (!$project->enableAnonSCM()) {
877 if (is_file($snapshot)) {
880 if (is_file($tarball)) {
886 // TODO: ideally we generate one snapshot per git repository
887 $toprepo = forge_get_config('repos_path', 'scmgit');
888 $repo = $toprepo . '/' . $project->getUnixName() . '/' . $project->getUnixName() . '.git';
890 if (!is_dir($repo)) {
891 if (is_file($snapshot)) {
894 if (is_file($tarball)) {
900 // Skip empty repo (no HEAD present in repository)
901 $ref = trim(`GIT_DIR=$repo git symbolic-ref HEAD`);
902 if (!file_exists($repo.'/'.$ref)) {
906 $tmp = trim(`mktemp -d`);
911 $today = date('Y-m-d');
912 system("GIT_DIR=\"$repo\" git archive --format=tar --prefix=$group_name-scm-$today/ HEAD |".forge_get_config('compression_method')." > $tmp/snapshot");
913 chmod("$tmp/snapshot", 0644);
914 copy("$tmp/snapshot", $snapshot);
915 unlink("$tmp/snapshot");
918 system("tar cCf $toprepo - ".$project->getUnixName() ."|".forge_get_config('compression_method')."> $tmp/tarball");
919 chmod("$tmp/tarball", 0644);
920 copy("$tmp/tarball", $tarball);
921 unlink("$tmp/tarball");
922 system("rm -rf $tmp");
927 * widgets - 'widgets' hook handler
929 * @param array $params
932 function widgets($params) {
933 require_once 'common/widget/WidgetLayoutManager.class.php';
934 if ($params['owner_type'] == WidgetLayoutManager::OWNER_TYPE_USER) {
935 $params['fusionforge_widgets'][] = 'plugin_scmgit_user_myrepositories';
941 * Process the 'widget_instance' hook to create instances of the widgets
943 * @param array $params
945 function myPageBox($params) {
947 $user = UserManager::instance()->getCurrentUser();
948 require_once 'common/widget/WidgetLayoutManager.class.php';
949 if ($params['widget'] == 'plugin_scmgit_user_myrepositories') {
950 require_once $gfplugins.$this->name.'/common/scmgit_Widget_MyRepositories.class.php';
951 $params['instance'] = new scmgit_Widget_MyRepositories(WidgetLayoutManager::OWNER_TYPE_USER, $user->getId());
955 function weekly(&$params) {
956 $res = db_query_params('SELECT group_id FROM groups WHERE status=$1 AND use_scm=1 ORDER BY group_id DESC',
959 $params['output'] .= 'ScmGit Plugin: Unable to get list of projects using SCM: '.db_error();
963 $params['output'] .= 'ScmGit Plugin: Running "git gc --quiet" on '.db_numrows($res).' repositories.'."\n";
964 while ($row = db_fetch_array($res)) {
965 $project = group_get_object($row['group_id']);
966 if (!$project || !is_object($project)) {
968 } elseif ($project->isError()) {
971 if (!$project->usesPlugin($this->name)) {
975 $project_name = $project->getUnixName();
976 $repo = forge_get_config('repos_path', 'scmgit') . '/' . $project_name . '/' . $project_name . '.git';
979 $params['output'] .= $project_name.': '.`git gc --quiet 2>&1`;
984 function activity($params) {
985 $project = $this->checkParams($params);
990 if (in_array('scmgit', $params['show']) || (count($params['show']) < 1)) {
991 if ($project->enableAnonSCM()) {
992 $server_script = '/anonscm/gitlog';
993 } elseif (session_loggedin()) {
994 $u = session_get_user();
995 $server_script = '/authscm/'.$u->getUnixName().'/gitlog';
999 $repo_list = $this->getRepositories($project);
1000 $protocol = forge_get_config('use_ssl', 'scmgit') ? 'https://' : 'http://';
1001 foreach ($repo_list as $repo_name) {
1003 $script_url = $protocol.$this->getBoxForProject($project)
1005 .'?unix_group_name='.$project->getUnixName()
1006 .'&repo_name='.$repo_name
1008 .'&begin='.$params['begin']
1009 .'&end='.$params['end'];
1010 $filename = tempnam('/tmp', 'gitlog');
1011 $f = fopen($filename, 'w');
1013 curl_setopt($ch, CURLOPT_URL, $script_url);
1014 curl_setopt($ch, CURLOPT_FILE, $f);
1015 curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
1016 curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
1017 curl_setopt($ch, CURLOPT_COOKIE, @$_SERVER['HTTP_COOKIE']); // for session validation
1018 curl_setopt($ch, CURLOPT_USERAGENT, $_SERVER['HTTP_USER_AGENT']); // for session validation
1019 curl_setopt($ch, CURLOPT_HTTPHEADER,
1020 array('X-Forwarded-For: '.$_SERVER['REMOTE_ADDR'])); // for session validation
1021 $body = curl_exec($ch);
1022 if ($body === false) {
1023 $this->setError(curl_error($ch));
1026 fclose($f); // flush buffer
1027 $f = fopen($filename, 'r');
1030 while (!feof($f) && $data = fgets($f)) {
1031 $line = trim($data);
1032 $splitedLine = explode('||', $line);
1033 if (sizeof($splitedLine) == 4) {
1035 $result['section'] = 'scm';
1036 $result['group_id'] = $project->getID();
1037 $result['ref_id'] = 'browser.php?group_id='.$project->getID().'&scm_plugin='.$this->name.'&commit='.$splitedLine[3];
1038 $result['description'] = htmlspecialchars($splitedLine[2]).' (repository: '.$repo_name.' commit: '.$splitedLine[3].')';
1039 $userObject = user_get_object_by_email($splitedLine[1]);
1040 if (is_a($userObject, 'FFUser')) {
1041 $result['realname'] = util_display_user($userObject->getUnixName(), $userObject->getID(), $userObject->getRealName());
1043 $result['realname'] = '';
1045 $splitedDate = explode(' ', $splitedLine[0]);
1046 $result['activity_date'] = $splitedDate[0];
1047 $result['subref_id'] = '';
1048 $params['results'][] = $result;
1053 if (!in_array($this->name, $params['ids']) && ($project->enableAnonSCM() || session_loggedin())) {
1054 $params['ids'][] = $this->name;
1055 $params['texts'][] = _('Git Commits');
1060 function scm_add_repo(&$params) {
1061 if ($params['scm_plugin'] != $this->name) {
1064 $project = $this->checkParams($params);
1069 if (!isset($params['repo_name'])) {
1073 if ($params['repo_name'] == $project->getUnixName()) {
1074 $params['error_msg'] = _('Cannot create a secondary repository with the same name as the primary');
1078 if (!util_is_valid_repository_name($params['repo_name'])) {
1079 $params['error_msg'] = _('This repository name is not valid');
1083 $result = db_query_params('SELECT count(*) AS count FROM scm_secondary_repos WHERE group_id=$1 AND repo_name = $2 AND plugin_id=$3',
1084 array($params['group_id'],
1085 $params['repo_name'],
1088 $params['error_msg'] = db_error();
1091 if (db_result($result, 0, 'count')) {
1092 $params['error_msg'] = sprintf(_('A repository %s already exists'), $params['repo_name']);
1098 if (isset($params['clone'])) {
1099 $url = $params['clone'];
1103 } elseif ((preg_match('|^git://|', $url) || preg_match('|^https?://|', $url))
1104 && preg_match('|^[-a-zA-Z0-9:./_]+$|', $url)) {
1105 // External URLs: OK, but they need to be validated to prevent a potential arbitrary command execution
1107 } elseif ($url == $project->getUnixName()) {
1109 } elseif (($result = db_query_params('SELECT count(*) AS count FROM scm_secondary_repos WHERE group_id=$1 AND repo_name = $2 AND plugin_id=$3',
1110 array($project->getID(),
1113 && db_result($result, 0, 'count')) {
1114 // Local repo: try to clone from an existing repo in same project
1118 $params['error_msg'] = _('Invalid URL from which to clone');
1123 if (isset($params['description'])) {
1124 $description = $params['description'];
1126 if ($clone && !$description) {
1127 $description = sprintf(_('Clone of %s'), $params['clone']);
1129 if (!$description) {
1130 $description = "Git repository $params[repo_name] for project ".$project->getUnixName();
1133 $result = db_query_params('INSERT INTO scm_secondary_repos (group_id, repo_name, description, clone_url, plugin_id) VALUES ($1, $2, $3, $4, $5)',
1134 array($params['group_id'],
1135 $params['repo_name'],
1140 $params['error_msg'] = db_error();
1147 function scm_admin_form(&$params) {
1149 $project = $this->checkParams($params);
1154 session_require_perm('project_admin', $params['group_id']);
1156 if (forge_get_config('allow_multiple_scm') && ($params['allow_multiple_scm'] > 1)) {
1157 echo html_ao('div', array('id' => 'tabber-'.$this->name, 'class' => 'tabbertab'));
1160 $project_name = $project->getUnixName();
1162 $result = db_query_params('SELECT repo_name, description, clone_url FROM scm_secondary_repos WHERE group_id=$1 AND next_action = $2 AND plugin_id=$3 ORDER BY repo_name',
1163 array($params['group_id'],
1164 SCM_EXTRA_REPO_ACTION_UPDATE,
1167 $params['error_msg'] = db_error();
1170 $existing_repos = array();
1171 while($data = db_fetch_array($result)) {
1172 $existing_repos[] = array('repo_name' => $data['repo_name'],
1173 'description' => $data['description'],
1174 'clone_url' => $data['clone_url']);
1176 if (count($existing_repos) == 0) {
1177 echo $HTML->information(_('No extra Git repository for project').' '.$project_name);
1179 echo html_e('h2', array(), sprintf(ngettext('Extra Git repository for project %1$s',
1180 'Extra Git repositories for project %1$s',
1181 count($existing_repos)), $project_name));
1182 $titleArr = array(_('Repository name'), ('Initial repository description'), _('Initial clone URL (if any)'), _('Delete'));
1183 echo $HTML->listTableTop($titleArr);
1184 foreach ($existing_repos as $key => $repo) {
1186 $cells[][] = html_e('kbd', array(), $repo['repo_name']);
1187 $cells[][] = $repo['description'];
1188 $cells[][] = $repo['clone_url'];
1189 $deleteForm = $HTML->openForm(array('name' => 'form_delete_repo_'.$repo['repo_name'], 'action' => getStringFromServer('PHP_SELF'), 'method' => 'post'));
1190 $deleteForm .= $HTML->html_input('group_id', '', '', 'hidden', $params['group_id']);
1191 $deleteForm .= $HTML->html_input('delete_repository', '', '', 'hidden', 1);
1192 $deleteForm .= $HTML->html_input('repo_name', '', '', 'hidden', $repo['repo_name']);
1193 $deleteForm .= $HTML->html_input('scm_plugin_id', '', '', 'hidden', $this->getID());
1194 $deleteForm .= $HTML->html_input('submit', '', '', 'submit', _('Delete'));
1195 $deleteForm .= $HTML->closeForm();
1196 $cells[][] = $deleteForm;
1197 echo $HTML->multiTableRow(array(), $cells);
1199 echo $HTML->listTableBottom();
1202 echo html_e('h2', array(), _('Create new Git repository for project').' '.$project_name);
1203 echo $HTML->openForm(array('name' => 'form_create_repo_scmgit', 'action' => getStringFromServer('PHP_SELF'), 'method' => 'post'));
1204 echo $HTML->html_input('group_id', '', '', 'hidden', $params['group_id']);
1205 echo $HTML->html_input('create_repository', '', '', 'hidden', 1);
1206 echo $HTML->html_input('scm_plugin', '', '', 'hidden', $this->name);
1207 echo $HTML->html_input('repo_name', '', html_e('strong', array(), _('Repository name')._(':')).utils_requiredField(), 'text', '', array('required' => 'required', 'size' => 20));
1209 echo $HTML->html_input('description', '', html_e('strong', array(), _('Description')._(':')), 'text', '', array('size' => 60));
1210 echo html_e('p', array(), html_e('strong', array(), _('Initial clone URL (or name of an existing repository in this project; leave empty to start with an empty repository)')._(':')).html_e('br').
1211 $HTML->html_input('clone', '', '', 'text', $project_name, array('size' => 60)));
1213 echo $HTML->html_input('cancel', '', '', 'submit', _('Cancel'), array(), array('style' => 'display: inline-block!important'));
1214 echo $HTML->html_input('submit', '', '', 'submit', _('Submit'), array(), array('style' => 'display: inline-block!important'));
1215 echo $HTML->closeForm();
1217 if ($project->usesPlugin('scmhook')) {
1218 $scmhookPlugin = plugin_get_object('scmhook');
1219 $scmhookPlugin->displayScmHook($project->getID(), $this->name);
1221 if (forge_get_config('allow_multiple_scm') && ($params['allow_multiple_scm'] > 1)) {
1222 echo html_ac(html_ap() - 1);
1226 function getCommits($project, $user = null, $nb_commits) {
1228 if ($project->usesPlugin($this->name) && forge_check_perm('scm', $project->getID(), 'read')) {
1229 // Grab&parse commit log
1230 $protocol = forge_get_config('use_ssl', 'scmgit') ? 'https://' : 'http://';
1231 $u = session_get_user();
1232 if ($project->enableAnonSCM())
1233 $server_script = '/anonscm/gitlog';
1235 $server_script = '/authscm/'.$u->getUnixName().'/gitlog';
1237 $email = $user->getEmail();
1238 $realname = $user->getFirstName().' '.$user->getLastName();
1239 $userunixname = $user->getUnixName();
1240 $params = '&mode=latest_user'
1241 .'&email='.urlencode($email)
1242 .'&realname='.urlencode($realname)
1243 .'&user_name='.$userunixname;
1245 $params = '&mode=latest';
1247 $script_url = $protocol.$this->getBoxForProject($project)
1249 .'?unix_group_name='.$project->getUnixName()
1251 .'&limit='.$nb_commits;
1252 $filename = tempnam('/tmp', 'gitlog');
1253 $f = fopen($filename, 'w');
1255 curl_setopt($ch, CURLOPT_URL, $script_url);
1256 curl_setopt($ch, CURLOPT_FILE, $f);
1257 curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
1258 curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
1259 curl_setopt($ch, CURLOPT_COOKIE, $_SERVER['HTTP_COOKIE']); // for session validation
1260 curl_setopt($ch, CURLOPT_USERAGENT, $_SERVER['HTTP_USER_AGENT']); // for session validation
1261 curl_setopt($ch, CURLOPT_HTTPHEADER,
1262 array('X-Forwarded-For: '.$_SERVER['REMOTE_ADDR'])); // for session validation
1263 $body = curl_exec($ch);
1264 if ($body === false) {
1265 $this->setError(curl_error($ch));
1268 fclose($f); // flush buffer
1269 $f = fopen($filename, 'r');
1273 while (!feof($f) && $data = fgets($f)) {
1274 $line = trim($data);
1275 $splitedLine = explode('||', $line);
1276 if (sizeof($splitedLine) == 4) {
1277 $commits[$i]['pluginName'] = $this->name;
1278 $commits[$i]['description'] = htmlspecialchars($splitedLine[2]);
1279 $commits[$i]['commit_id'] = $splitedLine[3];
1280 $splitedDate = explode(' ', $splitedLine[0]);
1281 $commits[$i]['date'] = $splitedDate[0];
1289 function get_scm_repo_list(&$params) {
1290 if (array_key_exists('group_name',$params)) {
1291 $unix_group_name = $params['group_name'];
1293 $unix_group_name = '';
1295 $protocol = forge_get_config('use_ssl', 'scmgit')? 'https' : 'http';
1296 if (session_loggedin()) {
1297 $u = user_get_object(user_getid());
1298 $d = $u->getUnixName();
1302 if ($unix_group_name) {
1303 $res = db_query_params("SELECT unix_group_name, groups.group_id FROM groups
1304 JOIN group_plugin ON (groups.group_id=group_plugin.group_id)
1305 WHERE groups.status=$1 AND group_plugin.plugin_id=$2 AND groups.unix_group_name=$3
1306 ORDER BY unix_group_name", array('A', $this->getID(), $unix_group_name));
1308 $res = db_query_params("SELECT unix_group_name, groups.group_id FROM groups
1309 JOIN group_plugin ON (groups.group_id=group_plugin.group_id)
1310 WHERE groups.status=$1 AND group_plugin.plugin_id=$2
1311 ORDER BY unix_group_name", array('A', $this->getID()));
1313 while ($arr = db_fetch_array($res)) {
1314 if (!forge_check_perm('scm', $arr['group_id'], 'read')) {
1318 $project = group_get_object($arr['group_id']);
1319 if (forge_get_config('use_smarthttp', 'scmgit')) {
1320 $urls[] = $protocol.'://'.$this->getBoxForProject($project).'/anonscm/git/'.$arr['unix_group_name'].'/'.$arr['unix_group_name'].'.git';
1322 if (session_loggedin()) {
1323 if (forge_get_config('use_ssh', 'scmgit')) {
1324 $urls[] = 'git+ssh://'.$d.'@'.$this->getBoxForProject($project).forge_get_config('repos_path', 'scmgit') .'/'. $arr['unix_group_name'] .'/'. $arr['unix_group_name'] .'.git';
1326 if (forge_get_config('use_smarthttp', 'scmgit')) {
1327 $urls[] = $protocol.'://'.$d.'@'.$this->getBoxForProject($project).'/authscm/'.$d.'/git/'.$arr['unix_group_name'].'/'.$arr['unix_group_name'].'.git';
1330 $results[] = array('group_id' => $arr['group_id'],
1331 'repository_type' => 'git',
1332 'repository_id' => $arr['unix_group_name'].'/git/'.$arr['unix_group_name'],
1333 'repository_urls' => $urls,
1336 if ($unix_group_name) {
1337 $res = db_query_params("SELECT unix_group_name, groups.group_id, repo_name
1339 JOIN group_plugin ON (groups.group_id=group_plugin.group_id)
1340 JOIN scm_secondary_repos ON (groups.group_id=scm_secondary_repos.group_id)
1341 WHERE groups.status=$1 AND group_plugin.plugin_id=$2 AND groups.unix_group_name=$3
1342 ORDER BY unix_group_name, repo_name", array('A', $this->getID(), $unix_group_name));
1344 $res = db_query_params("SELECT unix_group_name, groups.group_id, repo_name
1346 JOIN group_plugin ON (groups.group_id=group_plugin.group_id)
1347 JOIN scm_secondary_repos ON (groups.group_id=scm_secondary_repos.group_id)
1348 WHERE groups.status=$1 AND group_plugin.plugin_id=$2
1349 ORDER BY unix_group_name, repo_name", array('A', $this->getID()));
1351 while ($arr = db_fetch_array($res)) {
1352 if (!forge_check_perm('scm', $arr['group_id'], 'read')) {
1356 $project = group_get_object($arr['group_id']);
1357 if (forge_get_config('use_smarthttp', 'scmgit')) {
1358 $urls[] = $protocol.'://'.$this->getBoxForProject($project).'/anonscm/git/'.$arr['unix_group_name'].'/'.$arr['repo_name'].'.git';
1360 if (session_loggedin()) {
1361 if (forge_get_config('use_ssh', 'scmgit')) {
1362 $urls[] = 'git+ssh://'.$d.'@'.$this->getBoxForProject($project).forge_get_config('repos_path', 'scmgit') .'/'. $arr['unix_group_name'] .'/'. $arr['repo_name'] .'.git';
1364 if (forge_get_config('use_smarthttp', 'scmgit')) {
1365 $urls[] = $protocol.'://'.$d.'@'.$this->getBoxForProject($project).'/authscm/'.$d.'/git/'.$arr['unix_group_name'].'/'.$arr['repo_name'].'.git';
1368 $results[] = array('group_id' => $arr['group_id'],
1369 'repository_type' => 'git',
1370 'repository_id' => $arr['unix_group_name'].'/git/'.$arr['repo_name'],
1371 'repository_urls' => $urls,
1374 if ($unix_group_name) {
1375 $res = db_query_params("SELECT unix_group_name, groups.group_id, user_name
1377 JOIN group_plugin ON (groups.group_id=group_plugin.group_id)
1378 JOIN scm_personal_repos ON (groups.group_id=scm_personal_repos.group_id)
1379 JOIN users ON (scm_personal_repos.user_id=users.user_id)
1380 WHERE groups.status=$1 AND group_plugin.plugin_id=$2 AND users.status=$3 AND groups.unix_group_name=$4
1381 ORDER BY unix_group_name, user_name", array('A', $this->getID(), 'A', $unix_group_name));
1383 $res = db_query_params("SELECT unix_group_name, groups.group_id, user_name
1385 JOIN group_plugin ON (groups.group_id=group_plugin.group_id)
1386 JOIN scm_personal_repos ON (groups.group_id=scm_personal_repos.group_id)
1387 JOIN users ON (scm_personal_repos.user_id=users.user_id)
1388 WHERE groups.status=$1 AND group_plugin.plugin_id=$2 AND users.status=$3
1389 ORDER BY unix_group_name, user_name", array('A', $this->getID(), 'A'));
1391 while ($arr = db_fetch_array($res)) {
1392 if (!forge_check_perm('scm', $arr['group_id'], 'read')) {
1396 $project = group_get_object($arr['group_id']);
1397 if (forge_get_config('use_smarthttp', 'scmgit')) {
1398 $urls[] = $protocol.'://'.$this->getBoxForProject($project).'/anonscm/git/'.$arr['unix_group_name'].'/users/'.$arr['user_name'].'.git';
1400 if (session_loggedin()) {
1401 if (forge_get_config('use_ssh', 'scmgit')) {
1402 $urls[] = 'git+ssh://'.$d.'@'.$this->getBoxForProject($project).forge_get_config('repos_path', 'scmgit') .'/'. $arr['unix_group_name'] .'/users/'. $arr['user_name'] .'.git';
1404 if (forge_get_config('use_smarthttp', 'scmgit')) {
1405 $urls[] = $protocol.'://'.$d.'@'.$this->getBoxForProject($project).'/authscm/'.$d.'/git/'.$arr['unix_group_name'].'/users/'.$arr['user_name'].'.git';
1408 $results[] = array('group_id' => $arr['group_id'],
1409 'repository_type' => 'git',
1410 'repository_id' => $arr['unix_group_name'].'/git/users/'.$arr['user_name'],
1411 'repository_urls' => $urls,
1415 foreach ($results as $res) {
1416 $params['results'][] = $res;
1420 function get_scm_repo_info(&$params) {
1421 $rid = $params['repository_id'];
1422 $e = explode('/',$rid);
1423 if ($e[1] != 'git') {
1427 $p = array('group_name' => $g);
1428 $this->get_scm_repo_list($p);
1429 foreach ($p['results'] as $r) {
1430 if ($r['repository_id'] == $rid) {
1431 $params['results'] = $r;
1437 function parse_scm_repo_activities(&$params) {
1439 $res = db_query_params("SELECT unix_group_name, groups.group_id FROM groups
1440 JOIN group_plugin ON (groups.group_id=group_plugin.group_id)
1441 WHERE groups.status=$1 AND group_plugin.plugin_id=$2
1442 ORDER BY unix_group_name", array('A', $this->getID()));
1443 while ($arr = db_fetch_array($res)) {
1445 $el['rpath'] = forge_get_config('repos_path', 'scmgit') .'/'. $arr['unix_group_name'] .'/'. $arr['unix_group_name'] .'.git';
1446 $el['rid'] = $arr['unix_group_name'].'/git/'.$arr['unix_group_name'];
1447 $el['gid'] = $arr['group_id'];
1451 $res = db_query_params("SELECT unix_group_name, groups.group_id, repo_name
1453 JOIN group_plugin ON (groups.group_id=group_plugin.group_id)
1454 JOIN scm_secondary_repos ON (groups.group_id=scm_secondary_repos.group_id)
1455 WHERE groups.status=$1 AND group_plugin.plugin_id=$2
1456 ORDER BY unix_group_name, repo_name", array('A', $this->getID()));
1457 while ($arr = db_fetch_array($res)) {
1459 $el['rpath'] = forge_get_config('repos_path', 'scmgit') .'/'. $arr['unix_group_name'] .'/'. $arr['repo_name'] .'.git';
1460 $el['rid'] = $arr['unix_group_name'].'/git/'.$arr['repo_name'];
1461 $el['gid'] = $arr['group_id'];
1465 $res = db_query_params("SELECT unix_group_name, groups.group_id, user_name
1467 JOIN group_plugin ON (groups.group_id=group_plugin.group_id)
1468 JOIN scm_personal_repos ON (groups.group_id=scm_personal_repos.group_id)
1469 JOIN users ON (scm_personal_repos.user_id=users.user_id)
1470 WHERE groups.status=$1 AND group_plugin.plugin_id=$2 AND users.status=$3
1471 ORDER BY unix_group_name, user_name", array('A', $this->getID(), 'A'));
1472 while ($arr = db_fetch_array($res)) {
1474 $el['rpath'] = forge_get_config('repos_path', 'scmgit') .'/'. $arr['unix_group_name'] .'/users/'. $arr['user_name'] .'.git';
1475 $el['rid'] = $arr['unix_group_name'].'/git/users/'.$arr['user_name'];
1476 $el['gid'] = $arr['group_id'];
1480 $lastactivities = array();
1481 $res = db_query_params("SELECT repository_id, max(tstamp) AS last FROM scm_activities WHERE plugin_id=$1 GROUP BY repository_id",
1482 array($this->getID()));
1483 while ($arr = db_fetch_array($res)) {
1484 $lastactivities[$arr['repository_id']] = $arr['last'];
1487 foreach ($repos as $rdata) {
1489 if (array_key_exists($rdata['rid'], $lastactivities)) {
1490 $since = "--since=@".$lastactivities[$rdata['rid']];
1492 $rpath = $rdata['rpath'];
1494 $f = popen("GIT_DIR=\"$rpath\" git reflog --date=raw --all $since 2> /dev/null", "r");
1495 while (($l = fgets($f, 4096)) !== false) {
1496 if (preg_match("/@\{(?P<tstamp>[0-9]+) [-+0-9]+\}: push/", $l, $matches)) {
1497 if (array_key_exists($rdata['rid'], $lastactivities)
1498 && $matches['tstamp'] <= $lastactivities[$rdata['rid']]) {
1501 $tstamps[$matches['tstamp']] = 1;
1504 foreach ($tstamps as $t => $v) {
1505 $res = db_query_params("INSERT INTO scm_activities (group_id, plugin_id, repository_id, tstamp) VALUES ($1,$2,$3,$4)",
1506 array($rdata['gid'],
1514 function getRepositories($group, $autoinclude = true) {
1517 $repoarr[] = $group->getUnixName();
1519 $result = db_query_params('SELECT repo_name FROM scm_secondary_repos WHERE group_id = $1 AND next_action = $2 AND plugin_id = $3 ORDER BY repo_name',
1520 array($group->getID(),
1521 SCM_EXTRA_REPO_ACTION_UPDATE,
1523 while ($arr = db_fetch_array($result)) {
1524 $repoarr[] = $arr['repo_name'];
1532 // c-file-style: "bsd"