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 .= '<kbd>git clone '.$protocol.'://<i>'._('developername').'</i>@'.$this->getBoxForProject($project).'/authscm/<i>'._('developername').'</i>/git/'.$project->getUnixName() .'/'. $repo_name .'.git</kbd><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-'.$this->name);
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");
524 system("cd $repodir; LC_ALL=C git config core.sharedRepository group");
526 system("GIT_DIR=\"$repodir\" git init --quiet --bare --shared=group");
528 system("GIT_DIR=\"$repodir\" git update-server-info");
529 system("GIT_DIR=\"$repodir\" git config http.receivepack true");
530 system("GIT_DIR=\"$repodir\" git config core.logAllRefUpdates true");
531 if (is_file("$repodir/hooks/post-update.sample")) {
532 rename("$repodir/hooks/post-update.sample",
533 "$repodir/hooks/post-update");
535 if (!is_file("$repodir/hooks/post-update")) {
536 $f = fopen("$repodir/hooks/post-update", 'w');
537 fwrite($f, "exec git-update-server-info\n");
540 if (is_file("$repodir/hooks/post-update")) {
541 system("chmod +x $repodir/hooks/post-update");
543 $f = fopen("$repodir/description", "w");
544 fwrite($f, $description."\n");
546 system("chgrp -R $unix_group_rw $repodir");
547 system("chmod -R g=rwX,o=rX $repodir");
551 // Delete project-wide secondary repositories
552 $result = db_query_params ('SELECT repo_name FROM scm_secondary_repos WHERE group_id=$1 AND next_action = $2 AND plugin_id=$3',
553 array($project->getID(),
554 SCM_EXTRA_REPO_ACTION_DELETE,
556 $rows = db_numrows ($result);
557 for ($i=0; $i<$rows; $i++) {
558 $repo_name = db_result($result, $i, 'repo_name');
559 $repodir = $root . '/' . $repo_name . '.git';
560 if (util_is_valid_repository_name($repo_name)) {
561 system("rm -rf $repodir");
563 db_query_params ('DELETE FROM scm_secondary_repos WHERE group_id=$1 AND repo_name=$2 AND next_action = $3 AND plugin_id=$4',
564 array($project->getID(),
566 SCM_EXTRA_REPO_ACTION_DELETE,
570 // Create users' personal repositories
571 $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',
572 array($project->getID(),
575 $rows = db_numrows ($result);
576 if (!is_dir($root.'/users')) {
577 system("mkdir -p $root/users");
578 chgrp("$root/users", 'root'); // make it clear group members don't have write access
579 system("chmod 00755 $root/users");
581 for ($i=0; $i<$rows; $i++) {
582 $user_name = db_result($result, $i, 'user_name');
583 $repodir = $root . '/users/' . $user_name . '.git';
585 if (!is_dir($repodir) && mkdir ($repodir, 0700)) {
586 chown ($repodir, $user_name);
587 chgrp ($repodir, 'root'); // make it clear group members don't have write access
590 $params['project'] = $project;
591 $params['user_name'] = $user_name;
592 $params['root'] = $root;
593 $params['main_repo'] = $main_repo;
595 util_sudo_effective_user($user_name,
596 array("GitPlugin", "createUserRepo"),
600 $params['output'] = $output;
603 function updateRepositoryList($params) {
604 $config_dir = forge_get_config('config_path').'/plugins/scmgit';
605 if (!is_dir($config_dir)) {
606 mkdir($config_dir, 0755, true);
608 $fname = $config_dir . '/gitweb.conf';
609 $f = fopen($fname.'.new', 'w');
610 $rootdir = forge_get_config('repos_path', 'scmgit');
611 fwrite($f, "\$projectroot = '$rootdir';\n");
612 fwrite($f, "\$projects_list = '$config_dir/gitweb.list';\n");
613 fwrite($f, "\$anon_clone_url = '". util_make_url('/anonscm/git') . "';\n");
614 fwrite($f, "\$logo = '". util_make_url('/plugins/scmgit/git-logo.png') . "';\n");
615 fwrite($f, "\$favicon = '". util_make_url('/plugins/scmgit/git-favicon.png')."';\n");
616 fwrite($f, "\$stylesheet = '". util_make_url('/plugins/scmgit/gitweb.css')."';\n");
617 fwrite($f, "\$javascript = '". util_make_url('/plugins/scmgit/gitweb.js')."';\n");
618 fwrite($f, "\$site_html_head_string = '<script type=\"text/javascript\" src=\"". util_make_url('/scripts/iframe-resizer/iframeResizer.contentWindow.js'). "\" />';\n");
619 fwrite($f, "\$prevent_xss = 'true';\n");
620 fwrite($f, "\$site_footer = '".forge_get_config('source_path')."/plugins/scmgit/www/gitweb_footer.html';\n");
621 fwrite($f, "\$feature{'actions'}{'default'} = [('project home', '" .
622 util_make_url('/plugins/scmgit/?func=grouppage/%n') .
623 "', 'summary')];\n");
625 fwrite($f, "\$per_request_config = sub {\n");
627 fwrite($f, "push @git_base_url_list, qq,". util_make_url('/anonscm/git') .",;\n");
629 $protocol = forge_get_config('use_ssl', 'scmgit')? 'https' : 'http';
630 if (forge_get_config('use_smarthttp', 'scmgit')) {
631 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");
634 if (forge_get_config('use_ssh', 'scmgit')) {
635 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");
641 chmod($fname.'.new', 0644);
642 rename($fname.'.new', $fname);
644 # Optimized gitweb.list generation
645 # Useful to list all a project's repos: /gitweb?a=project_list;pf=project_name
646 $fname = $config_dir . '/gitweb.list';
647 $f = fopen($fname.'.new', 'w');
648 $res = db_query_params("SELECT unix_group_name FROM groups
649 JOIN group_plugin ON (groups.group_id=group_plugin.group_id)
650 WHERE groups.status=$1 AND group_plugin.plugin_id=$2
651 ORDER BY unix_group_name", array('A', $this->getID()));
652 while ($arr = db_fetch_array($res)) {
653 fwrite($f, $arr['unix_group_name'].'/'.$arr['unix_group_name'].".git\n");
655 $res = db_query_params("SELECT unix_group_name, repo_name
657 JOIN group_plugin ON (groups.group_id=group_plugin.group_id)
658 JOIN scm_secondary_repos ON (groups.group_id=scm_secondary_repos.group_id)
659 WHERE groups.status=$1 AND group_plugin.plugin_id=$2
660 ORDER BY unix_group_name, repo_name", array('A', $this->getID()));
661 while ($arr = db_fetch_array($res)) {
662 fwrite($f, $arr['unix_group_name'].'/'.$arr['repo_name'].".git\n");
664 $res = db_query_params("SELECT unix_group_name, user_name
666 JOIN group_plugin ON (groups.group_id=group_plugin.group_id)
667 JOIN scm_personal_repos ON (groups.group_id=scm_personal_repos.group_id)
668 JOIN users ON (scm_personal_repos.user_id=users.user_id)
669 WHERE groups.status=$1 AND group_plugin.plugin_id=$2 AND users.status=$3
670 ORDER BY unix_group_name, user_name", array('A', $this->getID(), 'A'));
671 while ($arr = db_fetch_array($res)) {
672 fwrite($f, $arr['unix_group_name'].'/users/'.$arr['user_name'].".git\n");
675 chmod($fname.'.new', 0644);
676 rename($fname.'.new', $fname);
679 function gatherStats($params) {
680 $project = $this->checkParams($params);
685 if ($params['mode'] == 'day') {
686 $year = $params['year'];
687 $month = $params['month'];
688 $day = $params['day'];
689 $month_string = sprintf("%04d%02d", $year, $month);
690 $start_time = gmmktime(0, 0, 0, $month, $day, $year);
691 $end_time = $start_time + 86400;
693 $repolist = $this->getRepositories($project);
694 foreach ($repolist as $repo_name) {
695 $this->gatherStatsRepo($project, $repo_name, $year, $month, $day);
700 function gatherStatsRepo($group, $project_reponame, $year, $month, $day) {
701 $month_string = sprintf("%04d%02d", $year, $month);
702 $start_time = gmmktime(0, 0, 0, $month, $day, $year);
703 $end_time = $start_time + 86400;
706 $usr_updates = array();
707 $usr_deletes = array();
708 $usr_commits = array();
715 $repo = forge_get_config('repos_path', 'scmgit') . '/' . $group->getUnixName() . '/' . $project_reponame . '.git';
716 if (!is_dir($repo) || !is_dir("$repo/refs")) {
717 // echo "No repository $repo\n";
721 # For each commit, get committer full name and e-mail (respecting git .mailmap file),
722 # and a list of files prefixed by their status (A/M/D)
723 $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');
727 // cleaning stats_cvs_* table for the current day for the default repository
728 $res = db_query_params('DELETE FROM stats_cvs_group WHERE month = $1 AND day = $2 AND group_id = $3 and reponame = $4',
734 echo "Error while cleaning stats_cvs_group\n";
739 $res = db_query_params ('DELETE FROM stats_cvs_user WHERE month = $1 AND day = $2 AND group_id = $3 and reponame = $4',
745 echo "Error while cleaning stats_cvs_user\n";
751 while (!feof($pipe) && $data = fgets($pipe)) {
753 // Replace bad UTF-8 with '?' - it's quite hard to make git output non-UTF-8
754 // (e.g. with i18n.commitEncoding = unknown) - but some users do!
755 // and this makes PostgreSQL choke (SQL> ERROR: invalid byte sequence for encoding "UTF8": 0xf9)
756 $line = mb_convert_encoding($line, 'UTF-8', 'UTF-8');
757 if (strlen($line) > 0) {
758 $result = preg_match("/^(?P<name>.+) <(?P<mail>.+)>/", $line, $matches);
761 $last_user = $matches['name'];
762 $user2email[$last_user] = $matches['mail'];
763 if (!isset($usr_adds[$last_user])) {
764 $usr_adds[$last_user] = 0;
765 $usr_updates[$last_user] = 0;
766 $usr_deletes[$last_user] = 0;
767 $usr_commits[$last_user] = 0;
770 $usr_commits[$last_user]++;
772 // Short-commit stats line
773 $result = preg_match("/^(?P<mode>[AMD])\s+(?P<file>.+)$/", $line, $matches);
776 if ($last_user == "")
778 if (!isset($usr_adds[$last_user]))
779 $usr_adds[$last_user] = 0;
780 if (!isset($usr_updates[$last_user]))
781 $usr_updates[$last_user] = 0;
782 if (!isset($usr_deletes[$last_user]))
783 $usr_deletes[$last_user] = 0;
784 if ($matches['mode'] == 'A') {
785 $usr_adds[$last_user]++;
787 } elseif ($matches['mode'] == 'M') {
788 $usr_updates[$last_user]++;
790 } elseif ($matches['mode'] == 'D') {
791 $usr_deletes[$last_user]++;
798 // inserting group results in stats_cvs_groups
799 if ($updates > 0 || $adds > 0 || $deletes > 0 || $commits > 0) {
800 if (!db_query_params('INSERT INTO stats_cvs_group (month, day, group_id, checkouts, commits, adds, updates, deletes, reponame)
801 VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)',
810 $project_reponame))) {
811 echo "Error while inserting into stats_cvs_group\n";
817 // building the user list
818 $user_list = array_unique(array_merge(array_keys($usr_adds), array_keys($usr_updates), array_keys($usr_deletes), array_keys($usr_commits)));
820 foreach ($user_list as $user) {
821 // Trying to get user id from user name or email
822 $u = user_get_object_by_name($user);
824 $user_id = $u->getID();
826 $res = db_query_params('SELECT user_id FROM users WHERE lower(realname)=$1 OR lower(email)=$2',
827 array(strtolower($user), strtolower($user2email[$user])));
828 if ($res && db_numrows($res) > 0) {
829 $user_id = db_result($res, 0, 'user_id');
835 $uc = isset($usr_commits[$user]) ? $usr_commits[$user] : 0;
836 $uu = isset($usr_updates[$user]) ? $usr_updates[$user] : 0;
837 $ua = isset($usr_adds[$user]) ? $usr_adds[$user] : 0;
838 $ud = isset($usr_deletes[$user]) ? $usr_deletes[$user] : 0;
839 if ($uu > 0 || $ua > 0 || $uc > 0 || $ud > 0) {
840 if (!db_query_params('INSERT INTO stats_cvs_user (month, day, group_id, user_id, commits, adds, updates, deletes, reponame)
841 VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)',
850 $project_reponame))) {
851 echo "Error while inserting into stats_cvs_user\n";
860 function generateSnapshots($params) {
861 $us = forge_get_config('use_scm_snapshots') ;
862 $ut = forge_get_config('use_scm_tarballs') ;
867 $project = $this->checkParams($params);
872 $group_name = $project->getUnixName();
874 $snapshot = forge_get_config('scm_snapshots_path').'/'.$group_name.'-scm-latest.tar'.util_get_compressed_file_extension();
875 $tarball = forge_get_config('scm_tarballs_path').'/'.$group_name.'-scmroot.tar'.util_get_compressed_file_extension();
877 if (!$project->enableAnonSCM()) {
878 if (is_file($snapshot)) {
881 if (is_file($tarball)) {
887 // TODO: ideally we generate one snapshot per git repository
888 $toprepo = forge_get_config('repos_path', 'scmgit');
889 $repo = $toprepo . '/' . $project->getUnixName() . '/' . $project->getUnixName() . '.git';
891 if (!is_dir($repo)) {
892 if (is_file($snapshot)) {
895 if (is_file($tarball)) {
901 // Skip empty repo (no HEAD present in repository)
902 $ref = trim(`GIT_DIR=$repo git symbolic-ref HEAD`);
903 if (!file_exists($repo.'/'.$ref)) {
907 $tmp = trim(`mktemp -d`);
912 $today = date('Y-m-d');
913 system("GIT_DIR=\"$repo\" git archive --format=tar --prefix=$group_name-scm-$today/ HEAD |".forge_get_config('compression_method')." > $tmp/snapshot");
914 chmod("$tmp/snapshot", 0644);
915 copy("$tmp/snapshot", $snapshot);
916 unlink("$tmp/snapshot");
919 system("tar cCf $toprepo - ".$project->getUnixName() ."|".forge_get_config('compression_method')."> $tmp/tarball");
920 chmod("$tmp/tarball", 0644);
921 copy("$tmp/tarball", $tarball);
922 unlink("$tmp/tarball");
923 system("rm -rf $tmp");
928 * widgets - 'widgets' hook handler
930 * @param array $params
933 function widgets($params) {
934 require_once 'common/widget/WidgetLayoutManager.class.php';
935 if ($params['owner_type'] == WidgetLayoutManager::OWNER_TYPE_USER) {
936 $params['fusionforge_widgets'][] = 'plugin_scmgit_user_myrepositories';
942 * Process the 'widget_instance' hook to create instances of the widgets
944 * @param array $params
946 function myPageBox($params) {
948 $user = UserManager::instance()->getCurrentUser();
949 require_once 'common/widget/WidgetLayoutManager.class.php';
950 if ($params['widget'] == 'plugin_scmgit_user_myrepositories') {
951 require_once $gfplugins.$this->name.'/common/scmgit_Widget_MyRepositories.class.php';
952 $params['instance'] = new scmgit_Widget_MyRepositories(WidgetLayoutManager::OWNER_TYPE_USER, $user->getId());
956 function weekly(&$params) {
957 $res = db_query_params('SELECT group_id FROM groups WHERE status=$1 AND use_scm=1 ORDER BY group_id DESC',
960 $params['output'] .= 'ScmGit Plugin: Unable to get list of projects using SCM: '.db_error();
964 $params['output'] .= 'ScmGit Plugin: Running "git gc --quiet" on '.db_numrows($res).' repositories.'."\n";
965 while ($row = db_fetch_array($res)) {
966 $project = group_get_object($row['group_id']);
967 if (!$project || !is_object($project)) {
969 } elseif ($project->isError()) {
972 if (!$project->usesPlugin($this->name)) {
976 $project_name = $project->getUnixName();
977 $repo = forge_get_config('repos_path', 'scmgit') . '/' . $project_name . '/' . $project_name . '.git';
980 $params['output'] .= $project_name.': '.`git gc --quiet 2>&1`;
985 function activity($params) {
986 $project = $this->checkParams($params);
990 if (isset($params['exclusive_area']) && ($params['exclusive_area'] != $this->name)) {
994 if (in_array('scmgit', $params['show']) || (count($params['show']) < 1)) {
995 if ($project->enableAnonSCM()) {
996 $server_script = '/anonscm/gitlog';
997 } elseif (session_loggedin()) {
998 $u = session_get_user();
999 $server_script = '/authscm/'.$u->getUnixName().'/gitlog';
1003 $repo_list = $this->getRepositories($project);
1004 $protocol = forge_get_config('use_ssl', 'scmgit') ? 'https://' : 'http://';
1005 foreach ($repo_list as $repo_name) {
1007 $script_url = $protocol.$this->getBoxForProject($project)
1009 .'?unix_group_name='.$project->getUnixName()
1010 .'&repo_name='.$repo_name
1012 .'&begin='.$params['begin']
1013 .'&end='.$params['end'];
1014 $filename = tempnam('/tmp', 'gitlog');
1015 $f = fopen($filename, 'w');
1017 curl_setopt($ch, CURLOPT_URL, $script_url);
1018 curl_setopt($ch, CURLOPT_FILE, $f);
1019 curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
1020 curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
1021 curl_setopt($ch, CURLOPT_COOKIE, @$_SERVER['HTTP_COOKIE']); // for session validation
1022 curl_setopt($ch, CURLOPT_USERAGENT, $_SERVER['HTTP_USER_AGENT']); // for session validation
1023 curl_setopt($ch, CURLOPT_HTTPHEADER,
1024 array('X-Forwarded-For: '.$_SERVER['REMOTE_ADDR'])); // for session validation
1025 $body = curl_exec($ch);
1026 if ($body === false) {
1027 $this->setError(curl_error($ch));
1030 fclose($f); // flush buffer
1031 $f = fopen($filename, 'r');
1034 while (!feof($f) && $data = fgets($f)) {
1035 $line = trim($data);
1036 $splitedLine = explode('||', $line);
1037 if (sizeof($splitedLine) == 4) {
1039 $result['section'] = 'scm';
1040 $result['group_id'] = $project->getID();
1041 $result['ref_id'] = 'browser.php?group_id='.$project->getID().'&scm_plugin='.$this->name.'&commit='.$splitedLine[3];
1042 $result['description'] = htmlspecialchars($splitedLine[2]).' (repository: '.$repo_name.' commit: '.$splitedLine[3].')';
1043 $userObject = user_get_object_by_email($splitedLine[1]);
1044 if (is_a($userObject, 'FFUser')) {
1045 $result['realname'] = util_display_user($userObject->getUnixName(), $userObject->getID(), $userObject->getRealName());
1047 $result['realname'] = '';
1049 $splitedDate = explode(' ', $splitedLine[0]);
1050 $result['activity_date'] = $splitedDate[0];
1051 $result['subref_id'] = '';
1052 $params['results'][] = $result;
1057 if (!in_array($this->name, $params['ids']) && ($project->enableAnonSCM() || session_loggedin())) {
1058 $params['ids'][] = $this->name;
1059 $params['texts'][] = _('Git Commits');
1064 function scm_add_repo(&$params) {
1065 if ($params['scm_plugin'] != $this->name) {
1068 $project = $this->checkParams($params);
1073 if (!isset($params['repo_name'])) {
1077 if ($params['repo_name'] == $project->getUnixName()) {
1078 $params['error_msg'] = _('Cannot create a secondary repository with the same name as the primary');
1082 if (!util_is_valid_repository_name($params['repo_name'])) {
1083 $params['error_msg'] = _('This repository name is not valid');
1087 $result = db_query_params('SELECT count(*) AS count FROM scm_secondary_repos WHERE group_id=$1 AND repo_name = $2 AND plugin_id=$3',
1088 array($params['group_id'],
1089 $params['repo_name'],
1092 $params['error_msg'] = db_error();
1095 if (db_result($result, 0, 'count')) {
1096 $params['error_msg'] = sprintf(_('A repository %s already exists'), $params['repo_name']);
1102 if (isset($params['clone'])) {
1103 $url = $params['clone'];
1107 } elseif ((preg_match('|^git://|', $url) || preg_match('|^https?://|', $url))
1108 && preg_match('|^[-a-zA-Z0-9:./_]+$|', $url)) {
1109 // External URLs: OK, but they need to be validated to prevent a potential arbitrary command execution
1111 } elseif ($url == $project->getUnixName()) {
1113 } 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',
1114 array($project->getID(),
1117 && db_result($result, 0, 'count')) {
1118 // Local repo: try to clone from an existing repo in same project
1122 $params['error_msg'] = _('Invalid URL from which to clone');
1127 if (isset($params['description'])) {
1128 $description = $params['description'];
1130 if ($clone && !$description) {
1131 $description = sprintf(_('Clone of %s'), $params['clone']);
1133 if (!$description) {
1134 $description = "Git repository $params[repo_name] for project ".$project->getUnixName();
1137 $result = db_query_params('INSERT INTO scm_secondary_repos (group_id, repo_name, description, clone_url, plugin_id) VALUES ($1, $2, $3, $4, $5)',
1138 array($params['group_id'],
1139 $params['repo_name'],
1144 $params['error_msg'] = db_error();
1151 function scm_admin_form(&$params) {
1153 $project = $this->checkParams($params);
1158 session_require_perm('project_admin', $params['group_id']);
1160 if (forge_get_config('allow_multiple_scm') && ($params['allow_multiple_scm'] > 1)) {
1161 echo html_ao('div', array('id' => 'tabber-'.$this->name, 'class' => 'tabbertab'));
1164 $project_name = $project->getUnixName();
1166 $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',
1167 array($params['group_id'],
1168 SCM_EXTRA_REPO_ACTION_UPDATE,
1171 $params['error_msg'] = db_error();
1174 $existing_repos = array();
1175 while($data = db_fetch_array($result)) {
1176 $existing_repos[] = array('repo_name' => $data['repo_name'],
1177 'description' => $data['description'],
1178 'clone_url' => $data['clone_url']);
1180 if (count($existing_repos) == 0) {
1181 echo $HTML->information(_('No extra Git repository for project').' '.$project_name);
1183 echo html_e('h2', array(), sprintf(ngettext('Extra Git repository for project %1$s',
1184 'Extra Git repositories for project %1$s',
1185 count($existing_repos)), $project_name));
1186 $titleArr = array(_('Repository name'), ('Initial repository description'), _('Initial clone URL (if any)'), _('Delete'));
1187 echo $HTML->listTableTop($titleArr);
1188 foreach ($existing_repos as $key => $repo) {
1190 $cells[][] = html_e('kbd', array(), $repo['repo_name']);
1191 $cells[][] = $repo['description'];
1192 $cells[][] = $repo['clone_url'];
1193 $deleteForm = $HTML->openForm(array('name' => 'form_delete_repo_'.$repo['repo_name'], 'action' => getStringFromServer('PHP_SELF'), 'method' => 'post'));
1194 $deleteForm .= $HTML->html_input('group_id', '', '', 'hidden', $params['group_id']);
1195 $deleteForm .= $HTML->html_input('delete_repository', '', '', 'hidden', 1);
1196 $deleteForm .= $HTML->html_input('repo_name', '', '', 'hidden', $repo['repo_name']);
1197 $deleteForm .= $HTML->html_input('scm_plugin_id', '', '', 'hidden', $this->getID());
1198 $deleteForm .= $HTML->html_input('submit', '', '', 'submit', _('Delete'));
1199 $deleteForm .= $HTML->closeForm();
1200 $cells[][] = $deleteForm;
1201 echo $HTML->multiTableRow(array(), $cells);
1203 echo $HTML->listTableBottom();
1206 echo html_e('h2', array(), _('Create new Git repository for project').' '.$project_name);
1207 echo $HTML->openForm(array('name' => 'form_create_repo_scmgit', 'action' => getStringFromServer('PHP_SELF'), 'method' => 'post'));
1208 echo $HTML->html_input('group_id', '', '', 'hidden', $params['group_id']);
1209 echo $HTML->html_input('create_repository', '', '', 'hidden', 1);
1210 echo $HTML->html_input('scm_plugin', '', '', 'hidden', $this->name);
1211 echo $HTML->html_input('repo_name', '', html_e('strong', array(), _('Repository name')._(':')).utils_requiredField(), 'text', '', array('required' => 'required', 'size' => 20));
1213 echo $HTML->html_input('description', '', html_e('strong', array(), _('Description')._(':')), 'text', '', array('size' => 60));
1214 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').
1215 $HTML->html_input('clone', '', '', 'text', $project_name, array('size' => 60)));
1217 echo $HTML->html_input('cancel', '', '', 'submit', _('Cancel'), array(), array('style' => 'display: inline-block!important'));
1218 echo $HTML->html_input('submit', '', '', 'submit', _('Submit'), array(), array('style' => 'display: inline-block!important'));
1219 echo $HTML->closeForm();
1221 if ($project->usesPlugin('scmhook')) {
1222 $scmhookPlugin = plugin_get_object('scmhook');
1223 $scmhookPlugin->displayScmHook($project->getID(), $this->name);
1225 if (forge_get_config('allow_multiple_scm') && ($params['allow_multiple_scm'] > 1)) {
1226 echo html_ac(html_ap() - 1);
1230 function getCommits($project, $user = null, $nb_commits) {
1232 if ($project->usesPlugin($this->name) && forge_check_perm('scm', $project->getID(), 'read')) {
1233 // Grab&parse commit log
1234 $protocol = forge_get_config('use_ssl', 'scmgit') ? 'https://' : 'http://';
1235 $u = session_get_user();
1236 if ($project->enableAnonSCM())
1237 $server_script = '/anonscm/gitlog';
1239 $server_script = '/authscm/'.$u->getUnixName().'/gitlog';
1241 $email = $user->getEmail();
1242 $realname = $user->getFirstName().' '.$user->getLastName();
1243 $userunixname = $user->getUnixName();
1244 $params = '&mode=latest_user'
1245 .'&email='.urlencode($email)
1246 .'&realname='.urlencode($realname)
1247 .'&user_name='.$userunixname;
1249 $params = '&mode=latest';
1251 $repo_list = $this->getRepositories($project);
1253 foreach ($repo_list as $repo_name) {
1254 $script_url = $protocol.$this->getBoxForProject($project)
1256 .'?unix_group_name='.$project->getUnixName()
1257 .'&repo_name='.$repo_name
1259 .'&limit='.$nb_commits;
1260 $filename = tempnam('/tmp', 'gitlog');
1261 $f = fopen($filename, 'w');
1263 curl_setopt($ch, CURLOPT_URL, $script_url);
1264 curl_setopt($ch, CURLOPT_FILE, $f);
1265 curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
1266 curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
1267 curl_setopt($ch, CURLOPT_COOKIE, $_SERVER['HTTP_COOKIE']); // for session validation
1268 curl_setopt($ch, CURLOPT_USERAGENT, $_SERVER['HTTP_USER_AGENT']); // for session validation
1269 curl_setopt($ch, CURLOPT_HTTPHEADER,
1270 array('X-Forwarded-For: '.$_SERVER['REMOTE_ADDR'])); // for session validation
1271 $body = curl_exec($ch);
1272 if ($body === false) {
1273 $this->setError(curl_error($ch));
1276 fclose($f); // flush buffer
1277 $f = fopen($filename, 'r');
1280 while (!feof($f) && $data = fgets($f)) {
1281 $line = trim($data);
1282 $splitedLine = explode('||', $line);
1283 if (sizeof($splitedLine) == 4) {
1284 $commits[$i]['pluginName'] = $this->name;
1285 $commits[$i]['description'] = htmlspecialchars($splitedLine[2]);
1286 $commits[$i]['commit_id'] = $splitedLine[3];
1287 $commits[$i]['repo_name'] = $repo_name;
1288 $splitedDate = explode(' ', $splitedLine[0]);
1289 $commits[$i]['date'] = $splitedDate[0];
1298 function get_scm_repo_list(&$params) {
1299 if (array_key_exists('group_name',$params)) {
1300 $unix_group_name = $params['group_name'];
1302 $unix_group_name = '';
1304 $protocol = forge_get_config('use_ssl', 'scmgit')? 'https' : 'http';
1305 if (session_loggedin()) {
1306 $u = user_get_object(user_getid());
1307 $d = $u->getUnixName();
1311 if ($unix_group_name) {
1312 $res = db_query_params("SELECT unix_group_name, groups.group_id FROM groups
1313 JOIN group_plugin ON (groups.group_id=group_plugin.group_id)
1314 WHERE groups.status=$1 AND group_plugin.plugin_id=$2 AND groups.unix_group_name=$3
1315 ORDER BY unix_group_name", array('A', $this->getID(), $unix_group_name));
1317 $res = db_query_params("SELECT unix_group_name, groups.group_id FROM groups
1318 JOIN group_plugin ON (groups.group_id=group_plugin.group_id)
1319 WHERE groups.status=$1 AND group_plugin.plugin_id=$2
1320 ORDER BY unix_group_name", array('A', $this->getID()));
1322 while ($arr = db_fetch_array($res)) {
1323 if (!forge_check_perm('scm', $arr['group_id'], 'read')) {
1327 $project = group_get_object($arr['group_id']);
1328 if (forge_get_config('use_smarthttp', 'scmgit')) {
1329 $urls[] = $protocol.'://'.$this->getBoxForProject($project).'/anonscm/git/'.$arr['unix_group_name'].'/'.$arr['unix_group_name'].'.git';
1331 if (session_loggedin()) {
1332 if (forge_get_config('use_ssh', 'scmgit')) {
1333 $urls[] = 'git+ssh://'.$d.'@'.$this->getBoxForProject($project).forge_get_config('repos_path', 'scmgit') .'/'. $arr['unix_group_name'] .'/'. $arr['unix_group_name'] .'.git';
1335 if (forge_get_config('use_smarthttp', 'scmgit')) {
1336 $urls[] = $protocol.'://'.$d.'@'.$this->getBoxForProject($project).'/authscm/'.$d.'/git/'.$arr['unix_group_name'].'/'.$arr['unix_group_name'].'.git';
1339 $results[] = array('group_id' => $arr['group_id'],
1340 'repository_type' => 'git',
1341 'repository_id' => $arr['unix_group_name'].'/git/'.$arr['unix_group_name'],
1342 'repository_urls' => $urls,
1345 if ($unix_group_name) {
1346 $res = db_query_params("SELECT unix_group_name, groups.group_id, repo_name
1348 JOIN group_plugin ON (groups.group_id=group_plugin.group_id)
1349 JOIN scm_secondary_repos ON (groups.group_id=scm_secondary_repos.group_id)
1350 WHERE groups.status=$1 AND group_plugin.plugin_id=$2 AND groups.unix_group_name=$3
1351 ORDER BY unix_group_name, repo_name", array('A', $this->getID(), $unix_group_name));
1353 $res = db_query_params("SELECT unix_group_name, groups.group_id, repo_name
1355 JOIN group_plugin ON (groups.group_id=group_plugin.group_id)
1356 JOIN scm_secondary_repos ON (groups.group_id=scm_secondary_repos.group_id)
1357 WHERE groups.status=$1 AND group_plugin.plugin_id=$2
1358 ORDER BY unix_group_name, repo_name", array('A', $this->getID()));
1360 while ($arr = db_fetch_array($res)) {
1361 if (!forge_check_perm('scm', $arr['group_id'], 'read')) {
1365 $project = group_get_object($arr['group_id']);
1366 if (forge_get_config('use_smarthttp', 'scmgit')) {
1367 $urls[] = $protocol.'://'.$this->getBoxForProject($project).'/anonscm/git/'.$arr['unix_group_name'].'/'.$arr['repo_name'].'.git';
1369 if (session_loggedin()) {
1370 if (forge_get_config('use_ssh', 'scmgit')) {
1371 $urls[] = 'git+ssh://'.$d.'@'.$this->getBoxForProject($project).forge_get_config('repos_path', 'scmgit') .'/'. $arr['unix_group_name'] .'/'. $arr['repo_name'] .'.git';
1373 if (forge_get_config('use_smarthttp', 'scmgit')) {
1374 $urls[] = $protocol.'://'.$d.'@'.$this->getBoxForProject($project).'/authscm/'.$d.'/git/'.$arr['unix_group_name'].'/'.$arr['repo_name'].'.git';
1377 $results[] = array('group_id' => $arr['group_id'],
1378 'repository_type' => 'git',
1379 'repository_id' => $arr['unix_group_name'].'/git/'.$arr['repo_name'],
1380 'repository_urls' => $urls,
1383 if ($unix_group_name) {
1384 $res = db_query_params("SELECT unix_group_name, groups.group_id, user_name
1386 JOIN group_plugin ON (groups.group_id=group_plugin.group_id)
1387 JOIN scm_personal_repos ON (groups.group_id=scm_personal_repos.group_id)
1388 JOIN users ON (scm_personal_repos.user_id=users.user_id)
1389 WHERE groups.status=$1 AND group_plugin.plugin_id=$2 AND users.status=$3 AND groups.unix_group_name=$4
1390 ORDER BY unix_group_name, user_name", array('A', $this->getID(), 'A', $unix_group_name));
1392 $res = db_query_params("SELECT unix_group_name, groups.group_id, user_name
1394 JOIN group_plugin ON (groups.group_id=group_plugin.group_id)
1395 JOIN scm_personal_repos ON (groups.group_id=scm_personal_repos.group_id)
1396 JOIN users ON (scm_personal_repos.user_id=users.user_id)
1397 WHERE groups.status=$1 AND group_plugin.plugin_id=$2 AND users.status=$3
1398 ORDER BY unix_group_name, user_name", array('A', $this->getID(), 'A'));
1400 while ($arr = db_fetch_array($res)) {
1401 if (!forge_check_perm('scm', $arr['group_id'], 'read')) {
1405 $project = group_get_object($arr['group_id']);
1406 if (forge_get_config('use_smarthttp', 'scmgit')) {
1407 $urls[] = $protocol.'://'.$this->getBoxForProject($project).'/anonscm/git/'.$arr['unix_group_name'].'/users/'.$arr['user_name'].'.git';
1409 if (session_loggedin()) {
1410 if (forge_get_config('use_ssh', 'scmgit')) {
1411 $urls[] = 'git+ssh://'.$d.'@'.$this->getBoxForProject($project).forge_get_config('repos_path', 'scmgit') .'/'. $arr['unix_group_name'] .'/users/'. $arr['user_name'] .'.git';
1413 if (forge_get_config('use_smarthttp', 'scmgit')) {
1414 $urls[] = $protocol.'://'.$d.'@'.$this->getBoxForProject($project).'/authscm/'.$d.'/git/'.$arr['unix_group_name'].'/users/'.$arr['user_name'].'.git';
1417 $results[] = array('group_id' => $arr['group_id'],
1418 'repository_type' => 'git',
1419 'repository_id' => $arr['unix_group_name'].'/git/users/'.$arr['user_name'],
1420 'repository_urls' => $urls,
1424 foreach ($results as $res) {
1425 $params['results'][] = $res;
1429 function get_scm_repo_info(&$params) {
1430 $rid = $params['repository_id'];
1431 $e = explode('/',$rid);
1432 if ($e[1] != 'git') {
1436 $p = array('group_name' => $g);
1437 $this->get_scm_repo_list($p);
1438 foreach ($p['results'] as $r) {
1439 if ($r['repository_id'] == $rid) {
1440 $params['results'] = $r;
1446 function parse_scm_repo_activities(&$params) {
1448 $res = db_query_params("SELECT unix_group_name, groups.group_id FROM groups
1449 JOIN group_plugin ON (groups.group_id=group_plugin.group_id)
1450 WHERE groups.status=$1 AND group_plugin.plugin_id=$2
1451 ORDER BY unix_group_name", array('A', $this->getID()));
1452 while ($arr = db_fetch_array($res)) {
1454 $el['rpath'] = forge_get_config('repos_path', 'scmgit') .'/'. $arr['unix_group_name'] .'/'. $arr['unix_group_name'] .'.git';
1455 $el['rid'] = $arr['unix_group_name'].'/git/'.$arr['unix_group_name'];
1456 $el['gid'] = $arr['group_id'];
1460 $res = db_query_params("SELECT unix_group_name, groups.group_id, repo_name
1462 JOIN group_plugin ON (groups.group_id=group_plugin.group_id)
1463 JOIN scm_secondary_repos ON (groups.group_id=scm_secondary_repos.group_id)
1464 WHERE groups.status=$1 AND group_plugin.plugin_id=$2
1465 ORDER BY unix_group_name, repo_name", array('A', $this->getID()));
1466 while ($arr = db_fetch_array($res)) {
1468 $el['rpath'] = forge_get_config('repos_path', 'scmgit') .'/'. $arr['unix_group_name'] .'/'. $arr['repo_name'] .'.git';
1469 $el['rid'] = $arr['unix_group_name'].'/git/'.$arr['repo_name'];
1470 $el['gid'] = $arr['group_id'];
1474 $res = db_query_params("SELECT unix_group_name, groups.group_id, user_name
1476 JOIN group_plugin ON (groups.group_id=group_plugin.group_id)
1477 JOIN scm_personal_repos ON (groups.group_id=scm_personal_repos.group_id)
1478 JOIN users ON (scm_personal_repos.user_id=users.user_id)
1479 WHERE groups.status=$1 AND group_plugin.plugin_id=$2 AND users.status=$3
1480 ORDER BY unix_group_name, user_name", array('A', $this->getID(), 'A'));
1481 while ($arr = db_fetch_array($res)) {
1483 $el['rpath'] = forge_get_config('repos_path', 'scmgit') .'/'. $arr['unix_group_name'] .'/users/'. $arr['user_name'] .'.git';
1484 $el['rid'] = $arr['unix_group_name'].'/git/users/'.$arr['user_name'];
1485 $el['gid'] = $arr['group_id'];
1489 $lastactivities = array();
1490 $res = db_query_params("SELECT repository_id, max(tstamp) AS last FROM scm_activities WHERE plugin_id=$1 GROUP BY repository_id",
1491 array($this->getID()));
1492 while ($arr = db_fetch_array($res)) {
1493 $lastactivities[$arr['repository_id']] = $arr['last'];
1496 foreach ($repos as $rdata) {
1498 if (array_key_exists($rdata['rid'], $lastactivities)) {
1499 $since = "--since=@".$lastactivities[$rdata['rid']];
1501 $rpath = $rdata['rpath'];
1503 $f = popen("GIT_DIR=\"$rpath\" git reflog --date=raw --all $since 2> /dev/null", "r");
1504 while (($l = fgets($f, 4096)) !== false) {
1505 if (preg_match("/@\{(?P<tstamp>[0-9]+) [-+0-9]+\}: push/", $l, $matches)) {
1506 if (array_key_exists($rdata['rid'], $lastactivities)
1507 && $matches['tstamp'] <= $lastactivities[$rdata['rid']]) {
1510 $tstamps[$matches['tstamp']] = 1;
1513 foreach ($tstamps as $t => $v) {
1514 $res = db_query_params("INSERT INTO scm_activities (group_id, plugin_id, repository_id, tstamp) VALUES ($1,$2,$3,$4)",
1515 array($rdata['gid'],
1523 function getRepositories($group, $autoinclude = true) {
1526 $repoarr[] = $group->getUnixName();
1528 $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',
1529 array($group->getID(),
1530 SCM_EXTRA_REPO_ACTION_UPDATE,
1532 while ($arr = db_fetch_array($result)) {
1533 $repoarr[] = $arr['repo_name'];
1541 // c-file-style: "bsd"