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)) {
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 function createOrUpdateRepo($params) {
365 $project = $this->checkParams($params);
370 if (! $project->usesPlugin($this->name)) {
374 $project_name = $project->getUnixName();
375 $root = forge_get_config('repos_path', 'scmgit') . '/' . $project_name;
376 if (!is_dir($root)) {
377 system("mkdir -p $root");
381 // Create main repository
382 $main_repo = $root . '/' . $project_name . '.git' ;
383 if (!is_file("$main_repo/HEAD") && !is_dir("$main_repo/objects") && !is_dir("$main_repo/refs")) {
384 exec("GIT_DIR=\"$main_repo\" git init --bare --shared=group", $result) ;
385 $output .= join("<br />", $result);
387 exec("GIT_DIR=\"$main_repo\" git update-server-info", $result) ;
388 $output .= join("<br />", $result);
389 if (is_file("$main_repo/hooks/post-update.sample")) {
390 rename("$main_repo/hooks/post-update.sample",
391 "$main_repo/hooks/post-update");
393 if (!is_file("$main_repo/hooks/post-update")) {
394 $f = fopen("$main_repo/hooks/post-update", 'w');
395 fwrite($f, "exec git-update-server-info\n");
398 if (is_file ("$main_repo/hooks/post-update")) {
399 system ("chmod +x $main_repo/hooks/post-update") ;
401 system ("echo \"Git repository for $project_name\" > $main_repo/description") ;
402 system ("find $main_repo -type d | xargs chmod g+s");
403 if (forge_get_config('use_dav','scmgit')) {
404 $f = fopen(forge_get_config('config_path').'/httpd.conf.d/plugin-scmgit-dav.inc','a');
405 fputs($f,'Use Project '.$project_name."\n");
407 system(forge_get_config('httpd_reload_cmd','scmgit'));
410 if (forge_get_config('use_ssh','scmgit')) {
411 $unix_group = 'scm_' . $project_name ;
412 system ("chgrp -R $unix_group $root") ;
413 system ("chmod g+s $root") ;
414 if ($project->enableAnonSCM()) {
415 system ("chmod g+wX,o+rX-w $root") ;
416 system ("chmod -R g+rwX,o+rX-w $main_repo") ;
418 system ("chmod g+wX,o-rwx $root") ;
419 system ("chmod -R g+rwX,o-rwx $main_repo") ;
422 $unix_user = forge_get_config('apache_user');
423 $unix_group = forge_get_config('apache_group');
424 system("chown -R $unix_user:$unix_group $main_repo");
425 system("chmod -R g-rwx,o-rwx $main_repo");
428 // Create project-wide secondary repositories
429 $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',
430 array ($project->getID(),
431 SCM_EXTRA_REPO_ACTION_UPDATE,
433 $rows = db_numrows ($result) ;
434 for ($i=0; $i<$rows; $i++) {
435 $repo_name = db_result($result,$i,'repo_name');
436 $description = db_result($result,$i,'description');
437 $clone_url = db_result($result,$i,'clone_url');
438 $repodir = $root . '/' . $repo_name . '.git' ;
439 if (!is_file ("$repodir/HEAD") && !is_dir("$repodir/objects") && !is_dir("$repodir/refs")) {
440 if ($clone_url != '') {
441 system ("cd $root;git clone --bare $clone_url $repodir") ;
443 system ("GIT_DIR=\"$repodir\" git init --bare --shared=group") ;
445 system ("GIT_DIR=\"$repodir\" git update-server-info") ;
446 if (is_file ("$repodir/hooks/post-update.sample")) {
447 rename ("$repodir/hooks/post-update.sample",
448 "$repodir/hooks/post-update") ;
450 if (!is_file ("$repodir/hooks/post-update")) {
451 $f = fopen ("$repodir/hooks/post-update") ;
452 fwrite ($f, "exec git-update-server-info\n") ;
455 if (is_file ("$repodir/hooks/post-update")) {
456 system ("chmod +x $repodir/hooks/post-update") ;
458 $f = fopen("$repodir/description", "w");
459 fwrite($f, $description."\n");
461 system ("chgrp -R $unix_group $repodir") ;
462 system ("chmod g+s $root") ;
463 if ($project->enableAnonSCM()) {
464 system ("chmod -R g+wX,o+rX-w $main_repo") ;
466 system ("chmod -R g+wX,o-rwx $main_repo") ;
471 // Delete project-wide secondary repositories
472 $result = db_query_params ('SELECT repo_name FROM scm_secondary_repos WHERE group_id=$1 AND next_action = $2 AND plugin_id=$3',
473 array ($project->getID(),
474 SCM_EXTRA_REPO_ACTION_DELETE,
476 $rows = db_numrows ($result) ;
477 for ($i=0; $i<$rows; $i++) {
478 $repo_name = db_result($result,$i,'repo_name');
479 $repodir = $root . '/' . $repo_name . '.git' ;
480 if (util_is_valid_repository_name($repo_name)) {
481 system ("rm -rf $repodir");
483 db_query_params ('DELETE FROM scm_secondary_repos WHERE group_id=$1 AND repo_name=$2 AND next_action = $3 AND plugin_id=$4',
484 array ($project->getID(),
486 SCM_EXTRA_REPO_ACTION_DELETE,
490 // Create users' personal repositories
491 $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',
492 array ($project->getID(),
495 $rows = db_numrows ($result) ;
496 for ($i=0; $i<$rows; $i++) {
497 system ("mkdir -p $root/users") ;
498 $user_name = db_result($result,$i,'user_name');
499 $repodir = $root . '/users/' . $user_name . '.git' ;
501 if (!is_file ("$repodir/HEAD") && !is_dir("$repodir/objects") && !is_dir("$repodir/refs")) {
502 system ("git clone --bare $main_repo $repodir") ;
503 system ("GIT_DIR=\"$repodir\" git update-server-info") ;
504 if (is_file ("$repodir/hooks/post-update.sample")) {
505 rename ("$repodir/hooks/post-update.sample",
506 "$repodir/hooks/post-update") ;
508 if (!is_file ("$repodir/hooks/post-update")) {
509 $f = fopen ("$repodir/hooks/post-update", 'w') ;
510 fwrite ($f, "exec git-update-server-info\n") ;
513 if (is_file ("$repodir/hooks/post-update")) {
514 system ("chmod +x $repodir/hooks/post-update") ;
516 system("echo \"Git repository for user $user_name in project $project_name\" > $repodir/description");
517 system ("chown -R $user_name:$unix_group $repodir") ;
520 if (is_dir ("$root/users")) {
521 if ($project->enableAnonSCM()) {
522 system ("chmod -R g+rX-w,o+rX-w $root/users") ;
524 system ("chmod -R g+rX-w,o-rwx $root/users") ;
527 $params['output'] = $output;
530 function updateRepositoryList($params) {
531 $groups = $this->getGroups();
533 foreach ($groups as $project) {
534 if ($this->browserDisplayable($project)) {
539 $config_dir = forge_get_config('config_path').'/plugins/scmgit';
540 if (!is_dir($config_dir)) {
541 mkdir($config_dir, 0755, true);
543 $fname = $config_dir . '/gitweb.conf' ;
544 $config_f = fopen($fname.'.new', 'w') ;
545 $rootdir = forge_get_config('repos_path', 'scmgit');
546 fwrite($config_f, "\$projectroot = '$rootdir';\n");
547 fwrite($config_f, "\$projects_list = '$config_dir/gitweb.list';\n");
548 fwrite($config_f, "@git_base_url_list = ('". util_make_url('/anonscm/git') . "');\n");
549 fwrite($config_f, "\$logo = '". util_make_url('/plugins/scmgit/git-logo.png') . "';\n");
550 fwrite($config_f, "\$favicon = '". util_make_url('/plugins/scmgit/git-favicon.png')."';\n");
551 fwrite($config_f, "\$stylesheet = '". util_make_url('/plugins/scmgit/gitweb.css')."';\n");
552 fwrite($config_f, "\$javascript = '". util_make_url('/plugins/scmgit/gitweb.js')."';\n");
553 fwrite($config_f, "\$prevent_xss = 'true';\n");
555 chmod($fname.'.new', 0644);
556 rename($fname.'.new', $fname);
558 $fname = $config_dir . '/gitweb.list';
559 $f = fopen($fname.'.new', 'w');
561 $engine = RBACEngine::getInstance();
562 foreach ($list as $project) {
563 $repos = $this->getRepositories($rootdir . "/" . $project->getUnixName());
564 foreach ($repos as $repo) {
565 $reldir = substr($repo, strlen($rootdir) + 1);
566 fwrite($f, $reldir . "\n");
568 $users = $engine->getUsersByAllowedAction('scm',$project->getID(),'write');
570 foreach ($users as $user) {
571 $password_data .= $user->getUnixName().':'.$user->getUnixPasswd()."\n";
573 $faname = forge_get_config('data_path').'/gituser-authfile.'.$project->getUnixName();
574 $fa = fopen($faname.'.new', 'w');
575 fwrite($fa, $password_data);
577 chmod($faname.'.new', 0644);
578 rename($faname.'.new', $faname);
581 chmod($fname.'.new', 0644);
582 rename($fname.'.new', $fname);
585 function getRepositories($path) {
586 if (! is_dir($path)) {
590 $entries = scandir($path);
591 foreach ($entries as $entry) {
592 $fullname = $path . "/" . $entry;
593 if (($entry == ".") or ($entry == ".."))
595 if (is_dir($fullname)) {
596 if (is_link($fullname))
598 $result = $this->getRepositories($fullname);
599 $list = array_merge($list, $result);
600 } elseif ($entry == "HEAD") {
607 function gatherStats ($params) {
608 $project = $this->checkParams ($params) ;
613 if (! $project->usesPlugin ($this->name)) {
617 if ($params['mode'] == 'day') {
618 $year = $params ['year'] ;
619 $month = $params ['month'] ;
620 $day = $params ['day'] ;
621 $month_string = sprintf( "%04d%02d", $year, $month );
622 $start_time = gmmktime( 0, 0, 0, $month, $day, $year);
623 $end_time = $start_time + 86400;
625 $usr_adds = array () ;
626 $usr_updates = array () ;
627 $usr_deletes = array () ;
632 $repo = forge_get_config('repos_path', 'scmgit') . '/' . $project->getUnixName() . '/' . $project->getUnixName() . '.git';
633 if (!is_dir ($repo) || !is_dir ("$repo/refs")) {
634 // echo "No repository\n" ;
638 $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' ) ;
642 // cleaning stats_cvs_* table for the current day
643 $res = db_query_params ('DELETE FROM stats_cvs_group WHERE month=$1 AND day=$2 AND group_id=$3',
644 array ($month_string,
646 $project->getID())) ;
648 echo "Error while cleaning stats_cvs_group\n" ;
654 while (!feof($pipe) && $data = fgets ($pipe)) {
656 if (strlen($line) > 0) {
657 $result = preg_match("/^(?P<name>.+) <(?P<mail>.+)>/", $line, $matches);
660 $last_user = $matches['name'];
661 $user2email[$last_user] = strtolower($matches['mail']);
662 if (!isset($usr_adds[$last_user])) {
663 $usr_adds[$last_user] = 0;
664 $usr_updates[$last_user] = 0;
665 $usr_deletes[$last_user] = 0;
668 // Short-commit stats line
669 preg_match("/^(?P<mode>[AM])\s+(?P<file>.+)$/", $line, $matches);
670 if ($last_user == "") continue;
671 if ($matches['mode'] == 'A') {
672 $usr_adds[$last_user]++;
674 } elseif ($matches['mode'] == 'M') {
675 $usr_updates[$last_user]++;
677 } elseif ($matches['mode'] == 'D') {
678 $usr_deletes[$last_user]++;
684 // inserting group results in stats_cvs_groups
685 if ($updates > 0 || $adds > 0) {
686 if (!db_query_params ('INSERT INTO stats_cvs_group (month,day,group_id,checkouts,commits,adds) VALUES ($1,$2,$3,$4,$5,$6)',
687 array ($month_string,
693 echo "Error while inserting into stats_cvs_group\n" ;
699 // building the user list
700 $user_list = array_unique( array_merge( array_keys( $usr_adds ), array_keys( $usr_updates ) ) );
702 foreach ( $user_list as $user ) {
703 // Trying to get user id from user name or email
704 $u = &user_get_object_by_name ($user) ;
706 $user_id = $u->getID();
708 $res=db_query_params('SELECT user_id FROM users WHERE lower(realname)=$1 OR email=$2',
709 array (strtolower($user), $user2email[$user]));
710 if ($res && db_numrows($res) > 0) {
711 $user_id = db_result($res,0,'user_id');
717 $uu = $usr_updates[$user] ? $usr_updates[$user] : 0 ;
718 $ua = $usr_adds[$user] ? $usr_adds[$user] : 0 ;
719 if ($uu > 0 || $ua > 0) {
720 if (!db_query_params ('INSERT INTO stats_cvs_user (month,day,group_id,user_id,commits,adds) VALUES ($1,$2,$3,$4,$5,$6)',
721 array ($month_string,
727 echo "Error while inserting into stats_cvs_user\n" ;
737 function generateSnapshots ($params) {
739 $project = $this->checkParams ($params) ;
744 $group_name = $project->getUnixName() ;
746 $snapshot = forge_get_config('scm_snapshots_path').'/'.$group_name.'-scm-latest.tar'.util_get_compressed_file_extension();
747 $tarball = forge_get_config('scm_tarballs_path').'/'.$group_name.'-scmroot.tar'.util_get_compressed_file_extension();
749 if (! $project->usesPlugin ($this->name)) {
753 if (! $project->enableAnonSCM()) {
754 if (is_file($snapshot)) {
757 if (is_file($tarball)) {
763 // TODO: ideally we generate one snapshot per git repository
764 $toprepo = forge_get_config('repos_path', 'scmgit') ;
765 $repo = $toprepo . '/' . $project->getUnixName() . '/' . $project->getUnixName() . '.git' ;
767 if (!is_dir ($repo)) {
768 if (is_file($snapshot)) {
771 if (is_file($tarball)) {
777 // Skip empty repo (no HEAD present in repository)
778 $ref = trim(`GIT_DIR=$repo git symbolic-ref HEAD`);
779 if (!file_exists($repo.'/'.$ref)) {
783 $tmp = trim (`mktemp -d`) ;
787 $today = date ('Y-m-d') ;
788 system ("GIT_DIR=\"$repo\" git archive --format=tar --prefix=$group_name-scm-$today/ HEAD |".forge_get_config('compression_method')." > $tmp/snapshot");
789 chmod ("$tmp/snapshot", 0644) ;
790 copy ("$tmp/snapshot", $snapshot) ;
791 unlink ("$tmp/snapshot") ;
793 system ("tar cCf $toprepo - ".$project->getUnixName() ."|".forge_get_config('compression_method')."> $tmp/tarball") ;
794 chmod ("$tmp/tarball", 0644) ;
795 copy ("$tmp/tarball", $tarball) ;
796 unlink ("$tmp/tarball") ;
797 system ("rm -rf $tmp") ;
801 * widgets - 'widgets' hook handler
802 * @param array $params
805 function widgets($params) {
806 require_once 'common/widget/WidgetLayoutManager.class.php';
807 if ($params['owner_type'] == WidgetLayoutManager::OWNER_TYPE_GROUP) {
808 $params['fusionforge_widgets'][] = 'plugin_scmgit_project_latestcommits';
810 if ($params['owner_type'] == WidgetLayoutManager::OWNER_TYPE_USER) {
811 $params['fusionforge_widgets'][] = 'plugin_scmgit_user_myrepositories';
817 * Process the 'widget_instance' hook to create instances of the widgets
818 * @param array $params
820 function myPageBox($params) {
822 $user = UserManager::instance()->getCurrentUser();
823 require_once 'common/widget/WidgetLayoutManager.class.php';
824 if ($params['widget'] == 'plugin_scmgit_user_myrepositories') {
825 require_once $gfplugins.$this->name.'/common/scmgit_Widget_MyRepositories.class.php';
826 $params['instance'] = new scmgit_Widget_MyRepositories(WidgetLayoutManager::OWNER_TYPE_USER, $user->getId());
830 function weekly(&$params) {
831 $res = db_query_params('SELECT group_id FROM groups WHERE status=$1 AND use_scm=1 ORDER BY group_id DESC',
834 $params['output'] .= 'ScmGit Plugin: Unable to get list of projects using SCM: '.db_error();
838 $params['output'] .= 'ScmGit Plugin: Running "git gc --quiet" on '.db_numrows($res).' repositories.'."\n";
839 while ($row = db_fetch_array($res)) {
840 $project = group_get_object($row['group_id']);
841 if (!$project || !is_object($project)) {
843 } elseif ($project->isError()) {
846 if (!$project->usesPlugin($this->name)) {
850 $project_name = $project->getUnixName();
851 $repo = forge_get_config('repos_path', 'scmgit') . '/' . $project_name . '/' . $project_name .'.git';
854 $params['output'] .= $project_name.': '.`git gc --quiet 2>&1`;
859 function activity($params) {
860 $group_id = $params['group'];
861 $project = group_get_object($group_id);
862 if (! $project->usesPlugin($this->name)) {
865 if (in_array('scmgit', $params['show'])) {
866 $start_time = $params['begin'];
867 $end_time = $params['end'];
868 $repo = forge_get_config('repos_path', 'scmgit') . '/' . $project->getUnixName() . '/' . $project->getUnixName() . '.git';
869 $pipe = popen("GIT_DIR=\"$repo\" git log --date=raw --since=@$start_time --until=@$end_time --all --pretty='format:%ad||%an||%s||%h' --name-status", 'r' );
870 while (!feof($pipe) && $data = fgets($pipe)) {
872 $splitedLine = explode('||', $line);
873 if (sizeof($splitedLine) == 4) {
875 $result['section'] = 'scm';
876 $result['group_id'] = $group_id;
877 $result['ref_id'] = 'browser.php?group_id='.$group_id;
878 $result['description'] = $splitedLine[2].' (commit '.$splitedLine[3].')';
879 $result['realname'] = '';
880 $splitedDate = explode(' ', $splitedLine[0]);
881 $result['activity_date'] = $splitedDate[0];
882 $result['subref_id'] = '';
883 $params['results'][] = $result;
887 $params['ids'][] = $this->name;
888 $params['texts'][] = _('Git Commits');
892 function scm_add_repo(&$params) {
893 $project = $this->checkParams($params);
897 if (! $project->usesPlugin ($this->name)) {
901 if (!isset($params['repo_name'])) {
905 if ($params['repo_name'] == $project->getUnixName()) {
906 $params['error_msg'] = _('Cannot create a secondary repository with the same name as the primary');
910 if (! util_is_valid_repository_name($params['repo_name'])) {
911 $params['error_msg'] = _('This repository name is not valid');
915 $result = db_query_params('SELECT count(*) AS count FROM scm_secondary_repos WHERE group_id=$1 AND repo_name = $2 AND plugin_id=$3',
916 array ($params['group_id'],
917 $params['repo_name'],
920 $params['error_msg'] = db_error();
923 if (db_result($result, 0, 'count')) {
924 $params['error_msg'] = sprintf(_('A repository %s already exists'), $params['repo_name']);
930 if (isset($params['clone'])) {
931 $url = $params['clone'];
935 } elseif (preg_match('|^git://|', $url) || preg_match('|^https?://|', $url)) {
938 } elseif ($url == $project->getUnixName()) {
940 } 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',
941 array ($project->getID(),
944 && db_result($result, 0, 'count')) {
945 // Local repo: try to clone from an existing repo in same project
949 $params['error_msg'] = _('Invalid URL from which to clone');
954 if (isset($params['description'])) {
955 $description = $params['description'];
957 if ($clone && !$description) {
958 $description = sprintf(_('Clone of %s'), $params['clone']);
961 $description = "Git repository $params[repo_name] for project ".$project->getUnixName();
964 $result = db_query_params ('INSERT INTO scm_secondary_repos (group_id, repo_name, description, clone_url, plugin_id) VALUES ($1, $2, $3, $4, $5)',
965 array ($params['group_id'],
966 $params['repo_name'],
971 $params['error_msg'] = db_error();
975 plugin_hook ("scm_admin_update", $params);
979 function scm_admin_form(&$params) {
980 $project = $this->checkParams($params);
984 if (! $project->usesPlugin ($this->name)) {
988 session_require_perm('project_admin', $params['group_id']);
990 $project_name = $project->getUnixName();
992 $select_repo = '<select name="frontpage">' . "\n";
993 $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',
994 array ($params['group_id'],
995 SCM_EXTRA_REPO_ACTION_UPDATE,
998 $params['error_msg'] = db_error();
1001 $existing_repos = array();
1002 while($data = db_fetch_array($result)) {
1003 $existing_repos[] = array('repo_name' => $data['repo_name'],
1004 'description' => $data['description'],
1005 'clone_url' => $data['clone_url']);
1007 if (count($existing_repos) == 0) {
1008 printf('<h2>'._('No extra Git repository for project %1$s').'</h2>', $project_name);
1010 $t = sprintf(ngettext('Extra Git repository for project %1$s',
1011 'Extra Git repositories for project %1$s',
1012 count($existing_repos)), $project_name);
1013 print '<h2>'.$t.'</h2>';
1014 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>';
1015 foreach ($existing_repos as $repo) {
1016 print "<tr><td><tt>$repo[repo_name]</tt></td><td>$repo[description]</td><td>$repo[clone_url]</td><td>";
1018 <form name="form_delete_repo_<?php echo $repo['repo_name']?>"
1019 action="<?php echo getStringFromServer('PHP_SELF'); ?>" method="post">
1020 <input type="hidden" name="group_id" value="<?php echo $params['group_id'] ?>" />
1021 <input type="hidden" name="delete_repository" value="1" />
1022 <input type="hidden" name="repo_name" value="<?php echo $repo['repo_name']?>" />
1023 <input type="submit" name="submit" value="<?php echo _('Delete') ?>" />
1026 print "</td></tr>\n";
1028 print '</tbody></table>';
1031 printf('<h2>'._('Create new Git repository for project %1$s').'</h2>', $project_name);
1034 <form name="form_create_repo"
1035 action="<?php echo getStringFromServer('PHP_SELF'); ?>" method="post">
1036 <input type="hidden" name="group_id" value="<?php echo $params['group_id'] ?>" />
1037 <input type="hidden" name="create_repository" value="1" />
1038 <p><strong><?php echo _('Repository name:') ?></strong><?php echo utils_requiredField(); ?><br />
1039 <input type="text" required="required" size="20" name="repo_name" value="" /></p>
1040 <p><strong><?php echo _('Description:'); ?></strong><br />
1041 <input type="text" size="60" name="description" value="" /></p>
1042 <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 />
1043 <input type="text" size="60" name="clone" value="<?php echo $project_name; ?>" /></p>
1044 <input type="submit" name="cancel" value="<?php echo _('Cancel') ?>" />
1045 <input type="submit" name="submit" value="<?php echo _('Submit') ?>" />
1055 // c-file-style: "bsd"