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,2021, 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.'));
181 $b .= html_e('p', array(), _('Additionally, a public ssh key must be available in the FusionForge settings of the respective user.'));
183 foreach ($repo_list as $repo_name) {
184 if (forge_get_config('use_shell_limited')) {
185 $htmlRepo .= html_e('kbd', array(), 'git clone '.$d.'@'.$this->getBoxForProject($project).$ssh_port.':'.$project->getUnixName().'/'.$repo_name.'.git').html_e('br');
187 $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');
190 $b .= html_e('p', array(), $htmlRepo);
193 if (forge_get_config('use_smarthttp', 'scmgit')) {
194 $b .= '<div id="tabber-gitsmarthttp" class="tabbertab" >';
195 $b .= html_e('p', array(), _('Enter your site password when prompted.'));
197 $protocol = forge_get_config('use_ssl', 'scmgit') ? 'https' : 'http';
198 foreach ($repo_list as $repo_name) {
199 $htmlRepo .= html_e('kbd', array(), 'git clone '.$protocol.'://'.$d.'@'.$this->getBoxForProject($project).'/authscm/'.$d.'/git/'.$project->getUnixName() .'/'. $repo_name .'.git').html_e('br');
201 $b .= html_e('p', array(), $htmlRepo);
205 if (forge_get_config('use_ssh', 'scmgit')) {
206 $b .= '<div id="tabber-gitssh" class="tabbertab" >';
207 $b .= html_e('p', array(),
208 ngettext('Only project developers can access the Git repository via this method.',
209 'Only project developers can access the Git repositories via this method.',
211 ' '. _('SSH must be installed on your client machine.').
212 ' '. _('Additionally, a public ssh key must be available in the FusionForge settings of the respective user.').
213 ' '. _('Substitute <em>developername</em> with the proper value.'));
215 foreach ($repo_list as $repo_name) {
216 if (forge_get_config('use_shell_limited')) {
217 $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');
219 $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');
222 $b .= html_e('p', array(), $htmlRepo);
225 if (forge_get_config('use_smarthttp', 'scmgit')) {
226 $protocol = forge_get_config('use_ssl', 'scmgit')? 'https' : 'http';
227 $b .= '<div id="tabber-gitsmarthttp" class="tabbertab" >';
228 $b .= html_e('p', array(),
229 ngettext('Only project developers can access the Git repository via this method.',
230 'Only project developers can access the Git repositories via this method.',
232 ' '. _('Enter your site password when prompted.'));
234 foreach ($repo_list as $repo_name) {
235 $htmlRepo .= '<kbd>git clone '.$protocol.'://<i>'._('developername').'</i>@'.$this->getBoxForProject($project).'/authscm/<i>'._('developername').'</i>/git/'.$project->getUnixName() .'/'. $repo_name .'.git</kbd><br />';
237 $b .= html_e('p', array(), $htmlRepo);
242 if (session_loggedin()) {
243 $u = user_get_object(user_getid());
244 if ($u->getUnixStatus() == 'A') {
245 $result = db_query_params('SELECT * FROM scm_personal_repos p WHERE p.group_id=$1 AND p.user_id=$2 AND plugin_id=$3',
246 array($project->getID(),
249 if ($result && db_numrows($result) > 0) {
250 $b .= html_e('h3', array(), _('Access to your personal repository'));
251 $b .= html_e('p', array(), _('You have a personal repository for this project, accessible through the following methods. Enter your site password when prompted.'));
252 if (forge_get_config('use_ssh', 'scmgit')) {
253 $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');
255 if (forge_get_config('use_smarthttp', 'scmgit')) {
256 $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');
259 $glist = $u->getGroups();
260 foreach ($glist as $g) {
261 if ($g->getID() == $project->getID()) {
262 $b .= html_e('h3', array(), _('Request a personal repository'));
263 $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)."));
264 $b .= html_e('p', array(), util_make_link('/plugins/scmgit/index.php?func=request-personal-repo&group_id='.$project->getID(), _('Request a personal repository')));
273 function getSnapshotPara($project) {
275 $filename = $project->getUnixName().'-scm-latest.tar'.util_get_compressed_file_extension();
276 if (file_exists(forge_get_config('scm_snapshots_path').'/'.$filename)) {
277 $b .= html_e('p', array(), '['.util_make_link('/snapshots.php?group_id='.$project->getID(), _('Download the nightly snapshot')).']');
282 function printBrowserPage($params) {
283 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);
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 $repolist = $this->getRepositories($project);
690 foreach ($repolist as $repo_name) {
691 $this->gatherStatsRepo($project, $repo_name, $year, $month, $day);
696 function gatherStatsRepo($group, $project_reponame, $year, $month, $day) {
697 $month_string = sprintf("%04d%02d", $year, $month);
698 $start_time = gmmktime(0, 0, 0, $month, $day, $year);
699 $end_time = $start_time + 86400;
702 $usr_updates = array();
703 $usr_deletes = array();
704 $usr_commits = array();
711 $repo = forge_get_config('repos_path', 'scmgit') . '/' . $group->getUnixName() . '/' . $project_reponame . '.git';
712 if (!is_dir($repo) || !is_dir("$repo/refs")) {
713 // echo "No repository $repo\n";
717 # For each commit, get committer full name and e-mail (respecting git .mailmap file),
718 # and a list of files prefixed by their status (A/M/D)
719 $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');
723 // cleaning stats_cvs_* table for the current day for the default repository
724 $res = db_query_params('DELETE FROM stats_cvs_group WHERE month = $1 AND day = $2 AND group_id = $3 and reponame = $4',
730 echo "Error while cleaning stats_cvs_group\n";
735 $res = db_query_params ('DELETE FROM stats_cvs_user WHERE month = $1 AND day = $2 AND group_id = $3 and reponame = $4',
741 echo "Error while cleaning stats_cvs_user\n";
747 while (!feof($pipe) && $data = fgets($pipe)) {
749 // Replace bad UTF-8 with '?' - it's quite hard to make git output non-UTF-8
750 // (e.g. with i18n.commitEncoding = unknown) - but some users do!
751 // and this makes PostgreSQL choke (SQL> ERROR: invalid byte sequence for encoding "UTF8": 0xf9)
752 $line = mb_convert_encoding($line, 'UTF-8', 'UTF-8');
753 if (strlen($line) > 0) {
754 $result = preg_match("/^(?P<name>.+) <(?P<mail>.+)>/", $line, $matches);
757 $last_user = $matches['name'];
758 $user2email[$last_user] = $matches['mail'];
759 if (!isset($usr_adds[$last_user])) {
760 $usr_adds[$last_user] = 0;
761 $usr_updates[$last_user] = 0;
762 $usr_deletes[$last_user] = 0;
763 $usr_commits[$last_user] = 0;
766 $usr_commits[$last_user]++;
768 // Short-commit stats line
769 $result = preg_match("/^(?P<mode>[AMD])\s+(?P<file>.+)$/", $line, $matches);
773 if ($last_user == "") {
776 if (!isset($usr_adds[$last_user])) {
777 $usr_adds[$last_user] = 0;
779 if (!isset($usr_updates[$last_user])) {
780 $usr_updates[$last_user] = 0;
782 if (!isset($usr_deletes[$last_user])) {
783 $usr_deletes[$last_user] = 0;
785 if ($matches['mode'] == 'A') {
786 $usr_adds[$last_user]++;
788 } elseif ($matches['mode'] == 'M') {
789 $usr_updates[$last_user]++;
791 } elseif ($matches['mode'] == 'D') {
792 $usr_deletes[$last_user]++;
799 // inserting group results in stats_cvs_groups
800 if ($updates > 0 || $adds > 0 || $deletes > 0 || $commits > 0) {
801 if (!db_query_params('INSERT INTO stats_cvs_group (month, day, group_id, checkouts, commits, adds, updates, deletes, reponame)
802 VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)',
811 $project_reponame))) {
812 echo "Error while inserting into stats_cvs_group\n";
818 // building the user list
819 $user_list = array_unique(array_merge(array_keys($usr_adds), array_keys($usr_updates), array_keys($usr_deletes), array_keys($usr_commits)));
821 foreach ($user_list as $user) {
822 // Trying to get user id from user name or email
823 $u = user_get_object_by_name($user);
825 $user_id = $u->getID();
827 $res = db_query_params('SELECT user_id FROM users WHERE lower(realname)=$1 OR lower(email)=$2',
828 array(strtolower($user), strtolower($user2email[$user])));
829 if ($res && db_numrows($res) > 0) {
830 $user_id = db_result($res, 0, 'user_id');
836 $uc = isset($usr_commits[$user]) ? $usr_commits[$user] : 0;
837 $uu = isset($usr_updates[$user]) ? $usr_updates[$user] : 0;
838 $ua = isset($usr_adds[$user]) ? $usr_adds[$user] : 0;
839 $ud = isset($usr_deletes[$user]) ? $usr_deletes[$user] : 0;
840 if ($uu > 0 || $ua > 0 || $uc > 0 || $ud > 0) {
841 if (!db_query_params('INSERT INTO stats_cvs_user (month, day, group_id, user_id, commits, adds, updates, deletes, reponame)
842 VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)',
851 $project_reponame))) {
852 echo "Error while inserting into stats_cvs_user\n";
861 function generateSnapshots($params) {
862 $us = forge_get_config('use_scm_snapshots') ;
863 $ut = forge_get_config('use_scm_tarballs') ;
868 $project = $this->checkParams($params);
873 $group_name = $project->getUnixName();
875 $snapshot = forge_get_config('scm_snapshots_path').'/'.$group_name.'-scm-latest.tar'.util_get_compressed_file_extension();
876 $tarball = forge_get_config('scm_tarballs_path').'/'.$group_name.'-scmroot.tar'.util_get_compressed_file_extension();
878 if (!$project->enableAnonSCM()) {
879 if (is_file($snapshot)) {
882 if (is_file($tarball)) {
888 // TODO: ideally we generate one snapshot per git repository
889 $toprepo = forge_get_config('repos_path', 'scmgit');
890 $repo = $toprepo . '/' . $project->getUnixName() . '/' . $project->getUnixName() . '.git';
892 if (!is_dir($repo)) {
893 if (is_file($snapshot)) {
896 if (is_file($tarball)) {
902 // Skip empty repo (no HEAD present in repository)
903 $ref = trim(`GIT_DIR=$repo git symbolic-ref HEAD`);
904 if (!file_exists($repo.'/'.$ref)) {
908 $tmp = trim(`mktemp -d`);
913 $today = date('Y-m-d');
914 system("GIT_DIR=\"$repo\" git archive --format=tar --prefix=$group_name-scm-$today/ HEAD |".forge_get_config('compression_method')." > $tmp/snapshot");
915 chmod("$tmp/snapshot", 0644);
916 copy("$tmp/snapshot", $snapshot);
917 unlink("$tmp/snapshot");
920 system("tar cCf $toprepo - ".$project->getUnixName() ."|".forge_get_config('compression_method')."> $tmp/tarball");
921 chmod("$tmp/tarball", 0644);
922 copy("$tmp/tarball", $tarball);
923 unlink("$tmp/tarball");
924 system("rm -rf $tmp");
929 * widgets - 'widgets' hook handler
931 * @param array $params
934 function widgets($params) {
935 require_once 'common/widget/WidgetLayoutManager.class.php';
936 if ($params['owner_type'] == WidgetLayoutManager::OWNER_TYPE_USER) {
937 $params['fusionforge_widgets'][] = 'plugin_scmgit_user_myrepositories';
938 } else if ($params['owner_type'] == WidgetLayoutManager::OWNER_TYPE_USERHOME) {
939 $params['fusionforge_widgets'][] = 'plugin_scmgit_user_userrepositories';
945 * Process the 'widget_instance' hook to create instances of the widgets
947 * @param array $params
949 function myPageBox($params) {
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() || !$project->isActive()) {
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, forge_get_config('use_ssl_verification'));
1025 curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, forge_get_config('use_ssl_verification'));
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.'&repo_name='.$repo_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 (empty($existing_repos)) {
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 $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, $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';
1247 $email = $user->getEmail();
1248 $realname = $user->getFirstName().' '.$user->getLastName();
1249 $userunixname = $user->getUnixName();
1250 $params = '&mode=latest_user'
1251 .'&email='.urlencode($email)
1252 .'&realname='.urlencode($realname)
1253 .'&user_name='.$userunixname;
1255 $params = '&mode=latest';
1257 $repo_list = $this->getRepositories($project);
1259 foreach ($repo_list as $repo_name) {
1260 $script_url = $protocol.$this->getBoxForProject($project)
1262 .'?unix_group_name='.$project->getUnixName()
1263 .'&repo_name='.$repo_name
1265 .'&limit='.$nb_commits;
1266 $filename = tempnam('/tmp', 'gitlog');
1267 $f = fopen($filename, 'w');
1269 curl_setopt($ch, CURLOPT_URL, $script_url);
1270 curl_setopt($ch, CURLOPT_FILE, $f);
1271 curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, forge_get_config('use_ssl_verification'));
1272 curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, forge_get_config('use_ssl_verification'));
1273 curl_setopt($ch, CURLOPT_COOKIE, $_SERVER['HTTP_COOKIE']); // for session validation
1274 curl_setopt($ch, CURLOPT_USERAGENT, $_SERVER['HTTP_USER_AGENT']); // for session validation
1275 curl_setopt($ch, CURLOPT_HTTPHEADER,
1276 array('X-Forwarded-For: '.$_SERVER['REMOTE_ADDR'])); // for session validation
1277 $body = curl_exec($ch);
1278 if ($body === false) {
1279 $this->setError(curl_error($ch));
1282 fclose($f); // flush buffer
1283 $f = fopen($filename, 'r');
1286 while (!feof($f) && $data = fgets($f)) {
1287 $line = trim($data);
1288 $splitedLine = explode('||', $line);
1289 if (sizeof($splitedLine) == 4) {
1290 $commits[$i]['pluginName'] = $this->name;
1291 $commits[$i]['description'] = htmlspecialchars($splitedLine[2]);
1292 $commits[$i]['commit_id'] = $splitedLine[3];
1293 $commits[$i]['repo_name'] = $repo_name;
1294 $splitedDate = explode(' ', $splitedLine[0]);
1295 $commits[$i]['date'] = $splitedDate[0];
1304 function get_scm_repo_list(&$params) {
1305 if (array_key_exists('group_name',$params)) {
1306 $unix_group_name = $params['group_name'];
1308 $unix_group_name = '';
1310 $protocol = forge_get_config('use_ssl', 'scmgit')? 'https' : 'http';
1311 if (session_loggedin()) {
1312 $u = user_get_object(user_getid());
1313 $d = $u->getUnixName();
1317 if ($unix_group_name) {
1318 $res = db_query_params("SELECT unix_group_name, groups.group_id FROM groups
1319 JOIN group_plugin ON (groups.group_id=group_plugin.group_id)
1320 WHERE groups.status=$1 AND group_plugin.plugin_id=$2 AND groups.unix_group_name=$3
1321 ORDER BY unix_group_name", array('A', $this->getID(), $unix_group_name));
1323 $res = db_query_params("SELECT unix_group_name, groups.group_id FROM groups
1324 JOIN group_plugin ON (groups.group_id=group_plugin.group_id)
1325 WHERE groups.status=$1 AND group_plugin.plugin_id=$2
1326 ORDER BY unix_group_name", array('A', $this->getID()));
1328 while ($arr = db_fetch_array($res)) {
1329 if (!forge_check_perm('scm', $arr['group_id'], 'read')) {
1333 $project = group_get_object($arr['group_id']);
1334 if (forge_get_config('use_smarthttp', 'scmgit')) {
1335 $urls[] = $protocol.'://'.$this->getBoxForProject($project).'/anonscm/git/'.$arr['unix_group_name'].'/'.$arr['unix_group_name'].'.git';
1337 if (session_loggedin()) {
1338 if (forge_get_config('use_ssh', 'scmgit')) {
1339 $urls[] = 'git+ssh://'.$d.'@'.$this->getBoxForProject($project).forge_get_config('repos_path', 'scmgit') .'/'. $arr['unix_group_name'] .'/'. $arr['unix_group_name'] .'.git';
1341 if (forge_get_config('use_smarthttp', 'scmgit')) {
1342 $urls[] = $protocol.'://'.$d.'@'.$this->getBoxForProject($project).'/authscm/'.$d.'/git/'.$arr['unix_group_name'].'/'.$arr['unix_group_name'].'.git';
1345 $results[] = array('group_id' => $arr['group_id'],
1346 'repository_type' => 'git',
1347 'repository_id' => $arr['unix_group_name'].'/git/'.$arr['unix_group_name'],
1348 'repository_urls' => $urls,
1351 if ($unix_group_name) {
1352 $res = db_query_params("SELECT unix_group_name, groups.group_id, repo_name
1354 JOIN group_plugin ON (groups.group_id=group_plugin.group_id)
1355 JOIN scm_secondary_repos ON (groups.group_id=scm_secondary_repos.group_id)
1356 WHERE groups.status=$1 AND group_plugin.plugin_id=$2 AND groups.unix_group_name=$3
1357 ORDER BY unix_group_name, repo_name", array('A', $this->getID(), $unix_group_name));
1359 $res = db_query_params("SELECT unix_group_name, groups.group_id, repo_name
1361 JOIN group_plugin ON (groups.group_id=group_plugin.group_id)
1362 JOIN scm_secondary_repos ON (groups.group_id=scm_secondary_repos.group_id)
1363 WHERE groups.status=$1 AND group_plugin.plugin_id=$2
1364 ORDER BY unix_group_name, repo_name", array('A', $this->getID()));
1366 while ($arr = db_fetch_array($res)) {
1367 if (!forge_check_perm('scm', $arr['group_id'], 'read')) {
1371 $project = group_get_object($arr['group_id']);
1372 if (forge_get_config('use_smarthttp', 'scmgit')) {
1373 $urls[] = $protocol.'://'.$this->getBoxForProject($project).'/anonscm/git/'.$arr['unix_group_name'].'/'.$arr['repo_name'].'.git';
1375 if (session_loggedin()) {
1376 if (forge_get_config('use_ssh', 'scmgit')) {
1377 $urls[] = 'git+ssh://'.$d.'@'.$this->getBoxForProject($project).forge_get_config('repos_path', 'scmgit') .'/'. $arr['unix_group_name'] .'/'. $arr['repo_name'] .'.git';
1379 if (forge_get_config('use_smarthttp', 'scmgit')) {
1380 $urls[] = $protocol.'://'.$d.'@'.$this->getBoxForProject($project).'/authscm/'.$d.'/git/'.$arr['unix_group_name'].'/'.$arr['repo_name'].'.git';
1383 $results[] = array('group_id' => $arr['group_id'],
1384 'repository_type' => 'git',
1385 'repository_id' => $arr['unix_group_name'].'/git/'.$arr['repo_name'],
1386 'repository_urls' => $urls,
1389 if ($unix_group_name) {
1390 $res = db_query_params("SELECT unix_group_name, groups.group_id, user_name
1392 JOIN group_plugin ON (groups.group_id=group_plugin.group_id)
1393 JOIN scm_personal_repos ON (groups.group_id=scm_personal_repos.group_id)
1394 JOIN users ON (scm_personal_repos.user_id=users.user_id)
1395 WHERE groups.status=$1 AND group_plugin.plugin_id=$2 AND users.status=$3 AND groups.unix_group_name=$4
1396 ORDER BY unix_group_name, user_name", array('A', $this->getID(), 'A', $unix_group_name));
1398 $res = db_query_params("SELECT unix_group_name, groups.group_id, user_name
1400 JOIN group_plugin ON (groups.group_id=group_plugin.group_id)
1401 JOIN scm_personal_repos ON (groups.group_id=scm_personal_repos.group_id)
1402 JOIN users ON (scm_personal_repos.user_id=users.user_id)
1403 WHERE groups.status=$1 AND group_plugin.plugin_id=$2 AND users.status=$3
1404 ORDER BY unix_group_name, user_name", array('A', $this->getID(), 'A'));
1406 while ($arr = db_fetch_array($res)) {
1407 if (!forge_check_perm('scm', $arr['group_id'], 'read')) {
1411 $project = group_get_object($arr['group_id']);
1412 if (forge_get_config('use_smarthttp', 'scmgit')) {
1413 $urls[] = $protocol.'://'.$this->getBoxForProject($project).'/anonscm/git/'.$arr['unix_group_name'].'/users/'.$arr['user_name'].'.git';
1415 if (session_loggedin()) {
1416 if (forge_get_config('use_ssh', 'scmgit')) {
1417 $urls[] = 'git+ssh://'.$d.'@'.$this->getBoxForProject($project).forge_get_config('repos_path', 'scmgit') .'/'. $arr['unix_group_name'] .'/users/'. $arr['user_name'] .'.git';
1419 if (forge_get_config('use_smarthttp', 'scmgit')) {
1420 $urls[] = $protocol.'://'.$d.'@'.$this->getBoxForProject($project).'/authscm/'.$d.'/git/'.$arr['unix_group_name'].'/users/'.$arr['user_name'].'.git';
1423 $results[] = array('group_id' => $arr['group_id'],
1424 'repository_type' => 'git',
1425 'repository_id' => $arr['unix_group_name'].'/git/users/'.$arr['user_name'],
1426 'repository_urls' => $urls,
1430 foreach ($results as $res) {
1431 $params['results'][] = $res;
1435 function get_scm_repo_info(&$params) {
1436 $rid = $params['repository_id'];
1437 $e = explode('/',$rid);
1438 if ($e[1] != 'git') {
1442 $p = array('group_name' => $g);
1443 $this->get_scm_repo_list($p);
1444 foreach ($p['results'] as $r) {
1445 if ($r['repository_id'] == $rid) {
1446 $params['results'] = $r;
1452 function parse_scm_repo_activities(&$params) {
1454 $res = db_query_params("SELECT unix_group_name, groups.group_id FROM groups
1455 JOIN group_plugin ON (groups.group_id=group_plugin.group_id)
1456 WHERE groups.status=$1 AND group_plugin.plugin_id=$2
1457 ORDER BY unix_group_name", array('A', $this->getID()));
1458 while ($arr = db_fetch_array($res)) {
1460 $el['rpath'] = forge_get_config('repos_path', 'scmgit') .'/'. $arr['unix_group_name'] .'/'. $arr['unix_group_name'] .'.git';
1461 $el['rid'] = $arr['unix_group_name'].'/git/'.$arr['unix_group_name'];
1462 $el['gid'] = $arr['group_id'];
1466 $res = db_query_params("SELECT unix_group_name, groups.group_id, repo_name
1468 JOIN group_plugin ON (groups.group_id=group_plugin.group_id)
1469 JOIN scm_secondary_repos ON (groups.group_id=scm_secondary_repos.group_id)
1470 WHERE groups.status=$1 AND group_plugin.plugin_id=$2
1471 ORDER BY unix_group_name, repo_name", array('A', $this->getID()));
1472 while ($arr = db_fetch_array($res)) {
1474 $el['rpath'] = forge_get_config('repos_path', 'scmgit') .'/'. $arr['unix_group_name'] .'/'. $arr['repo_name'] .'.git';
1475 $el['rid'] = $arr['unix_group_name'].'/git/'.$arr['repo_name'];
1476 $el['gid'] = $arr['group_id'];
1480 $res = db_query_params("SELECT unix_group_name, groups.group_id, user_name
1482 JOIN group_plugin ON (groups.group_id=group_plugin.group_id)
1483 JOIN scm_personal_repos ON (groups.group_id=scm_personal_repos.group_id)
1484 JOIN users ON (scm_personal_repos.user_id=users.user_id)
1485 WHERE groups.status=$1 AND group_plugin.plugin_id=$2 AND users.status=$3
1486 ORDER BY unix_group_name, user_name", array('A', $this->getID(), 'A'));
1487 while ($arr = db_fetch_array($res)) {
1489 $el['rpath'] = forge_get_config('repos_path', 'scmgit') .'/'. $arr['unix_group_name'] .'/users/'. $arr['user_name'] .'.git';
1490 $el['rid'] = $arr['unix_group_name'].'/git/users/'.$arr['user_name'];
1491 $el['gid'] = $arr['group_id'];
1495 $lastactivities = array();
1496 $res = db_query_params("SELECT repository_id, max(tstamp) AS last FROM scm_activities WHERE plugin_id=$1 GROUP BY repository_id",
1497 array($this->getID()));
1498 while ($arr = db_fetch_array($res)) {
1499 $lastactivities[$arr['repository_id']] = $arr['last'];
1502 foreach ($repos as $rdata) {
1504 if (array_key_exists($rdata['rid'], $lastactivities)) {
1505 $since = "--since=@".$lastactivities[$rdata['rid']];
1507 $rpath = $rdata['rpath'];
1509 $f = popen("GIT_DIR=\"$rpath\" git reflog --date=raw --all $since 2> /dev/null", "r");
1510 while (($l = fgets($f, 4096)) !== false) {
1511 if (preg_match("/@\{(?P<tstamp>[0-9]+) [-+0-9]+\}: push/", $l, $matches)) {
1512 if (array_key_exists($rdata['rid'], $lastactivities)
1513 && $matches['tstamp'] <= $lastactivities[$rdata['rid']]) {
1516 $tstamps[$matches['tstamp']] = 1;
1519 foreach ($tstamps as $t => $v) {
1520 db_query_params("INSERT INTO scm_activities (group_id, plugin_id, repository_id, tstamp) VALUES ($1,$2,$3,$4)",
1521 array($rdata['gid'],
1529 function getRepositories($group, $autoinclude = true) {
1532 $repoarr[] = $group->getUnixName();
1534 $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',
1535 array($group->getID(),
1536 SCM_EXTRA_REPO_ACTION_UPDATE,
1538 while ($arr = db_fetch_array($result)) {
1539 $repoarr[] = $arr['repo_name'];