3 * FusionForge Git plugin
5 * Copyright 2009, Roland Mas
6 * Copyright 2009, Mehdi Dogguy <mehdi@debian.org>
7 * Copyright 2012-2013, Franck Villaume - TrivialDev
9 * Thorsten Glaser <t.glaser@tarent.de>
10 * http://fusionforge.org
12 * This file is part of FusionForge.
14 * FusionForge is free software; you can redistribute it and/or modify
15 * it under the terms of the GNU General Public License as published
16 * by the Free Software Foundation; either version 2 of the License,
17 * or (at your option) any later version.
19 * FusionForge is distributed in the hope that it will be useful, but
20 * WITHOUT ANY WARRANTY; without even the implied warranty of
21 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
22 * General Public License for more details.
24 * You should have received a copy of the GNU General Public License along
25 * with this program; if not, write to the Free Software Foundation, Inc.,
26 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
29 forge_define_config_item('default_server', 'scmgit', forge_get_config('web_host'));
30 forge_define_config_item('repos_path', 'scmgit', forge_get_config('chroot').'/scmrepos/git');
31 forge_define_config_item('use_ssh', 'scmgit', false);
32 forge_set_config_item_bool('use_ssh', 'scmgit');
33 forge_define_config_item('use_dav', 'scmgit', true);
34 forge_set_config_item_bool('use_dav', 'scmgit');
35 forge_define_config_item('use_ssl', 'scmgit', true);
36 forge_set_config_item_bool('use_ssl', 'scmgit');
38 class GitPlugin extends SCMPlugin {
39 function GitPlugin() {
41 $this->name = 'scmgit';
43 $this->_addHook('scm_browser_page');
44 $this->_addHook('scm_update_repolist');
45 $this->_addHook('scm_generate_snapshots');
46 $this->_addHook('scm_gather_stats');
47 $this->_addHook('scm_admin_form');
48 $this->_addHook('scm_add_repo');
49 $this->_addHook('scm_delete_repo');
50 $this->_addHook('widget_instance', 'myPageBox', false);
51 $this->_addHook('widgets', 'widgets', false);
52 $this->_addHook('activity');
53 $this->_addHook('weekly');
57 function getDefaultServer() {
58 return forge_get_config('default_server', 'scmgit');
61 function printShortStats($params) {
62 $project = $this->checkParams($params);
67 if ($project->usesPlugin($this->name) && forge_check_perm('scm', $project->getID(), 'read')) {
68 $result = db_query_params('SELECT sum(commits) AS commits, sum(adds) AS adds FROM stats_cvs_group WHERE group_id=$1',
69 array($project->getID()));
70 $commit_num = db_result($result,0,'commits');
71 $add_num = db_result($result,0,'adds');
78 echo ' (Git: '.sprintf(_('<strong>%1$s</strong> commits, <strong>%2$s</strong> adds'), number_format($commit_num, 0), number_format($add_num, 0)).")";
84 . sprintf(_('Documentation for %1$s is available at <a href="%2$s">%2$s</a>.'),
86 'http://git-scm.com/')
90 function getInstructionsForAnon($project) {
91 $repo_list = array($project->getUnixName());
92 $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',
93 array($project->getID(),
94 SCM_EXTRA_REPO_ACTION_UPDATE,
96 $rows = db_numrows($result);
97 for ($i=0; $i<$rows; $i++) {
98 $repo_list[] = db_result($result,$i,'repo_name');
101 $b = '<h2>' . ngettext('Anonymous Access to the Git repository',
102 'Anonymous Access to the Git repositories',
103 count($repo_list)) . '</h2>';
106 $b .= ngettext('This project\'s Git repository can be checked out through anonymous access with the following command.',
107 'This project\'s Git repositories can be checked out through anonymous access with the following commands.',
112 foreach ($repo_list as $repo_name) {
114 $b .= '<tt>git clone '.util_make_url('/anonscm/git/'.$project->getUnixName().'/'.$repo_name.'.git').'</tt><br />';
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);
126 $b .= ngettext('Developer\'s repository',
127 'Developer\'s repositories',
131 $b .= ngettext('One of this project\'s members also has a personal Git repository that can be checked out anonymously.',
132 'Some of this project\'s members also have personal Git repositories that can be checked out anonymously.',
136 for ($i=0; $i<$rows; $i++) {
137 $user_id = db_result($result,$i,'user_id');
138 $user_name = db_result($result,$i,'user_name');
139 $real_name = db_result($result,$i,'realname');
140 $b .= '<tt>git clone '.util_make_url('/anonscm/git/'.$project->getUnixName().'/users/'.$user_name.'.git').'</tt> ('.util_make_link_u($user_name, $user_id, $real_name).') ['.util_make_link('/scm/browser.php?group_id='.$project->getID().'&user_id='.$user_id, _('Browse Git Repository')).']<br />';
148 function getInstructionsForRW($project) {
149 $repo_list = array($project->getUnixName());
151 $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',
152 array($project->getID(),
153 SCM_EXTRA_REPO_ACTION_UPDATE,
155 $rows = db_numrows($result);
156 for ($i=0; $i<$rows; $i++) {
157 $repo_list[] = db_result($result,$i,'repo_name');
160 if (session_loggedin()) {
161 $u = user_get_object(user_getid());
162 $d = $u->getUnixName();
165 if (forge_get_config('use_ssh', 'scmgit')) {
167 $b = '<h2>' . ngettext('Developer Access to the Git repository via SSH',
168 'Developer Access to the Git repositories via SSH',
169 count($repo_list)) . '</h2>';
172 $b .= ngettext('Only project developers can access the Git repository via this method. SSH must be installed on your client machine. Enter your site password when prompted.',
173 'Only project developers can access the Git repositories via this method. SSH must be installed on your client machine. Enter your site password when prompted.',
177 foreach ($repo_list as $repo_name) {
178 $b .= '<p><tt>git clone git+ssh://'.$d.'@' . $project->getSCMBox() . '/'. forge_get_config('repos_path', 'scmgit') .'/'. $project->getUnixName() .'/'. $repo_name .'.git</tt></p>';
183 if (forge_get_config('use_dav', 'scmgit')) {
184 $protocol = forge_get_config('use_ssl', 'scmgit')? 'https' : 'http';
186 $b = '<h2>' . ngettext('Developer Access to the Git repository via HTTP',
187 'Developer Access to the Git repositories via HTTP',
188 count($repo_list)) . '</h2>';
192 $b .= ngettext('Only project developers can access the Git repository via this method. Enter your site password when prompted.',
193 'Only project developers can access the Git repositories via this method. Enter your site password when prompted.',
197 foreach ($repo_list as $repo_name) {
198 $b .= '<p><tt>git clone '.$protocol.'://'.$d.'@' . $project->getSCMBox() . '/'. forge_get_config('scm_root', 'scmgit') .'/'. $project->getUnixName() .'/'. $repo_name .'.git</tt></p>';
203 if ($validSetup == 0) {
204 $b = '<p class="warning">'._('Missing configuration for access in scmgit.ini : use_ssh and use_dav disabled').'</p>';
207 if (forge_get_config('use_ssh', 'scmgit')) {
209 $b = '<h2>' . ngettext('Developer Access to the Git repository via SSH',
210 'Developer Access to the Git repositories via SSH',
211 count($repo_list)) . '</h2>';
215 $b .= ngettext('Only project developers can access the Git repository via this method. SSH must be installed on your client machine. Substitute <em>developername</em> with the proper value. Enter your site password when prompted.',
216 'Only project developers can access the Git repositories via this method. SSH must be installed on your client machine. Substitute <em>developername</em> with the proper value. Enter your site password when prompted.',
220 foreach ($repo_list as $repo_name) {
221 $b .= '<p><tt>git clone git+ssh://<i>'._('developername').'</i>@' . $project->getSCMBox() . '/'. forge_get_config('repos_path', 'scmgit') .'/'. $project->getUnixName() .'/'. $repo_name .'.git</tt></p>';
225 if (forge_get_config('use_dav', 'scmgit')) {
226 $protocol = forge_get_config('use_ssl', 'scmgit')? 'https' : 'http';
228 $b = '<h2>' . ngettext('Developer Access to the Git repository via HTTP',
229 'Developer Access to the Git repositories via HTTP',
230 count($repo_list)) . '</h2>';
233 $b .= ngettext('Only project developers can access the Git repository via this method. Enter your site password when prompted.',
234 'Only project developers can access the Git repositories via this method. Enter your site password when prompted.',
238 foreach ($repo_list as $repo_name) {
239 $b .= '<p><tt>git clone '.$protocol.'://<i>'._('developername').'</i>@' . $project->getSCMBox() . '/'. forge_get_config('scm_root', 'scmgit') .'/'. $project->getUnixName() .'/'. $repo_name .'.git</tt></p>';
245 if (session_loggedin()) {
246 $u =& user_get_object(user_getid());
247 if ($u->getUnixStatus() == 'A') {
248 $result = db_query_params('SELECT * FROM scm_personal_repos p WHERE p.group_id=$1 AND p.user_id=$2 AND plugin_id=$3',
249 array($project->getID(),
252 if ($result && db_numrows($result) > 0) {
254 $b .= _('Access to your personal repository');
257 $b .= _('You have a personal repository for this project, accessible through SSH with the following method. Enter your site password when prompted.');
259 $b .= '<p><tt>git clone git+ssh://'.$u->getUnixName().'@' . $this->getBoxForProject($project) . '/'. forge_get_config('repos_path', 'scmgit') .'/'. $project->getUnixName() .'/users/'. $u->getUnixName() .'.git</tt></p>';
261 $glist = $u->getGroups();
262 foreach ($glist as $g) {
263 if ($g->getID() == $project->getID()) {
265 $b .= _('Request a personal repository');
268 $b .= _('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).');
271 $b .= sprintf(_('<a href="%s">Request a personal repository</a>.'),
272 util_make_url('/plugins/scmgit/index.php?func=request-personal-repo&group_id='.$project->getID()));
282 function getSnapshotPara($project) {
285 $filename = $project->getUnixName().'-scm-latest.tar'.util_get_compressed_file_extension();
286 if (file_exists(forge_get_config('scm_snapshots_path').'/'.$filename)) {
288 $b .= util_make_link("/snapshots.php?group_id=".$project->getID(),
289 _('Download the nightly snapshot')
296 function printBrowserPage($params) {
297 $project = $this->checkParams($params);
302 if ($project->usesPlugin($this->name)) {
303 if ($params['user_id']) {
304 $user = user_get_object($params['user_id']);
305 echo $project->getUnixName().'/users/'.$user->getUnixName();
306 print '<iframe src="'.util_make_url("/plugins/scmgit/cgi-bin/gitweb.cgi?p=".$project->getUnixName().'/users/'.$user->getUnixName().'.git').'" frameborder="0" width=100% height=700></iframe>';
307 } elseif ($this->browserDisplayable($project)) {
308 print '<iframe src="'.util_make_url("/plugins/scmgit/cgi-bin/gitweb.cgi?p=".$project->getUnixName().'/'.$project->getUnixName().'.git').'" frameborder="0" width=100% height=700></iframe>';
313 function getBrowserLinkBlock($project) {
315 $b = $HTML->boxMiddle(_('Git Repository Browser'));
317 $b .= _('Browsing the Git tree gives you a view into the current status of this project\'s code. You may also view the complete histories of any file in the repository.');
320 $b .= util_make_link("/scm/browser.php?group_id=".$project->getID(),
321 _('Browse Git Repository')
327 function getStatsBlock($project) {
331 $result = db_query_params('SELECT u.realname, u.user_name, u.user_id, sum(commits) as commits, sum(adds) as adds, sum(adds+commits) as combined FROM stats_cvs_user s, users u WHERE group_id=$1 AND s.user_id=u.user_id AND (commits>0 OR adds >0) GROUP BY u.user_id, realname, user_name, u.user_id ORDER BY combined DESC, realname',
332 array($project->getID()));
334 if (db_numrows($result) > 0) {
335 // $b .= $HTML->boxMiddle(_('Repository Statistics'));
337 $tableHeaders = array(
342 $b .= $HTML->listTableTop($tableHeaders, false, '', 'repo-history');
345 $total = array('adds' => 0, 'commits' => 0);
347 while($data = db_fetch_array($result)) {
348 $b .= '<tr '. $HTML->boxGetAltRowStyle($i) .'>';
349 $b .= '<td class="halfwidth">';
350 $b .= util_make_link_u($data['user_name'], $data['user_id'], $data['realname']);
351 $b .= '</td><td class="onequarterwidth align-right">'.$data['adds']. '</td>'.
352 '<td class="onequarterwidth align-right">'.$data['commits'].'</td></tr>';
353 $total['adds'] += $data['adds'];
354 $total['commits'] += $data['commits'];
357 $b .= '<tr '. $HTML->boxGetAltRowStyle($i) .'>';
358 $b .= '<td class="halfwidth"><strong>'._('Total').':</strong></td>'.
359 '<td class="onequarterwidth align-right"><strong>'.$total['adds']. '</strong></td>'.
360 '<td class="onequarterwidth align-right"><strong>'.$total['commits'].'</strong></td>';
362 $b .= $HTML->listTableBottom();
368 static function createUserRepo($params) {
369 $project = $params['project'];
370 $project_name = $project->getUnixName();
371 $user_name = $params['user_name'];
372 $unix_group = $params['unix_group'];
373 $main_repo = $params['main_repo'];
374 $root = $params['root'];
376 $repodir = $root . '/users/' . $user_name . '.git';
377 chgrp($repodir, $unix_group);
378 if ($project->enableAnonSCM()) {
379 chmod($repodir, 02755);
381 chmod($repodir, 02750);
383 if (!is_file("$repodir/HEAD") && !is_dir("$repodir/objects") && !is_dir("$repodir/refs")) {
384 system("git clone --bare $main_repo $repodir");
385 system("GIT_DIR=\"$repodir\" git update-server-info");
386 if (is_file("$repodir/hooks/post-update.sample")) {
387 rename("$repodir/hooks/post-update.sample",
388 "$repodir/hooks/post-update");
390 if (!is_file("$repodir/hooks/post-update")) {
391 $f = fopen("$repodir/hooks/post-update","x+");
392 fwrite($f, "exec git-update-server-info\n");
395 if (is_file("$repodir/hooks/post-update")) {
396 system("chmod +x $repodir/hooks/post-update");
398 system("echo \"Git repository for user $user_name in project $project_name\" > $repodir/description");
402 function createOrUpdateRepo($params) {
403 $project = $this->checkParams($params);
408 if (!$project->usesPlugin($this->name)) {
412 $project_name = $project->getUnixName();
413 $root = forge_get_config('repos_path', 'scmgit') . '/' . $project_name;
414 if (!is_dir($root)) {
415 system("mkdir -p $root");
419 if (forge_get_config('use_ssh','scmgit')) {
420 $unix_group = 'scm_' . $project_name;
422 $unix_group = forge_get_config('apache_group');
424 system("chgrp $unix_group $root");
426 // Create main repository
427 $main_repo = $root . '/' . $project_name . '.git';
428 if (!is_dir($main_repo) || (!is_file("$main_repo/HEAD") &&
429 !is_dir("$main_repo/objects") && !is_dir("$main_repo/refs"))) {
430 $tmp_repo = util_mkdtemp('.git', $project_name);
431 if ($tmp_repo == false) {
435 exec("GIT_DIR=\"$tmp_repo\" git init --bare --shared=group", $result);
436 $output .= join("<br />", $result);
438 exec("GIT_DIR=\"$tmp_repo\" git update-server-info", $result);
439 $output .= join("<br />", $result);
440 if (is_file("$tmp_repo/hooks/post-update.sample")) {
441 rename("$tmp_repo/hooks/post-update.sample",
442 "$tmp_repo/hooks/post-update");
444 if (!is_file("$tmp_repo/hooks/post-update")) {
445 $f = fopen("$tmp_repo/hooks/post-update", 'w');
446 fwrite($f, "exec git-update-server-info\n");
449 if (is_file("$tmp_repo/hooks/post-update")) {
450 system("chmod +x $tmp_repo/hooks/post-update");
452 system("echo \"Git repository for $project_name\" > $tmp_repo/description");
453 system("find $tmp_repo -type d | xargs chmod g+s");
454 system("chgrp -R $unix_group $tmp_repo");
455 system("chmod -R g+wX,o+rX-w $tmp_repo");
456 if ($project->enableAnonSCM()) {
457 system("chmod g+wX,o+rX-w $root");
459 system("chmod g+wX,o-rwx $root");
460 system("chmod g+wX,o-rwx $tmp_repo");
464 * $main_repo can already exist, for example if it’s
465 * not a directory or doesn’t contain a HEAD file or
466 * an objects or refs subdirectory… move it out of
467 * the way in these cases
469 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");
470 /* here’s still a TOCTOU but we check $ret below */
471 system("mv $tmp_repo $main_repo", $ret);
475 system("echo \"Git repository for $project_name\" > $main_repo/description");
476 system("find $main_repo -type d | xargs chmod g+s");
477 if (forge_get_config('use_dav','scmgit')) {
478 $f = fopen(forge_get_config('config_path').'/httpd.conf.d/plugin-scmgit-dav.inc','a');
479 fputs($f,'Use Project '.$project_name."\n");
481 system(forge_get_config('httpd_reload_cmd','scmgit'));
484 if (forge_get_config('use_ssh','scmgit')) {
485 if ($project->enableAnonSCM()) {
486 system("chmod g+wX,o+rX-w $root");
487 system("chmod g+rwX,o+rX-w $main_repo");
489 system("chmod g+wX,o-rwx $root");
490 system("chmod g+rwX,o-rwx $main_repo");
493 $unix_user = forge_get_config('apache_user');
494 system("chown $unix_user:$unix_group $main_repo");
495 system("chmod g-rwx,o-rwx $main_repo");
498 // Create project-wide secondary repositories
499 $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',
500 array($project->getID(),
501 SCM_EXTRA_REPO_ACTION_UPDATE,
503 $rows = db_numrows($result);
504 for ($i=0; $i<$rows; $i++) {
505 $repo_name = db_result($result,$i,'repo_name');
506 $description = db_result($result,$i,'description');
507 $clone_url = db_result($result,$i,'clone_url');
508 $repodir = $root . '/' . $repo_name . '.git';
509 if (!is_file("$repodir/HEAD") && !is_dir("$repodir/objects") && !is_dir("$repodir/refs")) {
510 if ($clone_url != '') {
511 system("cd $root;git clone --bare $clone_url $repodir");
513 system("GIT_DIR=\"$repodir\" git init --bare --shared=group");
515 system("GIT_DIR=\"$repodir\" git update-server-info");
516 if (is_file("$repodir/hooks/post-update.sample")) {
517 rename("$repodir/hooks/post-update.sample",
518 "$repodir/hooks/post-update");
520 if (!is_file("$repodir/hooks/post-update")) {
521 $f = fopen("$repodir/hooks/post-update");
522 fwrite($f, "exec git-update-server-info\n");
525 if (is_file("$repodir/hooks/post-update")) {
526 system("chmod +x $repodir/hooks/post-update");
528 $f = fopen("$repodir/description", "w");
529 fwrite($f, $description."\n");
531 system("chgrp -R $unix_group $repodir");
532 system("chmod g+s $root");
533 if ($project->enableAnonSCM()) {
534 system("chmod -R g+wX,o+rX-w $main_repo");
536 system("chmod -R g+wX,o-rwx $main_repo");
541 // Delete project-wide secondary repositories
542 $result = db_query_params ('SELECT repo_name FROM scm_secondary_repos WHERE group_id=$1 AND next_action = $2 AND plugin_id=$3',
543 array($project->getID(),
544 SCM_EXTRA_REPO_ACTION_DELETE,
546 $rows = db_numrows ($result);
547 for ($i=0; $i<$rows; $i++) {
548 $repo_name = db_result($result,$i,'repo_name');
549 $repodir = $root . '/' . $repo_name . '.git';
550 if (util_is_valid_repository_name($repo_name)) {
551 system("rm -rf $repodir");
553 db_query_params ('DELETE FROM scm_secondary_repos WHERE group_id=$1 AND repo_name=$2 AND next_action = $3 AND plugin_id=$4',
554 array($project->getID(),
556 SCM_EXTRA_REPO_ACTION_DELETE,
560 // Create users' personal repositories
561 $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',
562 array($project->getID(),
565 $rows = db_numrows ($result);
566 for ($i=0; $i<$rows; $i++) {
567 system("mkdir -p $root/users");
568 $user_name = db_result($result,$i,'user_name');
569 $repodir = $root . '/users/' . $user_name . '.git';
571 if (!is_dir($repodir) && mkdir ($repodir, 0700)) {
572 chown ($repodir, $user_name);
575 $params['project'] = $project;
576 $params['user_name'] = $user_name;
577 $params['unix_group'] = $unix_group;
578 $params['root'] = $root;
579 $params['main_repo'] = $main_repo;
581 util_sudo_effective_user($user_name,
582 array("GitPlugin","createUserRepo"),
586 if (is_dir ("$root/users")) {
587 if ($project->enableAnonSCM()) {
588 system("chmod g+rX-w,o+rX-w $root/users");
590 system("chmod g+rX-w,o-rwx $root/users");
593 $params['output'] = $output;
596 function updateRepositoryList($params) {
597 $groups = $this->getGroups();
599 foreach ($groups as $project) {
600 if ($this->browserDisplayable($project)) {
605 $config_dir = forge_get_config('config_path').'/plugins/scmgit';
606 if (!is_dir($config_dir)) {
607 mkdir($config_dir, 0755, true);
609 $fname = $config_dir . '/gitweb.conf';
610 $config_f = fopen($fname.'.new', 'w');
611 $rootdir = forge_get_config('repos_path', 'scmgit');
612 fwrite($config_f, "\$projectroot = '$rootdir';\n");
613 fwrite($config_f, "\$projects_list = '$config_dir/gitweb.list';\n");
614 fwrite($config_f, "@git_base_url_list = ('". util_make_url('/anonscm/git') . "');\n");
615 fwrite($config_f, "\$logo = '". util_make_url('/plugins/scmgit/git-logo.png') . "';\n");
616 fwrite($config_f, "\$favicon = '". util_make_url('/plugins/scmgit/git-favicon.png')."';\n");
617 fwrite($config_f, "\$stylesheet = '". util_make_url('/plugins/scmgit/gitweb.css')."';\n");
618 fwrite($config_f, "\$javascript = '". util_make_url('/plugins/scmgit/gitweb.js')."';\n");
619 fwrite($config_f, "\$prevent_xss = 'true';\n");
620 fwrite($config_f, "\$feature{'actions'}{'default'} = [('project home', '" .
621 util_make_url('/plugins/scmgit/?func=grouppage/%n') .
622 "', 'summary')];\n");
624 chmod($fname.'.new', 0644);
625 rename($fname.'.new', $fname);
627 $fname = $config_dir . '/gitweb.list';
628 $f = fopen($fname.'.new', 'w');
630 $engine = RBACEngine::getInstance();
631 foreach ($list as $project) {
632 $repos = $this->getRepositories($rootdir . "/" . $project->getUnixName());
633 foreach ($repos as $repo) {
634 $reldir = substr($repo, strlen($rootdir) + 1);
635 fwrite($f, $reldir . "\n");
637 $users = $engine->getUsersByAllowedAction('scm',$project->getID(),'write');
639 foreach ($users as $user) {
640 $password_data .= $user->getUnixName().':'.$user->getUnixPasswd()."\n";
642 $faname = forge_get_config('data_path').'/gituser-authfile.'.$project->getUnixName();
643 $fa = fopen($faname.'.new', 'w');
644 fwrite($fa, $password_data);
646 chmod($faname.'.new', 0644);
647 rename($faname.'.new', $faname);
650 chmod($fname.'.new', 0644);
651 rename($fname.'.new', $fname);
654 function getRepositories($path) {
655 if (!is_dir($path)) {
659 $entries = scandir($path);
660 foreach ($entries as $entry) {
661 $fullname = $path . "/" . $entry;
662 if (($entry == ".") or ($entry == ".."))
664 if (is_dir($fullname)) {
665 if (is_link($fullname))
667 $result = $this->getRepositories($fullname);
668 $list = array_merge($list, $result);
669 } elseif ($entry == "HEAD") {
676 function gatherStats($params) {
677 $project = $this->checkParams($params);
682 if (!$project->usesPlugin($this->name)) {
686 if ($params['mode'] == 'day') {
687 $year = $params ['year'];
688 $month = $params ['month'];
689 $day = $params ['day'];
690 $month_string = sprintf( "%04d%02d", $year, $month );
691 $start_time = gmmktime( 0, 0, 0, $month, $day, $year);
692 $end_time = $start_time + 86400;
695 $usr_updates = array();
696 $usr_deletes = array();
701 $repo = forge_get_config('repos_path', 'scmgit') . '/' . $project->getUnixName() . '/' . $project->getUnixName() . '.git';
702 if (!is_dir($repo) || !is_dir("$repo/refs")) {
703 // echo "No repository $repo\n";
707 $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' );
711 // cleaning stats_cvs_* table for the current day
712 $res = db_query_params('DELETE FROM stats_cvs_group WHERE month=$1 AND day=$2 AND group_id=$3',
717 echo "Error while cleaning stats_cvs_group\n";
723 while (!feof($pipe) && $data = fgets($pipe)) {
725 if (strlen($line) > 0) {
726 $result = preg_match("/^(?P<name>.+) <(?P<mail>.+)>/", $line, $matches);
729 $last_user = $matches['name'];
730 $user2email[$last_user] = strtolower($matches['mail']);
731 if (!isset($usr_adds[$last_user])) {
732 $usr_adds[$last_user] = 0;
733 $usr_updates[$last_user] = 0;
734 $usr_deletes[$last_user] = 0;
737 // Short-commit stats line
738 $result = preg_match("/^(?P<mode>[AMD])\s+(?P<file>.+)$/", $line, $matches);
739 if (!$result) continue;
740 if ($last_user == "") continue;
741 if (!isset($usr_adds[$last_user])) $usr_adds[$last_user] = 0;
742 if (!isset($usr_updates[$last_user])) $usr_updates[$last_user] = 0;
743 if (!isset($usr_deletes[$last_user])) $usr_deletes[$last_user] = 0;
744 if ($matches['mode'] == 'A') {
745 $usr_adds[$last_user]++;
747 } elseif ($matches['mode'] == 'M') {
748 $usr_updates[$last_user]++;
750 } elseif ($matches['mode'] == 'D') {
751 $usr_deletes[$last_user]++;
757 // inserting group results in stats_cvs_groups
758 if ($updates > 0 || $adds > 0) {
759 if (!db_query_params('INSERT INTO stats_cvs_group (month,day,group_id,checkouts,commits,adds) VALUES ($1,$2,$3,$4,$5,$6)',
766 echo "Error while inserting into stats_cvs_group\n";
772 // building the user list
773 $user_list = array_unique( array_merge( array_keys( $usr_adds ), array_keys( $usr_updates ) ) );
775 foreach ($user_list as $user) {
776 // Trying to get user id from user name or email
777 $u = user_get_object_by_name($user);
779 $user_id = $u->getID();
781 $res=db_query_params('SELECT user_id FROM users WHERE lower(realname)=$1 OR email=$2',
782 array(strtolower($user), $user2email[$user]));
783 if ($res && db_numrows($res) > 0) {
784 $user_id = db_result($res,0,'user_id');
790 $uu = isset($usr_updates[$user]) ? $usr_updates[$user] : 0;
791 $ua = isset($usr_adds[$user]) ? $usr_adds[$user] : 0;
792 if ($uu > 0 || $ua > 0) {
793 if (!db_query_params('INSERT INTO stats_cvs_user (month,day,group_id,user_id,commits,adds) VALUES ($1,$2,$3,$4,$5,$6)',
800 echo "Error while inserting into stats_cvs_user\n";
810 function generateSnapshots($params) {
812 $project = $this->checkParams($params);
817 $group_name = $project->getUnixName();
819 $snapshot = forge_get_config('scm_snapshots_path').'/'.$group_name.'-scm-latest.tar'.util_get_compressed_file_extension();
820 $tarball = forge_get_config('scm_tarballs_path').'/'.$group_name.'-scmroot.tar'.util_get_compressed_file_extension();
822 if (!$project->usesPlugin($this->name)) {
826 if (!$project->enableAnonSCM()) {
827 if (is_file($snapshot)) {
830 if (is_file($tarball)) {
836 // TODO: ideally we generate one snapshot per git repository
837 $toprepo = forge_get_config('repos_path', 'scmgit');
838 $repo = $toprepo . '/' . $project->getUnixName() . '/' . $project->getUnixName() . '.git';
840 if (!is_dir($repo)) {
841 if (is_file($snapshot)) {
844 if (is_file($tarball)) {
850 // Skip empty repo (no HEAD present in repository)
851 $ref = trim(`GIT_DIR=$repo git symbolic-ref HEAD`);
852 if (!file_exists($repo.'/'.$ref)) {
856 $tmp = trim(`mktemp -d`);
860 $today = date('Y-m-d');
861 system("GIT_DIR=\"$repo\" git archive --format=tar --prefix=$group_name-scm-$today/ HEAD |".forge_get_config('compression_method')." > $tmp/snapshot");
862 chmod("$tmp/snapshot", 0644);
863 copy("$tmp/snapshot", $snapshot);
864 unlink("$tmp/snapshot");
866 system("tar cCf $toprepo - ".$project->getUnixName() ."|".forge_get_config('compression_method')."> $tmp/tarball");
867 chmod("$tmp/tarball", 0644);
868 copy("$tmp/tarball", $tarball);
869 unlink("$tmp/tarball");
870 system("rm -rf $tmp");
874 * widgets - 'widgets' hook handler
875 * @param array $params
878 function widgets($params) {
879 require_once 'common/widget/WidgetLayoutManager.class.php';
880 if ($params['owner_type'] == WidgetLayoutManager::OWNER_TYPE_GROUP) {
881 $params['fusionforge_widgets'][] = 'plugin_scmgit_project_latestcommits';
883 if ($params['owner_type'] == WidgetLayoutManager::OWNER_TYPE_USER) {
884 $params['fusionforge_widgets'][] = 'plugin_scmgit_user_myrepositories';
890 * Process the 'widget_instance' hook to create instances of the widgets
891 * @param array $params
893 function myPageBox($params) {
895 $user = UserManager::instance()->getCurrentUser();
896 require_once 'common/widget/WidgetLayoutManager.class.php';
897 if ($params['widget'] == 'plugin_scmgit_user_myrepositories') {
898 require_once $gfplugins.$this->name.'/common/scmgit_Widget_MyRepositories.class.php';
899 $params['instance'] = new scmgit_Widget_MyRepositories(WidgetLayoutManager::OWNER_TYPE_USER, $user->getId());
903 function weekly(&$params) {
904 $res = db_query_params('SELECT group_id FROM groups WHERE status=$1 AND use_scm=1 ORDER BY group_id DESC',
907 $params['output'] .= 'ScmGit Plugin: Unable to get list of projects using SCM: '.db_error();
911 $params['output'] .= 'ScmGit Plugin: Running "git gc --quiet" on '.db_numrows($res).' repositories.'."\n";
912 while ($row = db_fetch_array($res)) {
913 $project = group_get_object($row['group_id']);
914 if (!$project || !is_object($project)) {
916 } elseif ($project->isError()) {
919 if (!$project->usesPlugin($this->name)) {
923 $project_name = $project->getUnixName();
924 $repo = forge_get_config('repos_path', 'scmgit') . '/' . $project_name . '/' . $project_name .'.git';
927 $params['output'] .= $project_name.': '.`git gc --quiet 2>&1`;
932 function activity($params) {
933 $group_id = $params['group'];
934 $project = group_get_object($group_id);
935 if (!$project->usesPlugin($this->name)) {
938 if (in_array('scmgit', $params['show']) || (count($params['show']) < 1)) {
939 $start_time = $params['begin'];
940 $end_time = $params['end'];
941 $repo = forge_get_config('repos_path', 'scmgit') . '/' . $project->getUnixName() . '/' . $project->getUnixName() . '.git';
942 $pipe = popen("GIT_DIR=\"$repo\" git log --date=raw --since=@$start_time --until=@$end_time --all --pretty='format:%ad||%ae||%s||%h' --name-status", 'r' );
943 while (!feof($pipe) && $data = fgets($pipe)) {
945 $splitedLine = explode('||', $line);
946 if (sizeof($splitedLine) == 4) {
948 $result['section'] = 'scm';
949 $result['group_id'] = $group_id;
950 $result['ref_id'] = 'browser.php?group_id='.$group_id;
951 $result['description'] = $splitedLine[2].' (commit '.$splitedLine[3].')';
952 $userObject = user_get_object_by_email($splitedLine[1]);
953 if (is_a($userObject, 'GFUser')) {
954 $result['realname'] = make_user_link($userObject->getUnixName(), $userObject->getRealName());
956 $result['realname'] = '';
958 $splitedDate = explode(' ', $splitedLine[0]);
959 $result['activity_date'] = $splitedDate[0];
960 $result['subref_id'] = '';
961 $params['results'][] = $result;
965 $params['ids'][] = $this->name;
966 $params['texts'][] = _('Git Commits');
970 function scm_add_repo(&$params) {
971 $project = $this->checkParams($params);
975 if (!$project->usesPlugin($this->name)) {
979 if (!isset($params['repo_name'])) {
983 if ($params['repo_name'] == $project->getUnixName()) {
984 $params['error_msg'] = _('Cannot create a secondary repository with the same name as the primary');
988 if (!util_is_valid_repository_name($params['repo_name'])) {
989 $params['error_msg'] = _('This repository name is not valid');
993 $result = db_query_params('SELECT count(*) AS count FROM scm_secondary_repos WHERE group_id=$1 AND repo_name = $2 AND plugin_id=$3',
994 array($params['group_id'],
995 $params['repo_name'],
998 $params['error_msg'] = db_error();
1001 if (db_result($result, 0, 'count')) {
1002 $params['error_msg'] = sprintf(_('A repository %s already exists'), $params['repo_name']);
1008 if (isset($params['clone'])) {
1009 $url = $params['clone'];
1013 } elseif (preg_match('|^git://|', $url) || preg_match('|^https?://|', $url)) {
1014 // External URLs: OK
1016 } elseif ($url == $project->getUnixName()) {
1018 } 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',
1019 array($project->getID(),
1022 && db_result($result, 0, 'count')) {
1023 // Local repo: try to clone from an existing repo in same project
1027 $params['error_msg'] = _('Invalid URL from which to clone');
1032 if (isset($params['description'])) {
1033 $description = $params['description'];
1035 if ($clone && !$description) {
1036 $description = sprintf(_('Clone of %s'), $params['clone']);
1038 if (!$description) {
1039 $description = "Git repository $params[repo_name] for project ".$project->getUnixName();
1042 $result = db_query_params('INSERT INTO scm_secondary_repos (group_id, repo_name, description, clone_url, plugin_id) VALUES ($1, $2, $3, $4, $5)',
1043 array($params['group_id'],
1044 $params['repo_name'],
1049 $params['error_msg'] = db_error();
1053 plugin_hook ("scm_admin_update", $params);
1057 function scm_admin_form(&$params) {
1058 $project = $this->checkParams($params);
1062 if (!$project->usesPlugin($this->name)) {
1066 session_require_perm('project_admin', $params['group_id']);
1068 $project_name = $project->getUnixName();
1070 $select_repo = '<select name="frontpage">' . "\n";
1071 $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',
1072 array($params['group_id'],
1073 SCM_EXTRA_REPO_ACTION_UPDATE,
1076 $params['error_msg'] = db_error();
1079 $existing_repos = array();
1080 while($data = db_fetch_array($result)) {
1081 $existing_repos[] = array('repo_name' => $data['repo_name'],
1082 'description' => $data['description'],
1083 'clone_url' => $data['clone_url']);
1085 if (count($existing_repos) == 0) {
1086 printf('<h2>'._('No extra Git repository for project %1$s').'</h2>', $project_name);
1088 $t = sprintf(ngettext('Extra Git repository for project %1$s',
1089 'Extra Git repositories for project %1$s',
1090 count($existing_repos)), $project_name);
1091 print '<h2>'.$t.'</h2>';
1092 print '<table><thead><tr><th>'._('Repository name').'</th><th>'._('Initial repository description').'</th><th>'._('Initial clone URL (if any)').'</th><th>'._('Delete').'</th></tr></thead><tbody>';
1093 foreach ($existing_repos as $repo) {
1094 print "<tr><td><tt>$repo[repo_name]</tt></td><td>$repo[description]</td><td>$repo[clone_url]</td><td>";
1096 <form name="form_delete_repo_<?php echo $repo['repo_name']?>"
1097 action="<?php echo getStringFromServer('PHP_SELF'); ?>" method="post">
1098 <input type="hidden" name="group_id" value="<?php echo $params['group_id'] ?>" />
1099 <input type="hidden" name="delete_repository" value="1" />
1100 <input type="hidden" name="repo_name" value="<?php echo $repo['repo_name']?>" />
1101 <input type="submit" name="submit" value="<?php echo _('Delete') ?>" />
1104 print "</td></tr>\n";
1106 print '</tbody></table>';
1109 printf('<h2>'._('Create new Git repository for project %1$s').'</h2>', $project_name);
1112 <form name="form_create_repo"
1113 action="<?php echo getStringFromServer('PHP_SELF'); ?>" method="post">
1114 <input type="hidden" name="group_id" value="<?php echo $params['group_id'] ?>" />
1115 <input type="hidden" name="create_repository" value="1" />
1116 <p><strong><?php echo _('Repository name:') ?></strong><?php echo utils_requiredField(); ?><br />
1117 <input type="text" required="required" size="20" name="repo_name" value="" /></p>
1118 <p><strong><?php echo _('Description:'); ?></strong><br />
1119 <input type="text" size="60" name="description" value="" /></p>
1120 <p><strong><?php echo _('Initial clone URL (or name of an existing repository in this project; leave empty to start with an empty repository):') ?></strong><br />
1121 <input type="text" size="60" name="clone" value="<?php echo $project_name; ?>" /></p>
1122 <input type="submit" name="cancel" value="<?php echo _('Cancel') ?>" />
1123 <input type="submit" name="submit" value="<?php echo _('Submit') ?>" />
1133 // c-file-style: "bsd"