3 * FusionForge Subversion plugin
5 * Copyright 2003-2010, Roland Mas, Franck Villaume
6 * Copyright 2004, GForge, LLC
7 * Copyright 2010, Alain Peyrat <aljeux@free.fr>
8 * Copyright 2012-2014,2016-2018, Franck Villaume - TrivialDev
9 * Copyright 2013, French Ministry of National Education
11 * This file is part of FusionForge.
13 * FusionForge is free software; you can redistribute it and/or modify
14 * it under the terms of the GNU General Public License as published
15 * by the Free Software Foundation; either version 2 of the License,
16 * or (at your option) any later version.
18 * FusionForge is distributed in the hope that it will be useful, but
19 * WITHOUT ANY WARRANTY; without even the implied warranty of
20 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
21 * General Public License for more details.
23 * You should have received a copy of the GNU General Public License along
24 * with this program; if not, write to the Free Software Foundation, Inc.,
25 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
28 require_once $gfcommon.'include/plugins_utils.php';
30 forge_define_config_item('default_server', 'scmsvn', forge_get_config('scm_host'));
31 forge_define_config_item('repos_path', 'scmsvn', forge_get_config('chroot').'/scmrepos/svn');
32 forge_define_config_item('serve_path', 'scmsvn', forge_get_config('repos_path'));
33 forge_define_config_item('use_ssh', 'scmsvn', false);
34 forge_set_config_item_bool('use_ssh', 'scmsvn');
35 forge_define_config_item('use_dav', 'scmsvn', true);
36 forge_set_config_item_bool('use_dav', 'scmsvn');
37 forge_define_config_item('use_ssl', 'scmsvn', true);
38 forge_set_config_item_bool('use_ssl', 'scmsvn');
39 forge_define_config_item('ssh_port', 'core', 22);
41 class SVNPlugin extends SCMPlugin {
42 function __construct() {
43 parent::__construct();
44 $this->name = 'scmsvn';
45 $this->text = _('Subversion');
47 _("This plugin contains the Subversion subsystem of FusionForge. It allows
48 each FusionForge project to have its own Subversion repository, and gives
49 some control over it to the project's administrator.");
50 $this->svn_root_fs = forge_get_config('repos_path', $this->name);
51 $this->svn_root_dav = '/svn';
52 $this->_addHook('scm_admin_form');
53 $this->_addHook('scm_browser_page');
54 $this->_addHook('scm_update_repolist');
55 $this->_addHook('scm_regen_apache_auth');
56 $this->_addHook('scm_generate_snapshots');
57 $this->_addHook('scm_gather_stats');
58 $this->_addHook('scm_admin_form');
59 $this->_addHook('scm_add_repo');
60 $this->_addHook('scm_delete_repo');
61 $this->_addHook('get_scm_repo_list');
62 $this->_addHook('get_scm_repo_info');
63 $this->_addHook('parse_scm_repo_activities');
64 $this->_addHook('activity');
66 $this->provides['svn'] = true;
71 function getDefaultServer() {
72 return forge_get_config('default_server', 'scmsvn');
75 function printShortStats($params) {
76 $project = $this->checkParams($params);
81 if (forge_check_perm('scm', $project->getID(), 'read')) {
82 $result = db_query_params('SELECT sum(updates) AS updates, sum(adds) AS adds FROM stats_cvs_group WHERE group_id=$1',
83 array($project->getID()));
84 $commit_num = db_result($result,0,'updates');
85 $add_num = db_result($result,0,'adds');
92 $params['result'] .= ' (Subversion: '.sprintf(_('<strong>%1$s</strong> updates, <strong>%2$s</strong> adds'), number_format($commit_num, 0), number_format($add_num, 0)).")";
98 . sprintf(_('Documentation for %1$s is available at <a href="%2$s">%2$s</a>.'),
100 'http://svnbook.red-bean.com/')
104 function topModule($project, $repo_name) {
105 $scm_paths = array();
106 $scm_paths_file = forge_get_config('groupdir_prefix').'/'.$project->getUnixName().'/'.$repo_name.'_scmsvn_paths.txt';
107 if (file_exists($scm_paths_file)) {
108 $scm_paths = array_map("htmlentities", file($scm_paths_file));
110 // Check scm_path module presence in repository
112 $repo = 'file://' . forge_get_config('repos_path', $this->name).'/'.$repo_name;
113 foreach ($scm_paths as $scm_path) {
114 $scm_path = trim($scm_path);
115 if (strpos($scm_path, "!") === false) {
117 exec("svn info '$repo'", $res);
118 if (!preg_grep("/svn: warning: W170000: URL/", $res)) {
119 if (substr($scm_path, 0, 1) === '/') {
120 $modules[] = $scm_path;
122 $modules[] = '/'.$scm_path;
127 if (count($modules) == 0) {
133 function getInstructionsForAnon($project) {
134 $repo_list = array($project->getUnixName());
135 $result = db_query_params('SELECT repo_name FROM scm_secondary_repos WHERE group_id=$1 AND next_action = $2 AND plugin_id=$3 ORDER BY repo_name',
136 array($project->getID(), SCM_EXTRA_REPO_ACTION_UPDATE, $this->getID()));
137 $rows = db_numrows($result);
139 for ($i = 0; $i < $rows; $i++) {
140 $repo_list[] = db_result($result, $i, 'repo_name');
143 $b = html_e('h2', array(), _('Anonymous Access'));
144 $b .= html_e('p', array(),
145 ngettext("This project's SVN repository can be checked out through anonymous access with the following command(s).",
146 "This project's SVN repositories can be checked out through anonymous access with the following command(s).",
149 if (forge_get_config('use_ssh', 'scmsvn')) {
151 if (forge_get_config('ssh_port') != 22) {
152 $ssh_port = '--config-option="config:tunnels:ssh=ssh -p '.forge_get_config('ssh_port').'"';
154 $b .= html_e('h3', array(), _('via SVN'));
155 foreach ($repo_list as $repo_name) {
156 $modules = $this->topModule($project, $repo_name);
157 foreach ($modules as $module) {
158 $b .= html_e('kbd', array(), 'svn '.$ssh_port.' checkout svn://'.$this->getBoxForProject($project).$this->svn_root_fs.'/'.$repo_name.$module).html_e('br');
163 if (forge_get_config('use_dav', 'scmsvn')) {
164 $b .= html_e('h3', array(), _('via DAV'));
165 foreach ($repo_list as $repo_name) {
166 $modules = $this->topModule($project, $repo_name);
167 foreach ($modules as $module) {
168 $b .= html_e('kbd', array(), 'svn checkout http'.((forge_get_config('use_ssl', 'scmsvn')) ? 's' : '').'://'. $this->getBoxForProject($project). '/anonscm/svn/'.$repo_name.$module).html_e('br');
175 function getInstructionsForRW($project) {
176 $repo_list = array($project->getUnixName());
177 $result = db_query_params('SELECT repo_name FROM scm_secondary_repos WHERE group_id=$1 AND next_action = $2 AND plugin_id=$3 ORDER BY repo_name',
178 array($project->getID(), SCM_EXTRA_REPO_ACTION_UPDATE, $this->getID()));
179 $rows = db_numrows($result);
180 for ($i=0; $i<$rows; $i++) {
181 $repo_list[] = db_result($result, $i, 'repo_name');
184 $b = html_e('h2', array(), _('Developer Access'));
185 $b .= sprintf(_('Only project developers can access the %s tree via this method.'), 'Subversion');
186 $b .= '<div id="tabber-svn">';
188 if (forge_get_config('use_ssh', 'scmsvn')) {
189 $b .= '<li><a href="#tabber-svnssh">'._('via SSH').'</a></li>';
192 if (forge_get_config('use_dav', 'scmsvn')) {
193 $b .= '<li><a href="#tabber-svndav">'._('via DAV').'</a></li>';
197 if (session_loggedin()) {
198 $u = user_get_object(user_getid());
199 $d = $u->getUnixName();
200 if (forge_get_config('use_ssh', 'scmsvn')) {
201 $b .= '<div id="tabber-svnssh" class="tabbertab" >';
203 $b .= _('SSH must be installed on your client machine.');
205 $b .= _('Enter your site password when prompted.');
208 if (forge_get_config('ssh_port') != 22) {
209 $ssh_port = '--config-option="config:tunnels:ssh=ssh -p '.forge_get_config('ssh_port').'" ';
211 foreach ($repo_list as $repo_name) {
212 $modules = $this->topModule($project, $repo_name);
213 foreach ($modules as $module) {
214 if (forge_get_config('use_shell_limited')) {
215 $b .= html_e('kbd', array(), 'svn '.$ssh_port.'checkout svn+ssh://'.$d.'@'.$this->getBoxForProject($project).'/'.$repo_name.$module).html_e('br');
217 $b .= html_e('kbd', array(), 'svn '.$ssh_port.'checkout svn+ssh://'.$d.'@'.$this->getBoxForProject($project).$this->svn_root_fs.'/'.$repo_name.$module).html_e('br');
223 if (forge_get_config('use_dav', 'scmsvn')) {
224 $b .= '<div id="tabber-svndav" class="tabbertab" >';
226 $b .= _('Enter your site password when prompted.');
228 foreach ($repo_list as $repo_name) {
229 $modules = $this->topModule($project, $repo_name);
230 foreach ($modules as $module) {
231 $b .= html_e('kbd', array(), 'svn checkout --username '.$d.' http'.((forge_get_config('use_ssl', 'scmsvn')) ? 's' : '').'://'.$this->getBoxForProject($project).'/authscm/'.$d.'/svn/'.$repo_name.$module).html_e('br');
237 if (forge_get_config('use_ssh', 'scmsvn')) {
238 $b .= '<div id="tabber-svnssh" class="tabbertab" >';
240 $b .= _('SSH must be installed on your client machine.');
242 $b .= _('Substitute <em>developername</em> with the proper value.');
244 $b .= _('Enter your site password when prompted.');
247 if (forge_get_config('ssh_port') != 22) {
248 $ssh_port = '--config-option="config:tunnels:ssh=ssh -p '.forge_get_config('ssh_port').'" ';
250 foreach ($repo_list as $repo_name) {
251 $modules = $this->topModule($project, $repo_name);
252 foreach ($modules as $module) {
253 if (forge_get_config('use_shell_limited')) {
254 $b .= html_e('kbd', array(), 'svn '.$ssh_port.'checkout svn+ssh://<i>'._('developername').'</i>@'.$this->getBoxForProject($project).'/'.$repo_name.$module).html_e('br');
256 $b .= html_e('kbd', array(), 'svn '.$ssh_port.'checkout svn+ssh://<i>'._('developername').'</i>@'.$this->getBoxForProject($project).$this->svn_root_fs .'/'.$repo_name.$module).html_e('br');
262 if (forge_get_config('use_dav', 'scmsvn')) {
263 $b .= '<div id="tabber-svndav" class="tabbertab" >';
265 $b .= _('Substitute <em>developername</em> with the proper value.');
267 $b .= _('Enter your site password when prompted.');
269 foreach ($repo_list as $repo_name) {
270 $modules = $this->topModule($project, $repo_name);
271 foreach ($modules as $module) {
272 $b .= html_e('kbd', array(), 'svn checkout --username <i>'._('developername').'</i> http'.((forge_get_config('use_ssl', 'scmsvn')) ? 's' : '').'://'.$this->getBoxForProject($project).'/authscm/<i>'._('developername').'</i>/svn/'.$repo_name.$module).html_e('br');
282 function getSnapshotPara($project) {
284 $filename = $project->getUnixName().'-scm-latest.tar'.util_get_compressed_file_extension();
285 if (file_exists(forge_get_config('scm_snapshots_path').'/'.$filename)) {
286 $b .= html_e('p', array(), '['.util_make_link("/snapshots.php?group_id=".$project->getID(), _('Download the nightly snapshot')).']');
291 function getBrowserLinkBlock($project) {
292 $b = html_e('h2', array(), _('Subversion Repository Browser'));
293 $b .= html_e('p', array(),_("Browsing the Subversion tree gives you a view into the current status of this project's code.")
295 ._('You may also view the complete histories of any file in the repository.'));
296 $b .= html_e('p', array(), '['.util_make_link ("/scm/browser.php?group_id=".$project->getID().'&scm_plugin='.$this->name, sprintf(_('Browse %s Repository'), 'Subversion')).']');
298 $repo_list = $this->getRepositories($project, false);
299 foreach ($repo_list as $repo_name) {
300 $b .= '['.util_make_link('/scm/browser.php?group_id='.$project->getID().'&extra='.$repo_name.'&scm_plugin='.$this->name, _('Browse extra SVN repository')._(': ').$repo_name).']'.html_e('br');
305 function getStatsBlock($project) {
309 $result = db_query_params('SELECT u.realname, u.user_name, u.user_id, sum(updates) as updates, sum(adds) as adds, sum(adds+updates) as combined FROM stats_cvs_user s, users u WHERE group_id=$1 AND s.user_id=u.user_id AND (updates>0 OR adds >0) GROUP BY u.user_id, realname, user_name, u.user_id ORDER BY combined DESC, realname',
310 array($project->getID()));
312 if (db_numrows($result) > 0) {
313 $tableHeaders = array(
318 $b .= $HTML->listTableTop($tableHeaders, array(), '', 'repo-history-'.$this->name);
321 $total = array('adds' => 0, 'updates' => 0);
323 while($data = db_fetch_array($result)) {
325 $cells[] = array(util_display_user($data['user_name'], $data['user_id'], $data['realname']), 'class' => 'halfwidth');
326 $cells[] = array($data['adds'], 'class' => 'onequarterwidth align-right');
327 $cells[] = array($data['updates'], 'class' => 'onequarterwidth align-right');
328 $b .= $HTML->multiTableRow(array(), $cells);
329 $total['adds'] += $data['adds'];
330 $total['updates'] += $data['updates'];
334 $cells[] = array(html_e('strong', array(), _('Total')._(':')), 'class' => 'halfwidth');
335 $cells[] = array($total['adds'], 'class' => 'onequarterwidth align-right');
336 $cells[] = array($total['updates'], 'class' => 'onequarterwidth align-right');
337 $b .= $HTML->multiTableRow(array(), $cells);
338 $b .= $HTML->listTableBottom();
340 $b .= $HTML->warning_msg(_('No history yet.'));
346 function printBrowserPage($params) {
347 if ($params['scm_plugin'] != $this->name) {
352 $project = $this->checkParams($params);
356 if (isset($params['extra']) && !empty($params['extra']) && $params['extra'] != 'none') {
357 $iframe_src = '/scm/viewvc.php?root='.$params['extra'];
359 $iframe_src = '/scm/viewvc.php?root='.$project->getUnixName();
361 if ($params['commit']) {
362 $iframe_src .= '&view=rev&revision='.$params['commit'];
364 htmlIframe($iframe_src, array('id'=>'scmsvn_iframe'));
367 function createOrUpdateRepo($params) {
368 $project = $this->checkParams($params);
369 if (!$project) return false;
370 if (!$project->isActive()) return false;
372 $repo_prefix = forge_get_config('repos_path', 'scmsvn');
373 if (!is_dir($repo_prefix) && !mkdir($repo_prefix, 0755, true)) {
377 $repo = $repo_prefix.'/'.$project->getUnixName();
379 if (!is_dir ($repo) || !is_file ("$repo/format")) {
380 if (!mkdir($repo, 0700, true)) {
384 system ("svnadmin create $repo", $ret);
388 system ("sed -i '/enable-rep-sharing = false/s/^. //' $repo/db/fsfs.conf") ;
389 // dav/ and dav/activities.d directories are required by old svn clients (eg. svn 1.6.17 on ubuntu 12.04)
390 if (!is_dir ("$repo/dav")) {
393 if (!is_dir ("$repo/dav/activities.d")) {
394 mkdir("$repo/dav/activities.d");
396 system ("svn mkdir -m'Init' file:///$repo/trunk file:///$repo/tags file:///$repo/branches >/dev/null") ;
397 system ("find $repo -type d -print0 | xargs -r -0 chmod g+s") ;
398 // Allow read/write users to modify the SVN repository
399 $rw_unix_group = $project->getUnixName() . '_scmrw';
400 system("chgrp -R $rw_unix_group $repo");
401 // Allow read-only users to enter the (top-level) directory
402 $ro_unix_group = $project->getUnixName() . '_scmro';
403 system("chgrp $ro_unix_group $repo");
404 // open permissions to allow switching private/public easily
405 // see after to restrict the top-level directory
406 system ("chmod -R g+rwX,o+rX-w $repo") ;
409 if ($project->enableAnonSCM()) {
410 system("chmod g+rX-w,o+rX-w $repo") ;
412 system("chmod g+rX-w,o-rwx $repo") ;
415 // Create project-wide secondary repositories
416 $result = db_query_params('SELECT repo_name FROM scm_secondary_repos WHERE group_id = $1 AND next_action = $2 AND plugin_id = $3',
417 array($project->getID(),
418 SCM_EXTRA_REPO_ACTION_UPDATE,
420 $rows = db_numrows($result);
421 for ($i = 0; $i < $rows; $i++) {
422 $repo_name = db_result($result, $i, 'repo_name');
423 $repo = $repo_prefix.'/'.$repo_name;
424 if (!is_dir($repo) || !is_file("$repo/format")) {
425 if (!mkdir($repo, 0700, true)) {
429 system ("svnadmin create $repo", $ret);
433 system ("sed -i '/enable-rep-sharing = false/s/^. //' $repo/db/fsfs.conf") ;
434 // dav/ directory is required by old svn clients (eg. svn 1.6.17 on ubuntu 12.04)
435 if (!is_dir ("$repo/dav")) {
438 system ("svn mkdir -m'Init' file:///$repo/trunk file:///$repo/tags file:///$repo/branches >/dev/null") ;
439 system ("find $repo -type d -print0 | xargs -r -0 chmod g+s") ;
440 // Allow read/write users to modify the SVN repository
441 $rw_unix_group = $project->getUnixName() . '_scmrw';
442 system("chgrp -R $rw_unix_group $repo");
443 // Allow read-only users to enter the (top-level) directory
444 $ro_unix_group = $project->getUnixName() . '_scmro';
445 system("chgrp $ro_unix_group $repo");
446 // open permissions to allow switching private/public easily
447 // see after to restrict the top-level directory
448 system ("chmod -R g+rwX,o+rX-w $repo") ;
450 if ($project->enableAnonSCM()) {
451 system("chmod g+rX-w,o+rX-w $repo") ;
453 system("chmod g+rX-w,o-rwx $repo") ;
457 // Delete project-wide secondary repositories
458 $result = db_query_params('SELECT repo_name FROM scm_secondary_repos WHERE group_id=$1 AND next_action = $2 AND plugin_id=$3',
459 array($project->getID(),
460 SCM_EXTRA_REPO_ACTION_DELETE,
462 $rows = db_numrows ($result);
463 for ($i=0; $i<$rows; $i++) {
464 $repo_name = db_result($result, $i, 'repo_name');
465 $repodir = $repo_prefix.'/'.$repo_name;
466 if (util_is_valid_repository_name($repo_name)) {
467 system("rm -rf $repodir");
469 db_query_params ('DELETE FROM scm_secondary_repos WHERE group_id=$1 AND repo_name=$2 AND next_action = $3 AND plugin_id=$4',
470 array($project->getID(),
472 SCM_EXTRA_REPO_ACTION_DELETE,
475 $this->regenApacheAuth($params);
478 function updateRepositoryList(&$params) {
481 function regenApacheAuth(&$params) {
482 # Enable /authscm/$user/svn URLs
483 $config_fname = forge_get_config('data_path').'/scmsvn-auth.inc';
484 $config_f = fopen($config_fname.'.new', 'w');
486 $res = db_query_params("SELECT login FROM nss_passwd WHERE status=$1", array('A'));
487 while ($arr = db_fetch_array($res)) {
488 fwrite($config_f, 'Use ScmsvnUser '.$arr['login']."\n");
492 chmod($config_fname.'.new', 0644);
493 rename($config_fname.'.new', $config_fname);
496 function gatherStats($params) {
497 global $last_user, $last_time, $last_tag, $time_ok, $start_time, $end_time,
498 $adds, $deletes, $updates, $commits, $date_key,
499 $usr_adds, $usr_deletes, $usr_updates, $usr_commits;
503 $project = $this->checkParams($params);
508 if ($params['mode'] == 'day') {
511 $year = $params['year'];
512 $month = $params['month'];
513 $day = $params['day'];
514 $month_string = sprintf("%04d%02d", $year, $month);
515 $start_time = gmmktime(0, 0, 0, $month, $day, $year);
516 $end_time = $start_time + 86400;
524 $usr_updates = array();
525 $usr_deletes = array();
526 $usr_commits = array();
528 $repo = forge_get_config('repos_path', 'scmsvn').'/'.$project->getUnixName().'/'.$project->getUnixName();
529 if (!is_dir ($repo) || !is_file ("$repo/format")) {
534 $d1 = date('Y-m-d', $start_time - 150000);
535 $d2 = date('Y-m-d', $end_time + 150000);
537 $pipe = popen ("svn log file://$repo --xml -v -q -r '".'{'.$d2.'}:{'.$d1.'}'."' 2> /dev/null", 'r' ) ;
539 // cleaning stats_cvs_* table for the current day
540 $res = db_query_params('DELETE FROM stats_cvs_group WHERE month = $1 AND day = $2 AND group_id = $3 AND reponame = $4',
544 $project->getUnixName()));
546 echo "Error while cleaning stats_cvs_group\n";
551 $res = db_query_params('DELETE FROM stats_cvs_user WHERE month = $1 AND day = $2 AND group_id = $3 AND reponame = $4',
555 $project->getUnixName()));
557 echo "Error while cleaning stats_cvs_user\n" ;
562 $xml_parser = xml_parser_create();
563 xml_set_element_handler($xml_parser, "SVNPluginStartElement", "SVNPluginEndElement");
564 xml_set_character_data_handler($xml_parser, "SVNPluginCharData");
566 // Analyzing history stream
567 while (!feof($pipe) &&
568 $data = fgets ($pipe, 4096)) {
569 if (!xml_parse ($xml_parser, $data, feof ($pipe))) {
570 $this->setError("Unable to parse XML with error " .
571 xml_error_string(xml_get_error_code($xml_parser)) .
573 xml_get_current_line_number($xml_parser));
580 xml_parser_free($xml_parser);
582 // inserting group results in stats_cvs_groups
583 if ($updates > 0 || $adds > 0 || $deletes > 0 || $commits > 0) {
584 if (!db_query_params('INSERT INTO stats_cvs_group (month, day, group_id, checkouts, commits, adds, updates, deletes, reponame)
585 VALUES ($1, $2, $3, $4, $5, $6, $7, $8)',
594 $project->getUnixName()))) {
595 echo "Error while inserting into stats_cvs_group\n";
601 // building the user list
602 $user_list = array_unique( array_merge( array_keys( $usr_adds ), array_keys( $usr_updates ), array_keys( $usr_deletes ), array_keys( $usr_commits )) );
604 foreach ($user_list as $user) {
605 // Trying to get user id from user name
606 $u = user_get_object_by_name($user);
608 $user_id = $u->getID();
613 $uc = isset($usr_commits[$user]) ? $usr_commits[$user] : 0 ;
614 $uu = isset($usr_updates[$user]) ? $usr_updates[$user] : 0 ;
615 $ua = isset($usr_adds[$user]) ? $usr_adds[$user] : 0 ;
616 $ud = isset($usr_deletes[$user]) ? $usr_deletes[$user] : 0 ;
617 if ($uu > 0 || $ua > 0 || $uc > 0 || $ud > 0) {
618 if (!db_query_params('INSERT INTO stats_cvs_user (month, day, group_id, user_id, commits, adds, updates, deletes, reponame)
619 VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)',
620 array ($month_string,
628 $project->getUnixName()))) {
629 echo "Error while inserting into stats_cvs_user\n";
639 function generateSnapshots($params) {
640 $us = forge_get_config('use_scm_snapshots') ;
641 $ut = forge_get_config('use_scm_tarballs') ;
646 $project = $this->checkParams($params);
651 $group_name = $project->getUnixName();
653 $snapshot = forge_get_config('scm_snapshots_path').'/'.$group_name.'-scm-latest.tar'.util_get_compressed_file_extension();
654 $tarball = forge_get_config('scm_tarballs_path').'/'.$group_name.'-scmroot.tar'.util_get_compressed_file_extension();
656 if (!$project->enableAnonSCM()) {
657 if (is_file($snapshot)) {
660 if (is_file($tarball)) {
666 $toprepo = forge_get_config('repos_path', 'scmsvn');
667 $repo = $toprepo.'/'.$group_name.'/'.$group_name;
669 if (!is_dir($repo) || !is_file ("$repo/format")) {
670 if (is_file($snapshot)) {
673 if (is_file($tarball)) {
679 $tmp = trim(`mktemp -d`);
684 $today = date('Y-m-d');
685 $dir = $project->getUnixName ()."-$today" ;
686 system("mkdir -p $tmp") ;
688 system ("svn ls file://$repo/trunk > /dev/null 2> /dev/null", $code) ;
691 system ("cd $tmp ; svn export file://$repo/trunk $dir > /dev/null 2>&1") ;
692 system ("tar cCf $tmp - $dir |".forge_get_config('compression_method')."> $tmp/snapshot") ;
693 chmod("$tmp/snapshot", 0644);
694 copy("$tmp/snapshot", $snapshot);
695 unlink("$tmp/snapshot");
696 system ("rm -rf $tmp/$dir") ;
698 if (is_file($snapshot)) {
705 system("tar cCf $toprepo - ".$project->getUnixName() ."|".forge_get_config('compression_method')."> $tmp/tarball") ;
706 chmod("$tmp/tarball", 0644);
707 copy("$tmp/tarball", $tarball);
708 unlink("$tmp/tarball");
709 system("rm -rf $tmp");
713 function activity($params) {
714 global $last_user, $last_time, $last_tag, $time_ok, $start_time, $end_time,
715 $adds, $deletes, $updates, $commits, $date_key,
716 $messages, $last_message, $times, $revisions, $users, $xml_parser;
724 $revisions = array();
726 $project = $this->checkParams($params);
730 if (isset($params['exclusive_area']) && ($params['exclusive_area'] != $this->name)) {
734 if (in_array('scmsvn', $params['show']) || (count($params['show']) < 1)) {
735 $start_time = $params['begin'];
736 $end_time = $params['end'];
738 $xml_parser = xml_parser_create();
739 xml_set_element_handler($xml_parser, "SVNPluginStartElement", "SVNPluginEndElement");
740 xml_set_character_data_handler($xml_parser, "SVNPluginCharData");
742 // Grab&parse commit log
743 $protocol = forge_get_config('use_ssl', 'scmsvn') ? 'https://' : 'http://';
744 if ($project->enableAnonSCM()) {
745 $server_script = '/anonscm/svnlog';
747 $u = session_get_user();
748 if ($u && !$u->isError()) {
749 $server_script = '/authscm/'.$u->getUnixName().'/svnlog';
754 $script_url = $protocol.$this->getBoxForProject($project)
756 .'?unix_group_name='.$project->getUnixName()
758 .'&begin='.$params['begin']
759 .'&end='.$params['end'];
761 curl_setopt($ch, CURLOPT_URL, $script_url);
762 curl_setopt($ch, CURLOPT_WRITEFUNCTION, 'curl2xml');
763 curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
764 curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
765 curl_setopt($ch, CURLOPT_COOKIE, @$_SERVER['HTTP_COOKIE']); // for session validation
766 curl_setopt($ch, CURLOPT_USERAGENT, $_SERVER['HTTP_USER_AGENT']); // for session validation
767 curl_setopt($ch, CURLOPT_HTTPHEADER,
768 array('X-Forwarded-For: '.$_SERVER['REMOTE_ADDR'])); // for session validation
769 $body = curl_exec($ch);
770 if ($body === false) {
771 $this->setError(curl_error($ch));
776 if (!xml_parse($xml_parser, '', true))
777 $this->setError('Unable to parse XML with error '
778 . xml_error_string(xml_get_error_code($xml_parser))
779 . ' on line ' . xml_get_current_line_number($xml_parser));
780 xml_parser_free($xml_parser);
782 if ($adds > 0 || $updates > 0 || $commits > 0 || $deletes > 0) {
784 foreach ($messages as $message) {
786 $result['section'] = 'scm';
787 $result['group_id'] = $project->getID();
788 $result['ref_id'] = 'browser.php?group_id='.$project->getID().'&scm_plugin='.$this->name;
789 $result['description'] = htmlspecialchars($message).' (r'.$revisions[$i].')';
790 $userObject = user_get_object_by_name($users[$i]);
791 if (is_a($userObject, 'FFUser')) {
792 $result['realname'] = util_display_user($userObject->getUnixName(), $userObject->getID(), $userObject->getRealName());
794 $result['realname'] = '';
796 $result['activity_date'] = $times[$i];
797 $result['subref_id'] = '&commit='.$revisions[$i];
798 $params['results'][] = $result;
803 if (!in_array($this->name, $params['ids']) && ($project->enableAnonSCM() || session_loggedin())) {
804 $params['ids'][] = $this->name;
805 $params['texts'][] = _('Subversion Commits');
810 // Get latest commits for inclusion in a widget
811 function getCommits($project, $user, $nbCommits) {
812 global $commits, $users, $adds, $updates, $messages, $times, $revisions, $deletes, $time_ok, $user_list, $last_message, $notimecheck, $xml_parser;
819 $revisions = array();
822 $user_list = array();
825 $revisionsArr = array();
826 if ($project->usesPlugin($this->name) && forge_check_perm('scm', $project->getID(), 'read')) {
827 $xml_parser = xml_parser_create();
828 xml_set_element_handler($xml_parser, "SVNPluginStartElement", "SVNPluginEndElement");
829 xml_set_character_data_handler($xml_parser, "SVNPluginCharData");
831 // Grab&parse commit log
832 $protocol = forge_get_config('use_ssl', 'scmsvn') ? 'https://' : 'http://';
833 $u = session_get_user();
834 if ($project->enableAnonSCM())
835 $server_script = '/anonscm/svnlog';
837 $server_script = '/authscm/'.$u->getUnixName().'/svnlog';
839 $userunixname = $user->getUnixName();
840 $params = '&mode=latest_user&user_name='.$userunixname;
842 $params = '&mode=latest';
844 $script_url = $protocol.$this->getBoxForProject($project)
846 .'?unix_group_name='.$project->getUnixName()
848 .'&limit='.$nbCommits;
850 curl_setopt($ch, CURLOPT_URL, $script_url);
851 curl_setopt($ch, CURLOPT_WRITEFUNCTION, 'curl2xml');
852 curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
853 curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
854 curl_setopt($ch, CURLOPT_COOKIE, $_SERVER['HTTP_COOKIE']); // for session validation
855 curl_setopt($ch, CURLOPT_USERAGENT, $_SERVER['HTTP_USER_AGENT']); // for session validation
856 curl_setopt($ch, CURLOPT_HTTPHEADER,
857 array('X-Forwarded-For: '.$_SERVER['REMOTE_ADDR'])); // for session validation
858 $body = curl_exec($ch);
859 if ($body === false) {
860 $this->setError(curl_error($ch));
865 if (!xml_parse($xml_parser, '', true))
866 $this->setError('Unable to parse XML with error '
867 . xml_error_string(xml_get_error_code($xml_parser))
868 . ' on line ' . xml_get_current_line_number($xml_parser));
869 xml_parser_free($xml_parser);
871 if ($adds > 0 || $updates > 0 || $commits > 0 || $deletes > 0) {
873 foreach ($messages as $message) {
874 $revisionsArr[$i]['pluginName'] = 'scmsvn';
875 $revisionsArr[$i]['description'] = htmlspecialchars($message);
876 $revisionsArr[$i]['commit_id'] = $revisions[$i];
877 $revisionsArr[$i]['repo_name'] = $project->getUnixName();
878 $revisionsArr[$i]['date'] = $times[$i];
883 return $revisionsArr;
886 function scm_add_repo(&$params) {
887 $project = $this->checkParams($params);
892 if (!isset($params['repo_name'])) {
896 if ($params['repo_name'] == $project->getUnixName()) {
897 $params['error_msg'] = _('Cannot create a secondary repository with the same name as the primary');
901 if (!util_is_valid_repository_name($params['repo_name'])) {
902 $params['error_msg'] = _('This repository name is not valid');
906 $result = db_query_params('SELECT count(*) AS count FROM scm_secondary_repos WHERE repo_name = $1 AND plugin_id=$2',
907 array($params['repo_name'], $this->getID()));
909 $params['error_msg'] = db_error();
912 if (db_result($result, 0, 'count')) {
913 $params['error_msg'] = sprintf(_('A repository %s already exists'), $params['repo_name']);
918 if (isset($params['description'])) {
919 $description = $params['description'];
922 $description = "Subversion repository $params[repo_name] for project ".$project->getUnixName();
924 $result = db_query_params('INSERT INTO scm_secondary_repos (group_id, repo_name, description, clone_url, plugin_id) VALUES ($1, $2, $3, $4, $5)',
925 array($params['group_id'],
926 $params['repo_name'],
931 $params['error_msg'] = db_error();
935 plugin_hook ("scm_admin_update", $params);
939 function get_scm_repo_list(&$params) {
940 if (array_key_exists('group_name',$params)) {
941 $unix_group_name = $params['group_name'];
943 $unix_group_name = '';
945 $protocol = forge_get_config('use_ssl', 'scmsvn')? 'https' : 'http';
946 if (session_loggedin()) {
947 $u = user_get_object(user_getid());
948 $d = $u->getUnixName();
952 if ($unix_group_name) {
953 $res = db_query_params("SELECT unix_group_name, groups.group_id FROM groups
954 JOIN group_plugin ON (groups.group_id=group_plugin.group_id)
955 WHERE groups.status=$1 AND group_plugin.plugin_id=$2 AND groups.unix_group_name=$3
956 ORDER BY unix_group_name", array('A', $this->getID(),$unix_group_name));
958 $res = db_query_params("SELECT unix_group_name, groups.group_id FROM groups
959 JOIN group_plugin ON (groups.group_id=group_plugin.group_id)
960 WHERE groups.status=$1 AND group_plugin.plugin_id=$2
961 ORDER BY unix_group_name", array('A', $this->getID()));
963 while ($arr = db_fetch_array($res)) {
964 if (!forge_check_perm('scm', $arr['group_id'], 'read')) {
968 if (forge_get_config('use_dav', 'scmsvn')) {
969 $urls[] = $protocol.'://'.$this->getBoxForProject($project).'/anonscm/svn/'.$arr['unix_group_name'];
971 if (forge_get_config('use_ssh', 'scmsvn')) {
972 $urls[] = 'svn://'.$this->getBoxForProject($project).$this->svn_root_fs.'/'.$arr['unix_group_name'];
974 if (session_loggedin()) {
975 if (forge_get_config('use_dav', 'scmsvn')) {
976 $urls[] = $protocol.'://'.$this->getBoxForProject($project).'/authscm/'.$d.'/svn/'.$arr['unix_group_name'];
978 if (forge_get_config('use_ssh', 'scmsvn')) {
979 $urls[] = 'svn+ssh://'.$d.'@'.$this->getBoxForProject($project).$this->svn_root_fs .'/'. $arr['unix_group_name'];
982 $results[] = array('group_id' => $arr['group_id'],
983 'repository_type' => 'svn',
984 'repository_id' => $arr['unix_group_name'].'/svn/'.$arr['unix_group_name'],
985 'repository_urls' => $urls,
989 foreach ($results as $res) {
990 $params['results'][] = $res;
994 function get_scm_repo_info(&$params) {
995 $rid = $params['repository_id'];
996 $e = explode('/',$rid);
997 if ($e[1] != 'svn') {
1001 $p = array('group_name' => $g);
1002 $this->get_scm_repo_list($p);
1003 foreach ($p['results'] as $r) {
1004 if ($r['repository_id'] == $rid) {
1005 $params['results'] = $r;
1011 function parse_scm_repo_activities(&$params) {
1013 $res = db_query_params("SELECT unix_group_name, groups.group_id FROM groups
1014 JOIN group_plugin ON (groups.group_id=group_plugin.group_id)
1015 WHERE groups.status=$1 AND group_plugin.plugin_id=$2
1016 ORDER BY unix_group_name", array('A', $this->getID()));
1017 while ($arr = db_fetch_array($res)) {
1019 $el['rpath'] = $this->svn_root_fs.'/'.$arr['unix_group_name'];
1020 $el['rid'] = $arr['unix_group_name'].'/svn/'.$arr['unix_group_name'];
1021 $el['gid'] = $arr['group_id'];
1025 $lastactivities = array();
1026 $res = db_query_params("SELECT repository_id, max(tstamp) AS last FROM scm_activities WHERE plugin_id=$1 GROUP BY repository_id",
1027 array($this->getID()));
1028 while ($arr = db_fetch_array($res)) {
1029 $lastactivities[$arr['repository_id']] = $arr['last'];
1032 foreach ($repos as $rdata) {
1034 if (array_key_exists($rdata['rid'], $lastactivities)) {
1035 $since = '-r {$(date -d @'.$lastactivities[$rdata['rid']].' -Iseconds)}:HEAD';
1037 $rpath = $rdata['rpath'];
1039 $f = popen("svn log -q 'file:///$rpath' $since 2> /dev/null", "r");
1040 while (($l = fgets($f, 4096)) !== false) {
1041 if (preg_match("/.*?\|.*\|(?P<tstamp>[-0-9 :+]+)/", $l, $matches)) {
1042 $t = strtotime($matches['tstamp']);
1043 if (array_key_exists($rdata['rid'], $lastactivities)
1044 && $t <= $lastactivities[$rdata['rid']]) {
1050 foreach ($tstamps as $t => $v) {
1051 $res = db_query_params("INSERT INTO scm_activities (group_id, plugin_id, repository_id, tstamp) VALUES ($1,$2,$3,$4)",
1052 array($rdata['gid'],
1060 function scm_admin_form(&$params) {
1062 $project = $this->checkParams($params);
1067 session_require_perm('project_admin', $params['group_id']);
1068 if (forge_get_config('allow_multiple_scm') && ($params['allow_multiple_scm'] > 1)) {
1069 echo html_ao('div', array('id' => 'tabber-'.$this->name, 'class' => 'tabbertab'));
1072 $project_name = $project->getUnixName();
1074 $result = db_query_params('SELECT repo_name, description FROM scm_secondary_repos WHERE group_id=$1 AND next_action = $2 AND plugin_id=$3 ORDER BY repo_name',
1075 array($params['group_id'],
1076 SCM_EXTRA_REPO_ACTION_UPDATE,
1079 $params['error_msg'] = db_error();
1082 $existing_repos = array();
1083 while($data = db_fetch_array($result)) {
1084 $existing_repos[] = array('repo_name' => $data['repo_name'],
1085 'description' => $data['description']);
1087 if (count($existing_repos) == 0) {
1088 echo $HTML->information(_('No extra Subversion repository for project').' '.$project_name);
1090 echo html_e('h2', array(), sprintf(ngettext('Extra Subversion repository for project %1$s',
1091 'Extra Subversion repositories for project %1$s',
1092 count($existing_repos)), $project_name));
1093 $titleArr = array(_('Repository name'), ('Initial repository description'), _('Delete'));
1094 echo $HTML->listTableTop($titleArr);
1095 foreach ($existing_repos as $key => $repo) {
1097 $cells[][] = html_e('kbd', array(), $repo['repo_name']);
1098 $cells[][] = $repo['description'];
1099 $deleteForm = $HTML->openForm(array('name' => 'form_delete_repo_'.$repo['repo_name'], 'action' => getStringFromServer('PHP_SELF'), 'method' => 'post'));
1100 $deleteForm .= html_e('input', array('type' => 'hidden', 'name' => 'group_id', 'value' => $params['group_id']));
1101 $deleteForm .= html_e('input', array('type' => 'hidden', 'name' => 'delete_repository', 'value' => 1));
1102 $deleteForm .= $HTML->html_input('scm_plugin_id', '', '', 'hidden', $this->getID());
1103 $deleteForm .= html_e('input', array('type' => 'hidden', 'name' => 'repo_name', 'value' => $repo['repo_name']));
1104 $deleteForm .= html_e('input', array('type' => 'submit', 'name' => 'submit', 'value' => _('Delete')));
1105 $deleteForm .= $HTML->closeForm();
1106 $cells[][] = $deleteForm;
1107 echo $HTML->multiTableRow(array(), $cells);
1109 echo $HTML->listTableBottom();
1112 echo html_e('h2', array(), _('Create new Subversion repository for project').' '.$project_name);
1113 echo $HTML->openForm(array('name' => 'form_create_repo', 'action' => getStringFromServer('PHP_SELF'), 'method' => 'post'));
1114 echo html_e('input', array('type' => 'hidden', 'name' => 'group_id', 'value' => $params['group_id']));
1115 echo html_e('input', array('type' => 'hidden', 'name' => 'create_repository', 'value' => 1));
1116 echo html_e('p', array(), html_e('strong', array(), _('Repository name')._(':')).utils_requiredField().html_e('br').
1117 html_e('input', array('type' => 'text', 'required' => 'required', 'size' => 20, 'name' => 'repo_name', 'value' => '')));
1118 echo html_e('p', array(), html_e('strong', array(), _('Description')._(':')).html_e('br').
1119 html_e('input', array('type' => 'text', 'size' => 60, 'name' => 'description', 'value' => '')));
1120 echo html_e('input', array('type' => 'submit', 'name' => 'cancel', 'value' => _('Cancel')));
1121 echo html_e('input', array('type' => 'submit', 'name' => 'submit', 'value' => _('Submit')));
1122 echo $HTML->closeForm();
1124 if ($project->usesPlugin('scmhook')) {
1125 $scmhookPlugin = plugin_get_object('scmhook');
1126 $scmhookPlugin->displayScmHook($project->getID(), $this->name);
1128 if (forge_get_config('allow_multiple_scm') && ($params['allow_multiple_scm'] > 1)) {
1129 echo html_ac(html_ap() - 1);
1133 function getRepositories($group, $autoinclude = true) {
1136 $repoarr[] = $group->getUnixName();
1138 $result = db_query_params('SELECT repo_name FROM scm_secondary_repos WHERE group_id = $1 AND next_action = $2 AND plugin_id = $3 ORDER BY repo_name',
1139 array($group->getID(),
1140 SCM_EXTRA_REPO_ACTION_UPDATE,
1142 while ($arr = db_fetch_array($result)) {
1143 $repoarr[] = $arr['repo_name'];
1148 function getGroupIdFromSecondReponame($repo_name) {
1149 $result = db_query_params('SELECT scm_secondary_repos.group_id FROM scm_secondary_repos, groups WHERE scm_secondary_repos.group_id = groups.group_id AND repo_name = $1 AND plugin_id = $2 AND next_action = $3', array($repo_name, $this->getID(), SCM_EXTRA_REPO_ACTION_UPDATE));
1150 $arr = db_fetch_array($result);
1151 return $arr['group_id'];
1155 // End of class, helper functions now
1157 function SVNPluginCharData($parser, $chars) {
1158 global $last_tag, $last_user, $last_time, $start_time, $end_time, $usr_commits, $commits,
1159 $time_ok, $user_list, $last_message, $messages, $times, $users, $notimecheck;
1160 switch ($last_tag) {
1162 $last_user = preg_replace('/[^a-z0-9_-]/', '', strtolower(trim($chars)));
1163 $users[] = $last_user;
1164 $usr_commits[$last_user] = isset($usr_commits[$last_user]) ? ($usr_commits[$last_user]+1) : 1 ;
1169 $chars = preg_replace('/T(\d\d:\d\d:\d\d)\.\d+Z?$/', ' ${1}', $chars);
1170 $last_time = strtotime($chars);
1171 if ($start_time <= $last_time && $last_time < $end_time) {
1175 if (!isset($notimecheck)) {
1176 if ($last_user !== '') // empty in e.g. tags from cvs2svn
1177 $usr_commits[$last_user]--;
1181 $times[] = $last_time;
1185 if ($time_ok === true || isset($notimecheck)) {
1186 $messages[count($messages)-1] .= $chars;
1188 /* note: there may be more than one msg
1189 * (happen when the message contain accents).
1196 function SVNPluginStartElement($parser, $name, $attrs) {
1197 global $last_user, $last_time, $last_tag, $time_ok, $commits,
1198 $adds, $updates, $usr_adds, $usr_updates, $last_message, $messages, $times, $revisions, $deletes, $usr_deletes, $notimecheck;
1202 // Make sure we clean up before doing a new log entry
1205 $revisions[] = $attrs['REVISION'];
1209 if ($time_ok === true || isset($notimecheck)) {
1211 if ($attrs['ACTION'] == "M") {
1214 $usr_updates[$last_user] = isset($usr_updates[$last_user]) ? ($usr_updates[$last_user]+1) : 1 ;
1216 } elseif ($attrs['ACTION'] == "A") {
1219 $usr_adds[$last_user] = isset($usr_adds[$last_user]) ? ($usr_adds[$last_user]+1) : 1 ;
1221 } elseif ($attrs['ACTION'] == 'D') {
1224 $usr_deletes[$last_user] = isset($usr_deletes[$last_user]) ? ($usr_deletes[$last_user]+1) : 1 ;
1231 if ($time_ok === true || isset($notimecheck)) {
1239 function SVNPluginEndElement($parser, $name) {
1244 function curl2xml($ch, $data) {
1246 if (!xml_parse($xml_parser, $data, false))
1247 exit_error('Unable to parse XML with error '
1248 . xml_error_string(xml_get_error_code($xml_parser))
1249 . ' on line ' . xml_get_current_line_number($xml_parser),
1251 return strlen($data);
1255 // c-file-style: "bsd"