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 // Check toplevel module presence
106 $repo = 'file://' . forge_get_config('repos_path', $this->name).'/'.$repo_name.'/';
109 if (!is_file($repo.'/format')) {
112 exec("svn ls '$repo'", $res);
113 if (!in_array($module.'/', $res)) {
120 function getInstructionsForAnon($project) {
121 $repo_list = array($project->getUnixName());
122 $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',
123 array($project->getID(), SCM_EXTRA_REPO_ACTION_UPDATE, $this->getID()));
124 $rows = db_numrows($result);
126 for ($i = 0; $i < $rows; $i++) {
127 $repo_list[] = db_result($result, $i, 'repo_name');
130 $b = html_e('h2', array(), _('Anonymous Access'));
131 $b .= html_e('p', array(),
132 ngettext("This project's SVN repository can be checked out through anonymous access with the following command(s).",
133 "This project's SVN repositories can be checked out through anonymous access with the following command(s).",
136 if (forge_get_config('use_ssh', 'scmsvn')) {
138 if (forge_get_config('ssh_port') != 22) {
139 $ssh_port = '--config-option="config:tunnels:ssh=ssh -p '.forge_get_config('ssh_port').'"';
141 $b .= html_e('h3', array(), _('via SVN'));
142 foreach ($repo_list as $repo_name) {
143 $module = $this->topModule($project, $repo_name);
144 $b .= html_e('tt', array(), 'svn '.$ssh_port.' checkout svn://'.$this->getBoxForProject($project).$this->svn_root_fs.'/'.$repo_name.$module).html_e('br');
148 if (forge_get_config('use_dav', 'scmsvn')) {
149 $b .= html_e('h3', array(), _('via DAV'));
150 foreach ($repo_list as $repo_name) {
151 $module = $this->topModule($project, $repo_name);
152 $b .= html_e('tt', array(), 'svn checkout http'.((forge_get_config('use_ssl', 'scmsvn')) ? 's' : '').'://'. $this->getBoxForProject($project). '/anonscm/svn/'.$repo_name.$module).html_e('br');
158 function getInstructionsForRW($project) {
159 $repo_list = array($project->getUnixName());
160 $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',
161 array($project->getID(), SCM_EXTRA_REPO_ACTION_UPDATE, $this->getID()));
162 $rows = db_numrows($result);
163 for ($i=0; $i<$rows; $i++) {
164 $repo_list[] = db_result($result, $i, 'repo_name');
168 $b .= html_e('h2', array(), _('Developer Access'));
169 $b .= sprintf(_('Only project developers can access the %s tree via this method.'), 'Subversion');
170 $b .= '<div id="tabber-svn">';
172 if (forge_get_config('use_ssh', 'scmsvn')) {
173 $b .= '<li><a href="#tabber-svnssh">'._('via SSH').'</a></li>';
176 if (forge_get_config('use_dav', 'scmsvn')) {
177 $b .= '<li><a href="#tabber-svndav">'._('via DAV').'</a></li>';
181 if (session_loggedin()) {
182 $u = user_get_object(user_getid());
183 $d = $u->getUnixName();
184 if (forge_get_config('use_ssh', 'scmsvn')) {
185 $b .= '<div id="tabber-svnssh" class="tabbertab" >';
187 $b .= _('SSH must be installed on your client machine.');
189 $b .= _('Enter your site password when prompted.');
192 if (forge_get_config('ssh_port') != 22) {
193 $ssh_port = '--config-option="config:tunnels:ssh=ssh -p '.forge_get_config('ssh_port').'" ';
195 foreach ($repo_list as $repo_name) {
196 $module = $this->topModule($project, $repo_name);
197 if (forge_get_config('use_shell_limited')) {
198 $b .= html_e('tt', array(), 'svn '.$ssh_port.'checkout svn+ssh://'.$d.'@'.$this->getBoxForProject($project).'/'.$repo_name.$module).html_e('br');
200 $b .= html_e('tt', array(), 'svn '.$ssh_port.'checkout svn+ssh://'.$d.'@'.$this->getBoxForProject($project).$this->svn_root_fs.'/'.$repo_name.$module).html_e('br');
205 if (forge_get_config('use_dav', 'scmsvn')) {
206 $b .= '<div id="tabber-svndav" class="tabbertab" >';
208 $b .= _('Enter your site password when prompted.');
210 foreach ($repo_list as $repo_name) {
211 $module = $this->topModule($project, $repo_name);
212 $b .= html_e('tt', 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');
217 if (forge_get_config('use_ssh', 'scmsvn')) {
218 $b .= '<div id="tabber-svnssh" class="tabbertab" >';
220 $b .= _('SSH must be installed on your client machine.');
222 $b .= _('Substitute <em>developername</em> with the proper value.');
224 $b .= _('Enter your site password when prompted.');
227 if (forge_get_config('ssh_port') != 22) {
228 $ssh_port = '--config-option="config:tunnels:ssh=ssh -p '.forge_get_config('ssh_port').'" ';
230 foreach ($repo_list as $repo_name) {
231 $module = $this->topModule($project, $repo_name);
232 if (forge_get_config('use_shell_limited')) {
233 $b .= html_e('tt', array(), 'svn '.$ssh_port.'checkout svn+ssh://<i>'._('developername').'</i>@'.$this->getBoxForProject($project).'/'.$repo_name.$module).html_e('br');
235 $b .= html_e('tt', array(), 'svn '.$ssh_port.'checkout svn+ssh://<i>'._('developername').'</i>@'.$this->getBoxForProject($project).$this->svn_root_fs .'/'.$repo_name.$module).html_e('br');
240 if (forge_get_config('use_dav', 'scmsvn')) {
241 $b .= '<div id="tabber-svndav" class="tabbertab" >';
243 $b .= _('Substitute <em>developername</em> with the proper value.');
245 $b .= _('Enter your site password when prompted.');
247 foreach ($repo_list as $repo_name) {
248 $module = $this->topModule($project, $repo_name);
249 $b .= html_e('tt', 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');
258 function getSnapshotPara($project) {
260 $filename = $project->getUnixName().'-scm-latest.tar'.util_get_compressed_file_extension();
261 if (file_exists(forge_get_config('scm_snapshots_path').'/'.$filename)) {
262 $b .= html_e('p', array(), '['.util_make_link("/snapshots.php?group_id=".$project->getID(), _('Download the nightly snapshot')).']');
267 function getBrowserLinkBlock($project) {
268 $b = html_e('h2', array(), _('Subversion Repository Browser'));
269 $b .= html_e('p', array(),_("Browsing the Subversion tree gives you a view into the current status of this project's code.")
271 ._('You may also view the complete histories of any file in the repository.'));
272 $b .= html_e('p', array(), '['.util_make_link ("/scm/browser.php?group_id=".$project->getID().'&scm_plugin='.$this->name, sprintf(_('Browse %s Repository'), 'Subversion')).']');
274 $repo_list = $this->getRepositories($project, false);
275 foreach ($repo_list as $repo_name) {
276 $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');
281 function getStatsBlock($project) {
285 $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',
286 array($project->getID()));
288 if (db_numrows($result) > 0) {
289 $tableHeaders = array(
294 $b .= $HTML->listTableTop($tableHeaders, array(), '', 'repo-history');
297 $total = array('adds' => 0, 'updates' => 0);
299 while($data = db_fetch_array($result)) {
301 $cells[] = array(util_display_user($data['user_name'], $data['user_id'], $data['realname']), 'class' => 'halfwidth');
302 $cells[] = array($data['adds'], 'class' => 'onequarterwidth align-right');
303 $cells[] = array($data['updates'], 'class' => 'onequarterwidth align-right');
304 $b .= $HTML->multiTableRow(array(), $cells);
305 $total['adds'] += $data['adds'];
306 $total['updates'] += $data['updates'];
310 $cells[] = array(html_e('strong', array(), _('Total')._(':')), 'class' => 'halfwidth');
311 $cells[] = array($total['adds'], 'class' => 'onequarterwidth align-right');
312 $cells[] = array($total['updates'], 'class' => 'onequarterwidth align-right');
313 $b .= $HTML->multiTableRow(array(), $cells);
314 $b .= $HTML->listTableBottom();
316 $b .= $HTML->warning_msg(_('No history yet.'));
322 function printBrowserPage($params) {
323 if ($params['scm_plugin'] != $this->name) {
328 $project = $this->checkParams($params);
332 if (isset($params['extra']) && !empty($params['extra']) && $params['extra'] != 'none') {
333 $iframe_src = '/scm/viewvc.php?root='.$params['extra'];
335 $iframe_src = '/scm/viewvc.php?root='.$project->getUnixName();
337 if ($params['commit']) {
338 $iframe_src .= '&view=rev&revision='.$params['commit'];
340 htmlIframe($iframe_src, array('id'=>'scmsvn_iframe'));
343 function createOrUpdateRepo($params) {
344 $project = $this->checkParams($params);
345 if (!$project) return false;
346 if (!$project->isActive()) return false;
348 $repo_prefix = forge_get_config('repos_path', 'scmsvn');
349 if (!is_dir($repo_prefix) && !mkdir($repo_prefix, 0755, true)) {
353 $repo = $repo_prefix.'/'.$project->getUnixName();
355 if (!is_dir ($repo) || !is_file ("$repo/format")) {
356 if (!mkdir($repo, 0700, true)) {
360 system ("svnadmin create $repo", $ret);
364 system ("sed -i '/enable-rep-sharing = false/s/^. //' $repo/db/fsfs.conf") ;
365 // dav/ and dav/activities.d directories are required by old svn clients (eg. svn 1.6.17 on ubuntu 12.04)
366 if (!is_dir ("$repo/dav")) {
369 if (!is_dir ("$repo/dav/activities.d")) {
370 mkdir("$repo/dav/activities.d");
372 system ("svn mkdir -m'Init' file:///$repo/trunk file:///$repo/tags file:///$repo/branches >/dev/null") ;
373 system ("find $repo -type d -print0 | xargs -r -0 chmod g+s") ;
374 // Allow read/write users to modify the SVN repository
375 $rw_unix_group = $project->getUnixName() . '_scmrw';
376 system("chgrp -R $rw_unix_group $repo");
377 // Allow read-only users to enter the (top-level) directory
378 $ro_unix_group = $project->getUnixName() . '_scmro';
379 system("chgrp $ro_unix_group $repo");
380 // open permissions to allow switching private/public easily
381 // see after to restrict the top-level directory
382 system ("chmod -R g+rwX,o+rX-w $repo") ;
385 if ($project->enableAnonSCM()) {
386 system("chmod g+rX-w,o+rX-w $repo") ;
388 system("chmod g+rX-w,o-rwx $repo") ;
391 // Create project-wide secondary repositories
392 $result = db_query_params('SELECT repo_name FROM scm_secondary_repos WHERE group_id = $1 AND next_action = $2 AND plugin_id = $3',
393 array($project->getID(),
394 SCM_EXTRA_REPO_ACTION_UPDATE,
396 $rows = db_numrows($result);
397 for ($i = 0; $i < $rows; $i++) {
398 $repo_name = db_result($result, $i, 'repo_name');
399 $repo = $repo_prefix.'/'.$repo_name;
400 if (!is_dir($repo) || !is_file("$repo/format")) {
401 if (!mkdir($repo, 0700, true)) {
405 system ("svnadmin create $repo", $ret);
409 system ("sed -i '/enable-rep-sharing = false/s/^. //' $repo/db/fsfs.conf") ;
410 // dav/ directory is required by old svn clients (eg. svn 1.6.17 on ubuntu 12.04)
411 if (!is_dir ("$repo/dav")) {
414 system ("svn mkdir -m'Init' file:///$repo/trunk file:///$repo/tags file:///$repo/branches >/dev/null") ;
415 system ("find $repo -type d -print0 | xargs -r -0 chmod g+s") ;
416 // Allow read/write users to modify the SVN repository
417 $rw_unix_group = $project->getUnixName() . '_scmrw';
418 system("chgrp -R $rw_unix_group $repo");
419 // Allow read-only users to enter the (top-level) directory
420 $ro_unix_group = $project->getUnixName() . '_scmro';
421 system("chgrp $ro_unix_group $repo");
422 // open permissions to allow switching private/public easily
423 // see after to restrict the top-level directory
424 system ("chmod -R g+rwX,o+rX-w $repo") ;
426 if ($project->enableAnonSCM()) {
427 system("chmod g+rX-w,o+rX-w $repo") ;
429 system("chmod g+rX-w,o-rwx $repo") ;
433 // Delete project-wide secondary repositories
434 $result = db_query_params('SELECT repo_name FROM scm_secondary_repos WHERE group_id=$1 AND next_action = $2 AND plugin_id=$3',
435 array($project->getID(),
436 SCM_EXTRA_REPO_ACTION_DELETE,
438 $rows = db_numrows ($result);
439 for ($i=0; $i<$rows; $i++) {
440 $repo_name = db_result($result, $i, 'repo_name');
441 $repodir = $repo_prefix.'/'.$repo_name;
442 if (util_is_valid_repository_name($repo_name)) {
443 system("rm -rf $repodir");
445 db_query_params ('DELETE FROM scm_secondary_repos WHERE group_id=$1 AND repo_name=$2 AND next_action = $3 AND plugin_id=$4',
446 array($project->getID(),
448 SCM_EXTRA_REPO_ACTION_DELETE,
451 $this->regenApacheAuth($params);
454 function updateRepositoryList(&$params) {
457 function regenApacheAuth(&$params) {
458 # Enable /authscm/$user/svn URLs
459 $config_fname = forge_get_config('data_path').'/scmsvn-auth.inc';
460 $config_f = fopen($config_fname.'.new', 'w');
462 $res = db_query_params("SELECT login FROM nss_passwd WHERE status=$1", array('A'));
463 while ($arr = db_fetch_array($res)) {
464 fwrite($config_f, 'Use ScmsvnUser '.$arr['login']."\n");
468 chmod($config_fname.'.new', 0644);
469 rename($config_fname.'.new', $config_fname);
472 function gatherStats($params) {
473 global $last_user, $last_time, $last_tag, $time_ok, $start_time, $end_time,
474 $adds, $deletes, $updates, $commits, $date_key,
475 $usr_adds, $usr_deletes, $usr_updates, $usr_commits;
479 $project = $this->checkParams($params);
484 if ($params['mode'] == 'day') {
487 $year = $params['year'];
488 $month = $params['month'];
489 $day = $params['day'];
490 $month_string = sprintf("%04d%02d", $year, $month);
491 $start_time = gmmktime(0, 0, 0, $month, $day, $year);
492 $end_time = $start_time + 86400;
500 $usr_updates = array();
501 $usr_deletes = array();
502 $usr_commits = array();
504 $repo = forge_get_config('repos_path', 'scmsvn').'/'.$project->getUnixName().'/'.$project->getUnixName();
505 if (!is_dir ($repo) || !is_file ("$repo/format")) {
510 $d1 = date('Y-m-d', $start_time - 150000);
511 $d2 = date('Y-m-d', $end_time + 150000);
513 $pipe = popen ("svn log file://$repo --xml -v -q -r '".'{'.$d2.'}:{'.$d1.'}'."' 2> /dev/null", 'r' ) ;
515 // cleaning stats_cvs_* table for the current day
516 $res = db_query_params('DELETE FROM stats_cvs_group WHERE month = $1 AND day = $2 AND group_id = $3 AND reponame = $4',
520 $project->getUnixName()));
522 echo "Error while cleaning stats_cvs_group\n";
527 $res = db_query_params('DELETE FROM stats_cvs_user WHERE month = $1 AND day = $2 AND group_id = $3 AND reponame = $4',
531 $project->getUnixName()));
533 echo "Error while cleaning stats_cvs_user\n" ;
538 $xml_parser = xml_parser_create();
539 xml_set_element_handler($xml_parser, "SVNPluginStartElement", "SVNPluginEndElement");
540 xml_set_character_data_handler($xml_parser, "SVNPluginCharData");
542 // Analyzing history stream
543 while (!feof($pipe) &&
544 $data = fgets ($pipe, 4096)) {
545 if (!xml_parse ($xml_parser, $data, feof ($pipe))) {
546 $this->setError("Unable to parse XML with error " .
547 xml_error_string(xml_get_error_code($xml_parser)) .
549 xml_get_current_line_number($xml_parser));
556 xml_parser_free($xml_parser);
558 // inserting group results in stats_cvs_groups
559 if ($updates > 0 || $adds > 0 || $deletes > 0 || $commits > 0) {
560 if (!db_query_params('INSERT INTO stats_cvs_group (month, day, group_id, checkouts, commits, adds, updates, deletes, reponame)
561 VALUES ($1, $2, $3, $4, $5, $6, $7, $8)',
570 $project->getUnixName()))) {
571 echo "Error while inserting into stats_cvs_group\n";
577 // building the user list
578 $user_list = array_unique( array_merge( array_keys( $usr_adds ), array_keys( $usr_updates ), array_keys( $usr_deletes ), array_keys( $usr_commits )) );
580 foreach ($user_list as $user) {
581 // Trying to get user id from user name
582 $u = user_get_object_by_name($user);
584 $user_id = $u->getID();
589 $uc = isset($usr_commits[$user]) ? $usr_commits[$user] : 0 ;
590 $uu = isset($usr_updates[$user]) ? $usr_updates[$user] : 0 ;
591 $ua = isset($usr_adds[$user]) ? $usr_adds[$user] : 0 ;
592 $ud = isset($usr_deletes[$user]) ? $usr_deletes[$user] : 0 ;
593 if ($uu > 0 || $ua > 0 || $uc > 0 || $ud > 0) {
594 if (!db_query_params('INSERT INTO stats_cvs_user (month, day, group_id, user_id, commits, adds, updates, deletes, reponame)
595 VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)',
596 array ($month_string,
604 $project->getUnixName()))) {
605 echo "Error while inserting into stats_cvs_user\n";
615 function generateSnapshots($params) {
616 $us = forge_get_config('use_scm_snapshots') ;
617 $ut = forge_get_config('use_scm_tarballs') ;
622 $project = $this->checkParams($params);
627 $group_name = $project->getUnixName();
629 $snapshot = forge_get_config('scm_snapshots_path').'/'.$group_name.'-scm-latest.tar'.util_get_compressed_file_extension();
630 $tarball = forge_get_config('scm_tarballs_path').'/'.$group_name.'-scmroot.tar'.util_get_compressed_file_extension();
632 if (!$project->enableAnonSCM()) {
633 if (is_file($snapshot)) {
636 if (is_file($tarball)) {
642 $toprepo = forge_get_config('repos_path', 'scmsvn');
643 $repo = $toprepo.'/'.$group_name.'/'.$group_name;
645 if (!is_dir($repo) || !is_file ("$repo/format")) {
646 if (is_file($snapshot)) {
649 if (is_file($tarball)) {
655 $tmp = trim(`mktemp -d`);
660 $today = date('Y-m-d');
661 $dir = $project->getUnixName ()."-$today" ;
662 system("mkdir -p $tmp") ;
664 system ("svn ls file://$repo/trunk > /dev/null 2> /dev/null", $code) ;
667 system ("cd $tmp ; svn export file://$repo/trunk $dir > /dev/null 2>&1") ;
668 system ("tar cCf $tmp - $dir |".forge_get_config('compression_method')."> $tmp/snapshot") ;
669 chmod("$tmp/snapshot", 0644);
670 copy("$tmp/snapshot", $snapshot);
671 unlink("$tmp/snapshot");
672 system ("rm -rf $tmp/$dir") ;
674 if (is_file($snapshot)) {
681 system("tar cCf $toprepo - ".$project->getUnixName() ."|".forge_get_config('compression_method')."> $tmp/tarball") ;
682 chmod("$tmp/tarball", 0644);
683 copy("$tmp/tarball", $tarball);
684 unlink("$tmp/tarball");
685 system("rm -rf $tmp");
689 function activity($params) {
690 global $last_user, $last_time, $last_tag, $time_ok, $start_time, $end_time,
691 $adds, $deletes, $updates, $commits, $date_key,
692 $messages, $last_message, $times, $revisions, $users, $xml_parser;
700 $revisions = array();
702 $project = $this->checkParams($params);
707 if (in_array('scmsvn', $params['show']) || (count($params['show']) < 1)) {
708 $start_time = $params['begin'];
709 $end_time = $params['end'];
711 $xml_parser = xml_parser_create();
712 xml_set_element_handler($xml_parser, "SVNPluginStartElement", "SVNPluginEndElement");
713 xml_set_character_data_handler($xml_parser, "SVNPluginCharData");
715 // Grab&parse commit log
716 $protocol = forge_get_config('use_ssl', 'scmsvn') ? 'https://' : 'http://';
717 if ($project->enableAnonSCM()) {
718 $server_script = '/anonscm/svnlog';
720 $u = session_get_user();
721 if ($u && !$u->isError()) {
722 $server_script = '/authscm/'.$u->getUnixName().'/svnlog';
727 $script_url = $protocol.$this->getBoxForProject($project)
729 .'?unix_group_name='.$project->getUnixName()
731 .'&begin='.$params['begin']
732 .'&end='.$params['end'];
734 curl_setopt($ch, CURLOPT_URL, $script_url);
735 curl_setopt($ch, CURLOPT_WRITEFUNCTION, 'curl2xml');
736 curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
737 curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
738 curl_setopt($ch, CURLOPT_COOKIE, @$_SERVER['HTTP_COOKIE']); // for session validation
739 curl_setopt($ch, CURLOPT_USERAGENT, $_SERVER['HTTP_USER_AGENT']); // for session validation
740 curl_setopt($ch, CURLOPT_HTTPHEADER,
741 array('X-Forwarded-For: '.$_SERVER['REMOTE_ADDR'])); // for session validation
742 $body = curl_exec($ch);
743 if ($body === false) {
744 $this->setError(curl_error($ch));
749 if (!xml_parse($xml_parser, '', true))
750 $this->setError('Unable to parse XML with error '
751 . xml_error_string(xml_get_error_code($xml_parser))
752 . ' on line ' . xml_get_current_line_number($xml_parser));
753 xml_parser_free($xml_parser);
755 if ($adds > 0 || $updates > 0 || $commits > 0 || $deletes > 0) {
757 foreach ($messages as $message) {
759 $result['section'] = 'scm';
760 $result['group_id'] = $project->getID();
761 $result['ref_id'] = 'browser.php?group_id='.$project->getID().'&scm_plugin='.$this->name;
762 $result['description'] = htmlspecialchars($message).' (r'.$revisions[$i].')';
763 $userObject = user_get_object_by_name($users[$i]);
764 if (is_a($userObject, 'FFUser')) {
765 $result['realname'] = util_display_user($userObject->getUnixName(), $userObject->getID(), $userObject->getRealName());
767 $result['realname'] = '';
769 $result['activity_date'] = $times[$i];
770 $result['subref_id'] = '&commit='.$revisions[$i];
771 $params['results'][] = $result;
776 if (!in_array($this->name, $params['ids']) && ($project->enableAnonSCM() || session_loggedin())) {
777 $params['ids'][] = $this->name;
778 $params['texts'][] = _('Subversion Commits');
783 // Get latest commits for inclusion in a widget
784 function getCommits($project, $user = null, $nbCommits) {
785 global $commits, $users, $adds, $updates, $messages, $times, $revisions, $deletes, $time_ok, $user_list, $last_message, $notimecheck, $xml_parser;
792 $revisions = array();
795 $user_list = array();
798 $revisionsArr = array();
799 if ($project->usesPlugin($this->name) && forge_check_perm('scm', $project->getID(), 'read')) {
800 $xml_parser = xml_parser_create();
801 xml_set_element_handler($xml_parser, "SVNPluginStartElement", "SVNPluginEndElement");
802 xml_set_character_data_handler($xml_parser, "SVNPluginCharData");
804 // Grab&parse commit log
805 $protocol = forge_get_config('use_ssl', 'scmsvn') ? 'https://' : 'http://';
806 $u = session_get_user();
807 if ($project->enableAnonSCM())
808 $server_script = '/anonscm/svnlog';
810 $server_script = '/authscm/'.$u->getUnixName().'/svnlog';
812 $userunixname = $user->getUnixName();
813 $params = '&mode=latest_user&user_name='.$userunixname;
815 $params = '&mode=latest';
817 $script_url = $protocol.$this->getBoxForProject($project)
819 .'?unix_group_name='.$project->getUnixName()
821 .'&limit='.$nbCommits;
823 curl_setopt($ch, CURLOPT_URL, $script_url);
824 curl_setopt($ch, CURLOPT_WRITEFUNCTION, 'curl2xml');
825 curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
826 curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
827 curl_setopt($ch, CURLOPT_COOKIE, $_SERVER['HTTP_COOKIE']); // for session validation
828 curl_setopt($ch, CURLOPT_USERAGENT, $_SERVER['HTTP_USER_AGENT']); // for session validation
829 curl_setopt($ch, CURLOPT_HTTPHEADER,
830 array('X-Forwarded-For: '.$_SERVER['REMOTE_ADDR'])); // for session validation
831 $body = curl_exec($ch);
832 if ($body === false) {
833 $this->setError(curl_error($ch));
838 if (!xml_parse($xml_parser, '', true))
839 $this->setError('Unable to parse XML with error '
840 . xml_error_string(xml_get_error_code($xml_parser))
841 . ' on line ' . xml_get_current_line_number($xml_parser));
842 xml_parser_free($xml_parser);
844 if ($adds > 0 || $updates > 0 || $commits > 0 || $deletes > 0) {
846 foreach ($messages as $message) {
847 if ($user && ($users[$i] == $userunixname)) {
848 $revisionsArr[$i]['pluginName'] = 'scmsvn';
849 $revisionsArr[$i]['description'] = htmlspecialchars($message);
850 $revisionsArr[$i]['commit_id'] = $revisions[$i];
851 $revisionsArr[$i]['date'] = $times[$i];
853 $revisionsArr[$i]['pluginName'] = 'scmsvn';
854 $revisionsArr[$i]['description'] = htmlspecialchars($message);
855 $revisionsArr[$i]['commit_id'] = $revisions[$i];
856 $revisionsArr[$i]['date'] = $times[$i];
862 return $revisionsArr;
865 function scm_add_repo(&$params) {
866 $project = $this->checkParams($params);
871 if (!isset($params['repo_name'])) {
875 if ($params['repo_name'] == $project->getUnixName()) {
876 $params['error_msg'] = _('Cannot create a secondary repository with the same name as the primary');
880 if (!util_is_valid_repository_name($params['repo_name'])) {
881 $params['error_msg'] = _('This repository name is not valid');
885 $result = db_query_params('SELECT count(*) AS count FROM scm_secondary_repos WHERE repo_name = $1 AND plugin_id=$2',
886 array($params['repo_name'], $this->getID()));
888 $params['error_msg'] = db_error();
891 if (db_result($result, 0, 'count')) {
892 $params['error_msg'] = sprintf(_('A repository %s already exists'), $params['repo_name']);
897 if (isset($params['description'])) {
898 $description = $params['description'];
901 $description = "Subversion repository $params[repo_name] for project ".$project->getUnixName();
903 $result = db_query_params('INSERT INTO scm_secondary_repos (group_id, repo_name, description, clone_url, plugin_id) VALUES ($1, $2, $3, $4, $5)',
904 array($params['group_id'],
905 $params['repo_name'],
910 $params['error_msg'] = db_error();
914 plugin_hook ("scm_admin_update", $params);
918 function get_scm_repo_list(&$params) {
919 if (array_key_exists('group_name',$params)) {
920 $unix_group_name = $params['group_name'];
922 $unix_group_name = '';
924 $protocol = forge_get_config('use_ssl', 'scmsvn')? 'https' : 'http';
925 if (session_loggedin()) {
926 $u = user_get_object(user_getid());
927 $d = $u->getUnixName();
931 if ($unix_group_name) {
932 $res = db_query_params("SELECT unix_group_name, groups.group_id FROM groups
933 JOIN group_plugin ON (groups.group_id=group_plugin.group_id)
934 WHERE groups.status=$1 AND group_plugin.plugin_id=$2 AND groups.unix_group_name=$3
935 ORDER BY unix_group_name", array('A', $this->getID(),$unix_group_name));
937 $res = db_query_params("SELECT unix_group_name, groups.group_id FROM groups
938 JOIN group_plugin ON (groups.group_id=group_plugin.group_id)
939 WHERE groups.status=$1 AND group_plugin.plugin_id=$2
940 ORDER BY unix_group_name", array('A', $this->getID()));
942 while ($arr = db_fetch_array($res)) {
943 if (!forge_check_perm('scm', $arr['group_id'], 'read')) {
947 if (forge_get_config('use_dav', 'scmsvn')) {
948 $urls[] = $protocol.'://'.$this->getBoxForProject($project).'/anonscm/svn/'.$arr['unix_group_name'];
950 if (forge_get_config('use_ssh', 'scmsvn')) {
951 $urls[] = 'svn://'.$this->getBoxForProject($project).$this->svn_root_fs.'/'.$arr['unix_group_name'];
953 if (session_loggedin()) {
954 if (forge_get_config('use_dav', 'scmsvn')) {
955 $urls[] = $protocol.'://'.$this->getBoxForProject($project).'/authscm/'.$d.'/svn/'.$arr['unix_group_name'];
957 if (forge_get_config('use_ssh', 'scmsvn')) {
958 $urls[] = 'svn+ssh://'.$d.'@'.$this->getBoxForProject($project).$this->svn_root_fs .'/'. $arr['unix_group_name'];
961 $results[] = array('group_id' => $arr['group_id'],
962 'repository_type' => 'svn',
963 'repository_id' => $arr['unix_group_name'].'/svn/'.$arr['unix_group_name'],
964 'repository_urls' => $urls,
968 foreach ($results as $res) {
969 $params['results'][] = $res;
973 function get_scm_repo_info(&$params) {
974 $rid = $params['repository_id'];
975 $e = explode('/',$rid);
976 if ($e[1] != 'svn') {
980 $p = array('group_name' => $g);
981 $this->get_scm_repo_list($p);
982 foreach ($p['results'] as $r) {
983 if ($r['repository_id'] == $rid) {
984 $params['results'] = $r;
990 function parse_scm_repo_activities(&$params) {
992 $res = db_query_params("SELECT unix_group_name, groups.group_id FROM groups
993 JOIN group_plugin ON (groups.group_id=group_plugin.group_id)
994 WHERE groups.status=$1 AND group_plugin.plugin_id=$2
995 ORDER BY unix_group_name", array('A', $this->getID()));
996 while ($arr = db_fetch_array($res)) {
998 $el['rpath'] = $this->svn_root_fs.'/'.$arr['unix_group_name'];
999 $el['rid'] = $arr['unix_group_name'].'/svn/'.$arr['unix_group_name'];
1000 $el['gid'] = $arr['group_id'];
1004 $lastactivities = array();
1005 $res = db_query_params("SELECT repository_id, max(tstamp) AS last FROM scm_activities WHERE plugin_id=$1 GROUP BY repository_id",
1006 array($this->getID()));
1007 while ($arr = db_fetch_array($res)) {
1008 $lastactivities[$arr['repository_id']] = $arr['last'];
1011 foreach ($repos as $rdata) {
1013 if (array_key_exists($rdata['rid'], $lastactivities)) {
1014 $since = '-r {$(date -d @'.$lastactivities[$rdata['rid']].' -Iseconds)}:HEAD';
1016 $rpath = $rdata['rpath'];
1018 $f = popen("svn log -q 'file:///$rpath' $since 2> /dev/null", "r");
1019 while (($l = fgets($f, 4096)) !== false) {
1020 if (preg_match("/.*?\|.*\|(?P<tstamp>[-0-9 :+]+)/", $l, $matches)) {
1021 $t = strtotime($matches['tstamp']);
1022 if (array_key_exists($rdata['rid'], $lastactivities)
1023 && $t <= $lastactivities[$rdata['rid']]) {
1029 foreach ($tstamps as $t => $v) {
1030 $res = db_query_params("INSERT INTO scm_activities (group_id, plugin_id, repository_id, tstamp) VALUES ($1,$2,$3,$4)",
1031 array($rdata['gid'],
1039 function scm_admin_form(&$params) {
1041 $project = $this->checkParams($params);
1046 session_require_perm('project_admin', $params['group_id']);
1047 if (forge_get_config('allow_multiple_scm') && ($params['allow_multiple_scm'] > 1)) {
1048 echo html_ao('div', array('id' => 'tabber-'.$this->name, 'class' => 'tabbertab'));
1051 $project_name = $project->getUnixName();
1053 $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',
1054 array($params['group_id'],
1055 SCM_EXTRA_REPO_ACTION_UPDATE,
1058 $params['error_msg'] = db_error();
1061 $existing_repos = array();
1062 while($data = db_fetch_array($result)) {
1063 $existing_repos[] = array('repo_name' => $data['repo_name'],
1064 'description' => $data['description']);
1066 if (count($existing_repos) == 0) {
1067 echo $HTML->information(_('No extra Subversion repository for project').' '.$project_name);
1069 echo html_e('h2', array(), sprintf(ngettext('Extra Subversion repository for project %1$s',
1070 'Extra Subversion repositories for project %1$s',
1071 count($existing_repos)), $project_name));
1072 $titleArr = array(_('Repository name'), ('Initial repository description'), _('Delete'));
1073 echo $HTML->listTableTop($titleArr);
1074 foreach ($existing_repos as $key => $repo) {
1076 $cells[][] = html_e('kbd', array(), $repo['repo_name']);
1077 $cells[][] = $repo['description'];
1078 $deleteForm = $HTML->openForm(array('name' => 'form_delete_repo_'.$repo['repo_name'], 'action' => getStringFromServer('PHP_SELF'), 'method' => 'post'));
1079 $deleteForm .= html_e('input', array('type' => 'hidden', 'name' => 'group_id', 'value' => $params['group_id']));
1080 $deleteForm .= html_e('input', array('type' => 'hidden', 'name' => 'delete_repository', 'value' => 1));
1081 $deleteForm .= $HTML->html_input('scm_plugin_id', '', '', 'hidden', $this->getID());
1082 $deleteForm .= html_e('input', array('type' => 'hidden', 'name' => 'repo_name', 'value' => $repo['repo_name']));
1083 $deleteForm .= html_e('input', array('type' => 'submit', 'name' => 'submit', 'value' => _('Delete')));
1084 $deleteForm .= $HTML->closeForm();
1085 $cells[][] = $deleteForm;
1086 echo $HTML->multiTableRow(array(), $cells);
1088 echo $HTML->listTableBottom();
1091 echo html_e('h2', array(), _('Create new Subversion repository for project').' '.$project_name);
1092 echo $HTML->openForm(array('name' => 'form_create_repo', 'action' => getStringFromServer('PHP_SELF'), 'method' => 'post'));
1093 echo html_e('input', array('type' => 'hidden', 'name' => 'group_id', 'value' => $params['group_id']));
1094 echo html_e('input', array('type' => 'hidden', 'name' => 'create_repository', 'value' => 1));
1095 echo html_e('p', array(), html_e('strong', array(), _('Repository name')._(':')).utils_requiredField().html_e('br').
1096 html_e('input', array('type' => 'text', 'required' => 'required', 'size' => 20, 'name' => 'repo_name', 'value' => '')));
1097 echo html_e('p', array(), html_e('strong', array(), _('Description')._(':')).html_e('br').
1098 html_e('input', array('type' => 'text', 'size' => 60, 'name' => 'description', 'value' => '')));
1099 echo html_e('input', array('type' => 'submit', 'name' => 'cancel', 'value' => _('Cancel')));
1100 echo html_e('input', array('type' => 'submit', 'name' => 'submit', 'value' => _('Submit')));
1101 echo $HTML->closeForm();
1103 if ($project->usesPlugin('scmhook')) {
1104 $scmhookPlugin = plugin_get_object('scmhook');
1105 $scmhookPlugin->displayScmHook($project->getID(), $this->name);
1107 if (forge_get_config('allow_multiple_scm') && ($params['allow_multiple_scm'] > 1)) {
1108 echo html_ac(html_ap() - 1);
1112 function getRepositories($group, $autoinclude = true) {
1115 $repoarr[] = $group->getUnixName();
1117 $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',
1118 array($group->getID(),
1119 SCM_EXTRA_REPO_ACTION_UPDATE,
1121 while ($arr = db_fetch_array($result)) {
1122 $repoarr[] = $arr['repo_name'];
1127 function getGroupIdFromSecondReponame($repo_name) {
1128 $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));
1129 $arr = db_fetch_array($result);
1130 return $arr['group_id'];
1134 // End of class, helper functions now
1136 function SVNPluginCharData($parser, $chars) {
1137 global $last_tag, $last_user, $last_time, $start_time, $end_time, $usr_commits, $commits,
1138 $time_ok, $user_list, $last_message, $messages, $times, $users, $notimecheck;
1139 switch ($last_tag) {
1141 $last_user = preg_replace('/[^a-z0-9_-]/', '', strtolower(trim($chars)));
1142 $users[] = $last_user;
1143 $usr_commits[$last_user] = isset($usr_commits[$last_user]) ? ($usr_commits[$last_user]+1) : 1 ;
1148 $chars = preg_replace('/T(\d\d:\d\d:\d\d)\.\d+Z?$/', ' ${1}', $chars);
1149 $last_time = strtotime($chars);
1150 if ($start_time <= $last_time && $last_time < $end_time) {
1154 if (!isset($notimecheck)) {
1155 if ($last_user !== '') // empty in e.g. tags from cvs2svn
1156 $usr_commits[$last_user]--;
1160 $times[] = $last_time;
1164 if ($time_ok === true || isset($notimecheck)) {
1165 $messages[count($messages)-1] .= $chars;
1167 /* note: there may be more than one msg
1168 * (happen when the message contain accents).
1175 function SVNPluginStartElement($parser, $name, $attrs) {
1176 global $last_user, $last_time, $last_tag, $time_ok, $commits,
1177 $adds, $updates, $usr_adds, $usr_updates, $last_message, $messages, $times, $revisions, $deletes, $usr_deletes, $notimecheck;
1181 // Make sure we clean up before doing a new log entry
1184 $revisions[] = $attrs['REVISION'];
1188 if ($time_ok === true || isset($notimecheck)) {
1190 if ($attrs['ACTION'] == "M") {
1193 $usr_updates[$last_user] = isset($usr_updates[$last_user]) ? ($usr_updates[$last_user]+1) : 1 ;
1195 } elseif ($attrs['ACTION'] == "A") {
1198 $usr_adds[$last_user] = isset($usr_adds[$last_user]) ? ($usr_adds[$last_user]+1) : 1 ;
1200 } elseif ($attrs['ACTION'] == 'D') {
1203 $usr_deletes[$last_user] = isset($usr_deletes[$last_user]) ? ($usr_deletes[$last_user]+1) : 1 ;
1210 if ($time_ok === true || isset($notimecheck)) {
1218 function SVNPluginEndElement($parser, $name) {
1223 function curl2xml($ch, $data) {
1225 if (!xml_parse($xml_parser, $data, false))
1226 exit_error('Unable to parse XML with error '
1227 . xml_error_string(xml_get_error_code($xml_parser))
1228 . ' on line ' . xml_get_current_line_number($xml_parser),
1230 return strlen($data);
1234 // c-file-style: "bsd"