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 = forge_get_config('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>' ;
116 if (session_loggedin()) {
117 $u =& user_get_object(user_getid()) ;
118 if ($u->getUnixStatus() == 'A') {
119 $result = db_query_params ('SELECT * FROM plugin_scmgit_personal_repos p WHERE p.group_id=$1 AND p.user_id=$2',
120 array ($project->getID(),
122 if ($result && db_numrows ($result) > 0) {
123 $b .= _('<p><b>Access to your personal repository</b></p><p>You have a personal repository for this project, accessible through SSH with the following method. Enter your site password when prompted.</p>');
124 $b .= '<p><tt>git clone git+ssh://'.$u->getUnixName().'@' . $project->getSCMBox() . $this->git_root .'/'. $project->getUnixName() .'/users/'. $u->getUnixName() .'.git</tt></p>' ;
126 $glist = $u->getGroups();
127 foreach ($glist as $g) {
128 if ($g->getID() == $project->getID()) {
129 $b .= sprintf (_('<p><b>Request a personal repository</b></p><p>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).</p><p><a href="%s">Request a personal repository</a>.</p>'),
130 util_make_url ('/plugins/scmgit/index.php?func=request-personal-repo&group_id='.$project->getID()));
140 function getSnapshotPara ($project) {
143 $filename = $project->getUnixName().'-scm-latest.tar.gz';
144 if (file_exists(forge_get_config('scm_snapshots_path').'/'.$filename)) {
146 $b .= util_make_link ("/snapshots.php?group_id=".$project->getID(),
147 _('Download the nightly snapshot')
154 function printBrowserPage ($params) {
157 $project = $this->checkParams ($params) ;
162 if ($project->usesPlugin ($this->name)) {
163 if ($this->browserDisplayable ($project)) {
164 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>' ;
169 function getBrowserLinkBlock ($project) {
171 $b = $HTML->boxMiddle(_('Git Repository Browser'));
172 $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>');
174 $b .= util_make_link ("/scm/browser.php?group_id=".$project->getID(),
175 _('Browse Git Repository')
181 // function getStatsBlock ($project) {
185 // $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',
186 // array ($project->getID()));
188 // if (db_numrows($result) > 0) {
189 // $b .= $HTML->boxMiddle(_('Repository Statistics'));
191 // $tableHeaders = array(
196 // $b .= $HTML->listTableTop($tableHeaders);
199 // $total = array('adds' => 0, 'commits' => 0);
201 // while($data = db_fetch_array($result)) {
202 // $b .= '<tr '. $HTML->boxGetAltRowStyle($i) .'>';
203 // $b .= '<td width="50%">' ;
204 // $b .= util_make_link_u ($data['user_name'], $data['user_id'], $data['realname']) ;
205 // $b .= '</td><td width="25%" align="right">'.$data['adds']. '</td>'.
206 // '<td width="25%" align="right">'.$data['commits'].'</td></tr>';
207 // $total['adds'] += $data['adds'];
208 // $total['commits'] += $data['commits'];
211 // $b .= '<tr '. $HTML->boxGetAltRowStyle($i) .'>';
212 // $b .= '<td width="50%"><strong>'._('Total').':</strong></td>'.
213 // '<td width="25%" align="right"><strong>'.$total['adds']. '</strong></td>'.
214 // '<td width="25%" align="right"><strong>'.$total['commits'].'</strong></td>';
216 // $b .= $HTML->listTableBottom();
221 function getStatsBlock ($project) {
225 function createOrUpdateRepo ($params) {
226 $project = $this->checkParams ($params) ;
231 if (! $project->usesPlugin ($this->name)) {
235 $project_name = $project->getUnixName() ;
236 $root = $this->git_root . '/' . $project_name ;
237 $unix_group = 'scm_' . $project_name ;
238 system ("mkdir -p $root") ;
240 $main_repo = $root . '/' . $project_name . '.git' ;
241 if (!is_file ("$main_repo/HEAD") && !is_dir("$main_repo/objects") && !is_dir("$main_repo/refs")) {
242 system ("GIT_DIR=\"$main_repo\" git init --bare --shared=group") ;
243 system ("GIT_DIR=\"$main_repo\" git update-server-info") ;
244 if (is_file ("$main_repo/hooks/post-update.sample")) {
245 rename ("$main_repo/hooks/post-update.sample",
246 "$main_repo/hooks/post-update") ;
248 if (!is_file ("$main_repo/hooks/post-update")) {
249 $f = fopen ("$main_repo/hooks/post-update") ;
250 fwrite ($f, "exec git-update-server-info\n") ;
253 if (is_file ("$main_repo/hooks/post-update")) {
254 system ("chmod +x $main_repo/hooks/post-update") ;
256 system ("echo \"Git repository for $project_name\" > $main_repo/description") ;
257 system ("find $main_repo -type d | xargs chmod g+s") ;
259 system ("chgrp -R $unix_group $root") ;
260 system ("chmod g+s $root") ;
261 if ($project->enableAnonSCM()) {
262 system ("chmod g+wX,o+rX-w $root") ;
263 system ("chmod -R g+wX,o+rX-w $main_repo") ;
265 system ("chmod g+wX,o-rwx $root") ;
266 system ("chmod -R g+wX,o-rwx $main_repo") ;
269 $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',
270 array ($project->getID(),
272 $rows = db_numrows ($result) ;
273 for ($i=0; $i<$rows; $i++) {
274 system ("mkdir -p $root/users") ;
275 $user_name = db_result($result,$i,'user_name');
276 $repodir = $root . '/users/' . $user_name . '.git' ;
278 if (!is_file ("$repodir/HEAD") && !is_dir("$repodir/objects") && !is_dir("$repodir/refs")) {
279 system ("git clone --bare $main_repo $repodir") ;
280 system ("GIT_DIR=\"$repodir\" git update-server-info") ;
281 if (is_file ("$repodir/hooks/post-update.sample")) {
282 rename ("$repodir/hooks/post-update.sample",
283 "$repodir/hooks/post-update") ;
285 if (!is_file ("$repodir/hooks/post-update")) {
286 $f = fopen ("$repodir/hooks/post-update") ;
287 fwrite ($f, "exec git-update-server-info\n") ;
290 if (is_file ("$repodir/hooks/post-update")) {
291 system ("chmod +x $repodir/hooks/post-update") ;
293 system ("echo \"Git repository for user $owner in project $project_name\" > $repodir/description") ;
294 system ("chown -R $user_name:$unix_group $repodir") ;
297 if (is_dir ("$root/users")) {
298 if ($project->enableAnonSCM()) {
299 system ("chmod -R g+rX-w,o+rX-w $root/users") ;
301 system ("chmod -R g+rX-w,o-rwx $root/users") ;
306 function updateRepositoryList ($params) {
307 $groups = $this->getGroups () ;
309 foreach ($groups as $project) {
310 if ($this->browserDisplayable ($project)) {
315 $config_dir = '/etc/gforge/plugins/scmgit' ;
316 $fname = $config_dir . '/gitweb.conf' ;
317 $config_f = fopen ($fname.'.new', 'w') ;
318 $rootdir = $this->git_root;
319 fwrite($config_f, "\$projectroot = '$rootdir';\n");
320 fwrite($config_f, "\$projects_list = '$config_dir/gitweb.list';\n");
321 fwrite($config_f, "@git_base_url_list = ('". util_make_url ('/anonscm/git') . "');\n");
322 fwrite($config_f, "\$logo = '". util_make_url ('/plugins/scmgit/gitweb/git-logo.png') . "';\n");
323 fwrite($config_f, "\$favicon = '". util_make_url ('/plugins/scmgit/gitweb/git-favicon.png')."';\n");
324 fwrite($config_f, "\$stylesheet = '". util_make_url ('/plugins/scmgit/gitweb/gitweb.css')."';\n");
325 fwrite($config_f, "\$prevent_xss = 'true';\n");
327 chmod ($fname.'.new', 0644) ;
328 rename ($fname.'.new', $fname) ;
330 $fname = $config_dir . '/gitweb.list' ;
332 $f = fopen ($fname.'.new', 'w') ;
333 foreach ($list as $project) {
334 $repos = $this->getRepositories($rootdir . "/" . $project->getUnixName());
335 foreach ($repos as $repo) {
336 $reldir = substr($repo, strlen($rootdir) + 1);
337 fwrite ($f, $reldir . "\n");
341 chmod ($fname.'.new', 0644) ;
342 rename ($fname.'.new', $fname) ;
345 function getRepositories($path) {
349 $entries = scandir($path);
350 foreach ($entries as $entry) {
351 $fullname = $path . "/" . $entry;
352 if (($entry == ".") or ($entry == ".."))
354 if (is_dir($fullname)) {
355 if (is_link($fullname))
357 $result = $this->getRepositories($fullname);
358 $list = array_merge($list, $result);
359 } else if ($entry == "HEAD") {
366 function generateSnapshots ($params) {
369 $project = $this->checkParams ($params) ;
374 $group_name = $project->getUnixName() ;
376 $snapshot = forge_get_config('scm_snapshots_path').'/'.$group_name.'-scm-latest.tar.gz';
377 $tarball = forge_get_config('scm_tarballs_path').'/'.$group_name.'-scmroot.tar.gz';
379 if (! $project->usesPlugin ($this->name)) {
383 if (! $project->enableAnonSCM()) {
388 // TODO: ideally we generate one snapshot per git repository
389 $toprepo = $this->git_root ;
390 $repo = $toprepo . '/' . $project->getUnixName() . '/' . $project->getUnixName() . '.git' ;
392 if (!is_dir ($repo)) {
397 $today = date ('Y-m-d') ;
398 $tmp = trim (`mktemp -d`) ;
403 system ("GIT_DIR=\"$repo\" git archive --format=tar --prefix=$group_name-scm-$today/ HEAD | gzip > $tmp/snapshot.tar.gz");
404 chmod ("$tmp/snapshot.tar.gz", 0644) ;
405 copy ("$tmp/snapshot.tar.gz", $snapshot) ;
406 unlink ("$tmp/snapshot.tar.gz") ;
408 system ("tar czCf $toprepo $tmp/tarball.tar.gz " . $project->getUnixName()) ;
409 chmod ("$tmp/tarball.tar.gz", 0644) ;
410 copy ("$tmp/tarball.tar.gz", $tarball) ;
411 unlink ("$tmp/tarball.tar.gz") ;
412 system ("rm -rf $tmp") ;
415 function gatherStats ($params) {
416 global $last_user, $usr_adds, $usr_deletes,
417 $usr_updates, $updates, $adds;
419 $project = $this->checkParams ($params) ;
424 if (! $project->usesPlugin ($this->name)) {
428 if ($params['mode'] == 'day') {
431 $year = $params ['year'] ;
432 $month = $params ['month'] ;
433 $day = $params ['day'] ;
434 $month_string = sprintf( "%04d%02d", $year, $month );
435 $start_time = gmmktime( 0, 0, 0, $month, $day, $year);
436 $end_time = $start_time + 86400;
438 $usr_adds = array () ;
439 $usr_updates = array () ;
440 $usr_deletes = array () ;
445 $repo = $this->git_root . '/' . $project->getUnixName() . '/' . $project->getUnixName() . '.git';
446 if (!is_dir ($repo) || !is_dir ("$repo/refs")) {
447 // echo "No repository\n" ;
452 $pipe = popen ("GIT_DIR=\"$repo\" git log --since=@$start_time --until=@$end_time --all --pretty='format:%n%an <%ae>' --name-status", 'r' ) ;
454 // cleaning stats_cvs_* table for the current day
455 $res = db_query_params ('DELETE FROM stats_cvs_group WHERE month=$1 AND day=$2 AND group_id=$3',
456 array ($month_string,
458 $project->getID())) ;
460 echo "Error while cleaning stats_cvs_group\n" ;
466 while (!feof($pipe) && $data = fgets ($pipe)) {
468 if (strlen($line) > 0) {
469 $result = preg_match("/^(?<name>.+) <(?<mail>.+)>/", $line, $matches);
472 $last_user = $matches['name'];
474 // Short-commit stats line
475 preg_match("/^(?<mode>[AM])\s+(?<file>.+)$/", $line, $matches);
476 if ($last_user == "") continue;
477 if ($matches['mode'] == 'A') {
478 $usr_adds[$last_user]++;
480 } elseif ($matches['mode'] == 'M') {
481 $usr_updates[$last_user]++;
483 } elseif ($matches['mode'] == 'D') {
484 $usr_deletes[$last_user]++;
490 // inserting group results in stats_cvs_groups
491 if ($updates > 0 || $adds > 0) {
492 if (!db_query_params ('INSERT INTO stats_cvs_group (month,day,group_id,checkouts,commits,adds) VALUES ($1,$2,$3,$4,$5,$6)',
493 array ($month_string,
499 echo "Error while inserting into stats_cvs_group\n" ;
505 // building the user list
506 $user_list = array_unique( array_merge( array_keys( $usr_adds ), array_keys( $usr_updates ) ) );
508 foreach ( $user_list as $user ) {
509 // trying to get user id from user name
510 $u = &user_get_object_by_name ($user) ;
512 $user_id = $u->getID();
517 $uu = $usr_updates[$user] ? $usr_updates[$user] : 0 ;
518 $ua = $usr_adds[$user] ? $usr_adds[$user] : 0 ;
519 if ($uu > 0 || $ua > 0) {
520 if (!db_query_params ('INSERT INTO stats_cvs_user (month,day,group_id,user_id,commits,adds) VALUES ($1,$2,$3,$4,$5,$6)',
521 array ($month_string,
527 echo "Error while inserting into stats_cvs_user\n" ;
540 // c-file-style: "bsd"