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)).")";
83 . sprintf(_('Documentation for %1$s is available at <a href="%2$s">%2$s</a>.'),
85 'http://git-scm.com/')
89 function getInstructionsForAnon($project) {
90 $repo_list = array($project->getUnixName());
91 $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',
92 array ($project->getID(),
93 SCM_EXTRA_REPO_ACTION_UPDATE,
95 $rows = db_numrows ($result) ;
96 for ($i=0; $i<$rows; $i++) {
97 $repo_list[] = db_result($result,$i,'repo_name');
100 $b = '<h2>' . ngettext('Anonymous Access to the Git repository',
101 'Anonymous Access to the Git repositories',
102 count($repo_list)) . '</h2>';
105 $b .= ngettext('This project\'s Git repository can be checked out through anonymous access with the following command.',
106 'This project\'s Git repositories can be checked out through anonymous access with the following commands.',
111 foreach ($repo_list as $repo_name) {
113 $b .= '<tt>git clone '.util_make_url ('/anonscm/git/'.$project->getUnixName().'/'.$repo_name.'.git').'</tt><br />';
117 $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',
118 array ($project->getID(),
121 $rows = db_numrows($result);
125 $b .= ngettext('Developer\'s repository',
126 'Developer\'s repositories',
130 $b .= 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.',
135 for ($i=0; $i<$rows; $i++) {
136 $user_id = db_result($result,$i,'user_id');
137 $user_name = db_result($result,$i,'user_name');
138 $real_name = db_result($result,$i,'realname');
139 $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 />';
147 function getInstructionsForRW($project) {
148 $repo_list = array($project->getUnixName());
150 $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',
151 array ($project->getID(),
152 SCM_EXTRA_REPO_ACTION_UPDATE,
154 $rows = db_numrows ($result) ;
155 for ($i=0; $i<$rows; $i++) {
156 $repo_list[] = db_result($result,$i,'repo_name');
159 if (session_loggedin()) {
160 $u = user_get_object(user_getid());
161 $d = $u->getUnixName();
164 if (forge_get_config('use_ssh', 'scmgit')) {
166 $b = '<h2>' . ngettext('Developer Access to the Git repository via SSH',
167 'Developer Access to the Git repositories via SSH',
168 count($repo_list)) . '</h2>';
171 $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.',
172 '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.',
176 foreach ($repo_list as $repo_name) {
177 $b .= '<p><tt>git clone git+ssh://'.$d.'@' . $project->getSCMBox() . '/'. forge_get_config('repos_path', 'scmgit') .'/'. $project->getUnixName() .'/'. $repo_name .'.git</tt></p>' ;
182 if (forge_get_config('use_dav', 'scmgit')) {
183 $protocol = forge_get_config('use_ssl', 'scmgit')? 'https' : 'http';
185 $b = '<h2>' . ngettext('Developer Access to the Git repository via HTTP',
186 'Developer Access to the Git repositories via HTTP',
187 count($repo_list)) . '</h2>';
191 $b .= ngettext('Only project developers can access the GIT repository via this method. Enter your site password when prompted.',
192 'Only project developers can access the GIT repositories via this method. Enter your site password when prompted.',
196 foreach ($repo_list as $repo_name) {
197 $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 <i>developername</i> 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 <i>developername</i> 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');
425 // Create main repository
426 $main_repo = $root . '/' . $project_name . '.git' ;
427 if (!is_dir($main_repo) || (!is_file("$main_repo/HEAD") &&
428 !is_dir("$main_repo/objects") && !is_dir("$main_repo/refs"))) {
429 $tmp_repo = util_mkdtemp('.git', $project_name);
430 if ($tmp_repo == false) {
434 exec ("GIT_DIR=\"$tmp_repo\" git init --bare --shared=group", $result) ;
435 $output .= join("<br />", $result);
437 exec ("GIT_DIR=\"$tmp_repo\" git update-server-info", $result) ;
438 $output .= join("<br />", $result);
439 if (is_file ("$tmp_repo/hooks/post-update.sample")) {
440 rename ("$tmp_repo/hooks/post-update.sample",
441 "$tmp_repo/hooks/post-update") ;
443 if (!is_file ("$tmp_repo/hooks/post-update")) {
444 $f = fopen ("$tmp_repo/hooks/post-update", 'w') ;
445 fwrite ($f, "exec git-update-server-info\n") ;
448 if (is_file ("$tmp_repo/hooks/post-update")) {
449 system ("chmod +x $tmp_repo/hooks/post-update") ;
451 system ("echo \"Git repository for $project_name\" > $tmp_repo/description") ;
452 system ("find $tmp_repo -type d | xargs chmod g+s") ;
453 system ("chgrp -R $unix_group $tmp_repo") ;
454 system ("chmod -R g+wX,o+rX-w $tmp_repo") ;
455 if ($project->enableAnonSCM()) {
456 system ("chmod g+wX,o+rX-w $root") ;
458 system ("chmod g+wX,o-rwx $root") ;
459 system ("chmod g+wX,o-rwx $tmp_repo") ;
463 * $main_repo can already exist, for example if it’s
464 * not a directory or doesn’t contain a HEAD file or
465 * an objects or refs subdirectory… move it out of
466 * the way in these cases
468 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");
469 /* here’s still a TOCTOU but we check $ret below */
470 system("mv $tmp_repo $main_repo", $ret);
474 system ("echo \"Git repository for $project_name\" > $main_repo/description") ;
475 system ("find $main_repo -type d | xargs chmod g+s");
476 if (forge_get_config('use_dav','scmgit')) {
477 $f = fopen(forge_get_config('config_path').'/httpd.conf.d/plugin-scmgit-dav.inc','a');
478 fputs($f,'Use Project '.$project_name."\n");
480 system(forge_get_config('httpd_reload_cmd','scmgit'));
483 if (forge_get_config('use_ssh','scmgit')) {
484 if ($project->enableAnonSCM()) {
485 system ("chmod g+wX,o+rX-w $root") ;
486 system ("chmod g+rwX,o+rX-w $main_repo") ;
488 system ("chmod g+wX,o-rwx $root") ;
489 system ("chmod g+rwX,o-rwx $main_repo") ;
492 $unix_user = forge_get_config('apache_user');
493 system ("chown $unix_user:$unix_group $main_repo") ;
494 system ("chmod g-rwx,o-rwx $main_repo") ;
497 // Create project-wide secondary repositories
498 $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',
499 array ($project->getID(),
500 SCM_EXTRA_REPO_ACTION_UPDATE,
502 $rows = db_numrows ($result) ;
503 for ($i=0; $i<$rows; $i++) {
504 $repo_name = db_result($result,$i,'repo_name');
505 $description = db_result($result,$i,'description');
506 $clone_url = db_result($result,$i,'clone_url');
507 $repodir = $root . '/' . $repo_name . '.git' ;
508 if (!is_file ("$repodir/HEAD") && !is_dir("$repodir/objects") && !is_dir("$repodir/refs")) {
509 if ($clone_url != '') {
510 system ("cd $root;git clone --bare $clone_url $repodir") ;
512 system ("GIT_DIR=\"$repodir\" git init --bare --shared=group") ;
514 system ("GIT_DIR=\"$repodir\" git update-server-info") ;
515 if (is_file ("$repodir/hooks/post-update.sample")) {
516 rename ("$repodir/hooks/post-update.sample",
517 "$repodir/hooks/post-update") ;
519 if (!is_file ("$repodir/hooks/post-update")) {
520 $f = fopen ("$repodir/hooks/post-update") ;
521 fwrite ($f, "exec git-update-server-info\n") ;
524 if (is_file ("$repodir/hooks/post-update")) {
525 system ("chmod +x $repodir/hooks/post-update") ;
527 $f = fopen("$repodir/description", "w");
528 fwrite($f, $description."\n");
530 system ("chgrp -R $unix_group $repodir") ;
531 system ("chmod g+s $root") ;
532 if ($project->enableAnonSCM()) {
533 system ("chmod -R g+wX,o+rX-w $main_repo") ;
535 system ("chmod -R g+wX,o-rwx $main_repo") ;
540 // Delete project-wide secondary repositories
541 $result = db_query_params ('SELECT repo_name FROM scm_secondary_repos WHERE group_id=$1 AND next_action = $2 AND plugin_id=$3',
542 array ($project->getID(),
543 SCM_EXTRA_REPO_ACTION_DELETE,
545 $rows = db_numrows ($result) ;
546 for ($i=0; $i<$rows; $i++) {
547 $repo_name = db_result($result,$i,'repo_name');
548 $repodir = $root . '/' . $repo_name . '.git' ;
549 if (util_is_valid_repository_name($repo_name)) {
550 system ("rm -rf $repodir");
552 db_query_params ('DELETE FROM scm_secondary_repos WHERE group_id=$1 AND repo_name=$2 AND next_action = $3 AND plugin_id=$4',
553 array ($project->getID(),
555 SCM_EXTRA_REPO_ACTION_DELETE,
559 // Create users' personal repositories
560 $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',
561 array ($project->getID(),
564 $rows = db_numrows ($result) ;
565 for ($i=0; $i<$rows; $i++) {
566 system ("mkdir -p $root/users") ;
567 $user_name = db_result($result,$i,'user_name');
568 $repodir = $root . '/users/' . $user_name . '.git' ;
570 if (!is_dir($repodir) && mkdir ($repodir, 0700)) {
571 chown ($repodir, $user_name) ;
574 $params['project'] = $project;
575 $params['user_name'] = $user_name;
576 $params['unix_group'] = $unix_group;
577 $params['root'] = $root;
578 $params['main_repo'] = $main_repo;
580 util_sudo_effective_user($user_name,
581 array("GitPlugin","createUserRepo"),
585 if (is_dir ("$root/users")) {
586 if ($project->enableAnonSCM()) {
587 system ("chmod g+rX-w,o+rX-w $root/users") ;
589 system ("chmod g+rX-w,o-rwx $root/users") ;
592 $params['output'] = $output;
595 function updateRepositoryList($params) {
596 $groups = $this->getGroups();
598 foreach ($groups as $project) {
599 if ($this->browserDisplayable($project)) {
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 $config_f = fopen($fname.'.new', 'w') ;
610 $rootdir = forge_get_config('repos_path', 'scmgit');
611 fwrite($config_f, "\$projectroot = '$rootdir';\n");
612 fwrite($config_f, "\$projects_list = '$config_dir/gitweb.list';\n");
613 fwrite($config_f, "@git_base_url_list = ('". util_make_url('/anonscm/git') . "');\n");
614 fwrite($config_f, "\$logo = '". util_make_url('/plugins/scmgit/git-logo.png') . "';\n");
615 fwrite($config_f, "\$favicon = '". util_make_url('/plugins/scmgit/git-favicon.png')."';\n");
616 fwrite($config_f, "\$stylesheet = '". util_make_url('/plugins/scmgit/gitweb.css')."';\n");
617 fwrite($config_f, "\$javascript = '". util_make_url('/plugins/scmgit/gitweb.js')."';\n");
618 fwrite($config_f, "\$prevent_xss = 'true';\n");
620 chmod($fname.'.new', 0644);
621 rename($fname.'.new', $fname);
623 $fname = $config_dir . '/gitweb.list';
624 $f = fopen($fname.'.new', 'w');
626 $engine = RBACEngine::getInstance();
627 foreach ($list as $project) {
628 $repos = $this->getRepositories($rootdir . "/" . $project->getUnixName());
629 foreach ($repos as $repo) {
630 $reldir = substr($repo, strlen($rootdir) + 1);
631 fwrite($f, $reldir . "\n");
633 $users = $engine->getUsersByAllowedAction('scm',$project->getID(),'write');
635 foreach ($users as $user) {
636 $password_data .= $user->getUnixName().':'.$user->getUnixPasswd()."\n";
638 $faname = forge_get_config('data_path').'/gituser-authfile.'.$project->getUnixName();
639 $fa = fopen($faname.'.new', 'w');
640 fwrite($fa, $password_data);
642 chmod($faname.'.new', 0644);
643 rename($faname.'.new', $faname);
646 chmod($fname.'.new', 0644);
647 rename($fname.'.new', $fname);
650 function getRepositories($path) {
651 if (! is_dir($path)) {
655 $entries = scandir($path);
656 foreach ($entries as $entry) {
657 $fullname = $path . "/" . $entry;
658 if (($entry == ".") or ($entry == ".."))
660 if (is_dir($fullname)) {
661 if (is_link($fullname))
663 $result = $this->getRepositories($fullname);
664 $list = array_merge($list, $result);
665 } elseif ($entry == "HEAD") {
672 function gatherStats ($params) {
673 $project = $this->checkParams ($params) ;
678 if (! $project->usesPlugin ($this->name)) {
682 if ($params['mode'] == 'day') {
683 $year = $params ['year'] ;
684 $month = $params ['month'] ;
685 $day = $params ['day'] ;
686 $month_string = sprintf( "%04d%02d", $year, $month );
687 $start_time = gmmktime( 0, 0, 0, $month, $day, $year);
688 $end_time = $start_time + 86400;
690 $usr_adds = array () ;
691 $usr_updates = array () ;
692 $usr_deletes = array () ;
697 $repo = forge_get_config('repos_path', 'scmgit') . '/' . $project->getUnixName() . '/' . $project->getUnixName() . '.git';
698 if (!is_dir ($repo) || !is_dir ("$repo/refs")) {
699 // echo "No repository $repo\n" ;
703 $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' ) ;
707 // cleaning stats_cvs_* table for the current day
708 $res = db_query_params ('DELETE FROM stats_cvs_group WHERE month=$1 AND day=$2 AND group_id=$3',
709 array ($month_string,
711 $project->getID())) ;
713 echo "Error while cleaning stats_cvs_group\n" ;
719 while (!feof($pipe) && $data = fgets ($pipe)) {
721 if (strlen($line) > 0) {
722 $result = preg_match("/^(?P<name>.+) <(?P<mail>.+)>/", $line, $matches);
725 $last_user = $matches['name'];
726 $user2email[$last_user] = strtolower($matches['mail']);
727 if (!isset($usr_adds[$last_user])) {
728 $usr_adds[$last_user] = 0;
729 $usr_updates[$last_user] = 0;
730 $usr_deletes[$last_user] = 0;
733 // Short-commit stats line
734 preg_match("/^(?P<mode>[AM])\s+(?P<file>.+)$/", $line, $matches);
735 if ($last_user == "") continue;
736 if (!isset ($usr_adds[$last_user])) $usr_adds[$last_user] = 0;
737 if (!isset ($usr_updates[$last_user])) $usr_updates[$last_user] = 0;
738 if (!isset ($usr_deletes[$last_user])) $usr_deletes[$last_user] = 0;
739 if ($matches['mode'] == 'A') {
740 $usr_adds[$last_user]++;
742 } elseif ($matches['mode'] == 'M') {
743 $usr_updates[$last_user]++;
745 } elseif ($matches['mode'] == 'D') {
746 $usr_deletes[$last_user]++;
752 // inserting group results in stats_cvs_groups
753 if ($updates > 0 || $adds > 0) {
754 if (!db_query_params ('INSERT INTO stats_cvs_group (month,day,group_id,checkouts,commits,adds) VALUES ($1,$2,$3,$4,$5,$6)',
755 array ($month_string,
761 echo "Error while inserting into stats_cvs_group\n" ;
767 // building the user list
768 $user_list = array_unique( array_merge( array_keys( $usr_adds ), array_keys( $usr_updates ) ) );
770 foreach ( $user_list as $user ) {
771 // Trying to get user id from user name or email
772 $u = &user_get_object_by_name ($user) ;
774 $user_id = $u->getID();
776 $res=db_query_params('SELECT user_id FROM users WHERE lower(realname)=$1 OR email=$2',
777 array (strtolower($user), $user2email[$user]));
778 if ($res && db_numrows($res) > 0) {
779 $user_id = db_result($res,0,'user_id');
785 $uu = isset ($usr_updates[$user]) ? $usr_updates[$user] : 0 ;
786 $ua = isset ($usr_adds[$user]) ? $usr_adds[$user] : 0 ;
787 if ($uu > 0 || $ua > 0) {
788 if (!db_query_params ('INSERT INTO stats_cvs_user (month,day,group_id,user_id,commits,adds) VALUES ($1,$2,$3,$4,$5,$6)',
789 array ($month_string,
795 echo "Error while inserting into stats_cvs_user\n" ;
805 function generateSnapshots ($params) {
807 $project = $this->checkParams ($params) ;
812 $group_name = $project->getUnixName() ;
814 $snapshot = forge_get_config('scm_snapshots_path').'/'.$group_name.'-scm-latest.tar'.util_get_compressed_file_extension();
815 $tarball = forge_get_config('scm_tarballs_path').'/'.$group_name.'-scmroot.tar'.util_get_compressed_file_extension();
817 if (! $project->usesPlugin ($this->name)) {
821 if (! $project->enableAnonSCM()) {
822 if (is_file($snapshot)) {
825 if (is_file($tarball)) {
831 // TODO: ideally we generate one snapshot per git repository
832 $toprepo = forge_get_config('repos_path', 'scmgit') ;
833 $repo = $toprepo . '/' . $project->getUnixName() . '/' . $project->getUnixName() . '.git' ;
835 if (!is_dir ($repo)) {
836 if (is_file($snapshot)) {
839 if (is_file($tarball)) {
845 // Skip empty repo (no HEAD present in repository)
846 $ref = trim(`GIT_DIR=$repo git symbolic-ref HEAD`);
847 if (!file_exists($repo.'/'.$ref)) {
851 $tmp = trim (`mktemp -d`) ;
855 $today = date ('Y-m-d') ;
856 system ("GIT_DIR=\"$repo\" git archive --format=tar --prefix=$group_name-scm-$today/ HEAD |".forge_get_config('compression_method')." > $tmp/snapshot");
857 chmod ("$tmp/snapshot", 0644) ;
858 copy ("$tmp/snapshot", $snapshot) ;
859 unlink ("$tmp/snapshot") ;
861 system ("tar cCf $toprepo - ".$project->getUnixName() ."|".forge_get_config('compression_method')."> $tmp/tarball") ;
862 chmod ("$tmp/tarball", 0644) ;
863 copy ("$tmp/tarball", $tarball) ;
864 unlink ("$tmp/tarball") ;
865 system ("rm -rf $tmp") ;
869 * widgets - 'widgets' hook handler
870 * @param array $params
873 function widgets($params) {
874 require_once 'common/widget/WidgetLayoutManager.class.php';
875 if ($params['owner_type'] == WidgetLayoutManager::OWNER_TYPE_GROUP) {
876 $params['fusionforge_widgets'][] = 'plugin_scmgit_project_latestcommits';
878 if ($params['owner_type'] == WidgetLayoutManager::OWNER_TYPE_USER) {
879 $params['fusionforge_widgets'][] = 'plugin_scmgit_user_myrepositories';
885 * Process the 'widget_instance' hook to create instances of the widgets
886 * @param array $params
888 function myPageBox($params) {
890 $user = UserManager::instance()->getCurrentUser();
891 require_once 'common/widget/WidgetLayoutManager.class.php';
892 if ($params['widget'] == 'plugin_scmgit_user_myrepositories') {
893 require_once $gfplugins.$this->name.'/common/scmgit_Widget_MyRepositories.class.php';
894 $params['instance'] = new scmgit_Widget_MyRepositories(WidgetLayoutManager::OWNER_TYPE_USER, $user->getId());
898 function weekly(&$params) {
899 $res = db_query_params('SELECT group_id FROM groups WHERE status=$1 AND use_scm=1 ORDER BY group_id DESC',
902 $params['output'] .= 'ScmGit Plugin: Unable to get list of projects using SCM: '.db_error();
906 $params['output'] .= 'ScmGit Plugin: Running "git gc --quiet" on '.db_numrows($res).' repositories.'."\n";
907 while ($row = db_fetch_array($res)) {
908 $project = group_get_object($row['group_id']);
909 if (!$project || !is_object($project)) {
911 } elseif ($project->isError()) {
914 if (!$project->usesPlugin($this->name)) {
918 $project_name = $project->getUnixName();
919 $repo = forge_get_config('repos_path', 'scmgit') . '/' . $project_name . '/' . $project_name .'.git';
922 $params['output'] .= $project_name.': '.`git gc --quiet 2>&1`;
927 function activity($params) {
928 $group_id = $params['group'];
929 $project = group_get_object($group_id);
930 if (! $project->usesPlugin($this->name)) {
933 if (in_array('scmgit', $params['show']) || (count($params['show']) < 1)) {
934 $start_time = $params['begin'];
935 $end_time = $params['end'];
936 $repo = forge_get_config('repos_path', 'scmgit') . '/' . $project->getUnixName() . '/' . $project->getUnixName() . '.git';
937 $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' );
938 while (!feof($pipe) && $data = fgets($pipe)) {
940 $splitedLine = explode('||', $line);
941 if (sizeof($splitedLine) == 4) {
943 $result['section'] = 'scm';
944 $result['group_id'] = $group_id;
945 $result['ref_id'] = 'browser.php?group_id='.$group_id;
946 $result['description'] = $splitedLine[2].' (commit '.$splitedLine[3].')';
947 $userObject = user_get_object_by_email($splitedLine[1]);
948 if (is_a($userObject, 'GFUser')) {
949 $result['realname'] = make_user_link($userObject->getUnixName(), $userObject->getRealName());
951 $result['realname'] = '';
953 $splitedDate = explode(' ', $splitedLine[0]);
954 $result['activity_date'] = $splitedDate[0];
955 $result['subref_id'] = '';
956 $params['results'][] = $result;
960 $params['ids'][] = $this->name;
961 $params['texts'][] = _('Git Commits');
965 function scm_add_repo(&$params) {
966 $project = $this->checkParams($params);
970 if (! $project->usesPlugin ($this->name)) {
974 if (!isset($params['repo_name'])) {
978 if ($params['repo_name'] == $project->getUnixName()) {
979 $params['error_msg'] = _('Cannot create a secondary repository with the same name as the primary');
983 if (! util_is_valid_repository_name($params['repo_name'])) {
984 $params['error_msg'] = _('This repository name is not valid');
988 $result = db_query_params('SELECT count(*) AS count FROM scm_secondary_repos WHERE group_id=$1 AND repo_name = $2 AND plugin_id=$3',
989 array ($params['group_id'],
990 $params['repo_name'],
993 $params['error_msg'] = db_error();
996 if (db_result($result, 0, 'count')) {
997 $params['error_msg'] = sprintf(_('A repository %s already exists'), $params['repo_name']);
1003 if (isset($params['clone'])) {
1004 $url = $params['clone'];
1008 } elseif (preg_match('|^git://|', $url) || preg_match('|^https?://|', $url)) {
1009 // External URLs: OK
1011 } elseif ($url == $project->getUnixName()) {
1013 } 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',
1014 array ($project->getID(),
1017 && db_result($result, 0, 'count')) {
1018 // Local repo: try to clone from an existing repo in same project
1022 $params['error_msg'] = _('Invalid URL from which to clone');
1027 if (isset($params['description'])) {
1028 $description = $params['description'];
1030 if ($clone && !$description) {
1031 $description = sprintf(_('Clone of %s'), $params['clone']);
1033 if (!$description) {
1034 $description = "Git repository $params[repo_name] for project ".$project->getUnixName();
1037 $result = db_query_params ('INSERT INTO scm_secondary_repos (group_id, repo_name, description, clone_url, plugin_id) VALUES ($1, $2, $3, $4, $5)',
1038 array ($params['group_id'],
1039 $params['repo_name'],
1044 $params['error_msg'] = db_error();
1048 plugin_hook ("scm_admin_update", $params);
1052 function scm_admin_form(&$params) {
1053 $project = $this->checkParams($params);
1057 if (! $project->usesPlugin ($this->name)) {
1061 session_require_perm('project_admin', $params['group_id']);
1063 $project_name = $project->getUnixName();
1065 $select_repo = '<select name="frontpage">' . "\n";
1066 $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',
1067 array ($params['group_id'],
1068 SCM_EXTRA_REPO_ACTION_UPDATE,
1071 $params['error_msg'] = db_error();
1074 $existing_repos = array();
1075 while($data = db_fetch_array($result)) {
1076 $existing_repos[] = array('repo_name' => $data['repo_name'],
1077 'description' => $data['description'],
1078 'clone_url' => $data['clone_url']);
1080 if (count($existing_repos) == 0) {
1081 printf('<h2>'._('No extra Git repository for project %1$s').'</h2>', $project_name);
1083 $t = sprintf(ngettext('Extra Git repository for project %1$s',
1084 'Extra Git repositories for project %1$s',
1085 count($existing_repos)), $project_name);
1086 print '<h2>'.$t.'</h2>';
1087 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>';
1088 foreach ($existing_repos as $repo) {
1089 print "<tr><td><tt>$repo[repo_name]</tt></td><td>$repo[description]</td><td>$repo[clone_url]</td><td>";
1091 <form name="form_delete_repo_<?php echo $repo['repo_name']?>"
1092 action="<?php echo getStringFromServer('PHP_SELF'); ?>" method="post">
1093 <input type="hidden" name="group_id" value="<?php echo $params['group_id'] ?>" />
1094 <input type="hidden" name="delete_repository" value="1" />
1095 <input type="hidden" name="repo_name" value="<?php echo $repo['repo_name']?>" />
1096 <input type="submit" name="submit" value="<?php echo _('Delete') ?>" />
1099 print "</td></tr>\n";
1101 print '</tbody></table>';
1104 printf('<h2>'._('Create new Git repository for project %1$s').'</h2>', $project_name);
1107 <form name="form_create_repo"
1108 action="<?php echo getStringFromServer('PHP_SELF'); ?>" method="post">
1109 <input type="hidden" name="group_id" value="<?php echo $params['group_id'] ?>" />
1110 <input type="hidden" name="create_repository" value="1" />
1111 <p><strong><?php echo _('Repository name:') ?></strong><?php echo utils_requiredField(); ?><br />
1112 <input type="text" required="required" size="20" name="repo_name" value="" /></p>
1113 <p><strong><?php echo _('Description:'); ?></strong><br />
1114 <input type="text" size="60" name="description" value="" /></p>
1115 <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 />
1116 <input type="text" size="60" name="clone" value="<?php echo $project_name; ?>" /></p>
1117 <input type="submit" name="cancel" value="<?php echo _('Cancel') ?>" />
1118 <input type="submit" name="submit" value="<?php echo _('Submit') ?>" />
1128 // c-file-style: "bsd"