3 * FusionForge CVS plugin
5 * Copyright 2004-2009, Roland Mas
6 * Copyright 2013, Franck Villaume - TrivialDev
8 * This file is part of FusionForge.
10 * FusionForge is free software; you can redistribute it and/or modify
11 * it under the terms of the GNU General Public License as published
12 * by the Free Software Foundation; either version 2 of the License,
13 * or (at your option) any later version.
15 * FusionForge is distributed in the hope that it will be useful, but
16 * WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
18 * General Public License for more details.
20 * You should have received a copy of the GNU General Public License along
21 * with this program; if not, write to the Free Software Foundation, Inc.,
22 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
25 forge_define_config_item ('default_server', 'scmcvs', forge_get_config ('web_host')) ;
26 forge_define_config_item ('repos_path', 'scmcvs', forge_get_config('chroot').'/scmrepos/cvs') ;
28 class CVSPlugin extends SCMPlugin {
29 function CVSPlugin () {
32 global $cvsdir_prefix ;
34 $this->name = 'scmcvs';
36 $this->hooks[] = 'scm_browser_page';
37 $this->hooks[] = 'scm_generate_snapshots' ;
38 $this->hooks[] = 'scm_gather_stats' ;
40 $this->provides['cvs'] = true;
45 function getDefaultServer() {
46 return forge_get_config('default_server', 'scmcvs');
49 function printShortStats ($params) {
50 $project = $this->checkParams ($params) ;
55 if ($project->usesPlugin($this->name) && forge_check_perm('scm', $project->getID(), 'read')) {
56 $result = db_query_params('SELECT sum(commits) AS commits, sum(adds) AS adds FROM stats_cvs_group WHERE group_id=$1',
57 array ($project->getID())) ;
58 $commit_num = db_result($result,0,'commits');
59 $add_num = db_result($result,0,'adds');
66 echo ' (CVS: '.sprintf(_('<strong>%1$s</strong> commits, <strong>%2$s</strong> adds'), number_format($commit_num, 0), number_format($add_num, 0)).")";
70 function getBlurb () {
72 . sprintf(_('Documentation for %1$s is available at <a href="%2$s">%2$s</a>.'),
74 'http://cvsbook.red-bean.com/')
78 function getInstructionsForAnon ($project) {
79 $cvsrootend = $this->getBoxForProject($project).':'.forge_get_config('repos_path', 'scmcvs').'/'.$project->getUnixName();
80 $b = '<h2>' . _('Anonymous CVS Access') . '</h2>';
82 $b .= _('This project\'s CVS repository can be checked out through anonymous (pserver) CVS with the following instruction set. The module you wish to check out must be specified as the <i>modulename</i>. When prompted for a password for <i>anonymous</i>, simply press the Enter key.');
85 <tt>cvs -d :pserver:anonymous@' . $cvsrootend.' login</tt><br/>
86 <tt>cvs -d :pserver:anonymous@' . $cvsrootend.' checkout <em>'._('modulename').'</em></tt>
92 function getInstructionsForRW ($project) {
93 $cvsrootend = $this->getBoxForProject($project).':'.forge_get_config('repos_path', 'scmcvs').'/'.$project->getUnixName();
94 if (session_loggedin()) {
95 $u = user_get_object(user_getid()) ;
96 $d = $u->getUnixName() ;
98 $b .= sprintf(_('Developer %s Access via SSH'), 'CVS');
101 $b .= sprintf(_('Only project developers can access the %s tree via this method.'), 'CVS');
103 $b .= _('SSH must be installed on your client machine.');
105 $b .= _('Substitute <i>developername</i> with the proper values.');
107 $b .= _('Enter your site password when prompted.');
110 <tt>export CVS_RSH=ssh</tt><br/>
111 <tt>cvs -d :ext:'.$d.'@'.$cvsrootend.' checkout <em>'._('modulename').'</em></tt>
115 $b .= sprintf(_('Developer %s Access via SSH'), 'CVS');
118 $b .= sprintf(_('Only project developers can access the %s tree via this method.'), 'CVS');
120 $b .= _('SSH must be installed on your client machine.');
122 $b .= _('Substitute <i>developername</i> with the proper values.');
124 $b .= _('Enter your site password when prompted.');
127 <tt>export CVS_RSH=ssh</tt><br/>
128 <tt>cvs -d :ext:<em>'._('developername').'</em>@'.$cvsrootend.' checkout <em>'._('modulename').'</em></tt>
134 function getSnapshotPara ($project) {
137 $filename = $project->getUnixName().'-scm-latest.tar'.util_get_compressed_file_extension();
138 if (file_exists(forge_get_config('scm_snapshots_path').'/'.$filename)) {
140 $b .= util_make_link ("/snapshots.php?group_id=".$project->getID(),
141 _('Download the nightly snapshot')
148 function getBrowserLinkBlock ($project) {
150 $b = $HTML->boxMiddle(sprintf(_('%s Repository Browser'), 'CVS'));
152 $b .= sprintf(_("Browsing the %s tree gives you a view into the current status of this project's code."), 'CVS');
154 $b .= _('You may also view the complete histories of any file in the repository.');
157 $b .= util_make_link ("/scm/browser.php?group_id=".$project->getID(),
158 sprintf(_('Browse %s Repository'), 'CVS')
164 function getStatsBlock ($project) {
168 $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',
169 array ($project->getID()));
171 if (db_numrows($result) > 0) {
172 $b .= $HTML->boxMiddle(_('Repository Statistics'));
174 $tableHeaders = array(
179 $b .= $HTML->listTableTop($tableHeaders);
182 $total = array('adds' => 0, 'commits' => 0);
184 while($data = db_fetch_array($result)) {
185 $b .= '<tr '. $HTML->boxGetAltRowStyle($i) .'>';
186 $b .= '<td class="halfwidth">' ;
187 $b .= util_make_link_u ($data['user_name'], $data['user_id'], $data['realname']) ;
188 $b .= '</td><td class="onequarterwidth align-right">'.$data['adds']. '</td>'.
189 '<td class="onequarterwidth align-right">'.$data['commits'].'</td></tr>';
190 $total['adds'] += $data['adds'];
191 $total['commits'] += $data['commits'];
194 $b .= '<tr '. $HTML->boxGetAltRowStyle($i) .'>';
195 $b .= '<td class="halfwidth"><strong>'._('Total').':</strong></td>'.
196 '<td class="onequarterwidth align-right"><strong>'.$total['adds']. '</strong></td>'.
197 '<td class="onequarterwidth align-right"><strong>'.$total['commits'].'</strong></td>';
199 $b .= $HTML->listTableBottom();
205 function printBrowserPage ($params) {
206 $project = $this->checkParams ($params) ;
211 if ($project->usesPlugin ($this->name)) {
212 if ($this->browserDisplayable ($project)) {
213 session_redirect("/scm/viewvc.php/?root=".$project->getUnixName());
218 function createOrUpdateRepo ($params) {
219 $project = $this->checkParams ($params) ;
224 if (! $project->usesPlugin ($this->name)) {
228 $repo = forge_get_config('repos_path', 'scmcvs') . '/' . $project->getUnixName() ;
229 $locks_dir = forge_get_config('repos_path', 'scmcvs') . '/cvs-locks/' . $project->getUnixName() ;
231 $repo_exists = false ;
232 if (is_dir ($repo) && is_dir ("$repo/CVSROOT")) {
233 $repo_exists = true ;
236 if (forge_get_config('use_shell')) {
237 $unix_group = 'scm_' . $project->getUnixName() ;
239 $unix_group = forge_get_config ('apache_user') ;
243 if (!@mkdir($repo, 0700)) {
247 system ("cvs -d $repo init", $ret) ;
251 system ("mkdir -p $locks_dir") ;
252 system ("chgrp $unix_group $locks_dir") ;
253 system ("chmod 3777 $locks_dir") ;
255 if (forge_get_config('use_shell')) {
256 util_create_file_with_contents ("$repo/CVSROOT/config", "SystemAuth=no\nLockDir=$locks_dir\nUseNewInfoFmtStrings=yes\n");
257 if ($project->enableAnonSCM()) {
258 util_create_file_with_contents ("$repo/CVSROOT/readers", "anonymous\n");
259 util_create_file_with_contents ("$repo/CVSROOT/passwd", "anonymous:\n");
260 system ("chmod -R g+rwXs,o+rX-w $repo") ;
262 util_create_file_with_contents ("$repo/CVSROOT/readers", "\n");
263 util_create_file_with_contents ("$repo/CVSROOT/passwd", "\n");
264 system ("chmod -R g+rwXs,o-rwx $repo") ;
266 system ("chgrp -R $unix_group $repo") ;
268 $unix_user = forge_get_config ('apache_user') ;
269 system ("chmod -R g-rwx,o-rwx $repo") ;
270 system ("chown -R $unix_user:$unix_group $repo") ;
274 if (forge_get_config('use_shell')) {
275 if ($project->enableAnonSCM()) {
276 util_create_file_with_contents ("$repo/CVSROOT/readers", "anonymous\n");
277 util_create_file_with_contents ("$repo/CVSROOT/passwd", "anonymous:\n");
278 system ("chmod g+rwXs,o+rX-w $repo") ;
280 util_create_file_with_contents ("$repo/CVSROOT/readers", "\n");
281 util_create_file_with_contents ("$repo/CVSROOT/passwd", "\n");
282 system ("chmod g+rwXs,o-rwx $repo") ;
287 function gatherStats ($params) {
288 $project = $this->checkParams ($params) ;
293 if (! $project->usesPlugin ($this->name)) {
297 if ($params['mode'] == 'day') {
300 $year = $params ['year'] ;
301 $month = $params ['month'] ;
302 $day = $params ['day'] ;
303 $month_string = sprintf( "%04d%02d", $year, $month );
304 $day_begin = gmmktime( 0, 0, 0, $month, $day, $year);
305 $day_end = $day_begin + 86400;
307 $repo = forge_get_config('repos_path', 'scmcvs') . '/' . $project->getUnixName() ;
308 if (!is_dir ($repo) || !is_dir ("$repo/CVSROOT")) {
309 echo "No repository $repo\n" ;
317 $usr_commit = array();
320 $hist_file_path = $repo.'/CVSROOT/history';
321 if (!file_exists($hist_file_path)
322 || !is_readable($hist_file_path)
323 || filesize($hist_file_path) == 0) {
324 // echo "No history file\n" ;
329 $hist_file = fopen( $hist_file_path, 'r' );
330 if ( ! $hist_file ) {
331 echo "Unreadable history\n" ;
336 // cleaning stats_cvs_* table for the current day
337 $res = db_query_params ('DELETE FROM stats_cvs_group WHERE month=$1 AND day=$2 AND group_id=$3',
338 array ($month_string,
340 $project->getID())) ;
342 echo "Error while cleaning stats_cvs_group\n" ;
347 $res = db_query_params ('DELETE FROM stats_cvs_user WHERE month=$1 AND day=$2 AND group_id=$3',
348 array ($month_string,
350 $project->getID())) ;
352 echo "Error while cleaning stats_cvs_user\n" ;
357 // analyzing history file
358 while (!feof($hist_file)) {
359 $hist_line = fgets($hist_file, 1024);
360 if ( preg_match( '/^\s*$/', $hist_line ) ) {
363 list( $cvstime,$user,$curdir,$module,$rev,$file ) = explode( '|', $hist_line );
365 $type = substr($cvstime, 0, 1);
366 $time_parsed = hexdec( substr($cvstime, 1, 8) );
368 if ( ($time_parsed > $day_begin) && ($time_parsed < $day_end) ) {
369 if ( $type == 'M' ) {
371 if(!isset($usr_commit[$user])) $usr_commit[$user] = 0;
372 $usr_commit[$user]++;
373 } elseif ( $type == 'A' ) {
375 if(!isset($usr_add[$user])) $usr_add[$user] = 0;
377 } elseif ( $type == 'O' || $type == 'E' ) {
379 // ignoring checkouts on a per-user
381 } elseif ( $time_parsed > $day_end ) {
385 fclose( $hist_file );
387 // inserting group results in stats_cvs_groups
388 if (!db_query_params ('INSERT INTO stats_cvs_group (month,day,group_id,checkouts,commits,adds) VALUES ($1,$2,$3,$4,$5,$6)',
389 array ($month_string,
395 echo "Error while inserting into stats_cvs_group\n" ;
400 // building the user list
401 $user_list = array_unique( array_merge( array_keys( $usr_add ), array_keys( $usr_commit ) ) );
403 foreach ( $user_list as $user ) {
404 // trying to get user id from user name
405 $u = user_get_object_by_name ($user) ;
407 $user_id = $u->getID();
412 if (!db_query_params ('INSERT INTO stats_cvs_user (month,day,group_id,user_id,commits,adds) VALUES ($1,$2,$3,$4,$5,$6)',
413 array ($month_string,
417 isset ($usr_commit[$user]) ? $usr_commit[$user] : 0,
418 isset ($usr_add[$user]) ? $usr_add[$user] : 0))) {
419 echo "Error while inserting into stats_cvs_user\n" ;
428 function generateSnapshots ($params) {
430 $project = $this->checkParams ($params) ;
435 $group_name = $project->getUnixName() ;
437 $snapshot = forge_get_config('scm_snapshots_path').'/'.$group_name.'-scm-latest.tar'.util_get_compressed_file_extension();
438 $tarball = forge_get_config('scm_tarballs_path').'/'.$group_name.'-scmroot.tar'.util_get_compressed_file_extension();
440 if (! $project->usesPlugin ($this->name)) {
444 if (! $project->enableAnonSCM()) {
445 if (file_exists($snapshot)) unlink ($snapshot) ;
446 if (file_exists($tarball)) unlink ($tarball) ;
450 $toprepo = forge_get_config('repos_path', 'scmcvs') ;
451 $repo = $toprepo . '/' . $project->getUnixName() ;
453 $repo_exists = false ;
454 if (is_dir ($repo) && is_dir ("$repo/CVSROOT")) {
455 $repo_exists = true ;
459 if (file_exists($snapshot)) unlink ($snapshot) ;
460 if (file_exists($tarball)) unlink ($tarball) ;
464 $tmp = trim (`mktemp -d`) ;
468 $today = date ('Y-m-d') ;
469 $dir = $project->getUnixName ()."-$today" ;
470 system ("mkdir -p $tmp/$dir") ;
471 system ("cd $tmp/$dir ; cvs -d $repo export -D now . > /dev/null 2>&1") ;
472 system ("tar cCf $tmp - $dir |".forge_get_config('compression_method')."> $tmp/snapshot") ;
473 chmod ("$tmp/snapshot", 0644) ;
474 copy ("$tmp/snapshot", $snapshot) ;
475 unlink ("$tmp/snapshot") ;
476 system ("rm -rf $tmp/$dir") ;
478 system ("tar cCf $toprepo - ".$project->getUnixName() ."|".forge_get_config('compression_method')."> $tmp/tarball") ;
479 chmod ("$tmp/tarball", 0644) ;
480 copy ("$tmp/tarball", $tarball) ;
481 unlink ("$tmp/tarball") ;
482 system ("rm -rf $tmp") ;
488 // c-file-style: "bsd"