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 () {
71 return '<p>' . _('CVS documentation is available <a href="http://cvsbook.red-bean.com/">here</a>.') . '</p>';
74 function getInstructionsForAnon ($project) {
75 $cvsrootend = $this->getBoxForProject($project).':'.forge_get_config('repos_path', 'scmcvs').'/'.$project->getUnixName();
76 $b = '<h2>' . _('Anonymous CVS Access') . '</h2>';
78 $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.');
81 <tt>cvs -d :pserver:anonymous@' . $cvsrootend.' login</tt><br/>
82 <tt>cvs -d :pserver:anonymous@' . $cvsrootend.' checkout <em>'._('modulename').'</em></tt>
88 function getInstructionsForRW ($project) {
89 $cvsrootend = $this->getBoxForProject($project).':'.forge_get_config('repos_path', 'scmcvs').'/'.$project->getUnixName();
90 if (session_loggedin()) {
91 $u =& user_get_object(user_getid()) ;
92 $d = $u->getUnixName() ;
93 $b = '<h2>' . _('Developer CVS Access via SSH') . '</h2>';
95 $b .= _('Only project developers can access the CVS tree via this method. SSH must be installed on your client machine. Substitute <i>modulename</i> with the proper values. Enter your site password when prompted.');
98 <tt>export CVS_RSH=ssh</tt><br/>
99 <tt>cvs -d :ext:'.$d.'@'.$cvsrootend.' checkout <em>'._('modulename').'</em></tt>
102 $b = '<h2>' . _('Developer CVS Access via SSH') . '</h2>';
104 $b .= _('Only project developers can access the CVS tree via this method. SSH must be installed on your client machine. Substitute <i>modulename</i> and <i>developername</i> with the proper values. Enter your site password when prompted.');
107 <tt>export CVS_RSH=ssh</tt><br/>
108 <tt>cvs -d :ext:<em>'._('developername').'</em>@'.$cvsrootend.' checkout <em>'._('modulename').'</em></tt>
114 function getSnapshotPara ($project) {
117 $filename = $project->getUnixName().'-scm-latest.tar'.util_get_compressed_file_extension();
118 if (file_exists(forge_get_config('scm_snapshots_path').'/'.$filename)) {
120 $b .= util_make_link ("/snapshots.php?group_id=".$project->getID(),
121 _('Download the nightly snapshot')
128 function getBrowserLinkBlock ($project) {
130 $b = $HTML->boxMiddle(_('CVS Repository Browser'));
132 $b .= _('Browsing the CVS 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.');
135 $b .= util_make_link ("/scm/browser.php?group_id=".$project->getID(),
136 _('Browse CVS Repository')
142 function getStatsBlock ($project) {
146 $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',
147 array ($project->getID()));
149 if (db_numrows($result) > 0) {
150 $b .= $HTML->boxMiddle(_('Repository Statistics'));
152 $tableHeaders = array(
157 $b .= $HTML->listTableTop($tableHeaders);
160 $total = array('adds' => 0, 'commits' => 0);
162 while($data = db_fetch_array($result)) {
163 $b .= '<tr '. $HTML->boxGetAltRowStyle($i) .'>';
164 $b .= '<td class="halfwidth">' ;
165 $b .= util_make_link_u ($data['user_name'], $data['user_id'], $data['realname']) ;
166 $b .= '</td><td class="onequarterwidth align-right">'.$data['adds']. '</td>'.
167 '<td class="onequarterwidth align-right">'.$data['commits'].'</td></tr>';
168 $total['adds'] += $data['adds'];
169 $total['commits'] += $data['commits'];
172 $b .= '<tr '. $HTML->boxGetAltRowStyle($i) .'>';
173 $b .= '<td class="halfwidth"><strong>'._('Total').':</strong></td>'.
174 '<td class="onequarterwidth align-right"><strong>'.$total['adds']. '</strong></td>'.
175 '<td class="onequarterwidth align-right"><strong>'.$total['commits'].'</strong></td>';
177 $b .= $HTML->listTableBottom();
183 function printBrowserPage ($params) {
186 $project = $this->checkParams ($params) ;
191 if ($project->usesPlugin ($this->name)) {
192 if ($this->browserDisplayable ($project)) {
193 session_redirect("/scm/viewvc.php/?root=".$project->getUnixName());
198 function createOrUpdateRepo ($params) {
199 $project = $this->checkParams ($params) ;
204 if (! $project->usesPlugin ($this->name)) {
208 $repo = forge_get_config('repos_path', 'scmcvs') . '/' . $project->getUnixName() ;
209 $locks_dir = forge_get_config('repos_path', 'scmcvs') . '/cvs-locks/' . $project->getUnixName() ;
211 $repo_exists = false ;
212 if (is_dir ($repo) && is_dir ("$repo/CVSROOT")) {
213 $repo_exists = true ;
216 if (forge_get_config('use_shell')) {
217 $unix_group = 'scm_' . $project->getUnixName() ;
219 $unix_group = forge_get_config ('apache_user') ;
223 if (!mkdir($repo, 0700)) {
227 system ("cvs -d $repo init", $ret) ;
231 system ("mkdir -p $locks_dir") ;
232 system ("chgrp $unix_group $locks_dir") ;
233 system ("chmod 3777 $locks_dir") ;
235 if (forge_get_config('use_shell')) {
236 util_create_file_with_contents ("$repo/CVSROOT/config", "SystemAuth=no\nLockDir=$locks_dir\nUseNewInfoFmtStrings=yes\n");
237 if ($project->enableAnonSCM()) {
238 util_create_file_with_contents ("$repo/CVSROOT/readers", "anonymous\n");
239 util_create_file_with_contents ("$repo/CVSROOT/passwd", "anonymous:\n");
240 system ("chmod -R g+wXs,o+rX-w $repo") ;
242 util_create_file_with_contents ("$repo/CVSROOT/readers", "\n");
243 util_create_file_with_contents ("$repo/CVSROOT/passwd", "\n");
244 system ("chmod -R g+wXs,o-rwx $repo") ;
246 system ("chgrp -R $unix_group $repo") ;
248 $unix_user = forge_get_config ('apache_user') ;
249 system ("chmod -R g-rwx,o-rwx $repo") ;
250 system ("chown -R $unix_user:$unix_group $repo") ;
254 if (forge_get_config('use_shell')) {
255 if ($project->enableAnonSCM()) {
256 util_create_file_with_contents ("$repo/CVSROOT/readers", "anonymous\n");
257 util_create_file_with_contents ("$repo/CVSROOT/passwd", "anonymous:\n");
258 system ("chmod g+wXs,o+rX-w $repo") ;
260 util_create_file_with_contents ("$repo/CVSROOT/readers", "\n");
261 util_create_file_with_contents ("$repo/CVSROOT/passwd", "\n");
262 system ("chmod g+wXs,o-rwx $repo") ;
267 function gatherStats ($params) {
268 $project = $this->checkParams ($params) ;
273 if (! $project->usesPlugin ($this->name)) {
277 if ($params['mode'] == 'day') {
280 $year = $params ['year'] ;
281 $month = $params ['month'] ;
282 $day = $params ['day'] ;
283 $month_string = sprintf( "%04d%02d", $year, $month );
284 $day_begin = gmmktime( 0, 0, 0, $month, $day, $year);
285 $day_end = $day_begin + 86400;
287 $repo = forge_get_config('repos_path', 'scmcvs') . '/' . $project->getUnixName() ;
288 if (!is_dir ($repo) || !is_dir ("$repo/CVSROOT")) {
289 echo "No repository\n" ;
297 $usr_commit = array();
300 $hist_file_path = $repo.'/CVSROOT/history';
301 if (!file_exists($hist_file_path)
302 || !is_readable($hist_file_path)
303 || filesize($hist_file_path) == 0) {
304 // echo "No history file\n" ;
309 $hist_file =& fopen( $hist_file_path, 'r' );
310 if ( ! $hist_file ) {
311 echo "Unreadable history\n" ;
316 // cleaning stats_cvs_* table for the current day
317 $res = db_query_params ('DELETE FROM stats_cvs_group WHERE month=$1 AND day=$2 AND group_id=$3',
318 array ($month_string,
320 $project->getID())) ;
322 echo "Error while cleaning stats_cvs_group\n" ;
327 $res = db_query_params ('DELETE FROM stats_cvs_user WHERE month=$1 AND day=$2 AND group_id=$3',
328 array ($month_string,
330 $project->getID())) ;
332 echo "Error while cleaning stats_cvs_user\n" ;
338 // analyzing history file
339 while (!feof($hist_file)) {
340 $hist_line = fgets($hist_file, 1024);
341 if ( preg_match( '/^\s*$/', $hist_line ) ) {
344 list( $cvstime,$user,$curdir,$module,$rev,$file ) = explode( '|', $hist_line );
346 $type = substr($cvstime, 0, 1);
347 $time_parsed = hexdec( substr($cvstime, 1, 8) );
349 if ( ($time_parsed > $day_begin) && ($time_parsed < $day_end) ) {
350 if ( $type == 'M' ) {
352 if(!isset($usr_commit[$user])) $usr_commit[$user] = 0;
353 $usr_commit[$user]++;
354 } elseif ( $type == 'A' ) {
356 if(!isset($usr_add[$user])) $usr_add[$user] = 0;
358 } elseif ( $type == 'O' || $type == 'E' ) {
360 // ignoring checkouts on a per-user
362 } elseif ( $time_parsed > $day_end ) {
366 fclose( $hist_file );
368 // inserting group results in stats_cvs_groups
369 if (!db_query_params ('INSERT INTO stats_cvs_group (month,day,group_id,checkouts,commits,adds) VALUES ($1,$2,$3,$4,$5,$6)',
370 array ($month_string,
376 echo "Error while inserting into stats_cvs_group\n" ;
381 // building the user list
382 $user_list = array_unique( array_merge( array_keys( $usr_add ), array_keys( $usr_commit ) ) );
384 foreach ( $user_list as $user ) {
385 // trying to get user id from user name
386 $u = &user_get_object_by_name ($user) ;
388 $user_id = $u->getID();
393 if (!db_query_params ('INSERT INTO stats_cvs_user (month,day,group_id,user_id,commits,adds) VALUES ($1,$2,$3,$4,$5,$6)',
394 array ($month_string,
398 $usr_commit[$user] ? $usr_commit[$user] : 0,
399 $usr_add[$user] ? $usr_add[$user] : 0))) {
400 echo "Error while inserting into stats_cvs_user\n" ;
409 function generateSnapshots ($params) {
413 $project = $this->checkParams ($params) ;
418 $group_name = $project->getUnixName() ;
420 $snapshot = forge_get_config('scm_snapshots_path').'/'.$group_name.'-scm-latest.tar'.util_get_compressed_file_extension();
421 $tarball = forge_get_config('scm_tarballs_path').'/'.$group_name.'-scmroot.tar'.util_get_compressed_file_extension();
423 if (! $project->usesPlugin ($this->name)) {
427 if (! $project->enableAnonSCM()) {
428 if (file_exists($snapshot)) unlink ($snapshot) ;
429 if (file_exists($tarball)) unlink ($tarball) ;
433 $toprepo = forge_get_config('repos_path', 'scmcvs') ;
434 $repo = $toprepo . '/' . $project->getUnixName() ;
436 $repo_exists = false ;
437 if (is_dir ($repo) && is_dir ("$repo/CVSROOT")) {
438 $repo_exists = true ;
442 if (file_exists($snapshot)) unlink ($snapshot) ;
443 if (file_exists($tarball)) unlink ($tarball) ;
447 $tmp = trim (`mktemp -d`) ;
451 $today = date ('Y-m-d') ;
452 $dir = $project->getUnixName ()."-$today" ;
453 system ("mkdir -p $tmp/$dir") ;
454 system ("cd $tmp/$dir ; cvs -d $repo export -D now . > /dev/null 2>&1") ;
455 system ("tar cCf $tmp - $dir |".forge_get_config('compression_method')."> $tmp/snapshot") ;
456 chmod ("$tmp/snapshot", 0644) ;
457 copy ("$tmp/snapshot", $snapshot) ;
458 unlink ("$tmp/snapshot") ;
459 system ("rm -rf $tmp/$dir") ;
461 system ("tar cCf $toprepo - ".$project->getUnixName() ."|".forge_get_config('compression_method')."> $tmp/tarball") ;
462 chmod ("$tmp/tarball", 0644) ;
463 copy ("$tmp/tarball", $tarball) ;
464 unlink ("$tmp/tarball") ;
465 system ("rm -rf $tmp") ;
471 // c-file-style: "bsd"