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 along
20 * with this program; if not, write to the Free Software Foundation, Inc.,
21 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
24 forge_define_config_item('default_server', 'scmgit', forge_get_config ('web_host')) ;
25 forge_define_config_item('repos_path', 'scmgit', forge_get_config('chroot').'/scmrepos/git') ;
27 class GitPlugin extends SCMPlugin {
28 function GitPlugin() {
30 $this->name = 'scmgit';
32 $this->_addHook('scm_browser_page');
33 $this->_addHook('scm_update_repolist');
34 $this->_addHook('scm_generate_snapshots');
35 $this->_addHook('scm_gather_stats');
36 $this->_addHook('widget_instance', 'myPageBox', false);
37 $this->_addHook('widgets', 'widgets', false);
41 function getDefaultServer() {
42 return forge_get_config('default_server', 'scmgit') ;
45 function printShortStats ($params) {
46 $project = $this->checkParams($params);
51 if ($project->usesPlugin($this->name)) {
52 $result = db_query_params('SELECT sum(commits) AS commits, sum(adds) AS adds FROM stats_cvs_group WHERE group_id=$1',
53 array ($project->getID())) ;
54 $commit_num = db_result($result,0,'commits');
55 $add_num = db_result($result,0,'adds');
62 echo ' (Git: '.sprintf(_('<strong>%1$s</strong> commits, <strong>%2$s</strong> adds'), number_format($commit_num, 0), number_format($add_num, 0)).")";
67 return '<p>' . _('Documentation for Git is available at <a href="http://git-scm.com/">http://git-scm.com/</a>.') . '</p>';
70 function getInstructionsForAnon($project) {
71 $b = '<h2>' . _('Anonymous Git Access') . '</h2>';
73 $b .= _('This project\'s Git repository can be checked out through anonymous access with the following command.');
77 $b .= '<tt>git clone '.util_make_url ('/anonscm/git/'.$project->getUnixName().'/'.$project->getUnixName().'.git').'</tt><br />';
80 $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',
81 array ($project->getID(),
83 $rows = db_numrows($result);
87 $b .= _('Developer\'s repository');
90 $b .= ngettext('One of this project\'s members also has a personal Git repository that can be checked out anonymously.',
91 'Some of this project\'s members also have personal Git repositories that can be checked out anonymously.',
95 for ($i=0; $i<$rows; $i++) {
96 $user_id = db_result($result,$i,'user_id');
97 $user_name = db_result($result,$i,'user_name');
98 $real_name = db_result($result,$i,'realname');
99 $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 />';
107 function getInstructionsForRW($project) {
109 if (session_loggedin()) {
110 $u =& user_get_object(user_getid());
111 $d = $u->getUnixName();
112 if (forge_get_config('use_ssh', 'scmgit')) {
114 $b .= _('Developer GIT Access via SSH');
117 $b .= _('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.');
119 $b .= '<p><tt>git clone git+ssh://'.$d.'@' . $this->getBoxForProject($project) . '/'. forge_get_config('repos_path', 'scmgit') .'/'. $project->getUnixName() .'/'. $project->getUnixName() .'.git</tt></p>' ;
120 } elseif (forge_get_config('use_dav', 'scmgit')) {
121 $protocol = forge_get_config('use_ssl', 'scmgit')? 'https' : 'http';
123 $b .= _('Developer GIT Access via HTTP');
126 $b .= _('Only project developers can access the GIT tree via this method. Enter your site password when prompted.');
128 $b .= '<p><tt>git clone '.$protocol.'://'.$d.'@' . $this->getBoxForProject($project) . '/'. forge_get_config('scm_root', 'scmgit') .'/'. $project->getUnixName() .'/'. $project->getUnixName() .'.git</tt></p>' ;
130 $b = '<p class="warning">'._('Missing configuration for access in scmgit.ini : use_ssh and use_dav disabled').'</p>';
133 if (forge_get_config('use_ssh', 'scmgit')) {
135 $b .= _('Developer GIT Access via SSH');
138 $b .= _('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.');
140 $b .= '<p><tt>git clone git+ssh://<i>'._('developername').'</i>@' . $this->getBoxForProject($project) . '/'. forge_get_config('repos_path', 'scmgit') .'/'. $project->getUnixName() .'/'. $project->getUnixName() .'.git</tt></p>' ;
141 } elseif (forge_get_config('use_dav', 'scmgit')) {
142 $protocol = forge_get_config('use_ssl', 'scmgit')? 'https' : 'http';
144 $b .= _('Developer GIT Access via HTTP');
147 $b .= _('Only project developers can access the GIT tree via this method. Enter your site password when prompted.');
149 $b .= '<p><tt>git clone '.$protocol.'://<i>'._('developername').'</i>@' . $this->getBoxForProject($project) . '/'. forge_get_config('scm_root', 'scmgit') .'/'. $project->getUnixName() .'/'. $project->getUnixName() .'.git</tt></p>' ;
153 if (session_loggedin()) {
154 $u =& user_get_object(user_getid()) ;
155 if ($u->getUnixStatus() == 'A') {
156 $result = db_query_params('SELECT * FROM plugin_scmgit_personal_repos p WHERE p.group_id=$1 AND p.user_id=$2',
157 array ($project->getID(),
159 if ($result && db_numrows ($result) > 0) {
161 $b .= _('Access to your personal repository');
164 $b .= _('You have a personal repository for this project, accessible through SSH with the following method. Enter your site password when prompted.');
166 $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>' ;
168 $glist = $u->getGroups();
169 foreach ($glist as $g) {
170 if ($g->getID() == $project->getID()) {
172 $b .= _('Request a personal repository');
175 $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).');
178 $b .= sprintf (_('<a href="%s">Request a personal repository</a>.'),
179 util_make_url ('/plugins/scmgit/index.php?func=request-personal-repo&group_id='.$project->getID()));
189 function getSnapshotPara($project) {
192 $filename = $project->getUnixName().'-scm-latest.tar'.util_get_compressed_file_extension();
193 if (file_exists(forge_get_config('scm_snapshots_path').'/'.$filename)) {
195 $b .= util_make_link("/snapshots.php?group_id=".$project->getID(),
196 _('Download the nightly snapshot')
203 function printBrowserPage($params) {
206 $project = $this->checkParams($params);
211 if ($project->usesPlugin($this->name)) {
212 if ($this->browserDisplayable($project)) {
213 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>' ;
218 function getBrowserLinkBlock($project) {
220 $b = $HTML->boxMiddle(_('Git Repository Browser'));
222 $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.');
225 $b .= util_make_link ("/scm/browser.php?group_id=".$project->getID(),
226 _('Browse Git Repository')
232 // function getStatsBlock ($project) {
236 // $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',
237 // array ($project->getID()));
239 // if (db_numrows($result) > 0) {
240 // $b .= $HTML->boxMiddle(_('Repository Statistics'));
242 // $tableHeaders = array(
247 // $b .= $HTML->listTableTop($tableHeaders);
250 // $total = array('adds' => 0, 'commits' => 0);
252 // while($data = db_fetch_array($result)) {
253 // $b .= '<tr '. $HTML->boxGetAltRowStyle($i) .'>';
254 // $b .= '<td width="50%">' ;
255 // $b .= util_make_link_u ($data['user_name'], $data['user_id'], $data['realname']) ;
256 // $b .= '</td><td width="25%" align="right">'.$data['adds']. '</td>'.
257 // '<td width="25%" align="right">'.$data['commits'].'</td></tr>';
258 // $total['adds'] += $data['adds'];
259 // $total['commits'] += $data['commits'];
262 // $b .= '<tr '. $HTML->boxGetAltRowStyle($i) .'>';
263 // $b .= '<td width="50%"><strong>'._('Total').':</strong></td>'.
264 // '<td width="25%" align="right"><strong>'.$total['adds']. '</strong></td>'.
265 // '<td width="25%" align="right"><strong>'.$total['commits'].'</strong></td>';
267 // $b .= $HTML->listTableBottom();
273 function getStatsBlock($project) {
277 function createOrUpdateRepo($params) {
278 $project = $this->checkParams($params);
283 if (! $project->usesPlugin ($this->name)) {
287 $project_name = $project->getUnixName();
288 $root = forge_get_config('repos_path', 'scmgit') . '/' . $project_name ;
289 $unix_group = 'scm_' . $project_name;
290 system ("mkdir -p $root");
292 $main_repo = $root . '/' . $project_name . '.git' ;
293 if (!is_file ("$main_repo/HEAD") && !is_dir("$main_repo/objects") && !is_dir("$main_repo/refs")) {
294 system ("GIT_DIR=\"$main_repo\" git init --bare --shared=group") ;
295 system ("GIT_DIR=\"$main_repo\" git update-server-info") ;
296 if (is_file ("$main_repo/hooks/post-update.sample")) {
297 rename ("$main_repo/hooks/post-update.sample",
298 "$main_repo/hooks/post-update") ;
300 if (!is_file ("$main_repo/hooks/post-update")) {
301 $f = fopen ("$main_repo/hooks/post-update") ;
302 fwrite ($f, "exec git-update-server-info\n") ;
305 if (is_file ("$main_repo/hooks/post-update")) {
306 system ("chmod +x $main_repo/hooks/post-update") ;
308 system ("echo \"Git repository for $project_name\" > $main_repo/description") ;
309 system ("find $main_repo -type d | xargs chmod g+s") ;
311 system ("chgrp -R $unix_group $root") ;
312 system ("chmod g+s $root") ;
313 if ($project->enableAnonSCM()) {
314 system ("chmod g+wX,o+rX-w $root") ;
315 system ("chmod -R g+wX,o+rX-w $main_repo") ;
317 system ("chmod g+wX,o-rwx $root") ;
318 system ("chmod -R g+wX,o-rwx $main_repo") ;
321 $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',
322 array ($project->getID(),
324 $rows = db_numrows ($result) ;
325 for ($i=0; $i<$rows; $i++) {
326 system ("mkdir -p $root/users") ;
327 $user_name = db_result($result,$i,'user_name');
328 $repodir = $root . '/users/' . $user_name . '.git' ;
330 if (!is_file ("$repodir/HEAD") && !is_dir("$repodir/objects") && !is_dir("$repodir/refs")) {
331 system ("git clone --bare $main_repo $repodir") ;
332 system ("GIT_DIR=\"$repodir\" git update-server-info") ;
333 if (is_file ("$repodir/hooks/post-update.sample")) {
334 rename ("$repodir/hooks/post-update.sample",
335 "$repodir/hooks/post-update") ;
337 if (!is_file ("$repodir/hooks/post-update")) {
338 $f = fopen ("$repodir/hooks/post-update") ;
339 fwrite ($f, "exec git-update-server-info\n") ;
342 if (is_file ("$repodir/hooks/post-update")) {
343 system ("chmod +x $repodir/hooks/post-update") ;
345 system("echo \"Git repository for user $user_name in project $project_name\" > $repodir/description");
346 system ("chown -R $user_name:$unix_group $repodir") ;
349 if (is_dir ("$root/users")) {
350 if ($project->enableAnonSCM()) {
351 system ("chmod -R g+rX-w,o+rX-w $root/users") ;
353 system ("chmod -R g+rX-w,o-rwx $root/users") ;
358 function updateRepositoryList($params) {
359 $groups = $this->getGroups();
361 foreach ($groups as $project) {
362 if ($this->browserDisplayable($project)) {
367 $config_dir = forge_get_config('config_path').'/plugins/scmgit';
368 $fname = $config_dir . '/gitweb.conf' ;
369 $config_f = fopen($fname.'.new', 'w') ;
370 $rootdir = forge_get_config('repos_path', 'scmgit');
371 fwrite($config_f, "\$projectroot = '$rootdir';\n");
372 fwrite($config_f, "\$projects_list = '$config_dir/gitweb.list';\n");
373 fwrite($config_f, "@git_base_url_list = ('". util_make_url('/anonscm/git') . "');\n");
374 fwrite($config_f, "\$logo = '". util_make_url('/plugins/scmgit/git-logo.png') . "';\n");
375 fwrite($config_f, "\$favicon = '". util_make_url('/plugins/scmgit/git-favicon.png')."';\n");
376 fwrite($config_f, "\$stylesheet = '". util_make_url('/plugins/scmgit/gitweb.css')."';\n");
377 fwrite($config_f, "\$prevent_xss = 'true';\n");
379 chmod ($fname.'.new', 0644) ;
380 rename ($fname.'.new', $fname) ;
382 $fname = $config_dir . '/gitweb.list' ;
384 $f = fopen ($fname.'.new', 'w');
385 foreach ($list as $project) {
386 $repos = $this->getRepositories($rootdir . "/" . $project->getUnixName());
387 foreach ($repos as $repo) {
388 $reldir = substr($repo, strlen($rootdir) + 1);
389 fwrite($f, $reldir . "\n");
393 chmod($fname.'.new', 0644);
394 rename($fname.'.new', $fname);
397 function getRepositories($path) {
398 if (! is_dir($path)) {
399 echo 'pas de path ?';
403 $entries = scandir($path);
404 foreach ($entries as $entry) {
405 $fullname = $path . "/" . $entry;
406 if (($entry == ".") or ($entry == ".."))
408 if (is_dir($fullname)) {
409 if (is_link($fullname))
411 $result = $this->getRepositories($fullname);
412 $list = array_merge($list, $result);
413 } else if ($entry == "HEAD") {
420 function gatherStats ($params) {
421 global $last_user, $usr_adds, $usr_deletes,
422 $usr_updates, $updates, $adds;
424 $project = $this->checkParams ($params) ;
429 if (! $project->usesPlugin ($this->name)) {
433 if ($params['mode'] == 'day') {
436 $year = $params ['year'] ;
437 $month = $params ['month'] ;
438 $day = $params ['day'] ;
439 $month_string = sprintf( "%04d%02d", $year, $month );
440 $start_time = gmmktime( 0, 0, 0, $month, $day, $year);
441 $end_time = $start_time + 86400;
443 $usr_adds = array () ;
444 $usr_updates = array () ;
445 $usr_deletes = array () ;
450 $repo = forge_get_config('repos_path', 'scmgit') . '/' . $project->getUnixName() . '/' . $project->getUnixName() . '.git';
451 if (!is_dir ($repo) || !is_dir ("$repo/refs")) {
452 // echo "No repository\n" ;
457 $pipe = popen ("GIT_DIR=\"$repo\" git log --since=@$start_time --until=@$end_time --all --pretty='format:%n%an <%ae>' --name-status", 'r' ) ;
459 // cleaning stats_cvs_* table for the current day
460 $res = db_query_params ('DELETE FROM stats_cvs_group WHERE month=$1 AND day=$2 AND group_id=$3',
461 array ($month_string,
463 $project->getID())) ;
465 echo "Error while cleaning stats_cvs_group\n" ;
471 while (!feof($pipe) && $data = fgets ($pipe)) {
473 if (strlen($line) > 0) {
474 $result = preg_match("/^(?<name>.+) <(?<mail>.+)>/", $line, $matches);
477 $last_user = $matches['name'];
479 // Short-commit stats line
480 preg_match("/^(?<mode>[AM])\s+(?<file>.+)$/", $line, $matches);
481 if ($last_user == "") continue;
482 if ($matches['mode'] == 'A') {
483 $usr_adds[$last_user]++;
485 } elseif ($matches['mode'] == 'M') {
486 $usr_updates[$last_user]++;
488 } elseif ($matches['mode'] == 'D') {
489 $usr_deletes[$last_user]++;
495 // inserting group results in stats_cvs_groups
496 if ($updates > 0 || $adds > 0) {
497 if (!db_query_params ('INSERT INTO stats_cvs_group (month,day,group_id,checkouts,commits,adds) VALUES ($1,$2,$3,$4,$5,$6)',
498 array ($month_string,
504 echo "Error while inserting into stats_cvs_group\n" ;
510 // building the user list
511 $user_list = array_unique( array_merge( array_keys( $usr_adds ), array_keys( $usr_updates ) ) );
513 foreach ( $user_list as $user ) {
514 // trying to get user id from user name
515 $u = &user_get_object_by_name ($user) ;
517 $user_id = $u->getID();
522 $uu = $usr_updates[$user] ? $usr_updates[$user] : 0 ;
523 $ua = $usr_adds[$user] ? $usr_adds[$user] : 0 ;
524 if ($uu > 0 || $ua > 0) {
525 if (!db_query_params ('INSERT INTO stats_cvs_user (month,day,group_id,user_id,commits,adds) VALUES ($1,$2,$3,$4,$5,$6)',
526 array ($month_string,
532 echo "Error while inserting into stats_cvs_user\n" ;
542 function generateSnapshots ($params) {
544 $project = $this->checkParams ($params) ;
549 $group_name = $project->getUnixName() ;
551 $snapshot = forge_get_config('scm_snapshots_path').'/'.$group_name.'-scm-latest.tar'.util_get_compressed_file_extension();
552 $tarball = forge_get_config('scm_tarballs_path').'/'.$group_name.'-scmroot.tar'.util_get_compressed_file_extension();
554 if (! $project->usesPlugin ($this->name)) {
558 if (! $project->enableAnonSCM()) {
559 if (is_file($snapshot)) {
562 if (is_file($tarball)) {
568 // TODO: ideally we generate one snapshot per git repository
569 $toprepo = forge_get_config('repos_path', 'scmgit') ;
570 $repo = $toprepo . '/' . $project->getUnixName() . '/' . $project->getUnixName() . '.git' ;
572 if (!is_dir ($repo)) {
573 if (is_file($snapshot)) {
576 if (is_file($tarball)) {
582 $tmp = trim (`mktemp -d`) ;
586 $today = date ('Y-m-d') ;
587 system ("GIT_DIR=\"$repo\" git archive --format=tar --prefix=$group_name-scm-$today/ HEAD |".forge_get_config('compression_method')." > $tmp/snapshot");
588 chmod ("$tmp/snapshot", 0644) ;
589 copy ("$tmp/snapshot", $snapshot) ;
590 unlink ("$tmp/snapshot") ;
592 system ("tar cCf $toprepo - ".$project->getUnixName() ."|".forge_get_config('compression_method')."> $tmp/tarball") ;
593 chmod ("$tmp/tarball", 0644) ;
594 copy ("$tmp/tarball", $tarball) ;
595 unlink ("$tmp/tarball") ;
596 system ("rm -rf $tmp") ;
599 function widgets($params) {
600 require_once('common/widget/WidgetLayoutManager.class.php');
601 if ($params['owner_type'] == WidgetLayoutManager::OWNER_TYPE_GROUP) {
602 $params['fusionforge_widgets'][] = 'plugin_scmgit_project_latestcommits';
604 if ($params['owner_type'] == WidgetLayoutManager::OWNER_TYPE_USER) {
605 $params['fusionforge_widgets'][] = 'plugin_scmgit_user_myrepositories';
610 function myPageBox($params) {
612 $user = UserManager::instance()->getCurrentUser();
613 require_once('common/widget/WidgetLayoutManager.class.php');
614 if ($params['widget'] == 'plugin_scmgit_user_myrepositories') {
615 require_once $gfplugins.$this->name.'/common/scmgit_Widget_MyRepositories.class.php';
616 $params['instance'] = new scmgit_Widget_MyRepositories(WidgetLayoutManager::OWNER_TYPE_USER, $user->getId());
623 // c-file-style: "bsd"