2 /** FusionForge Subversion plugin
4 * Copyright 2003-2009, Roland Mas
5 * Copyright 2004, GForge, LLC
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 SVNPlugin extends SCMPlugin {
26 function SVNPlugin () {
29 $this->name = 'scmsvn';
31 $this->hooks[] = 'scm_browser_page';
32 $this->hooks[] = 'scm_update_repolist' ;
33 $this->hooks[] = 'scm_generate_snapshots' ;
34 $this->hooks[] = 'scm_gather_stats' ;
36 require_once $gfconfig.'plugins/scmsvn/config.php' ;
38 $this->default_svn_server = $default_svn_server ;
39 $this->use_ssh = $use_ssh;
40 $this->use_dav = $use_dav;
41 $this->use_ssl = $use_ssl;
42 if (isset ($svn_root)) {
43 $this->svn_root = $svn_root;
45 $this->svn_root = $GLOBALS['sys_chroot'].'/scmrepos/svn' ;
51 function getDefaultServer() {
52 return $this->default_svn_server ;
55 function printShortStats ($params) {
56 $project = $this->checkParams ($params) ;
61 if ($project->usesPlugin($this->name)) {
62 $result = db_query_params('SELECT sum(commits) AS commits, sum(adds) AS adds FROM stats_cvs_group WHERE group_id=$1',
63 array ($project->getID())) ;
64 $commit_num = db_result($result,0,'commits');
65 $add_num = db_result($result,0,'adds');
72 echo ' (SVN: '.sprintf(_('<strong>%1$s</strong> commits, <strong>%2$s</strong> adds'), number_format($commit_num, 0), number_format($add_num, 0)).")";
76 function getBlurb () {
77 return _('<p>Documentation for Subversion (sometimes referred to as "SVN") is available <a href="http://svnbook.red-bean.com/">here</a>.</p>') ;
80 function getInstructionsForAnon ($project) {
81 $b = _('<p><b>Anonymous Subversion Access</b></p><p>This project\'s SVN repository can be checked out through anonymous access with the following command(s).</p>');
84 $b .= '<tt>svn checkout svn://'.$project->getSCMBox().$this->svn_root.'/'.$project->getUnixName().'</tt><br />';
87 $b .= '<tt>svn checkout --username anonsvn http'.(($this->use_ssl) ? 's' : '').'://' . $project->getSCMBox(). $this->svn_root .'/'. $project->getUnixName() .'</tt><br/><br/>';
88 $b .= _('The password is \'anonsvn\'').'<br/>';
94 function getInstructionsForRW ($project) {
96 if (session_loggedin()) {
97 $u =& user_get_object(user_getid()) ;
98 $d = $u->getUnixName() ;
100 $b .= _('<p><b>Developer Subversion Access via SSH</b></p><p>Only project developers can access the SVN tree via this method. SSH must be installed on your client machine. Enter your site password when prompted.</p>');
101 $b .= '<p><tt>svn checkout svn+ssh://'.$d.'@' . $project->getSCMBox() . $this->svn_root .'/'. $project->getUnixName().'</tt></p>' ;
103 if ($this->use_dav) {
104 $b .= _('<p><b>Developer Subversion Access via DAV</b></p><p>Only project developers can access the SVN tree via this method. Enter your site password when prompted.</p>');
105 $b .= '<p><tt>svn checkout --username '.$d.' http'.(($this->use_ssl) ? 's' : '').'://'. $project->getSCMBox() . $this->svn_root .'/'.$project->getUnixName().'</tt></p>' ;
108 if ($this->use_ssh) {
109 $b .= _('<p><b>Developer Subversion Access via SSH</b></p><p>Only project developers can access the SVN tree via this method. SSH must be installed on your client machine. Substitute <i>developername</i> with the proper values. Enter your site password when prompted.</p>');
110 $b .= '<p><tt>svn checkout svn+ssh://<i>'._('developername').'</i>@' . $project->getSCMBox() . $this->svn_root .'/'. $project->getUnixName().'</tt></p>' ;
112 if ($this->use_dav) {
113 $b .= _('<p><b>Developer Subversion Access via DAV</b></p><p>Only project developers can access the SVN tree via this method. Substitute <i>developername</i> with the proper values. Enter your site password when prompted.</p>');
114 $b .= '<p><tt>svn checkout --username <i>'._('developername').'</i> http'.(($this->use_ssl) ? 's' : '').'://'. $project->getSCMBox() . $this->svn_root .'/'.$project->getUnixName().'</tt></p>' ;
120 function getSnapshotPara ($project) {
124 function getBrowserLinkBlock ($project) {
126 $b = $HTML->boxMiddle(_('Subversion Repository Browser'));
127 $b .= _('<p>Browsing the Subversion 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>');
129 $b .= util_make_link ("/scm/browser.php?group_id=".$project->getID(),
130 _('Browse Subversion Repository')
136 function getStatsBlock ($project) {
140 $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',
141 array ($project->getID()));
143 if (db_numrows($result) > 0) {
144 $b .= $HTML->boxMiddle(_('Repository Statistics'));
146 $tableHeaders = array(
151 $b .= $HTML->listTableTop($tableHeaders);
154 $total = array('adds' => 0, 'commits' => 0);
156 while($data = db_fetch_array($result)) {
157 $b .= '<tr '. $HTML->boxGetAltRowStyle($i) .'>';
158 $b .= '<td width="50%">' ;
159 $b .= util_make_link_u ($data['user_name'], $data['user_id'], $data['realname']) ;
160 $b .= '</td><td width="25%" align="right">'.$data['adds']. '</td>'.
161 '<td width="25%" align="right">'.$data['commits'].'</td></tr>';
162 $total['adds'] += $data['adds'];
163 $total['commits'] += $data['commits'];
166 $b .= '<tr '. $HTML->boxGetAltRowStyle($i) .'>';
167 $b .= '<td width="50%"><strong>'._('Total').':</strong></td>'.
168 '<td width="25%" align="right"><strong>'.$total['adds']. '</strong></td>'.
169 '<td width="25%" align="right"><strong>'.$total['commits'].'</strong></td>';
171 $b .= $HTML->listTableBottom();
172 $b .= '<hr size="1" />';
178 function printBrowserPage ($params) {
181 $project = $this->checkParams ($params) ;
186 if ($project->usesPlugin ($this->name)) {
187 if ($this->browserDisplayable ($project)) {
188 print '<iframe src="'.util_make_url ("/scm/viewvc.php/?root=".$project->getUnixName()).'" frameborder="no" width=100% height=700></iframe>' ;
193 function createOrUpdateRepo ($params) {
194 $project = $this->checkParams ($params) ;
199 if (! $project->usesPlugin ($this->name)) {
203 $repo = $this->svn_root . '/' . $project->getUnixName() ;
204 $unix_group = 'scm_' . $project->getUnixName() ;
206 if (!is_dir ($repo) || !is_file ("$repo/format")) {
207 system ("svnadmin create $repo") ;
208 system ("svn mkdir -m'Init' file:///$repo/trunk file:///$repo/tags file:///$repo/branches") ;
209 system ("find $repo -type d | xargs chmod g+s") ;
212 if ($this->use_ssh) {
213 system ("chgrp -R $unix_group $repo") ;
214 if ($project->enableAnonSCM()) {
215 system ("chmod -R g+wX,o+rX-w $repo") ;
217 system ("chmod -R g+wX,o-rwx $repo") ;
220 $unix_user = $GLOBALS['sys_apache_user'];
221 system ("chown -R $unix_user:$unix_group $repo") ;
222 if ($project->enableAnonSCM()) {
223 system ("chmod -R g+wX,o+rX-w $repo") ;
225 system ("chmod -R g+wX,o-rwx $repo") ;
230 function updateRepositoryList ($params) {
231 $groups = $this->getGroups () ;
233 // Update WebDAV stuff
234 if (!$this->use_dav) {
239 $password_data = '' ;
241 $svnusers = array () ;
242 foreach ($groups as $project) {
243 $users = $project->getMembers () ;
244 $perm = $project->getPermission ($user) ;
245 if ($perm->isMember ('scm', 0)) {
246 $svnusers[$user->getID()] = $user ;
249 $access_data .= '[' . $project->getUnixName () . ":/]\n" ;
250 if ($perm->isMember ('scm', 1)) {
251 $access_data .= $user->getUnixName() . "= rw\n" ;
252 } elseif ($perm->isMember ('scm', 0)) {
253 $access_data .= $user->getUnixName() . "= r\n" ;
255 if ( $project->enableAnonSCM() ) {
256 $access_data .= "anonsvn= r\n" ;
257 $access_data .= "* = r\n" ;
260 $access_data .= "\n" ;
263 foreach ($svnusers as $user_id => $user) {
264 $password_data .= $user->getUnixName ().':'.$user->getMD5Passwd ()."\n" ;
266 $password_data .= "anonsvn:$apr1$Kfr69/..$J08mbyNpD81y42x7xlFDm.\n" ;
268 $fname = $sys_var_path.'/svnroot-access' ;
269 $f = fopen ($fname.'.new', 'w') ;
270 fwrite ($f, $password_data) ;
272 chmod ($fname.'.new', 0644) ;
273 rename ($fname.'.new', $fname) ;
275 $fname = $sys_var_path.'/svnroot-authfile' ;
276 $f = fopen ($fname.'.new', 'w') ;
277 fwrite ($f, $access_data) ;
279 chmod ($fname.'.new', 0644) ;
280 rename ($fname.'.new', $fname) ;
283 function gatherStats ($params) {
284 global $last_user, $last_time, $last_tag, $time_ok, $start_time, $end_time,
285 $adds, $deletes, $updates, $commits, $date_key,
286 $usr_adds, $usr_deletes, $usr_updates;
290 $project = $this->checkParams ($params) ;
295 if (! $project->usesPlugin ($this->name)) {
299 if ($params['mode'] == 'day') {
302 $year = $params ['year'] ;
303 $month = $params ['month'] ;
304 $day = $params ['day'] ;
305 $month_string = sprintf( "%04d%02d", $year, $month );
306 $start_time = gmmktime( 0, 0, 0, $month, $day, $year);
307 $end_time = $start_time + 86400;
311 $usr_adds = array () ;
312 $usr_updates = array () ;
314 $repo = $this->svn_root . '/' . $project->getUnixName() ;
315 if (!is_dir ($repo) || !is_file ("$repo/format")) {
316 echo "No repository\n" ;
321 $d1 = date ('Y-m-d', $start_time - 150000) ;
322 $d2 = date ('Y-m-d', $end_time + 150000) ;
324 $pipe = popen ("svn log file://$repo --xml -v -q -r '".'{'.$d2.'}:{'.$d1.'}'."' 2> /dev/null", 'r' ) ;
326 // cleaning stats_cvs_* table for the current day
327 $res = db_query_params ('DELETE FROM stats_cvs_group WHERE month=$1 AND day=$2 AND group_id=$3',
328 array ($month_string,
330 $project->getID())) ;
332 echo "Error while cleaning stats_cvs_group\n" ;
337 $res = db_query_params ('DELETE FROM stats_cvs_user WHERE month=$1 AND day=$2 AND group_id=$3',
338 array ($month_string,
340 $project->getID())) ;
342 echo "Error while cleaning stats_cvs_user\n" ;
347 $xml_parser = xml_parser_create();
348 xml_set_element_handler($xml_parser, "SVNPluginStartElement", "SVNPluginEndElement");
349 xml_set_character_data_handler($xml_parser, "SVNPluginCharData");
351 // Analyzing history stream
352 while (!feof($pipe) &&
353 $data = fgets ($pipe, 4096)) {
355 if (!xml_parse ($xml_parser, $data, feof ($pipe))) {
356 debug("Unable to parse XML with error " .
357 xml_error_string(xml_get_error_code($xml_parser)) .
359 xml_get_current_line_number($xml_parser));
365 if (!$time_ok && $last_time && $last_time > $end_time) {
370 xml_parser_free ($xml_parser);
372 // inserting group results in stats_cvs_groups
373 if ($updates > 0 || $adds > 0) {
374 if (!db_query_params ('INSERT INTO stats_cvs_group (month,day,group_id,checkouts,commits,adds) VALUES ($1,$2,$3,$4,$5,$6)',
375 array ($month_string,
381 echo "Error while inserting into stats_cvs_group\n" ;
387 // building the user list
388 $user_list = array_unique( array_merge( array_keys( $usr_adds ), array_keys( $usr_updates ) ) );
390 foreach ( $user_list as $user ) {
391 // trying to get user id from user name
392 $u = &user_get_object_by_name ($user) ;
394 $user_id = $u->getID();
399 $uu = $usr_updates[$user] ? $usr_updates[$user] : 0 ;
400 $ua = $usr_adds[$user] ? $usr_adds[$user] : 0 ;
401 if ($uu > 0 || $ua > 0) {
402 if (!db_query_params ('INSERT INTO stats_cvs_user (month,day,group_id,user_id,commits,adds) VALUES ($1,$2,$3,$4,$5,$6)',
403 array ($month_string,
409 echo "Error while inserting into stats_cvs_user\n" ;
419 function generateSnapshots ($params) {
420 global $sys_scm_snapshots_path ;
421 global $sys_scm_tarballs_path ;
423 $project = $this->checkParams ($params) ;
428 $group_name = $project->getUnixName() ;
430 $snapshot = $sys_scm_snapshots_path.'/'.$group_name.'-scm-latest.tar.gz';
431 $tarball = $sys_scm_tarballs_path.'/'.$group_name.'-scmroot.tar.gz';
433 if (! $project->usesPlugin ($this->name)) {
437 if (! $project->enableAnonSCM()) {
443 $toprepo = $this->svn_root ;
444 $repo = $toprepo . '/' . $project->getUnixName() ;
446 if (!is_dir ($repo) || !is_file ("$repo/format")) {
452 $tmp = trim (`mktemp -d`) ;
456 $today = date ('Y-m-d') ;
457 $dir = $project->getUnixName ()."-$today" ;
458 system ("mkdir -p $tmp") ;
460 system ("svn ls file://$repo/trunk", $code) ;
462 system ("cd $tmp ; svn checkout file://$repo/trunk $dir > /dev/null 2>&1") ;
463 system ("tar czCf $tmp $tmp/snapshot.tar.gz $dir") ;
464 chmod ("$tmp/snapshot.tar.gz", 0644) ;
465 copy ("$tmp/snapshot.tar.gz", $snapshot) ;
466 unlink ("$tmp/snapshot.tar.gz") ;
467 system ("rm -rf $tmp/$dir") ;
472 system ("tar czCf $toprepo $tmp/tarball.tar.gz " . $project->getUnixName()) ;
473 chmod ("$tmp/tarball.tar.gz", 0644) ;
474 copy ("$tmp/tarball.tar.gz", $tarball) ;
475 unlink ("$tmp/tarball.tar.gz") ;
476 system ("rm -rf $tmp") ;
480 // End of class, helper functions now
482 function SVNPluginCharData ($parser, $chars) {
483 global $last_tag, $last_user, $last_time, $start_time, $end_time,
484 $time_ok, $user_list;
487 $last_user = ereg_replace ('[^a-z0-9_-]', '',
488 strtolower (trim ($chars))) ;
491 $chars = preg_replace('/T(\d\d:\d\d:\d\d)\.\d+Z?$/', ' ${1}', $chars);
492 $last_time = strtotime($chars);
493 if ($start_time <= $last_time && $last_time < $end_time) {
502 function SVNPluginStartElement($parser, $name, $attrs) {
503 global $last_user, $last_time, $last_tag, $time_ok,
504 $adds, $updates, $usr_adds, $usr_updates;
508 // Make sure we clean up before doing a new log entry
514 if ($attrs['ACTION'] == "M") {
517 $usr_updates[$last_user]++;
519 } elseif ($attrs['ACTION'] == "A") {
522 $usr_adds[$last_user]++;
530 function SVNPluginEndElement ($parser, $name) {
531 global $time_ok, $last_tag, $commits;
532 if ($name == "LOGENTRY" && $time_ok) {
540 // c-file-style: "bsd"