3 * FusionForge Git plugin
5 * Copyright 2009, Roland Mas
6 * Copyright 2009, Mehdi Dogguy <mehdi@debian.org>
7 * Copyright 2012-2013, Franck Villaume - TrivialDev
8 * http://fusionforge.org
10 * This file is part of FusionForge.
12 * FusionForge is free software; you can redistribute it and/or modify
13 * it under the terms of the GNU General Public License as published
14 * by the Free Software Foundation; either version 2 of the License,
15 * or (at your option) any later version.
17 * FusionForge is distributed in the hope that it will be useful, but
18 * WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
20 * General Public License for more details.
22 * You should have received a copy of the GNU General Public License along
23 * with this program; if not, write to the Free Software Foundation, Inc.,
24 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
27 forge_define_config_item('default_server', 'scmgit', forge_get_config ('web_host'));
28 forge_define_config_item('repos_path', 'scmgit', forge_get_config('chroot').'/scmrepos/git');
29 forge_define_config_item('use_ssh', 'scmgit', false);
30 forge_set_config_item_bool('use_ssh', 'scmgit');
31 forge_define_config_item('use_dav', 'scmgit', true);
32 forge_set_config_item_bool('use_dav', 'scmgit');
33 forge_define_config_item('use_ssl', 'scmgit', true);
34 forge_set_config_item_bool('use_ssl', 'scmgit');
37 class GitPlugin extends SCMPlugin {
38 function GitPlugin() {
40 $this->name = 'scmgit';
42 $this->_addHook('scm_browser_page');
43 $this->_addHook('scm_update_repolist');
44 $this->_addHook('scm_generate_snapshots');
45 $this->_addHook('scm_gather_stats');
46 $this->_addHook('scm_admin_form');
47 $this->_addHook('scm_add_repo');
48 $this->_addHook('scm_delete_repo');
49 $this->_addHook('widget_instance', 'myPageBox', false);
50 $this->_addHook('widgets', 'widgets', false);
51 $this->_addHook('activity');
52 $this->_addHook('weekly');
56 function getDefaultServer() {
57 return forge_get_config('default_server', 'scmgit') ;
60 function printShortStats ($params) {
61 $project = $this->checkParams($params);
66 if ($project->usesPlugin($this->name) && forge_check_perm('scm', $project->getID(), 'read')) {
67 $result = db_query_params('SELECT sum(commits) AS commits, sum(adds) AS adds FROM stats_cvs_group WHERE group_id=$1',
68 array ($project->getID())) ;
69 $commit_num = db_result($result,0,'commits');
70 $add_num = db_result($result,0,'adds');
77 echo ' (Git: '.sprintf(_('<strong>%1$s</strong> commits, <strong>%2$s</strong> adds'), number_format($commit_num, 0), number_format($add_num, 0)).")";
82 return '<p>' . _('Documentation for Git is available at <a href="http://git-scm.com/">http://git-scm.com/</a>.') . '</p>';
85 function getInstructionsForAnon($project) {
86 $repo_list = array($project->getUnixName());
87 $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',
88 array ($project->getID(),
89 SCM_EXTRA_REPO_ACTION_UPDATE,
91 $rows = db_numrows ($result) ;
92 for ($i=0; $i<$rows; $i++) {
93 $repo_list[] = db_result($result,$i,'repo_name');
96 $b = '<h2>' . ngettext('Anonymous Access to the Git repository',
97 'Anonymous Access to the Git repositories',
98 count($repo_list)) . '</h2>';
101 $b .= ngettext('This project\'s Git repository can be checked out through anonymous access with the following command.',
102 'This project\'s Git repositories can be checked out through anonymous access with the following commands.',
107 foreach ($repo_list as $repo_name) {
109 $b .= '<tt>git clone '.util_make_url ('/anonscm/git/'.$project->getUnixName().'/'.$repo_name.'.git').'</tt><br />';
113 $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',
114 array ($project->getID(),
117 $rows = db_numrows($result);
121 $b .= ngettext('Developer\'s repository',
122 'Developer\'s repositories',
126 $b .= ngettext('One of this project\'s members also has a personal Git repository that can be checked out anonymously.',
127 'Some of this project\'s members also have personal Git repositories that can be checked out anonymously.',
131 for ($i=0; $i<$rows; $i++) {
132 $user_id = db_result($result,$i,'user_id');
133 $user_name = db_result($result,$i,'user_name');
134 $real_name = db_result($result,$i,'realname');
135 $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 />';
143 function getInstructionsForRW($project) {
144 $repo_list = array($project->getUnixName());
146 $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',
147 array ($project->getID(),
148 SCM_EXTRA_REPO_ACTION_UPDATE,
150 $rows = db_numrows ($result) ;
151 for ($i=0; $i<$rows; $i++) {
152 $repo_list[] = db_result($result,$i,'repo_name');
155 if (session_loggedin()) {
156 $u = user_get_object(user_getid());
157 $d = $u->getUnixName();
160 if (forge_get_config('use_ssh', 'scmgit')) {
162 $b = '<h2>' . ngettext('Developer Access to the Git repository via SSH',
163 'Developer Access to the Git repositories via SSH',
164 count($repo_list)) . '</h2>';
167 $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.',
168 '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.',
172 foreach ($repo_list as $repo_name) {
173 $b .= '<p><tt>git clone git+ssh://'.$d.'@' . $project->getSCMBox() . '/'. forge_get_config('repos_path', 'scmgit') .'/'. $project->getUnixName() .'/'. $repo_name .'.git</tt></p>' ;
178 if (forge_get_config('use_dav', 'scmgit')) {
179 $protocol = forge_get_config('use_ssl', 'scmgit')? 'https' : 'http';
181 $b = '<h2>' . ngettext('Developer Access to the Git repository via HTTP',
182 'Developer Access to the Git repositories via HTTP',
183 count($repo_list)) . '</h2>';
187 $b .= ngettext('Only project developers can access the GIT repository via this method. Enter your site password when prompted.',
188 'Only project developers can access the GIT repositories via this method. Enter your site password when prompted.',
192 foreach ($repo_list as $repo_name) {
193 $b .= '<p><tt>git clone '.$protocol.'://'.$d.'@' . $project->getSCMBox() . '/'. forge_get_config('scm_root', 'scmgit') .'/'. $project->getUnixName() .'/'. $repo_name .'.git</tt></p>' ;
199 if ($validSetup == 0) {
200 $b = '<p class="warning">'._('Missing configuration for access in scmgit.ini : use_ssh and use_dav disabled').'</p>';
203 if (forge_get_config('use_ssh', 'scmgit')) {
205 $b = '<h2>' . ngettext('Developer Access to the Git repository via SSH',
206 'Developer Access to the Git repositories via SSH',
207 count($repo_list)) . '</h2>';
211 $b .= ngettext('Only project developers can access the GIT repository via this method. SSH must be installed on your client machine. Substitute <i>developername</i> with the proper value. Enter your site password when prompted.',
212 'Only project developers can access the GIT repositories via this method. SSH must be installed on your client machine. Substitute <i>developername</i> with the proper value. Enter your site password when prompted.',
216 foreach ($repo_list as $repo_name) {
217 $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>' ;
221 if (forge_get_config('use_dav', 'scmgit')) {
222 $protocol = forge_get_config('use_ssl', 'scmgit')? 'https' : 'http';
224 $b = '<h2>' . ngettext('Developer Access to the Git repository via HTTP',
225 'Developer Access to the Git repositories via HTTP',
226 count($repo_list)) . '</h2>';
229 $b .= ngettext('Only project developers can access the GIT repository via this method. Enter your site password when prompted.',
230 'Only project developers can access the GIT repositories via this method. Enter your site password when prompted.',
234 foreach ($repo_list as $repo_name) {
235 $b .= '<p><tt>git clone '.$protocol.'://<i>'._('developername').'</i>@' . $project->getSCMBox() . '/'. forge_get_config('scm_root', 'scmgit') .'/'. $project->getUnixName() .'/'. $repo_name .'.git</tt></p>' ;
241 if (session_loggedin()) {
242 $u =& user_get_object(user_getid()) ;
243 if ($u->getUnixStatus() == 'A') {
244 $result = db_query_params('SELECT * FROM scm_personal_repos p WHERE p.group_id=$1 AND p.user_id=$2 AND plugin_id=$3',
245 array ($project->getID(),
248 if ($result && db_numrows ($result) > 0) {
250 $b .= _('Access to your personal repository');
253 $b .= _('You have a personal repository for this project, accessible through SSH with the following method. Enter your site password when prompted.');
255 $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>' ;
257 $glist = $u->getGroups();
258 foreach ($glist as $g) {
259 if ($g->getID() == $project->getID()) {
261 $b .= _('Request a personal repository');
264 $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).');
267 $b .= sprintf (_('<a href="%s">Request a personal repository</a>.'),
268 util_make_url ('/plugins/scmgit/index.php?func=request-personal-repo&group_id='.$project->getID()));
278 function getSnapshotPara($project) {
281 $filename = $project->getUnixName().'-scm-latest.tar'.util_get_compressed_file_extension();
282 if (file_exists(forge_get_config('scm_snapshots_path').'/'.$filename)) {
284 $b .= util_make_link("/snapshots.php?group_id=".$project->getID(),
285 _('Download the nightly snapshot')
292 function printBrowserPage($params) {
293 $project = $this->checkParams($params);
298 if ($project->usesPlugin($this->name)) {
299 if ($params['user_id']) {
300 $user = user_get_object($params['user_id']);
301 echo $project->getUnixName().'/users/'.$user->getUnixName();
302 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>' ;
303 } elseif ($this->browserDisplayable($project)) {
304 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>' ;
309 function getBrowserLinkBlock($project) {
311 $b = $HTML->boxMiddle(_('Git Repository Browser'));
313 $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.');
316 $b .= util_make_link ("/scm/browser.php?group_id=".$project->getID(),
317 _('Browse Git Repository')
323 function getStatsBlock ($project) {
327 $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',
328 array ($project->getID()));
330 if (db_numrows($result) > 0) {
331 // $b .= $HTML->boxMiddle(_('Repository Statistics'));
333 $tableHeaders = array(
338 $b .= $HTML->listTableTop($tableHeaders, false, '', 'repo-history');
341 $total = array('adds' => 0, 'commits' => 0);
343 while($data = db_fetch_array($result)) {
344 $b .= '<tr '. $HTML->boxGetAltRowStyle($i) .'>';
345 $b .= '<td class="halfwidth">' ;
346 $b .= util_make_link_u ($data['user_name'], $data['user_id'], $data['realname']) ;
347 $b .= '</td><td class="onequarterwidth align-right">'.$data['adds']. '</td>'.
348 '<td class="onequarterwidth align-right">'.$data['commits'].'</td></tr>';
349 $total['adds'] += $data['adds'];
350 $total['commits'] += $data['commits'];
353 $b .= '<tr '. $HTML->boxGetAltRowStyle($i) .'>';
354 $b .= '<td class="halfwidth"><strong>'._('Total').':</strong></td>'.
355 '<td class="onequarterwidth align-right"><strong>'.$total['adds']. '</strong></td>'.
356 '<td class="onequarterwidth align-right"><strong>'.$total['commits'].'</strong></td>';
358 $b .= $HTML->listTableBottom();
364 static function createUserRepo($params) {
365 $project = $params['project'];
366 $project_name = $project->getUnixName();
367 $user_name = $params['user_name'];
368 $unix_group = $params['unix_group'];
369 $main_repo = $params['main_repo'];
370 $root = $params['root'];
372 $repodir = $root . '/users/' . $user_name . '.git' ;
373 chgrp($repodir, $unix_group);
374 if ($project->enableAnonSCM()) {
375 chmod ($repodir, 02755);
377 chmod ($repodir, 02750);
379 if (!is_file ("$repodir/HEAD") && !is_dir("$repodir/objects") && !is_dir("$repodir/refs")) {
380 system ("git clone --bare $main_repo $repodir") ;
381 system ("GIT_DIR=\"$repodir\" git update-server-info") ;
382 if (is_file ("$repodir/hooks/post-update.sample")) {
383 rename ("$repodir/hooks/post-update.sample",
384 "$repodir/hooks/post-update") ;
386 if (!is_file ("$repodir/hooks/post-update")) {
387 $f = fopen ("$repodir/hooks/post-update","x+") ;
388 fwrite ($f, "exec git-update-server-info\n") ;
391 if (is_file ("$repodir/hooks/post-update")) {
392 system ("chmod +x $repodir/hooks/post-update") ;
394 system("echo \"Git repository for user $user_name in project $project_name\" > $repodir/description");
398 function createOrUpdateRepo($params) {
399 $project = $this->checkParams ($params) ;
404 if (! $project->usesPlugin($this->name)) {
408 $project_name = $project->getUnixName();
409 $root = forge_get_config('repos_path', 'scmgit') . '/' . $project_name;
410 if (!is_dir($root)) {
411 system("mkdir -p $root");
415 if (forge_get_config('use_ssh','scmgit')) {
416 $unix_group = 'scm_' . $project_name ;
418 $unix_group = forge_get_config('apache_group');
421 // Create main repository
422 $main_repo = $root . '/' . $project_name . '.git' ;
423 if (!is_dir($main_repo) || (!is_file("$main_repo/HEAD") &&
424 !is_dir("$main_repo/objects") && !is_dir("$main_repo/refs"))) {
425 $tmp_repo = util_mkdtemp('.git', $project_name);
426 if ($tmp_repo == false) {
430 exec ("GIT_DIR=\"$tmp_repo\" git init --bare --shared=group", $result) ;
431 $output .= join("<br />", $result);
433 exec ("GIT_DIR=\"$tmp_repo\" git update-server-info", $result) ;
434 $output .= join("<br />", $result);
435 if (is_file ("$tmp_repo/hooks/post-update.sample")) {
436 rename ("$tmp_repo/hooks/post-update.sample",
437 "$tmp_repo/hooks/post-update") ;
439 if (!is_file ("$tmp_repo/hooks/post-update")) {
440 $f = fopen ("$tmp_repo/hooks/post-update", 'w') ;
441 fwrite ($f, "exec git-update-server-info\n") ;
444 if (is_file ("$tmp_repo/hooks/post-update")) {
445 system ("chmod +x $tmp_repo/hooks/post-update") ;
447 system ("echo \"Git repository for $project_name\" > $tmp_repo/description") ;
448 system ("find $tmp_repo -type d | xargs chmod g+s") ;
449 system ("chgrp -R $unix_group $tmp_repo") ;
450 if ($project->enableAnonSCM()) {
451 system ("chmod g+wX,o+rX-w $root") ;
452 system ("chmod -R g+wX,o+rX-w $tmp_repo") ;
454 system ("chmod g+wX,o-rwx $root") ;
455 system ("chmod -R g+wX,o-rwx $tmp_repo") ;
459 * $main_repo can already exist, for example if it’s
460 * not a directory or doesn’t contain a HEAD file or
461 * an objects or refs subdirectory… move it out of
462 * the way in these cases
464 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");
465 /* here’s still a TOCTOU but we check $ret below */
466 system("mv $tmp_repo $main_repo", $ret);
470 system ("echo \"Git repository for $project_name\" > $main_repo/description") ;
471 system ("find $main_repo -type d | xargs chmod g+s");
472 if (forge_get_config('use_dav','scmgit')) {
473 $f = fopen(forge_get_config('config_path').'/httpd.conf.d/plugin-scmgit-dav.inc','a');
474 fputs($f,'Use Project '.$project_name."\n");
476 system(forge_get_config('httpd_reload_cmd','scmgit'));
479 if (forge_get_config('use_ssh','scmgit')) {
480 if ($project->enableAnonSCM()) {
481 system ("chmod g+wX,o+rX-w $root") ;
482 system ("chmod g+rwX,o+rX-w $main_repo") ;
484 system ("chmod g+wX,o-rwx $root") ;
485 system ("chmod g+rwX,o-rwx $main_repo") ;
488 $unix_user = forge_get_config('apache_user');
489 system ("chown $unix_user:$unix_group $main_repo") ;
490 system ("chmod g-rwx,o-rwx $main_repo") ;
493 // Create project-wide secondary repositories
494 $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',
495 array ($project->getID(),
496 SCM_EXTRA_REPO_ACTION_UPDATE,
498 $rows = db_numrows ($result) ;
499 for ($i=0; $i<$rows; $i++) {
500 $repo_name = db_result($result,$i,'repo_name');
501 $description = db_result($result,$i,'description');
502 $clone_url = db_result($result,$i,'clone_url');
503 $repodir = $root . '/' . $repo_name . '.git' ;
504 if (!is_file ("$repodir/HEAD") && !is_dir("$repodir/objects") && !is_dir("$repodir/refs")) {
505 if ($clone_url != '') {
506 system ("cd $root;git clone --bare $clone_url $repodir") ;
508 system ("GIT_DIR=\"$repodir\" git init --bare --shared=group") ;
510 system ("GIT_DIR=\"$repodir\" git update-server-info") ;
511 if (is_file ("$repodir/hooks/post-update.sample")) {
512 rename ("$repodir/hooks/post-update.sample",
513 "$repodir/hooks/post-update") ;
515 if (!is_file ("$repodir/hooks/post-update")) {
516 $f = fopen ("$repodir/hooks/post-update") ;
517 fwrite ($f, "exec git-update-server-info\n") ;
520 if (is_file ("$repodir/hooks/post-update")) {
521 system ("chmod +x $repodir/hooks/post-update") ;
523 $f = fopen("$repodir/description", "w");
524 fwrite($f, $description."\n");
526 system ("chgrp -R $unix_group $repodir") ;
527 system ("chmod g+s $root") ;
528 if ($project->enableAnonSCM()) {
529 system ("chmod -R g+wX,o+rX-w $main_repo") ;
531 system ("chmod -R g+wX,o-rwx $main_repo") ;
536 // Delete project-wide secondary repositories
537 $result = db_query_params ('SELECT repo_name FROM scm_secondary_repos WHERE group_id=$1 AND next_action = $2 AND plugin_id=$3',
538 array ($project->getID(),
539 SCM_EXTRA_REPO_ACTION_DELETE,
541 $rows = db_numrows ($result) ;
542 for ($i=0; $i<$rows; $i++) {
543 $repo_name = db_result($result,$i,'repo_name');
544 $repodir = $root . '/' . $repo_name . '.git' ;
545 if (util_is_valid_repository_name($repo_name)) {
546 system ("rm -rf $repodir");
548 db_query_params ('DELETE FROM scm_secondary_repos WHERE group_id=$1 AND repo_name=$2 AND next_action = $3 AND plugin_id=$4',
549 array ($project->getID(),
551 SCM_EXTRA_REPO_ACTION_DELETE,
555 // Create users' personal repositories
556 $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',
557 array ($project->getID(),
560 $rows = db_numrows ($result) ;
561 for ($i=0; $i<$rows; $i++) {
562 system ("mkdir -p $root/users") ;
563 $user_name = db_result($result,$i,'user_name');
564 $repodir = $root . '/users/' . $user_name . '.git' ;
566 if (!is_dir($repodir) && mkdir ($repodir, 0700)) {
567 chown ($repodir, $user_name) ;
570 $params['project'] = $project;
571 $params['user_name'] = $user_name;
572 $params['unix_group'] = $unix_group;
573 $params['root'] = $root;
574 $params['main_repo'] = $main_repo;
576 util_sudo_effective_user($user_name,
577 array("GitPlugin","createUserRepo"),
581 if (is_dir ("$root/users")) {
582 if ($project->enableAnonSCM()) {
583 system ("chmod g+rX-w,o+rX-w $root/users") ;
585 system ("chmod g+rX-w,o-rwx $root/users") ;
588 $params['output'] = $output;
591 function updateRepositoryList($params) {
592 $groups = $this->getGroups();
594 foreach ($groups as $project) {
595 if ($this->browserDisplayable($project)) {
600 $config_dir = forge_get_config('config_path').'/plugins/scmgit';
601 if (!is_dir($config_dir)) {
602 mkdir($config_dir, 0755, true);
604 $fname = $config_dir . '/gitweb.conf' ;
605 $config_f = fopen($fname.'.new', 'w') ;
606 $rootdir = forge_get_config('repos_path', 'scmgit');
607 fwrite($config_f, "\$projectroot = '$rootdir';\n");
608 fwrite($config_f, "\$projects_list = '$config_dir/gitweb.list';\n");
609 fwrite($config_f, "@git_base_url_list = ('". util_make_url('/anonscm/git') . "');\n");
610 fwrite($config_f, "\$logo = '". util_make_url('/plugins/scmgit/git-logo.png') . "';\n");
611 fwrite($config_f, "\$favicon = '". util_make_url('/plugins/scmgit/git-favicon.png')."';\n");
612 fwrite($config_f, "\$stylesheet = '". util_make_url('/plugins/scmgit/gitweb.css')."';\n");
613 fwrite($config_f, "\$javascript = '". util_make_url('/plugins/scmgit/gitweb.js')."';\n");
614 fwrite($config_f, "\$prevent_xss = 'true';\n");
616 chmod($fname.'.new', 0644);
617 rename($fname.'.new', $fname);
619 $fname = $config_dir . '/gitweb.list';
620 $f = fopen($fname.'.new', 'w');
622 $engine = RBACEngine::getInstance();
623 foreach ($list as $project) {
624 $repos = $this->getRepositories($rootdir . "/" . $project->getUnixName());
625 foreach ($repos as $repo) {
626 $reldir = substr($repo, strlen($rootdir) + 1);
627 fwrite($f, $reldir . "\n");
629 $users = $engine->getUsersByAllowedAction('scm',$project->getID(),'write');
631 foreach ($users as $user) {
632 $password_data .= $user->getUnixName().':'.$user->getUnixPasswd()."\n";
634 $faname = forge_get_config('data_path').'/gituser-authfile.'.$project->getUnixName();
635 $fa = fopen($faname.'.new', 'w');
636 fwrite($fa, $password_data);
638 chmod($faname.'.new', 0644);
639 rename($faname.'.new', $faname);
642 chmod($fname.'.new', 0644);
643 rename($fname.'.new', $fname);
646 function getRepositories($path) {
647 if (! is_dir($path)) {
651 $entries = scandir($path);
652 foreach ($entries as $entry) {
653 $fullname = $path . "/" . $entry;
654 if (($entry == ".") or ($entry == ".."))
656 if (is_dir($fullname)) {
657 if (is_link($fullname))
659 $result = $this->getRepositories($fullname);
660 $list = array_merge($list, $result);
661 } elseif ($entry == "HEAD") {
668 function gatherStats ($params) {
669 $project = $this->checkParams ($params) ;
674 if (! $project->usesPlugin ($this->name)) {
678 if ($params['mode'] == 'day') {
679 $year = $params ['year'] ;
680 $month = $params ['month'] ;
681 $day = $params ['day'] ;
682 $month_string = sprintf( "%04d%02d", $year, $month );
683 $start_time = gmmktime( 0, 0, 0, $month, $day, $year);
684 $end_time = $start_time + 86400;
686 $usr_adds = array () ;
687 $usr_updates = array () ;
688 $usr_deletes = array () ;
693 $repo = forge_get_config('repos_path', 'scmgit') . '/' . $project->getUnixName() . '/' . $project->getUnixName() . '.git';
694 if (!is_dir ($repo) || !is_dir ("$repo/refs")) {
695 // echo "No repository\n" ;
699 $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' ) ;
703 // cleaning stats_cvs_* table for the current day
704 $res = db_query_params ('DELETE FROM stats_cvs_group WHERE month=$1 AND day=$2 AND group_id=$3',
705 array ($month_string,
707 $project->getID())) ;
709 echo "Error while cleaning stats_cvs_group\n" ;
715 while (!feof($pipe) && $data = fgets ($pipe)) {
717 if (strlen($line) > 0) {
718 $result = preg_match("/^(?P<name>.+) <(?P<mail>.+)>/", $line, $matches);
721 $last_user = $matches['name'];
722 $user2email[$last_user] = strtolower($matches['mail']);
723 if (!isset($usr_adds[$last_user])) {
724 $usr_adds[$last_user] = 0;
725 $usr_updates[$last_user] = 0;
726 $usr_deletes[$last_user] = 0;
729 // Short-commit stats line
730 preg_match("/^(?P<mode>[AM])\s+(?P<file>.+)$/", $line, $matches);
731 if ($last_user == "") continue;
732 if ($matches['mode'] == 'A') {
733 $usr_adds[$last_user]++;
735 } elseif ($matches['mode'] == 'M') {
736 $usr_updates[$last_user]++;
738 } elseif ($matches['mode'] == 'D') {
739 $usr_deletes[$last_user]++;
745 // inserting group results in stats_cvs_groups
746 if ($updates > 0 || $adds > 0) {
747 if (!db_query_params ('INSERT INTO stats_cvs_group (month,day,group_id,checkouts,commits,adds) VALUES ($1,$2,$3,$4,$5,$6)',
748 array ($month_string,
754 echo "Error while inserting into stats_cvs_group\n" ;
760 // building the user list
761 $user_list = array_unique( array_merge( array_keys( $usr_adds ), array_keys( $usr_updates ) ) );
763 foreach ( $user_list as $user ) {
764 // Trying to get user id from user name or email
765 $u = &user_get_object_by_name ($user) ;
767 $user_id = $u->getID();
769 $res=db_query_params('SELECT user_id FROM users WHERE lower(realname)=$1 OR email=$2',
770 array (strtolower($user), $user2email[$user]));
771 if ($res && db_numrows($res) > 0) {
772 $user_id = db_result($res,0,'user_id');
778 $uu = $usr_updates[$user] ? $usr_updates[$user] : 0 ;
779 $ua = $usr_adds[$user] ? $usr_adds[$user] : 0 ;
780 if ($uu > 0 || $ua > 0) {
781 if (!db_query_params ('INSERT INTO stats_cvs_user (month,day,group_id,user_id,commits,adds) VALUES ($1,$2,$3,$4,$5,$6)',
782 array ($month_string,
788 echo "Error while inserting into stats_cvs_user\n" ;
798 function generateSnapshots ($params) {
800 $project = $this->checkParams ($params) ;
805 $group_name = $project->getUnixName() ;
807 $snapshot = forge_get_config('scm_snapshots_path').'/'.$group_name.'-scm-latest.tar'.util_get_compressed_file_extension();
808 $tarball = forge_get_config('scm_tarballs_path').'/'.$group_name.'-scmroot.tar'.util_get_compressed_file_extension();
810 if (! $project->usesPlugin ($this->name)) {
814 if (! $project->enableAnonSCM()) {
815 if (is_file($snapshot)) {
818 if (is_file($tarball)) {
824 // TODO: ideally we generate one snapshot per git repository
825 $toprepo = forge_get_config('repos_path', 'scmgit') ;
826 $repo = $toprepo . '/' . $project->getUnixName() . '/' . $project->getUnixName() . '.git' ;
828 if (!is_dir ($repo)) {
829 if (is_file($snapshot)) {
832 if (is_file($tarball)) {
838 // Skip empty repo (no HEAD present in repository)
839 $ref = trim(`GIT_DIR=$repo git symbolic-ref HEAD`);
840 if (!file_exists($repo.'/'.$ref)) {
844 $tmp = trim (`mktemp -d`) ;
848 $today = date ('Y-m-d') ;
849 system ("GIT_DIR=\"$repo\" git archive --format=tar --prefix=$group_name-scm-$today/ HEAD |".forge_get_config('compression_method')." > $tmp/snapshot");
850 chmod ("$tmp/snapshot", 0644) ;
851 copy ("$tmp/snapshot", $snapshot) ;
852 unlink ("$tmp/snapshot") ;
854 system ("tar cCf $toprepo - ".$project->getUnixName() ."|".forge_get_config('compression_method')."> $tmp/tarball") ;
855 chmod ("$tmp/tarball", 0644) ;
856 copy ("$tmp/tarball", $tarball) ;
857 unlink ("$tmp/tarball") ;
858 system ("rm -rf $tmp") ;
862 * widgets - 'widgets' hook handler
863 * @param array $params
866 function widgets($params) {
867 require_once 'common/widget/WidgetLayoutManager.class.php';
868 if ($params['owner_type'] == WidgetLayoutManager::OWNER_TYPE_GROUP) {
869 $params['fusionforge_widgets'][] = 'plugin_scmgit_project_latestcommits';
871 if ($params['owner_type'] == WidgetLayoutManager::OWNER_TYPE_USER) {
872 $params['fusionforge_widgets'][] = 'plugin_scmgit_user_myrepositories';
878 * Process the 'widget_instance' hook to create instances of the widgets
879 * @param array $params
881 function myPageBox($params) {
883 $user = UserManager::instance()->getCurrentUser();
884 require_once 'common/widget/WidgetLayoutManager.class.php';
885 if ($params['widget'] == 'plugin_scmgit_user_myrepositories') {
886 require_once $gfplugins.$this->name.'/common/scmgit_Widget_MyRepositories.class.php';
887 $params['instance'] = new scmgit_Widget_MyRepositories(WidgetLayoutManager::OWNER_TYPE_USER, $user->getId());
891 function weekly(&$params) {
892 $res = db_query_params('SELECT group_id FROM groups WHERE status=$1 AND use_scm=1 ORDER BY group_id DESC',
895 $params['output'] .= 'ScmGit Plugin: Unable to get list of projects using SCM: '.db_error();
899 $params['output'] .= 'ScmGit Plugin: Running "git gc --quiet" on '.db_numrows($res).' repositories.'."\n";
900 while ($row = db_fetch_array($res)) {
901 $project = group_get_object($row['group_id']);
902 if (!$project || !is_object($project)) {
904 } elseif ($project->isError()) {
907 if (!$project->usesPlugin($this->name)) {
911 $project_name = $project->getUnixName();
912 $repo = forge_get_config('repos_path', 'scmgit') . '/' . $project_name . '/' . $project_name .'.git';
915 $params['output'] .= $project_name.': '.`git gc --quiet 2>&1`;
920 function activity($params) {
921 $group_id = $params['group'];
922 $project = group_get_object($group_id);
923 if (! $project->usesPlugin($this->name)) {
926 if (in_array('scmgit', $params['show']) || (count($params['show']) < 1)) {
927 $start_time = $params['begin'];
928 $end_time = $params['end'];
929 $repo = forge_get_config('repos_path', 'scmgit') . '/' . $project->getUnixName() . '/' . $project->getUnixName() . '.git';
930 $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' );
931 while (!feof($pipe) && $data = fgets($pipe)) {
933 $splitedLine = explode('||', $line);
934 if (sizeof($splitedLine) == 4) {
936 $result['section'] = 'scm';
937 $result['group_id'] = $group_id;
938 $result['ref_id'] = 'browser.php?group_id='.$group_id;
939 $result['description'] = $splitedLine[2].' (commit '.$splitedLine[3].')';
940 $userObject = user_get_object_by_email($splitedLine[1]);
941 if (is_a($userObject, 'GFUser')) {
942 $result['realname'] = make_user_link($userObject->getUnixName(), $userObject->getRealName());
944 $result['realname'] = '';
946 $splitedDate = explode(' ', $splitedLine[0]);
947 $result['activity_date'] = $splitedDate[0];
948 $result['subref_id'] = '';
949 $params['results'][] = $result;
953 $params['ids'][] = $this->name;
954 $params['texts'][] = _('Git Commits');
958 function scm_add_repo(&$params) {
959 $project = $this->checkParams($params);
963 if (! $project->usesPlugin ($this->name)) {
967 if (!isset($params['repo_name'])) {
971 if ($params['repo_name'] == $project->getUnixName()) {
972 $params['error_msg'] = _('Cannot create a secondary repository with the same name as the primary');
976 if (! util_is_valid_repository_name($params['repo_name'])) {
977 $params['error_msg'] = _('This repository name is not valid');
981 $result = db_query_params('SELECT count(*) AS count FROM scm_secondary_repos WHERE group_id=$1 AND repo_name = $2 AND plugin_id=$3',
982 array ($params['group_id'],
983 $params['repo_name'],
986 $params['error_msg'] = db_error();
989 if (db_result($result, 0, 'count')) {
990 $params['error_msg'] = sprintf(_('A repository %s already exists'), $params['repo_name']);
996 if (isset($params['clone'])) {
997 $url = $params['clone'];
1001 } elseif (preg_match('|^git://|', $url) || preg_match('|^https?://|', $url)) {
1002 // External URLs: OK
1004 } elseif ($url == $project->getUnixName()) {
1006 } 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',
1007 array ($project->getID(),
1010 && db_result($result, 0, 'count')) {
1011 // Local repo: try to clone from an existing repo in same project
1015 $params['error_msg'] = _('Invalid URL from which to clone');
1020 if (isset($params['description'])) {
1021 $description = $params['description'];
1023 if ($clone && !$description) {
1024 $description = sprintf(_('Clone of %s'), $params['clone']);
1026 if (!$description) {
1027 $description = "Git repository $params[repo_name] for project ".$project->getUnixName();
1030 $result = db_query_params ('INSERT INTO scm_secondary_repos (group_id, repo_name, description, clone_url, plugin_id) VALUES ($1, $2, $3, $4, $5)',
1031 array ($params['group_id'],
1032 $params['repo_name'],
1037 $params['error_msg'] = db_error();
1041 plugin_hook ("scm_admin_update", $params);
1045 function scm_admin_form(&$params) {
1046 $project = $this->checkParams($params);
1050 if (! $project->usesPlugin ($this->name)) {
1054 session_require_perm('project_admin', $params['group_id']);
1056 $project_name = $project->getUnixName();
1058 $select_repo = '<select name="frontpage">' . "\n";
1059 $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',
1060 array ($params['group_id'],
1061 SCM_EXTRA_REPO_ACTION_UPDATE,
1064 $params['error_msg'] = db_error();
1067 $existing_repos = array();
1068 while($data = db_fetch_array($result)) {
1069 $existing_repos[] = array('repo_name' => $data['repo_name'],
1070 'description' => $data['description'],
1071 'clone_url' => $data['clone_url']);
1073 if (count($existing_repos) == 0) {
1074 printf('<h2>'._('No extra Git repository for project %1$s').'</h2>', $project_name);
1076 $t = sprintf(ngettext('Extra Git repository for project %1$s',
1077 'Extra Git repositories for project %1$s',
1078 count($existing_repos)), $project_name);
1079 print '<h2>'.$t.'</h2>';
1080 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>';
1081 foreach ($existing_repos as $repo) {
1082 print "<tr><td><tt>$repo[repo_name]</tt></td><td>$repo[description]</td><td>$repo[clone_url]</td><td>";
1084 <form name="form_delete_repo_<?php echo $repo['repo_name']?>"
1085 action="<?php echo getStringFromServer('PHP_SELF'); ?>" method="post">
1086 <input type="hidden" name="group_id" value="<?php echo $params['group_id'] ?>" />
1087 <input type="hidden" name="delete_repository" value="1" />
1088 <input type="hidden" name="repo_name" value="<?php echo $repo['repo_name']?>" />
1089 <input type="submit" name="submit" value="<?php echo _('Delete') ?>" />
1092 print "</td></tr>\n";
1094 print '</tbody></table>';
1097 printf('<h2>'._('Create new Git repository for project %1$s').'</h2>', $project_name);
1100 <form name="form_create_repo"
1101 action="<?php echo getStringFromServer('PHP_SELF'); ?>" method="post">
1102 <input type="hidden" name="group_id" value="<?php echo $params['group_id'] ?>" />
1103 <input type="hidden" name="create_repository" value="1" />
1104 <p><strong><?php echo _('Repository name:') ?></strong><?php echo utils_requiredField(); ?><br />
1105 <input type="text" required="required" size="20" name="repo_name" value="" /></p>
1106 <p><strong><?php echo _('Description:'); ?></strong><br />
1107 <input type="text" size="60" name="description" value="" /></p>
1108 <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 />
1109 <input type="text" size="60" name="clone" value="<?php echo $project_name; ?>" /></p>
1110 <input type="submit" name="cancel" value="<?php echo _('Cancel') ?>" />
1111 <input type="submit" name="submit" value="<?php echo _('Submit') ?>" />
1121 // c-file-style: "bsd"