2 /** FusionForge Git plugin
4 * Copyright 2009, Roland Mas
5 * Copyright 2009, Mehdi Dogguy <mehdi@debian.org>
7 * This file is part of FusionForge.
9 * FusionForge is free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published
11 * by the Free Software Foundation; either version 2 of the License,
12 * or (at your option) any later version.
14 * FusionForge is distributed in the hope that it will be useful, but
15 * WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 * General Public License for more details.
19 * You should have received a copy of the GNU General Public License
20 * along with FusionForge; if not, write to the Free Software
21 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
25 class GitPlugin extends SCMPlugin {
26 function GitPlugin () {
29 $this->name = 'scmgit';
31 $this->hooks[] = 'scm_update_repolist' ;
32 $this->hooks[] = 'scm_browser_page' ;
33 $this->hooks[] = 'scm_gather_stats' ;
34 $this->hooks[] = 'scm_generate_snapshots' ;
36 require_once $gfconfig.'plugins/scmgit/config.php' ;
38 $this->default_git_server = $default_git_server ;
39 if (isset ($git_root)) {
40 $this->git_root = $git_root;
42 $this->git_root = $GLOBALS['sys_chroot'].'/scmrepos/git' ;
48 function getDefaultServer() {
49 return $this->default_git_server ;
52 function printShortStats ($params) {
53 $project = $this->checkParams ($params) ;
58 if ($project->usesPlugin($this->name)) {
59 $result = db_query_params('SELECT sum(commits) AS commits, sum(adds) AS adds FROM stats_cvs_group WHERE group_id=$1',
60 array ($project->getID())) ;
61 $commit_num = db_result($result,0,'commits');
62 $add_num = db_result($result,0,'adds');
69 echo ' (Git: '.sprintf(_('<strong>%1$s</strong> commits, <strong>%2$s</strong> adds'), number_format($commit_num, 0), number_format($add_num, 0)).")";
73 function getBlurb () {
74 return _('<p>Documentation for Git is available <a href="http://git-scm.com/">here</a>.</p>') ;
77 function getInstructionsForAnon ($project) {
78 $b = _('<p><b>Anonymous Git Access</b></p><p>This project\'s Git repository can be checked out through anonymous access with the following command.</p>');
80 $b .= '<tt>git clone '.util_make_url ('/anonscm/git/'.$project->getUnixName().'/'.$project->getUnixName().'.git').'</tt><br />';
83 $result = db_query_params ('SELECT u.user_id, u.user_name, u.realname FROM plugin_scmgit_personal_repos p, users u WHERE p.group_id=$1 AND u.user_id=p.user_id AND u.unix_status=$2',
84 array ($project->getID(),
86 $rows = db_numrows ($result) ;
89 $b .= ngettext ('<p><b>Developer\'s repository</b></p><p>One of this project\'s members also has a personal Git repository that can be checked out anonymously.</p>',
90 '<p><b>Developers\' repositories</b></p><p>Some of this project\'s members also have personal Git repositories that can be checked out anonymously.</p>',
93 for ($i=0; $i<$rows; $i++) {
94 $user_id = db_result($result,$i,'user_id');
95 $user_name = db_result($result,$i,'user_name');
96 $real_name = db_result($result,$i,'realname');
97 $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).')<br />';
105 function getInstructionsForRW ($project) {
106 if (session_loggedin()) {
107 $u =& user_get_object(user_getid()) ;
108 $d = $u->getUnixName() ;
109 $b = _('<p><b>Developer GIT Access via SSH</b></p><p>Only project developers can access the GIT tree via this method. SSH must be installed on your client machine. Enter your site password when prompted.</p>');
110 $b .= '<p><tt>git clone git+ssh://'.$d.'@' . $project->getSCMBox() . $this->git_root .'/'. $project->getUnixName() .'/'. $project->getUnixName() .'.git</tt></p>' ;
112 $b = _('<p><b>Developer GIT Access via SSH</b></p><p>Only project developers can access the GIT tree 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.</p>');
113 $b .= '<p><tt>git clone git+ssh://<i>'._('developername').'</i>@' . $project->getSCMBox() . $this->git_root .'/'. $project->getUnixName() .'/'. $project->getUnixName() .'.git</tt></p>' ;
115 if (session_logged_in()) {
116 $u =& user_get_object(user_getid()) ;
117 if ($u->getUnixStatus() == 'A') {
118 $result = db_query_params ('SELECT * FROM plugin_scmgit_personal_repos p WHERE p.group_id=$1 AND p.user_id=$2',
119 array ($project->getID(),
121 if ($result && db_numrows ($result) > 0) {
122 $b .= _('<p><b>Access to your private repository</b></p><p>You have a private repository for this project, accessible through SSH with the following method. Enter your site password when prompted.</p>');
123 $b .= '<p><tt>git clone git+ssh://'.$u->getUnixName().'@' . $project->getSCMBox() . $this->git_root .'/'. $project->getUnixName() .'/users/'. $u->getUnixName() .'.git</tt></p>' ;
132 function getSnapshotPara ($project) {
133 global $sys_scm_snapshots_path ;
135 $filename = $project->getUnixName().'-scm-latest.tar.gz';
136 if (file_exists($sys_scm_snapshots_path.'/'.$filename)) {
138 $b .= util_make_link ("/snapshots.php?group_id=".$project->getID(),
139 _('Download the nightly snapshot')
146 function printBrowserPage ($params) {
149 $project = $this->checkParams ($params) ;
154 if ($project->usesPlugin ($this->name)) {
155 if ($this->browserDisplayable ($project)) {
156 print '<iframe src="'.util_make_url ("/plugins/scmgit/cgi-bin/gitweb.cgi?p=".$project->getUnixName().'/'.$project->getUnixName().'.git').'" frameborder="no" width=100% height=700></iframe>' ;
161 function getBrowserLinkBlock ($project) {
163 $b = $HTML->boxMiddle(_('Git Repository Browser'));
164 $b .= _('<p>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.</p>');
166 $b .= util_make_link ("/scm/browser.php?group_id=".$project->getID(),
167 _('Browse Git Repository')
173 // function getStatsBlock ($project) {
177 // $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',
178 // array ($project->getID()));
180 // if (db_numrows($result) > 0) {
181 // $b .= $HTML->boxMiddle(_('Repository Statistics'));
183 // $tableHeaders = array(
188 // $b .= $HTML->listTableTop($tableHeaders);
191 // $total = array('adds' => 0, 'commits' => 0);
193 // while($data = db_fetch_array($result)) {
194 // $b .= '<tr '. $HTML->boxGetAltRowStyle($i) .'>';
195 // $b .= '<td width="50%">' ;
196 // $b .= util_make_link_u ($data['user_name'], $data['user_id'], $data['realname']) ;
197 // $b .= '</td><td width="25%" align="right">'.$data['adds']. '</td>'.
198 // '<td width="25%" align="right">'.$data['commits'].'</td></tr>';
199 // $total['adds'] += $data['adds'];
200 // $total['commits'] += $data['commits'];
203 // $b .= '<tr '. $HTML->boxGetAltRowStyle($i) .'>';
204 // $b .= '<td width="50%"><strong>'._('Total').':</strong></td>'.
205 // '<td width="25%" align="right"><strong>'.$total['adds']. '</strong></td>'.
206 // '<td width="25%" align="right"><strong>'.$total['commits'].'</strong></td>';
208 // $b .= $HTML->listTableBottom();
213 function getStatsBlock ($project) {
217 function createOrUpdateRepo ($params) {
218 $project = $this->checkParams ($params) ;
223 if (! $project->usesPlugin ($this->name)) {
227 $project_name = $project->getUnixName() ;
228 $root = $this->git_root . '/' . $project_name ;
229 $unix_group = 'scm_' . $project_name ;
230 system ("mkdir -p $root") ;
232 $main_repo = $root . '/' . $project_name . '.git' ;
233 if (!is_file ("$main_repo/HEAD") && !is_dir("$main_repo/objects") && !is_dir("$main_repo/refs")) {
234 system ("GIT_DIR=\"$main_repo\" git init --bare --shared=group") ;
235 system ("GIT_DIR=\"$main_repo\" git update-server-info") ;
236 if (is_file ("$main_repo/hooks/post-update.sample")) {
237 rename ("$main_repo/hooks/post-update.sample",
238 "$main_repo/hooks/post-update") ;
240 if (!is_file ("$main_repo/hooks/post-update")) {
241 $f = fopen ("$main_repo/hooks/post-update") ;
242 fwrite ($f, "exec git-update-server-info\n") ;
245 if (is_file ("$main_repo/hooks/post-update")) {
246 system ("chmod +x $main_repo/hooks/post-update") ;
248 system ("echo \"Git repository for $project_name\" > $main_repo/description") ;
249 system ("find $main_repo -type d | xargs chmod g+s") ;
251 system ("chgrp -R $unix_group $root") ;
252 system ("chmod g+s $root") ;
253 if ($project->enableAnonSCM()) {
254 system ("chmod g+wX,o+rX-w $root") ;
255 system ("chmod -R g+wX,o+rX-w $main_repo") ;
257 system ("chmod g+wX,o-rwx $root") ;
258 system ("chmod -R g+wX,o-rwx $main_repo") ;
261 $result = db_query_params ('SELECT u.user_name FROM plugin_scmgit_personal_repos p, users u WHERE p.group_id=$1 AND u.user_id=p.user_id AND u.unix_status=$2',
262 array ($project->getID(),
264 $rows = db_numrows ($result) ;
265 for ($i=0; $i<$rows; $i++) {
266 system ("mkdir -p $root/users") ;
267 $user_name = db_result($result,$i,'user_name');
268 $repodir = $root . '/users/' . $user_name . '.git' ;
270 if (!is_file ("$repodir/HEAD") && !is_dir("$repodir/objects") && !is_dir("$repodir/refs")) {
271 system ("git clone --bare $main_repo $repodir") ;
272 system ("GIT_DIR=\"$repodir\" git update-server-info") ;
273 system ("echo \"Git repository for user $owner in project $project_name\" > $repodir/description") ;
274 system ("chown -R $user_name:$unix_group $repodir") ;
276 if ($project->enableAnonSCM()) {
277 system ("chmod -R g+rX-w,o+rX-w $repodir") ;
279 system ("chmod -R g+rX-w,o-rwx $repodir") ;
284 function updateRepositoryList ($params) {
285 $groups = $this->getGroups () ;
287 foreach ($groups as $project) {
288 if ($this->browserDisplayable ($project)) {
293 $config_dir = '/etc/gforge/plugins/scmgit' ;
294 $fname = $config_dir . '/gitweb.conf' ;
295 $config_f = fopen ($fname.'.new', 'w') ;
296 $rootdir = $this->git_root;
297 fwrite($config_f, "\$projectroot = '$rootdir';\n");
298 fwrite($config_f, "\$projects_list = '$config_dir/gitweb.list';\n");
299 fwrite($config_f, "@git_base_url_list = ('". util_make_url ('/anonscm/git') . "');\n");
300 fwrite($config_f, "\$logo = '". util_make_url ('/plugins/scmgit/gitweb/git-logo.png') . "';\n");
301 fwrite($config_f, "\$favicon = '". util_make_url ('/plugins/scmgit/gitweb/git-favicon.png')."';\n");
302 fwrite($config_f, "\$stylesheet = '". util_make_url ('/plugins/scmgit/gitweb/gitweb.css')."';\n");
303 fwrite($config_f, "\$prevent_xss = 'true';\n");
305 chmod ($fname.'.new', 0644) ;
306 rename ($fname.'.new', $fname) ;
308 $fname = $config_dir . '/gitweb.list' ;
310 $f = fopen ($fname.'.new', 'w') ;
311 foreach ($list as $project) {
312 $repos = $this->getRepositories($rootdir . "/" . $project->getUnixName());
313 foreach ($repos as $repo) {
314 $reldir = substr($repo, strlen($rootdir) + 1);
315 fwrite ($f, $reldir . "\n");
319 chmod ($fname.'.new', 0644) ;
320 rename ($fname.'.new', $fname) ;
323 function getRepositories($path) {
327 $entries = scandir($path);
328 foreach ($entries as $entry) {
329 $fullname = $path . "/" . $entry;
330 if (($entry == ".") or ($entry == ".."))
332 if (is_dir($fullname)) {
333 if (is_link($fullname))
335 $result = $this->getRepositories($fullname);
336 $list = array_merge($list, $result);
337 } else if ($entry == "HEAD") {
344 function generateSnapshots ($params) {
345 global $sys_scm_tarballs_path ;
347 $project = $this->checkParams ($params) ;
352 $group_name = $project->getUnixName() ;
354 $snapshot = $sys_scm_snapshots_path.'/'.$group_name.'-scm-latest.tar.gz';
355 $tarball = $sys_scm_tarballs_path.'/'.$group_name.'-scmroot.tar.gz';
357 if (! $project->usesPlugin ($this->name)) {
361 if (! $project->enableAnonSCM()) {
366 // TODO: ideally we generate one snapshot per git repository
367 $toprepo = $this->git_root ;
368 $repo = $toprepo . '/' . $project->getUnixName() . '/' . $project->getUnixName() . '.git' ;
370 if (!is_dir ($repo)) {
375 $today = date ('Y-m-d') ;
376 $tmp = trim (`mktemp -d`) ;
381 system ("GIT_DIR=\"$repo\" git archive --format=tar --prefix=$group_name-scm-$today/ HEAD | gzip > $tmp/snapshot.tar.gz");
382 chmod ("$tmp/snapshot.tar.gz", 0644) ;
383 copy ("$tmp/snapshot.tar.gz", $snapshot) ;
384 unlink ("$tmp/snapshot.tar.gz") ;
386 system ("tar czCf $toprepo $tmp/tarball.tar.gz " . $project->getUnixName()) ;
387 chmod ("$tmp/tarball.tar.gz", 0644) ;
388 copy ("$tmp/tarball.tar.gz", $tarball) ;
389 unlink ("$tmp/tarball.tar.gz") ;
390 system ("rm -rf $tmp") ;
393 function gatherStats ($params) {
394 global $last_user, $usr_adds, $usr_deletes,
395 $usr_updates, $updates, $adds;
397 $project = $this->checkParams ($params) ;
402 if (! $project->usesPlugin ($this->name)) {
406 if ($params['mode'] == 'day') {
409 $year = $params ['year'] ;
410 $month = $params ['month'] ;
411 $day = $params ['day'] ;
412 $month_string = sprintf( "%04d%02d", $year, $month );
413 $start_time = gmmktime( 0, 0, 0, $month, $day, $year);
414 $end_time = $start_time + 86400;
416 $usr_adds = array () ;
417 $usr_updates = array () ;
418 $usr_deletes = array () ;
423 $repo = $this->git_root . '/' . $project->getUnixName() . '/' . $project->getUnixName() . '.git';
424 if (!is_dir ($repo) || !is_dir ("$repo/refs")) {
425 // echo "No repository\n" ;
430 $pipe = popen ("GIT_DIR=\"$repo\" git log --since=@$start_time --until=@$end_time --all --pretty='format:%n%an <%ae>' --name-status", 'r' ) ;
432 // cleaning stats_cvs_* table for the current day
433 $res = db_query_params ('DELETE FROM stats_cvs_group WHERE month=$1 AND day=$2 AND group_id=$3',
434 array ($month_string,
436 $project->getID())) ;
438 echo "Error while cleaning stats_cvs_group\n" ;
444 while (!feof($pipe) && $data = fgets ($pipe)) {
446 if (strlen($line) > 0) {
447 $result = preg_match("/^(?<name>.+) <(?<mail>.+)>/", $line, $matches);
450 $last_user = $matches['name'];
452 // Short-commit stats line
453 preg_match("/^(?<mode>[AM])\s+(?<file>.+)$/", $line, $matches);
454 if ($last_user == "") continue;
455 if ($matches['mode'] == 'A') {
456 $usr_adds[$last_user]++;
458 } elseif ($matches['mode'] == 'M') {
459 $usr_updates[$last_user]++;
461 } elseif ($matches['mode'] == 'D') {
462 $usr_deletes[$last_user]++;
468 // inserting group results in stats_cvs_groups
469 if ($updates > 0 || $adds > 0) {
470 if (!db_query_params ('INSERT INTO stats_cvs_group (month,day,group_id,checkouts,commits,adds) VALUES ($1,$2,$3,$4,$5,$6)',
471 array ($month_string,
477 echo "Error while inserting into stats_cvs_group\n" ;
483 // building the user list
484 $user_list = array_unique( array_merge( array_keys( $usr_adds ), array_keys( $usr_updates ) ) );
486 foreach ( $user_list as $user ) {
487 // trying to get user id from user name
488 $u = &user_get_object_by_name ($user) ;
490 $user_id = $u->getID();
495 $uu = $usr_updates[$user] ? $usr_updates[$user] : 0 ;
496 $ua = $usr_adds[$user] ? $usr_adds[$user] : 0 ;
497 if ($uu > 0 || $ua > 0) {
498 if (!db_query_params ('INSERT INTO stats_cvs_user (month,day,group_id,user_id,commits,adds) VALUES ($1,$2,$3,$4,$5,$6)',
499 array ($month_string,
505 echo "Error while inserting into stats_cvs_user\n" ;
518 // c-file-style: "bsd"