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 foreach ($scm_paths as $scm_path) {
113 if (strpos($scm_path, "!") === false) {
114 $repo = 'file://' . forge_get_config('repos_path', $this->name).'/'.$repo_name;
116 if (!(exec("svn ls '$repo'", $res) && in_array($scm_path.'/', $res))) {
117 $modules[] = '/'.$scm_path;
121 if (count($modules) == 0) {
127 function getInstructionsForAnon($project) {
128 $repo_list = array($project->getUnixName());
129 $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',
130 array($project->getID(), SCM_EXTRA_REPO_ACTION_UPDATE, $this->getID()));
131 $rows = db_numrows($result);
133 for ($i = 0; $i < $rows; $i++) {
134 $repo_list[] = db_result($result, $i, 'repo_name');
137 $b = html_e('h2', array(), _('Anonymous Access'));
138 $b .= html_e('p', array(),
139 ngettext("This project's SVN repository can be checked out through anonymous access with the following command(s).",
140 "This project's SVN repositories can be checked out through anonymous access with the following command(s).",
143 if (forge_get_config('use_ssh', 'scmsvn')) {
145 if (forge_get_config('ssh_port') != 22) {
146 $ssh_port = '--config-option="config:tunnels:ssh=ssh -p '.forge_get_config('ssh_port').'"';
148 $b .= html_e('h3', array(), _('via SVN'));
149 foreach ($repo_list as $repo_name) {
150 $modules = $this->topModule($project, $repo_name);
151 foreach ($modules as $module) {
152 $b .= html_e('kbd', array(), 'svn '.$ssh_port.' checkout svn://'.$this->getBoxForProject($project).$this->svn_root_fs.'/'.$repo_name.$module).html_e('br');
157 if (forge_get_config('use_dav', 'scmsvn')) {
158 $b .= html_e('h3', array(), _('via DAV'));
159 foreach ($repo_list as $repo_name) {
160 $modules = $this->topModule($project, $repo_name);
161 foreach ($modules as $module) {
162 $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');
169 function getInstructionsForRW($project) {
170 $repo_list = array($project->getUnixName());
171 $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',
172 array($project->getID(), SCM_EXTRA_REPO_ACTION_UPDATE, $this->getID()));
173 $rows = db_numrows($result);
174 for ($i=0; $i<$rows; $i++) {
175 $repo_list[] = db_result($result, $i, 'repo_name');
178 $b = html_e('h2', array(), _('Developer Access'));
179 $b .= sprintf(_('Only project developers can access the %s tree via this method.'), 'Subversion');
180 $b .= '<div id="tabber-svn">';
182 if (forge_get_config('use_ssh', 'scmsvn')) {
183 $b .= '<li><a href="#tabber-svnssh">'._('via SSH').'</a></li>';
186 if (forge_get_config('use_dav', 'scmsvn')) {
187 $b .= '<li><a href="#tabber-svndav">'._('via DAV').'</a></li>';
191 if (session_loggedin()) {
192 $u = user_get_object(user_getid());
193 $d = $u->getUnixName();
194 if (forge_get_config('use_ssh', 'scmsvn')) {
195 $b .= '<div id="tabber-svnssh" class="tabbertab" >';
197 $b .= _('SSH must be installed on your client machine.');
199 $b .= _('Enter your site password when prompted.');
202 if (forge_get_config('ssh_port') != 22) {
203 $ssh_port = '--config-option="config:tunnels:ssh=ssh -p '.forge_get_config('ssh_port').'" ';
205 foreach ($repo_list as $repo_name) {
206 $modules = $this->topModule($project, $repo_name);
207 foreach ($modules as $module) {
208 if (forge_get_config('use_shell_limited')) {
209 $b .= html_e('kbd', array(), 'svn '.$ssh_port.'checkout svn+ssh://'.$d.'@'.$this->getBoxForProject($project).'/'.$repo_name.$module).html_e('br');
211 $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');
217 if (forge_get_config('use_dav', 'scmsvn')) {
218 $b .= '<div id="tabber-svndav" class="tabbertab" >';
220 $b .= _('Enter your site password when prompted.');
222 foreach ($repo_list as $repo_name) {
223 $modules = $this->topModule($project, $repo_name);
224 foreach ($modules as $module) {
225 $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');
231 if (forge_get_config('use_ssh', 'scmsvn')) {
232 $b .= '<div id="tabber-svnssh" class="tabbertab" >';
234 $b .= _('SSH must be installed on your client machine.');
236 $b .= _('Substitute <em>developername</em> with the proper value.');
238 $b .= _('Enter your site password when prompted.');
241 if (forge_get_config('ssh_port') != 22) {
242 $ssh_port = '--config-option="config:tunnels:ssh=ssh -p '.forge_get_config('ssh_port').'" ';
244 foreach ($repo_list as $repo_name) {
245 $modules = $this->topModule($project, $repo_name);
246 foreach ($modules as $module) {
247 if (forge_get_config('use_shell_limited')) {
248 $b .= html_e('kbd', array(), 'svn '.$ssh_port.'checkout svn+ssh://<i>'._('developername').'</i>@'.$this->getBoxForProject($project).'/'.$repo_name.$module).html_e('br');
250 $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');
256 if (forge_get_config('use_dav', 'scmsvn')) {
257 $b .= '<div id="tabber-svndav" class="tabbertab" >';
259 $b .= _('Substitute <em>developername</em> with the proper value.');
261 $b .= _('Enter your site password when prompted.');
263 foreach ($repo_list as $repo_name) {
264 $modules = $this->topModule($project, $repo_name);
265 foreach ($modules as $module) {
266 $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');
276 function getSnapshotPara($project) {
278 $filename = $project->getUnixName().'-scm-latest.tar'.util_get_compressed_file_extension();
279 if (file_exists(forge_get_config('scm_snapshots_path').'/'.$filename)) {
280 $b .= html_e('p', array(), '['.util_make_link("/snapshots.php?group_id=".$project->getID(), _('Download the nightly snapshot')).']');
285 function getBrowserLinkBlock($project) {
286 $b = html_e('h2', array(), _('Subversion Repository Browser'));
287 $b .= html_e('p', array(),_("Browsing the Subversion tree gives you a view into the current status of this project's code.")
289 ._('You may also view the complete histories of any file in the repository.'));
290 $b .= html_e('p', array(), '['.util_make_link ("/scm/browser.php?group_id=".$project->getID().'&scm_plugin='.$this->name, sprintf(_('Browse %s Repository'), 'Subversion')).']');
292 $repo_list = $this->getRepositories($project, false);
293 foreach ($repo_list as $repo_name) {
294 $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');
299 function getStatsBlock($project) {
303 $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',
304 array($project->getID()));
306 if (db_numrows($result) > 0) {
307 $tableHeaders = array(
312 $b .= $HTML->listTableTop($tableHeaders, array(), '', 'repo-history-'.$this->name);
315 $total = array('adds' => 0, 'updates' => 0);
317 while($data = db_fetch_array($result)) {
319 $cells[] = array(util_display_user($data['user_name'], $data['user_id'], $data['realname']), 'class' => 'halfwidth');
320 $cells[] = array($data['adds'], 'class' => 'onequarterwidth align-right');
321 $cells[] = array($data['updates'], 'class' => 'onequarterwidth align-right');
322 $b .= $HTML->multiTableRow(array(), $cells);
323 $total['adds'] += $data['adds'];
324 $total['updates'] += $data['updates'];
328 $cells[] = array(html_e('strong', array(), _('Total')._(':')), 'class' => 'halfwidth');
329 $cells[] = array($total['adds'], 'class' => 'onequarterwidth align-right');
330 $cells[] = array($total['updates'], 'class' => 'onequarterwidth align-right');
331 $b .= $HTML->multiTableRow(array(), $cells);
332 $b .= $HTML->listTableBottom();
334 $b .= $HTML->warning_msg(_('No history yet.'));
340 function printBrowserPage($params) {
341 if ($params['scm_plugin'] != $this->name) {
346 $project = $this->checkParams($params);
350 if (isset($params['extra']) && !empty($params['extra']) && $params['extra'] != 'none') {
351 $iframe_src = '/scm/viewvc.php?root='.$params['extra'];
353 $iframe_src = '/scm/viewvc.php?root='.$project->getUnixName();
355 if ($params['commit']) {
356 $iframe_src .= '&view=rev&revision='.$params['commit'];
358 htmlIframe($iframe_src, array('id'=>'scmsvn_iframe'));
361 function createOrUpdateRepo($params) {
362 $project = $this->checkParams($params);
363 if (!$project) return false;
364 if (!$project->isActive()) return false;
366 $repo_prefix = forge_get_config('repos_path', 'scmsvn');
367 if (!is_dir($repo_prefix) && !mkdir($repo_prefix, 0755, true)) {
371 $repo = $repo_prefix.'/'.$project->getUnixName();
373 if (!is_dir ($repo) || !is_file ("$repo/format")) {
374 if (!mkdir($repo, 0700, true)) {
378 system ("svnadmin create $repo", $ret);
382 system ("sed -i '/enable-rep-sharing = false/s/^. //' $repo/db/fsfs.conf") ;
383 // dav/ and dav/activities.d directories are required by old svn clients (eg. svn 1.6.17 on ubuntu 12.04)
384 if (!is_dir ("$repo/dav")) {
387 if (!is_dir ("$repo/dav/activities.d")) {
388 mkdir("$repo/dav/activities.d");
390 system ("svn mkdir -m'Init' file:///$repo/trunk file:///$repo/tags file:///$repo/branches >/dev/null") ;
391 system ("find $repo -type d -print0 | xargs -r -0 chmod g+s") ;
392 // Allow read/write users to modify the SVN repository
393 $rw_unix_group = $project->getUnixName() . '_scmrw';
394 system("chgrp -R $rw_unix_group $repo");
395 // Allow read-only users to enter the (top-level) directory
396 $ro_unix_group = $project->getUnixName() . '_scmro';
397 system("chgrp $ro_unix_group $repo");
398 // open permissions to allow switching private/public easily
399 // see after to restrict the top-level directory
400 system ("chmod -R g+rwX,o+rX-w $repo") ;
403 if ($project->enableAnonSCM()) {
404 system("chmod g+rX-w,o+rX-w $repo") ;
406 system("chmod g+rX-w,o-rwx $repo") ;
409 // Create project-wide secondary repositories
410 $result = db_query_params('SELECT repo_name FROM scm_secondary_repos WHERE group_id = $1 AND next_action = $2 AND plugin_id = $3',
411 array($project->getID(),
412 SCM_EXTRA_REPO_ACTION_UPDATE,
414 $rows = db_numrows($result);
415 for ($i = 0; $i < $rows; $i++) {
416 $repo_name = db_result($result, $i, 'repo_name');
417 $repo = $repo_prefix.'/'.$repo_name;
418 if (!is_dir($repo) || !is_file("$repo/format")) {
419 if (!mkdir($repo, 0700, true)) {
423 system ("svnadmin create $repo", $ret);
427 system ("sed -i '/enable-rep-sharing = false/s/^. //' $repo/db/fsfs.conf") ;
428 // dav/ directory is required by old svn clients (eg. svn 1.6.17 on ubuntu 12.04)
429 if (!is_dir ("$repo/dav")) {
432 system ("svn mkdir -m'Init' file:///$repo/trunk file:///$repo/tags file:///$repo/branches >/dev/null") ;
433 system ("find $repo -type d -print0 | xargs -r -0 chmod g+s") ;
434 // Allow read/write users to modify the SVN repository
435 $rw_unix_group = $project->getUnixName() . '_scmrw';
436 system("chgrp -R $rw_unix_group $repo");
437 // Allow read-only users to enter the (top-level) directory
438 $ro_unix_group = $project->getUnixName() . '_scmro';
439 system("chgrp $ro_unix_group $repo");
440 // open permissions to allow switching private/public easily
441 // see after to restrict the top-level directory
442 system ("chmod -R g+rwX,o+rX-w $repo") ;
444 if ($project->enableAnonSCM()) {
445 system("chmod g+rX-w,o+rX-w $repo") ;
447 system("chmod g+rX-w,o-rwx $repo") ;
451 // Delete project-wide secondary repositories
452 $result = db_query_params('SELECT repo_name FROM scm_secondary_repos WHERE group_id=$1 AND next_action = $2 AND plugin_id=$3',
453 array($project->getID(),
454 SCM_EXTRA_REPO_ACTION_DELETE,
456 $rows = db_numrows ($result);
457 for ($i=0; $i<$rows; $i++) {
458 $repo_name = db_result($result, $i, 'repo_name');
459 $repodir = $repo_prefix.'/'.$repo_name;
460 if (util_is_valid_repository_name($repo_name)) {
461 system("rm -rf $repodir");
463 db_query_params ('DELETE FROM scm_secondary_repos WHERE group_id=$1 AND repo_name=$2 AND next_action = $3 AND plugin_id=$4',
464 array($project->getID(),
466 SCM_EXTRA_REPO_ACTION_DELETE,
469 $this->regenApacheAuth($params);
472 function updateRepositoryList(&$params) {
475 function regenApacheAuth(&$params) {
476 # Enable /authscm/$user/svn URLs
477 $config_fname = forge_get_config('data_path').'/scmsvn-auth.inc';
478 $config_f = fopen($config_fname.'.new', 'w');
480 $res = db_query_params("SELECT login FROM nss_passwd WHERE status=$1", array('A'));
481 while ($arr = db_fetch_array($res)) {
482 fwrite($config_f, 'Use ScmsvnUser '.$arr['login']."\n");
486 chmod($config_fname.'.new', 0644);
487 rename($config_fname.'.new', $config_fname);
490 function gatherStats($params) {
491 global $last_user, $last_time, $last_tag, $time_ok, $start_time, $end_time,
492 $adds, $deletes, $updates, $commits, $date_key,
493 $usr_adds, $usr_deletes, $usr_updates, $usr_commits;
497 $project = $this->checkParams($params);
502 if ($params['mode'] == 'day') {
505 $year = $params['year'];
506 $month = $params['month'];
507 $day = $params['day'];
508 $month_string = sprintf("%04d%02d", $year, $month);
509 $start_time = gmmktime(0, 0, 0, $month, $day, $year);
510 $end_time = $start_time + 86400;
518 $usr_updates = array();
519 $usr_deletes = array();
520 $usr_commits = array();
522 $repo = forge_get_config('repos_path', 'scmsvn').'/'.$project->getUnixName().'/'.$project->getUnixName();
523 if (!is_dir ($repo) || !is_file ("$repo/format")) {
528 $d1 = date('Y-m-d', $start_time - 150000);
529 $d2 = date('Y-m-d', $end_time + 150000);
531 $pipe = popen ("svn log file://$repo --xml -v -q -r '".'{'.$d2.'}:{'.$d1.'}'."' 2> /dev/null", 'r' ) ;
533 // cleaning stats_cvs_* table for the current day
534 $res = db_query_params('DELETE FROM stats_cvs_group WHERE month = $1 AND day = $2 AND group_id = $3 AND reponame = $4',
538 $project->getUnixName()));
540 echo "Error while cleaning stats_cvs_group\n";
545 $res = db_query_params('DELETE FROM stats_cvs_user WHERE month = $1 AND day = $2 AND group_id = $3 AND reponame = $4',
549 $project->getUnixName()));
551 echo "Error while cleaning stats_cvs_user\n" ;
556 $xml_parser = xml_parser_create();
557 xml_set_element_handler($xml_parser, "SVNPluginStartElement", "SVNPluginEndElement");
558 xml_set_character_data_handler($xml_parser, "SVNPluginCharData");
560 // Analyzing history stream
561 while (!feof($pipe) &&
562 $data = fgets ($pipe, 4096)) {
563 if (!xml_parse ($xml_parser, $data, feof ($pipe))) {
564 $this->setError("Unable to parse XML with error " .
565 xml_error_string(xml_get_error_code($xml_parser)) .
567 xml_get_current_line_number($xml_parser));
574 xml_parser_free($xml_parser);
576 // inserting group results in stats_cvs_groups
577 if ($updates > 0 || $adds > 0 || $deletes > 0 || $commits > 0) {
578 if (!db_query_params('INSERT INTO stats_cvs_group (month, day, group_id, checkouts, commits, adds, updates, deletes, reponame)
579 VALUES ($1, $2, $3, $4, $5, $6, $7, $8)',
588 $project->getUnixName()))) {
589 echo "Error while inserting into stats_cvs_group\n";
595 // building the user list
596 $user_list = array_unique( array_merge( array_keys( $usr_adds ), array_keys( $usr_updates ), array_keys( $usr_deletes ), array_keys( $usr_commits )) );
598 foreach ($user_list as $user) {
599 // Trying to get user id from user name
600 $u = user_get_object_by_name($user);
602 $user_id = $u->getID();
607 $uc = isset($usr_commits[$user]) ? $usr_commits[$user] : 0 ;
608 $uu = isset($usr_updates[$user]) ? $usr_updates[$user] : 0 ;
609 $ua = isset($usr_adds[$user]) ? $usr_adds[$user] : 0 ;
610 $ud = isset($usr_deletes[$user]) ? $usr_deletes[$user] : 0 ;
611 if ($uu > 0 || $ua > 0 || $uc > 0 || $ud > 0) {
612 if (!db_query_params('INSERT INTO stats_cvs_user (month, day, group_id, user_id, commits, adds, updates, deletes, reponame)
613 VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)',
614 array ($month_string,
622 $project->getUnixName()))) {
623 echo "Error while inserting into stats_cvs_user\n";
633 function generateSnapshots($params) {
634 $us = forge_get_config('use_scm_snapshots') ;
635 $ut = forge_get_config('use_scm_tarballs') ;
640 $project = $this->checkParams($params);
645 $group_name = $project->getUnixName();
647 $snapshot = forge_get_config('scm_snapshots_path').'/'.$group_name.'-scm-latest.tar'.util_get_compressed_file_extension();
648 $tarball = forge_get_config('scm_tarballs_path').'/'.$group_name.'-scmroot.tar'.util_get_compressed_file_extension();
650 if (!$project->enableAnonSCM()) {
651 if (is_file($snapshot)) {
654 if (is_file($tarball)) {
660 $toprepo = forge_get_config('repos_path', 'scmsvn');
661 $repo = $toprepo.'/'.$group_name.'/'.$group_name;
663 if (!is_dir($repo) || !is_file ("$repo/format")) {
664 if (is_file($snapshot)) {
667 if (is_file($tarball)) {
673 $tmp = trim(`mktemp -d`);
678 $today = date('Y-m-d');
679 $dir = $project->getUnixName ()."-$today" ;
680 system("mkdir -p $tmp") ;
682 system ("svn ls file://$repo/trunk > /dev/null 2> /dev/null", $code) ;
685 system ("cd $tmp ; svn export file://$repo/trunk $dir > /dev/null 2>&1") ;
686 system ("tar cCf $tmp - $dir |".forge_get_config('compression_method')."> $tmp/snapshot") ;
687 chmod("$tmp/snapshot", 0644);
688 copy("$tmp/snapshot", $snapshot);
689 unlink("$tmp/snapshot");
690 system ("rm -rf $tmp/$dir") ;
692 if (is_file($snapshot)) {
699 system("tar cCf $toprepo - ".$project->getUnixName() ."|".forge_get_config('compression_method')."> $tmp/tarball") ;
700 chmod("$tmp/tarball", 0644);
701 copy("$tmp/tarball", $tarball);
702 unlink("$tmp/tarball");
703 system("rm -rf $tmp");
707 function activity($params) {
708 global $last_user, $last_time, $last_tag, $time_ok, $start_time, $end_time,
709 $adds, $deletes, $updates, $commits, $date_key,
710 $messages, $last_message, $times, $revisions, $users, $xml_parser;
718 $revisions = array();
720 $project = $this->checkParams($params);
725 if (in_array('scmsvn', $params['show']) || (count($params['show']) < 1)) {
726 $start_time = $params['begin'];
727 $end_time = $params['end'];
729 $xml_parser = xml_parser_create();
730 xml_set_element_handler($xml_parser, "SVNPluginStartElement", "SVNPluginEndElement");
731 xml_set_character_data_handler($xml_parser, "SVNPluginCharData");
733 // Grab&parse commit log
734 $protocol = forge_get_config('use_ssl', 'scmsvn') ? 'https://' : 'http://';
735 if ($project->enableAnonSCM()) {
736 $server_script = '/anonscm/svnlog';
738 $u = session_get_user();
739 if ($u && !$u->isError()) {
740 $server_script = '/authscm/'.$u->getUnixName().'/svnlog';
745 $script_url = $protocol.$this->getBoxForProject($project)
747 .'?unix_group_name='.$project->getUnixName()
749 .'&begin='.$params['begin']
750 .'&end='.$params['end'];
752 curl_setopt($ch, CURLOPT_URL, $script_url);
753 curl_setopt($ch, CURLOPT_WRITEFUNCTION, 'curl2xml');
754 curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
755 curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
756 curl_setopt($ch, CURLOPT_COOKIE, @$_SERVER['HTTP_COOKIE']); // for session validation
757 curl_setopt($ch, CURLOPT_USERAGENT, $_SERVER['HTTP_USER_AGENT']); // for session validation
758 curl_setopt($ch, CURLOPT_HTTPHEADER,
759 array('X-Forwarded-For: '.$_SERVER['REMOTE_ADDR'])); // for session validation
760 $body = curl_exec($ch);
761 if ($body === false) {
762 $this->setError(curl_error($ch));
767 if (!xml_parse($xml_parser, '', true))
768 $this->setError('Unable to parse XML with error '
769 . xml_error_string(xml_get_error_code($xml_parser))
770 . ' on line ' . xml_get_current_line_number($xml_parser));
771 xml_parser_free($xml_parser);
773 if ($adds > 0 || $updates > 0 || $commits > 0 || $deletes > 0) {
775 foreach ($messages as $message) {
777 $result['section'] = 'scm';
778 $result['group_id'] = $project->getID();
779 $result['ref_id'] = 'browser.php?group_id='.$project->getID().'&scm_plugin='.$this->name;
780 $result['description'] = htmlspecialchars($message).' (r'.$revisions[$i].')';
781 $userObject = user_get_object_by_name($users[$i]);
782 if (is_a($userObject, 'FFUser')) {
783 $result['realname'] = util_display_user($userObject->getUnixName(), $userObject->getID(), $userObject->getRealName());
785 $result['realname'] = '';
787 $result['activity_date'] = $times[$i];
788 $result['subref_id'] = '&commit='.$revisions[$i];
789 $params['results'][] = $result;
794 if (!in_array($this->name, $params['ids']) && ($project->enableAnonSCM() || session_loggedin())) {
795 $params['ids'][] = $this->name;
796 $params['texts'][] = _('Subversion Commits');
801 // Get latest commits for inclusion in a widget
802 function getCommits($project, $user = null, $nbCommits) {
803 global $commits, $users, $adds, $updates, $messages, $times, $revisions, $deletes, $time_ok, $user_list, $last_message, $notimecheck, $xml_parser;
810 $revisions = array();
813 $user_list = array();
816 $revisionsArr = array();
817 if ($project->usesPlugin($this->name) && forge_check_perm('scm', $project->getID(), 'read')) {
818 $xml_parser = xml_parser_create();
819 xml_set_element_handler($xml_parser, "SVNPluginStartElement", "SVNPluginEndElement");
820 xml_set_character_data_handler($xml_parser, "SVNPluginCharData");
822 // Grab&parse commit log
823 $protocol = forge_get_config('use_ssl', 'scmsvn') ? 'https://' : 'http://';
824 $u = session_get_user();
825 if ($project->enableAnonSCM())
826 $server_script = '/anonscm/svnlog';
828 $server_script = '/authscm/'.$u->getUnixName().'/svnlog';
830 $userunixname = $user->getUnixName();
831 $params = '&mode=latest_user&user_name='.$userunixname;
833 $params = '&mode=latest';
835 $script_url = $protocol.$this->getBoxForProject($project)
837 .'?unix_group_name='.$project->getUnixName()
839 .'&limit='.$nbCommits;
841 curl_setopt($ch, CURLOPT_URL, $script_url);
842 curl_setopt($ch, CURLOPT_WRITEFUNCTION, 'curl2xml');
843 curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
844 curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
845 curl_setopt($ch, CURLOPT_COOKIE, $_SERVER['HTTP_COOKIE']); // for session validation
846 curl_setopt($ch, CURLOPT_USERAGENT, $_SERVER['HTTP_USER_AGENT']); // for session validation
847 curl_setopt($ch, CURLOPT_HTTPHEADER,
848 array('X-Forwarded-For: '.$_SERVER['REMOTE_ADDR'])); // for session validation
849 $body = curl_exec($ch);
850 if ($body === false) {
851 $this->setError(curl_error($ch));
856 if (!xml_parse($xml_parser, '', true))
857 $this->setError('Unable to parse XML with error '
858 . xml_error_string(xml_get_error_code($xml_parser))
859 . ' on line ' . xml_get_current_line_number($xml_parser));
860 xml_parser_free($xml_parser);
862 if ($adds > 0 || $updates > 0 || $commits > 0 || $deletes > 0) {
864 foreach ($messages as $message) {
865 $revisionsArr[$i]['pluginName'] = 'scmsvn';
866 $revisionsArr[$i]['description'] = htmlspecialchars($message);
867 $revisionsArr[$i]['commit_id'] = $revisions[$i];
868 $revisionsArr[$i]['repo_name'] = $project->getUnixName();
869 $revisionsArr[$i]['date'] = $times[$i];
874 return $revisionsArr;
877 function scm_add_repo(&$params) {
878 $project = $this->checkParams($params);
883 if (!isset($params['repo_name'])) {
887 if ($params['repo_name'] == $project->getUnixName()) {
888 $params['error_msg'] = _('Cannot create a secondary repository with the same name as the primary');
892 if (!util_is_valid_repository_name($params['repo_name'])) {
893 $params['error_msg'] = _('This repository name is not valid');
897 $result = db_query_params('SELECT count(*) AS count FROM scm_secondary_repos WHERE repo_name = $1 AND plugin_id=$2',
898 array($params['repo_name'], $this->getID()));
900 $params['error_msg'] = db_error();
903 if (db_result($result, 0, 'count')) {
904 $params['error_msg'] = sprintf(_('A repository %s already exists'), $params['repo_name']);
909 if (isset($params['description'])) {
910 $description = $params['description'];
913 $description = "Subversion repository $params[repo_name] for project ".$project->getUnixName();
915 $result = db_query_params('INSERT INTO scm_secondary_repos (group_id, repo_name, description, clone_url, plugin_id) VALUES ($1, $2, $3, $4, $5)',
916 array($params['group_id'],
917 $params['repo_name'],
922 $params['error_msg'] = db_error();
926 plugin_hook ("scm_admin_update", $params);
930 function get_scm_repo_list(&$params) {
931 if (array_key_exists('group_name',$params)) {
932 $unix_group_name = $params['group_name'];
934 $unix_group_name = '';
936 $protocol = forge_get_config('use_ssl', 'scmsvn')? 'https' : 'http';
937 if (session_loggedin()) {
938 $u = user_get_object(user_getid());
939 $d = $u->getUnixName();
943 if ($unix_group_name) {
944 $res = db_query_params("SELECT unix_group_name, groups.group_id FROM groups
945 JOIN group_plugin ON (groups.group_id=group_plugin.group_id)
946 WHERE groups.status=$1 AND group_plugin.plugin_id=$2 AND groups.unix_group_name=$3
947 ORDER BY unix_group_name", array('A', $this->getID(),$unix_group_name));
949 $res = db_query_params("SELECT unix_group_name, groups.group_id FROM groups
950 JOIN group_plugin ON (groups.group_id=group_plugin.group_id)
951 WHERE groups.status=$1 AND group_plugin.plugin_id=$2
952 ORDER BY unix_group_name", array('A', $this->getID()));
954 while ($arr = db_fetch_array($res)) {
955 if (!forge_check_perm('scm', $arr['group_id'], 'read')) {
959 if (forge_get_config('use_dav', 'scmsvn')) {
960 $urls[] = $protocol.'://'.$this->getBoxForProject($project).'/anonscm/svn/'.$arr['unix_group_name'];
962 if (forge_get_config('use_ssh', 'scmsvn')) {
963 $urls[] = 'svn://'.$this->getBoxForProject($project).$this->svn_root_fs.'/'.$arr['unix_group_name'];
965 if (session_loggedin()) {
966 if (forge_get_config('use_dav', 'scmsvn')) {
967 $urls[] = $protocol.'://'.$this->getBoxForProject($project).'/authscm/'.$d.'/svn/'.$arr['unix_group_name'];
969 if (forge_get_config('use_ssh', 'scmsvn')) {
970 $urls[] = 'svn+ssh://'.$d.'@'.$this->getBoxForProject($project).$this->svn_root_fs .'/'. $arr['unix_group_name'];
973 $results[] = array('group_id' => $arr['group_id'],
974 'repository_type' => 'svn',
975 'repository_id' => $arr['unix_group_name'].'/svn/'.$arr['unix_group_name'],
976 'repository_urls' => $urls,
980 foreach ($results as $res) {
981 $params['results'][] = $res;
985 function get_scm_repo_info(&$params) {
986 $rid = $params['repository_id'];
987 $e = explode('/',$rid);
988 if ($e[1] != 'svn') {
992 $p = array('group_name' => $g);
993 $this->get_scm_repo_list($p);
994 foreach ($p['results'] as $r) {
995 if ($r['repository_id'] == $rid) {
996 $params['results'] = $r;
1002 function parse_scm_repo_activities(&$params) {
1004 $res = db_query_params("SELECT unix_group_name, groups.group_id FROM groups
1005 JOIN group_plugin ON (groups.group_id=group_plugin.group_id)
1006 WHERE groups.status=$1 AND group_plugin.plugin_id=$2
1007 ORDER BY unix_group_name", array('A', $this->getID()));
1008 while ($arr = db_fetch_array($res)) {
1010 $el['rpath'] = $this->svn_root_fs.'/'.$arr['unix_group_name'];
1011 $el['rid'] = $arr['unix_group_name'].'/svn/'.$arr['unix_group_name'];
1012 $el['gid'] = $arr['group_id'];
1016 $lastactivities = array();
1017 $res = db_query_params("SELECT repository_id, max(tstamp) AS last FROM scm_activities WHERE plugin_id=$1 GROUP BY repository_id",
1018 array($this->getID()));
1019 while ($arr = db_fetch_array($res)) {
1020 $lastactivities[$arr['repository_id']] = $arr['last'];
1023 foreach ($repos as $rdata) {
1025 if (array_key_exists($rdata['rid'], $lastactivities)) {
1026 $since = '-r {$(date -d @'.$lastactivities[$rdata['rid']].' -Iseconds)}:HEAD';
1028 $rpath = $rdata['rpath'];
1030 $f = popen("svn log -q 'file:///$rpath' $since 2> /dev/null", "r");
1031 while (($l = fgets($f, 4096)) !== false) {
1032 if (preg_match("/.*?\|.*\|(?P<tstamp>[-0-9 :+]+)/", $l, $matches)) {
1033 $t = strtotime($matches['tstamp']);
1034 if (array_key_exists($rdata['rid'], $lastactivities)
1035 && $t <= $lastactivities[$rdata['rid']]) {
1041 foreach ($tstamps as $t => $v) {
1042 $res = db_query_params("INSERT INTO scm_activities (group_id, plugin_id, repository_id, tstamp) VALUES ($1,$2,$3,$4)",
1043 array($rdata['gid'],
1051 function scm_admin_form(&$params) {
1053 $project = $this->checkParams($params);
1058 session_require_perm('project_admin', $params['group_id']);
1059 if (forge_get_config('allow_multiple_scm') && ($params['allow_multiple_scm'] > 1)) {
1060 echo html_ao('div', array('id' => 'tabber-'.$this->name, 'class' => 'tabbertab'));
1063 $project_name = $project->getUnixName();
1065 $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',
1066 array($params['group_id'],
1067 SCM_EXTRA_REPO_ACTION_UPDATE,
1070 $params['error_msg'] = db_error();
1073 $existing_repos = array();
1074 while($data = db_fetch_array($result)) {
1075 $existing_repos[] = array('repo_name' => $data['repo_name'],
1076 'description' => $data['description']);
1078 if (count($existing_repos) == 0) {
1079 echo $HTML->information(_('No extra Subversion repository for project').' '.$project_name);
1081 echo html_e('h2', array(), sprintf(ngettext('Extra Subversion repository for project %1$s',
1082 'Extra Subversion repositories for project %1$s',
1083 count($existing_repos)), $project_name));
1084 $titleArr = array(_('Repository name'), ('Initial repository description'), _('Delete'));
1085 echo $HTML->listTableTop($titleArr);
1086 foreach ($existing_repos as $key => $repo) {
1088 $cells[][] = html_e('kbd', array(), $repo['repo_name']);
1089 $cells[][] = $repo['description'];
1090 $deleteForm = $HTML->openForm(array('name' => 'form_delete_repo_'.$repo['repo_name'], 'action' => getStringFromServer('PHP_SELF'), 'method' => 'post'));
1091 $deleteForm .= html_e('input', array('type' => 'hidden', 'name' => 'group_id', 'value' => $params['group_id']));
1092 $deleteForm .= html_e('input', array('type' => 'hidden', 'name' => 'delete_repository', 'value' => 1));
1093 $deleteForm .= $HTML->html_input('scm_plugin_id', '', '', 'hidden', $this->getID());
1094 $deleteForm .= html_e('input', array('type' => 'hidden', 'name' => 'repo_name', 'value' => $repo['repo_name']));
1095 $deleteForm .= html_e('input', array('type' => 'submit', 'name' => 'submit', 'value' => _('Delete')));
1096 $deleteForm .= $HTML->closeForm();
1097 $cells[][] = $deleteForm;
1098 echo $HTML->multiTableRow(array(), $cells);
1100 echo $HTML->listTableBottom();
1103 echo html_e('h2', array(), _('Create new Subversion repository for project').' '.$project_name);
1104 echo $HTML->openForm(array('name' => 'form_create_repo', 'action' => getStringFromServer('PHP_SELF'), 'method' => 'post'));
1105 echo html_e('input', array('type' => 'hidden', 'name' => 'group_id', 'value' => $params['group_id']));
1106 echo html_e('input', array('type' => 'hidden', 'name' => 'create_repository', 'value' => 1));
1107 echo html_e('p', array(), html_e('strong', array(), _('Repository name')._(':')).utils_requiredField().html_e('br').
1108 html_e('input', array('type' => 'text', 'required' => 'required', 'size' => 20, 'name' => 'repo_name', 'value' => '')));
1109 echo html_e('p', array(), html_e('strong', array(), _('Description')._(':')).html_e('br').
1110 html_e('input', array('type' => 'text', 'size' => 60, 'name' => 'description', 'value' => '')));
1111 echo html_e('input', array('type' => 'submit', 'name' => 'cancel', 'value' => _('Cancel')));
1112 echo html_e('input', array('type' => 'submit', 'name' => 'submit', 'value' => _('Submit')));
1113 echo $HTML->closeForm();
1115 if ($project->usesPlugin('scmhook')) {
1116 $scmhookPlugin = plugin_get_object('scmhook');
1117 $scmhookPlugin->displayScmHook($project->getID(), $this->name);
1119 if (forge_get_config('allow_multiple_scm') && ($params['allow_multiple_scm'] > 1)) {
1120 echo html_ac(html_ap() - 1);
1124 function getRepositories($group, $autoinclude = true) {
1127 $repoarr[] = $group->getUnixName();
1129 $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',
1130 array($group->getID(),
1131 SCM_EXTRA_REPO_ACTION_UPDATE,
1133 while ($arr = db_fetch_array($result)) {
1134 $repoarr[] = $arr['repo_name'];
1139 function getGroupIdFromSecondReponame($repo_name) {
1140 $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));
1141 $arr = db_fetch_array($result);
1142 return $arr['group_id'];
1146 // End of class, helper functions now
1148 function SVNPluginCharData($parser, $chars) {
1149 global $last_tag, $last_user, $last_time, $start_time, $end_time, $usr_commits, $commits,
1150 $time_ok, $user_list, $last_message, $messages, $times, $users, $notimecheck;
1151 switch ($last_tag) {
1153 $last_user = preg_replace('/[^a-z0-9_-]/', '', strtolower(trim($chars)));
1154 $users[] = $last_user;
1155 $usr_commits[$last_user] = isset($usr_commits[$last_user]) ? ($usr_commits[$last_user]+1) : 1 ;
1160 $chars = preg_replace('/T(\d\d:\d\d:\d\d)\.\d+Z?$/', ' ${1}', $chars);
1161 $last_time = strtotime($chars);
1162 if ($start_time <= $last_time && $last_time < $end_time) {
1166 if (!isset($notimecheck)) {
1167 if ($last_user !== '') // empty in e.g. tags from cvs2svn
1168 $usr_commits[$last_user]--;
1172 $times[] = $last_time;
1176 if ($time_ok === true || isset($notimecheck)) {
1177 $messages[count($messages)-1] .= $chars;
1179 /* note: there may be more than one msg
1180 * (happen when the message contain accents).
1187 function SVNPluginStartElement($parser, $name, $attrs) {
1188 global $last_user, $last_time, $last_tag, $time_ok, $commits,
1189 $adds, $updates, $usr_adds, $usr_updates, $last_message, $messages, $times, $revisions, $deletes, $usr_deletes, $notimecheck;
1193 // Make sure we clean up before doing a new log entry
1196 $revisions[] = $attrs['REVISION'];
1200 if ($time_ok === true || isset($notimecheck)) {
1202 if ($attrs['ACTION'] == "M") {
1205 $usr_updates[$last_user] = isset($usr_updates[$last_user]) ? ($usr_updates[$last_user]+1) : 1 ;
1207 } elseif ($attrs['ACTION'] == "A") {
1210 $usr_adds[$last_user] = isset($usr_adds[$last_user]) ? ($usr_adds[$last_user]+1) : 1 ;
1212 } elseif ($attrs['ACTION'] == 'D') {
1215 $usr_deletes[$last_user] = isset($usr_deletes[$last_user]) ? ($usr_deletes[$last_user]+1) : 1 ;
1222 if ($time_ok === true || isset($notimecheck)) {
1230 function SVNPluginEndElement($parser, $name) {
1235 function curl2xml($ch, $data) {
1237 if (!xml_parse($xml_parser, $data, false))
1238 exit_error('Unable to parse XML with error '
1239 . xml_error_string(xml_get_error_code($xml_parser))
1240 . ' on line ' . xml_get_current_line_number($xml_parser),
1242 return strlen($data);
1246 // c-file-style: "bsd"