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 $protocol = forge_get_config('use_ssl', 'scmgit')? 'https://' : 'http://';
614 fwrite($f, "\$anon_clone_url = '".$protocol.forge_get_config('scm_host').'/anonscm/git'."';\n");
615 fwrite($f, "\$logo = '".$protocol.forge_get_config('scm_host').'/plugins/scmgit/git-logo.png'."';\n");
616 fwrite($f, "\$favicon = '".$protocol.forge_get_config('scm_host').'/plugins/scmgit/git-favicon.png'."';\n");
617 fwrite($f, "\$stylesheet = '".$protocol.forge_get_config('scm_host').'/plugins/scmgit/gitweb.css'."';\n");
618 fwrite($f, "\$javascript = '".$protocol.forge_get_config('scm_host').'/plugins/scmgit/gitweb.js'."';\n");
619 fwrite($f, "\$site_html_head_string = '<script type=\"text/javascript\" src=\"".$protocol.forge_get_config('scm_host').'/scripts/iframe-resizer/iframeResizer.contentWindow.js'. "\" />';\n");
620 fwrite($f, "\$prevent_xss = 'true';\n");
621 fwrite($f, "\$site_footer = '".forge_get_config('source_path')."/plugins/scmgit/www/gitweb_footer.html';\n");
622 fwrite($f, "\$feature{'actions'}{'default'} = [('project home', '" .
623 util_make_url('/plugins/scmgit/?func=grouppage/%n') .
624 "', 'summary')];\n");
626 fwrite($f, "\$per_request_config = sub {\n");
628 fwrite($f, "push @git_base_url_list, qq,".forge_get_config('scm_host').'/anonscm/git'.",;\n");
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';
937 } else if ($params['owner_type'] == WidgetLayoutManager::OWNER_TYPE_USERHOME) {
938 $params['fusionforge_widgets'][] = 'plugin_scmgit_user_userrepositories';
944 * Process the 'widget_instance' hook to create instances of the widgets
946 * @param array $params
948 function myPageBox($params) {
950 $user = UserManager::instance()->getCurrentUser();
951 require_once 'common/widget/WidgetLayoutManager.class.php';
952 if ($params['widget'] == 'plugin_scmgit_user_myrepositories') {
953 require_once $gfplugins.$this->name.'/common/scmgit_Widget_MyRepositories.class.php';
954 $params['instance'] = new scmgit_Widget_MyRepositories(WidgetLayoutManager::OWNER_TYPE_USER, $user->getId());
955 } elseif ($params['widget'] == 'plugin_scmgit_user_userrepositories') {
956 require_once $gfplugins.$this->name.'/common/scmgit_Widget_UserRepositories.class.php';
957 $params['instance'] = new scmgit_Widget_UserRepositories(WidgetLayoutManager::OWNER_TYPE_USERHOME, $user->getId());
961 function weekly(&$params) {
962 $res = db_query_params('SELECT group_id FROM groups WHERE status=$1 AND use_scm=1 ORDER BY group_id DESC',
965 $params['output'] .= 'ScmGit Plugin: Unable to get list of projects using SCM: '.db_error();
969 $params['output'] .= 'ScmGit Plugin: Running "git gc --quiet" on '.db_numrows($res).' repositories.'."\n";
970 while ($row = db_fetch_array($res)) {
971 $project = group_get_object($row['group_id']);
972 if (!$project || !is_object($project)) {
974 } elseif ($project->isError()) {
977 if (!$project->usesPlugin($this->name)) {
981 $project_name = $project->getUnixName();
982 $repo = forge_get_config('repos_path', 'scmgit') . '/' . $project_name . '/' . $project_name . '.git';
985 $params['output'] .= $project_name.': '.`git gc --quiet 2>&1`;
990 function activity($params) {
991 $project = $this->checkParams($params);
995 if (isset($params['exclusive_area']) && ($params['exclusive_area'] != $this->name)) {
999 if (in_array('scmgit', $params['show']) || (count($params['show']) < 1)) {
1000 if ($project->enableAnonSCM()) {
1001 $server_script = '/anonscm/gitlog';
1002 } elseif (session_loggedin()) {
1003 $u = session_get_user();
1004 $server_script = '/authscm/'.$u->getUnixName().'/gitlog';
1008 $repo_list = $this->getRepositories($project);
1009 $protocol = forge_get_config('use_ssl', 'scmgit') ? 'https://' : 'http://';
1010 foreach ($repo_list as $repo_name) {
1012 $script_url = $protocol.$this->getBoxForProject($project)
1014 .'?unix_group_name='.$project->getUnixName()
1015 .'&repo_name='.$repo_name
1017 .'&begin='.$params['begin']
1018 .'&end='.$params['end'];
1019 $filename = tempnam('/tmp', 'gitlog');
1020 $f = fopen($filename, 'w');
1022 curl_setopt($ch, CURLOPT_URL, $script_url);
1023 curl_setopt($ch, CURLOPT_FILE, $f);
1024 curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
1025 curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
1026 curl_setopt($ch, CURLOPT_COOKIE, @$_SERVER['HTTP_COOKIE']); // for session validation
1027 curl_setopt($ch, CURLOPT_USERAGENT, $_SERVER['HTTP_USER_AGENT']); // for session validation
1028 curl_setopt($ch, CURLOPT_HTTPHEADER,
1029 array('X-Forwarded-For: '.$_SERVER['REMOTE_ADDR'])); // for session validation
1030 $body = curl_exec($ch);
1031 if ($body === false) {
1032 $this->setError(curl_error($ch));
1035 fclose($f); // flush buffer
1036 $f = fopen($filename, 'r');
1039 while (!feof($f) && $data = fgets($f)) {
1040 $line = trim($data);
1041 $splitedLine = explode('||', $line);
1042 if (sizeof($splitedLine) == 4) {
1044 $result['section'] = 'scm';
1045 $result['group_id'] = $project->getID();
1046 $result['ref_id'] = 'browser.php?group_id='.$project->getID().'&scm_plugin='.$this->name.'&commit='.$splitedLine[3];
1047 $result['description'] = htmlspecialchars($splitedLine[2]).' (repository: '.$repo_name.' commit: '.$splitedLine[3].')';
1048 $userObject = user_get_object_by_email($splitedLine[1]);
1049 if (is_a($userObject, 'FFUser')) {
1050 $result['realname'] = util_display_user($userObject->getUnixName(), $userObject->getID(), $userObject->getRealName());
1052 $result['realname'] = '';
1054 $splitedDate = explode(' ', $splitedLine[0]);
1055 $result['activity_date'] = $splitedDate[0];
1056 $result['subref_id'] = '';
1057 $params['results'][] = $result;
1062 if (!in_array($this->name, $params['ids']) && ($project->enableAnonSCM() || session_loggedin())) {
1063 $params['ids'][] = $this->name;
1064 $params['texts'][] = _('Git Commits');
1069 function scm_add_repo(&$params) {
1070 if ($params['scm_plugin'] != $this->name) {
1073 $project = $this->checkParams($params);
1078 if (!isset($params['repo_name'])) {
1082 if ($params['repo_name'] == $project->getUnixName()) {
1083 $params['error_msg'] = _('Cannot create a secondary repository with the same name as the primary');
1087 if (!util_is_valid_repository_name($params['repo_name'])) {
1088 $params['error_msg'] = _('This repository name is not valid');
1092 $result = db_query_params('SELECT count(*) AS count FROM scm_secondary_repos WHERE group_id=$1 AND repo_name = $2 AND plugin_id=$3',
1093 array($params['group_id'],
1094 $params['repo_name'],
1097 $params['error_msg'] = db_error();
1100 if (db_result($result, 0, 'count')) {
1101 $params['error_msg'] = sprintf(_('A repository %s already exists'), $params['repo_name']);
1107 if (isset($params['clone'])) {
1108 $url = $params['clone'];
1112 } elseif ((preg_match('|^git://|', $url) || preg_match('|^https?://|', $url))
1113 && preg_match('|^[-a-zA-Z0-9:./_]+$|', $url)) {
1114 // External URLs: OK, but they need to be validated to prevent a potential arbitrary command execution
1116 } elseif ($url == $project->getUnixName()) {
1118 } 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',
1119 array($project->getID(),
1122 && db_result($result, 0, 'count')) {
1123 // Local repo: try to clone from an existing repo in same project
1127 $params['error_msg'] = _('Invalid URL from which to clone');
1132 if (isset($params['description'])) {
1133 $description = $params['description'];
1135 if ($clone && !$description) {
1136 $description = sprintf(_('Clone of %s'), $params['clone']);
1138 if (!$description) {
1139 $description = "Git repository $params[repo_name] for project ".$project->getUnixName();
1142 $result = db_query_params('INSERT INTO scm_secondary_repos (group_id, repo_name, description, clone_url, plugin_id) VALUES ($1, $2, $3, $4, $5)',
1143 array($params['group_id'],
1144 $params['repo_name'],
1149 $params['error_msg'] = db_error();
1156 function scm_admin_form(&$params) {
1158 $project = $this->checkParams($params);
1163 session_require_perm('project_admin', $params['group_id']);
1165 if (forge_get_config('allow_multiple_scm') && ($params['allow_multiple_scm'] > 1)) {
1166 echo html_ao('div', array('id' => 'tabber-'.$this->name, 'class' => 'tabbertab'));
1169 $project_name = $project->getUnixName();
1171 $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',
1172 array($params['group_id'],
1173 SCM_EXTRA_REPO_ACTION_UPDATE,
1176 $params['error_msg'] = db_error();
1179 $existing_repos = array();
1180 while($data = db_fetch_array($result)) {
1181 $existing_repos[] = array('repo_name' => $data['repo_name'],
1182 'description' => $data['description'],
1183 'clone_url' => $data['clone_url']);
1185 if (count($existing_repos) == 0) {
1186 echo $HTML->information(_('No extra Git repository for project').' '.$project_name);
1188 echo html_e('h2', array(), sprintf(ngettext('Extra Git repository for project %1$s',
1189 'Extra Git repositories for project %1$s',
1190 count($existing_repos)), $project_name));
1191 $titleArr = array(_('Repository name'), ('Initial repository description'), _('Initial clone URL (if any)'), _('Delete'));
1192 echo $HTML->listTableTop($titleArr);
1193 foreach ($existing_repos as $key => $repo) {
1195 $cells[][] = html_e('kbd', array(), $repo['repo_name']);
1196 $cells[][] = $repo['description'];
1197 $cells[][] = $repo['clone_url'];
1198 $deleteForm = $HTML->openForm(array('name' => 'form_delete_repo_'.$repo['repo_name'], 'action' => getStringFromServer('PHP_SELF'), 'method' => 'post'));
1199 $deleteForm .= $HTML->html_input('group_id', '', '', 'hidden', $params['group_id']);
1200 $deleteForm .= $HTML->html_input('delete_repository', '', '', 'hidden', 1);
1201 $deleteForm .= $HTML->html_input('repo_name', '', '', 'hidden', $repo['repo_name']);
1202 $deleteForm .= $HTML->html_input('scm_plugin_id', '', '', 'hidden', $this->getID());
1203 $deleteForm .= $HTML->html_input('submit', '', '', 'submit', _('Delete'));
1204 $deleteForm .= $HTML->closeForm();
1205 $cells[][] = $deleteForm;
1206 echo $HTML->multiTableRow(array(), $cells);
1208 echo $HTML->listTableBottom();
1211 echo html_e('h2', array(), _('Create new Git repository for project').' '.$project_name);
1212 echo $HTML->openForm(array('name' => 'form_create_repo_scmgit', 'action' => getStringFromServer('PHP_SELF'), 'method' => 'post'));
1213 echo $HTML->html_input('group_id', '', '', 'hidden', $params['group_id']);
1214 echo $HTML->html_input('create_repository', '', '', 'hidden', 1);
1215 echo $HTML->html_input('scm_plugin', '', '', 'hidden', $this->name);
1216 echo $HTML->html_input('repo_name', '', html_e('strong', array(), _('Repository name')._(':')).utils_requiredField(), 'text', '', array('required' => 'required', 'size' => 20));
1218 echo $HTML->html_input('description', '', html_e('strong', array(), _('Description')._(':')), 'text', '', array('size' => 60));
1219 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').
1220 $HTML->html_input('clone', '', '', 'text', $project_name, array('size' => 60)));
1222 echo $HTML->html_input('cancel', '', '', 'submit', _('Cancel'), array(), array('style' => 'display: inline-block!important'));
1223 echo $HTML->html_input('submit', '', '', 'submit', _('Submit'), array(), array('style' => 'display: inline-block!important'));
1224 echo $HTML->closeForm();
1226 if ($project->usesPlugin('scmhook')) {
1227 $scmhookPlugin = plugin_get_object('scmhook');
1228 $scmhookPlugin->displayScmHook($project->getID(), $this->name);
1230 if (forge_get_config('allow_multiple_scm') && ($params['allow_multiple_scm'] > 1)) {
1231 echo html_ac(html_ap() - 1);
1235 function getCommits($project, $user = null, $nb_commits) {
1237 if ($project->usesPlugin($this->name) && forge_check_perm('scm', $project->getID(), 'read')) {
1238 // Grab&parse commit log
1239 $protocol = forge_get_config('use_ssl', 'scmgit') ? 'https://' : 'http://';
1240 $u = session_get_user();
1241 if ($project->enableAnonSCM())
1242 $server_script = '/anonscm/gitlog';
1244 $server_script = '/authscm/'.$u->getUnixName().'/gitlog';
1246 $email = $user->getEmail();
1247 $realname = $user->getFirstName().' '.$user->getLastName();
1248 $userunixname = $user->getUnixName();
1249 $params = '&mode=latest_user'
1250 .'&email='.urlencode($email)
1251 .'&realname='.urlencode($realname)
1252 .'&user_name='.$userunixname;
1254 $params = '&mode=latest';
1256 $repo_list = $this->getRepositories($project);
1258 foreach ($repo_list as $repo_name) {
1259 $script_url = $protocol.$this->getBoxForProject($project)
1261 .'?unix_group_name='.$project->getUnixName()
1262 .'&repo_name='.$repo_name
1264 .'&limit='.$nb_commits;
1265 $filename = tempnam('/tmp', 'gitlog');
1266 $f = fopen($filename, 'w');
1268 curl_setopt($ch, CURLOPT_URL, $script_url);
1269 curl_setopt($ch, CURLOPT_FILE, $f);
1270 curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
1271 curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
1272 curl_setopt($ch, CURLOPT_COOKIE, $_SERVER['HTTP_COOKIE']); // for session validation
1273 curl_setopt($ch, CURLOPT_USERAGENT, $_SERVER['HTTP_USER_AGENT']); // for session validation
1274 curl_setopt($ch, CURLOPT_HTTPHEADER,
1275 array('X-Forwarded-For: '.$_SERVER['REMOTE_ADDR'])); // for session validation
1276 $body = curl_exec($ch);
1277 if ($body === false) {
1278 $this->setError(curl_error($ch));
1281 fclose($f); // flush buffer
1282 $f = fopen($filename, 'r');
1285 while (!feof($f) && $data = fgets($f)) {
1286 $line = trim($data);
1287 $splitedLine = explode('||', $line);
1288 if (sizeof($splitedLine) == 4) {
1289 $commits[$i]['pluginName'] = $this->name;
1290 $commits[$i]['description'] = htmlspecialchars($splitedLine[2]);
1291 $commits[$i]['commit_id'] = $splitedLine[3];
1292 $commits[$i]['repo_name'] = $repo_name;
1293 $splitedDate = explode(' ', $splitedLine[0]);
1294 $commits[$i]['date'] = $splitedDate[0];
1303 function get_scm_repo_list(&$params) {
1304 if (array_key_exists('group_name',$params)) {
1305 $unix_group_name = $params['group_name'];
1307 $unix_group_name = '';
1309 $protocol = forge_get_config('use_ssl', 'scmgit')? 'https' : 'http';
1310 if (session_loggedin()) {
1311 $u = user_get_object(user_getid());
1312 $d = $u->getUnixName();
1316 if ($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 AND groups.unix_group_name=$3
1320 ORDER BY unix_group_name", array('A', $this->getID(), $unix_group_name));
1322 $res = db_query_params("SELECT unix_group_name, groups.group_id FROM groups
1323 JOIN group_plugin ON (groups.group_id=group_plugin.group_id)
1324 WHERE groups.status=$1 AND group_plugin.plugin_id=$2
1325 ORDER BY unix_group_name", array('A', $this->getID()));
1327 while ($arr = db_fetch_array($res)) {
1328 if (!forge_check_perm('scm', $arr['group_id'], 'read')) {
1332 $project = group_get_object($arr['group_id']);
1333 if (forge_get_config('use_smarthttp', 'scmgit')) {
1334 $urls[] = $protocol.'://'.$this->getBoxForProject($project).'/anonscm/git/'.$arr['unix_group_name'].'/'.$arr['unix_group_name'].'.git';
1336 if (session_loggedin()) {
1337 if (forge_get_config('use_ssh', 'scmgit')) {
1338 $urls[] = 'git+ssh://'.$d.'@'.$this->getBoxForProject($project).forge_get_config('repos_path', 'scmgit') .'/'. $arr['unix_group_name'] .'/'. $arr['unix_group_name'] .'.git';
1340 if (forge_get_config('use_smarthttp', 'scmgit')) {
1341 $urls[] = $protocol.'://'.$d.'@'.$this->getBoxForProject($project).'/authscm/'.$d.'/git/'.$arr['unix_group_name'].'/'.$arr['unix_group_name'].'.git';
1344 $results[] = array('group_id' => $arr['group_id'],
1345 'repository_type' => 'git',
1346 'repository_id' => $arr['unix_group_name'].'/git/'.$arr['unix_group_name'],
1347 'repository_urls' => $urls,
1350 if ($unix_group_name) {
1351 $res = db_query_params("SELECT unix_group_name, groups.group_id, repo_name
1353 JOIN group_plugin ON (groups.group_id=group_plugin.group_id)
1354 JOIN scm_secondary_repos ON (groups.group_id=scm_secondary_repos.group_id)
1355 WHERE groups.status=$1 AND group_plugin.plugin_id=$2 AND groups.unix_group_name=$3
1356 ORDER BY unix_group_name, repo_name", array('A', $this->getID(), $unix_group_name));
1358 $res = db_query_params("SELECT unix_group_name, groups.group_id, repo_name
1360 JOIN group_plugin ON (groups.group_id=group_plugin.group_id)
1361 JOIN scm_secondary_repos ON (groups.group_id=scm_secondary_repos.group_id)
1362 WHERE groups.status=$1 AND group_plugin.plugin_id=$2
1363 ORDER BY unix_group_name, repo_name", array('A', $this->getID()));
1365 while ($arr = db_fetch_array($res)) {
1366 if (!forge_check_perm('scm', $arr['group_id'], 'read')) {
1370 $project = group_get_object($arr['group_id']);
1371 if (forge_get_config('use_smarthttp', 'scmgit')) {
1372 $urls[] = $protocol.'://'.$this->getBoxForProject($project).'/anonscm/git/'.$arr['unix_group_name'].'/'.$arr['repo_name'].'.git';
1374 if (session_loggedin()) {
1375 if (forge_get_config('use_ssh', 'scmgit')) {
1376 $urls[] = 'git+ssh://'.$d.'@'.$this->getBoxForProject($project).forge_get_config('repos_path', 'scmgit') .'/'. $arr['unix_group_name'] .'/'. $arr['repo_name'] .'.git';
1378 if (forge_get_config('use_smarthttp', 'scmgit')) {
1379 $urls[] = $protocol.'://'.$d.'@'.$this->getBoxForProject($project).'/authscm/'.$d.'/git/'.$arr['unix_group_name'].'/'.$arr['repo_name'].'.git';
1382 $results[] = array('group_id' => $arr['group_id'],
1383 'repository_type' => 'git',
1384 'repository_id' => $arr['unix_group_name'].'/git/'.$arr['repo_name'],
1385 'repository_urls' => $urls,
1388 if ($unix_group_name) {
1389 $res = db_query_params("SELECT unix_group_name, groups.group_id, user_name
1391 JOIN group_plugin ON (groups.group_id=group_plugin.group_id)
1392 JOIN scm_personal_repos ON (groups.group_id=scm_personal_repos.group_id)
1393 JOIN users ON (scm_personal_repos.user_id=users.user_id)
1394 WHERE groups.status=$1 AND group_plugin.plugin_id=$2 AND users.status=$3 AND groups.unix_group_name=$4
1395 ORDER BY unix_group_name, user_name", array('A', $this->getID(), 'A', $unix_group_name));
1397 $res = db_query_params("SELECT unix_group_name, groups.group_id, user_name
1399 JOIN group_plugin ON (groups.group_id=group_plugin.group_id)
1400 JOIN scm_personal_repos ON (groups.group_id=scm_personal_repos.group_id)
1401 JOIN users ON (scm_personal_repos.user_id=users.user_id)
1402 WHERE groups.status=$1 AND group_plugin.plugin_id=$2 AND users.status=$3
1403 ORDER BY unix_group_name, user_name", array('A', $this->getID(), 'A'));
1405 while ($arr = db_fetch_array($res)) {
1406 if (!forge_check_perm('scm', $arr['group_id'], 'read')) {
1410 $project = group_get_object($arr['group_id']);
1411 if (forge_get_config('use_smarthttp', 'scmgit')) {
1412 $urls[] = $protocol.'://'.$this->getBoxForProject($project).'/anonscm/git/'.$arr['unix_group_name'].'/users/'.$arr['user_name'].'.git';
1414 if (session_loggedin()) {
1415 if (forge_get_config('use_ssh', 'scmgit')) {
1416 $urls[] = 'git+ssh://'.$d.'@'.$this->getBoxForProject($project).forge_get_config('repos_path', 'scmgit') .'/'. $arr['unix_group_name'] .'/users/'. $arr['user_name'] .'.git';
1418 if (forge_get_config('use_smarthttp', 'scmgit')) {
1419 $urls[] = $protocol.'://'.$d.'@'.$this->getBoxForProject($project).'/authscm/'.$d.'/git/'.$arr['unix_group_name'].'/users/'.$arr['user_name'].'.git';
1422 $results[] = array('group_id' => $arr['group_id'],
1423 'repository_type' => 'git',
1424 'repository_id' => $arr['unix_group_name'].'/git/users/'.$arr['user_name'],
1425 'repository_urls' => $urls,
1429 foreach ($results as $res) {
1430 $params['results'][] = $res;
1434 function get_scm_repo_info(&$params) {
1435 $rid = $params['repository_id'];
1436 $e = explode('/',$rid);
1437 if ($e[1] != 'git') {
1441 $p = array('group_name' => $g);
1442 $this->get_scm_repo_list($p);
1443 foreach ($p['results'] as $r) {
1444 if ($r['repository_id'] == $rid) {
1445 $params['results'] = $r;
1451 function parse_scm_repo_activities(&$params) {
1453 $res = db_query_params("SELECT unix_group_name, groups.group_id FROM groups
1454 JOIN group_plugin ON (groups.group_id=group_plugin.group_id)
1455 WHERE groups.status=$1 AND group_plugin.plugin_id=$2
1456 ORDER BY unix_group_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['unix_group_name'] .'.git';
1460 $el['rid'] = $arr['unix_group_name'].'/git/'.$arr['unix_group_name'];
1461 $el['gid'] = $arr['group_id'];
1465 $res = db_query_params("SELECT unix_group_name, groups.group_id, repo_name
1467 JOIN group_plugin ON (groups.group_id=group_plugin.group_id)
1468 JOIN scm_secondary_repos ON (groups.group_id=scm_secondary_repos.group_id)
1469 WHERE groups.status=$1 AND group_plugin.plugin_id=$2
1470 ORDER BY unix_group_name, repo_name", array('A', $this->getID()));
1471 while ($arr = db_fetch_array($res)) {
1473 $el['rpath'] = forge_get_config('repos_path', 'scmgit') .'/'. $arr['unix_group_name'] .'/'. $arr['repo_name'] .'.git';
1474 $el['rid'] = $arr['unix_group_name'].'/git/'.$arr['repo_name'];
1475 $el['gid'] = $arr['group_id'];
1479 $res = db_query_params("SELECT unix_group_name, groups.group_id, user_name
1481 JOIN group_plugin ON (groups.group_id=group_plugin.group_id)
1482 JOIN scm_personal_repos ON (groups.group_id=scm_personal_repos.group_id)
1483 JOIN users ON (scm_personal_repos.user_id=users.user_id)
1484 WHERE groups.status=$1 AND group_plugin.plugin_id=$2 AND users.status=$3
1485 ORDER BY unix_group_name, user_name", array('A', $this->getID(), 'A'));
1486 while ($arr = db_fetch_array($res)) {
1488 $el['rpath'] = forge_get_config('repos_path', 'scmgit') .'/'. $arr['unix_group_name'] .'/users/'. $arr['user_name'] .'.git';
1489 $el['rid'] = $arr['unix_group_name'].'/git/users/'.$arr['user_name'];
1490 $el['gid'] = $arr['group_id'];
1494 $lastactivities = array();
1495 $res = db_query_params("SELECT repository_id, max(tstamp) AS last FROM scm_activities WHERE plugin_id=$1 GROUP BY repository_id",
1496 array($this->getID()));
1497 while ($arr = db_fetch_array($res)) {
1498 $lastactivities[$arr['repository_id']] = $arr['last'];
1501 foreach ($repos as $rdata) {
1503 if (array_key_exists($rdata['rid'], $lastactivities)) {
1504 $since = "--since=@".$lastactivities[$rdata['rid']];
1506 $rpath = $rdata['rpath'];
1508 $f = popen("GIT_DIR=\"$rpath\" git reflog --date=raw --all $since 2> /dev/null", "r");
1509 while (($l = fgets($f, 4096)) !== false) {
1510 if (preg_match("/@\{(?P<tstamp>[0-9]+) [-+0-9]+\}: push/", $l, $matches)) {
1511 if (array_key_exists($rdata['rid'], $lastactivities)
1512 && $matches['tstamp'] <= $lastactivities[$rdata['rid']]) {
1515 $tstamps[$matches['tstamp']] = 1;
1518 foreach ($tstamps as $t => $v) {
1519 $res = db_query_params("INSERT INTO scm_activities (group_id, plugin_id, repository_id, tstamp) VALUES ($1,$2,$3,$4)",
1520 array($rdata['gid'],
1528 function getRepositories($group, $autoinclude = true) {
1531 $repoarr[] = $group->getUnixName();
1533 $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',
1534 array($group->getID(),
1535 SCM_EXTRA_REPO_ACTION_UPDATE,
1537 while ($arr = db_fetch_array($result)) {
1538 $repoarr[] = $arr['repo_name'];
1546 // c-file-style: "bsd"