3 * FusionForge Darcs plugin
5 * Copyright 2009, Roland Mas
6 * Copyright 2013, Franck Villaume - TrivialDev
8 * This file is part of FusionForge.
10 * FusionForge is free software; you can redistribute it and/or modify
11 * it under the terms of the GNU General Public License as published
12 * by the Free Software Foundation; either version 2 of the License,
13 * or (at your option) any later version.
15 * FusionForge is distributed in the hope that it will be useful, but
16 * WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
18 * General Public License for more details.
20 * You should have received a copy of the GNU General Public License along
21 * with this program; if not, write to the Free Software Foundation, Inc.,
22 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
25 forge_define_config_item ('default_server', 'scmdarcs', forge_get_config ('web_host')) ;
26 forge_define_config_item ('repos_path', 'scmdarcs', forge_get_config('chroot').'/scmrepos/darcs') ;
28 class DarcsPlugin extends SCMPlugin {
29 function DarcsPlugin () {
32 $this->name = 'scmdarcs';
33 $this->text = 'Darcs';
34 $this->hooks[] = 'scm_generate_snapshots' ;
35 $this->hooks[] = 'scm_update_repolist' ;
36 $this->hooks[] = 'scm_browser_page' ;
37 $this->hooks[] = 'scm_gather_stats' ;
42 function getDefaultServer() {
43 return forge_get_config('default_server', 'scmdarcs') ;
46 function getRootRepositories ($project) {
47 return (forge_get_config('repos_path', 'scmdarcs') . '/' . $project->getUnixName());
50 function getRepositories ($project) {
52 $toprepo = $this->getRootRepositories($project);
55 foreach (scandir($toprepo) as $repo_name)
57 $repo = $toprepo . '/' . $repo_name;
58 if (is_dir($repo) && is_dir($repo . '/_darcs'))
67 function printShortStats ($params) {
68 $project = $this->checkParams ($params) ;
73 if ($project->usesPlugin($this->name) && forge_check_perm('scm', $project->getID(), 'read')) {
74 $result = db_query_params('SELECT sum(commits) AS commits, sum(adds) AS adds FROM stats_cvs_group WHERE group_id=$1',
75 array ($project->getID())) ;
76 $commit_num = db_result($result,0,'commits');
77 $add_num = db_result($result,0,'adds');
84 echo ' (Darcs: '.sprintf(_('<strong>%1$s</strong> commits, <strong>%2$s</strong> adds'), number_format($commit_num, 0), number_format($add_num, 0)).")";
88 function getBlurb () {
90 . sprintf(_('Documentation for %1$s is available at <a href="%2$s">%2$s</a>.'),
96 function getInstructionForDarcs ($project, $rw) {
97 $repo_names = $this->getRepositories($project);
98 if (count($repo_names) > 0)
100 $default_repo = "REPO";
101 if (count($repo_names) == 1)
103 $default_repo = $repo_names[0];
107 $url = $this->getBoxForProject($project) . ':'. $this->getRootRepositories($project) . '/' . $default_repo;
111 $url = util_make_url ('/anonscm/darcs/'.$project->getUnixName().'/' . $default_repo);
113 $b = '<p><tt>darcs get ' . $url . '</tt></p>';
114 if (count($repo_names) > 1)
116 $b .= '<p>'._('where REPO can be: ') . implode(_(', '), $repo_names) . '</p>';
119 else if (is_dir($this->getRootRepositories($project)))
121 $b = '<p><em>'._('No repositories defined.').'</em></p>';
125 $b = '<p><em>'._('Repository not yet created, wait an hour.').'</em></p>';
130 function getInstructionsForAnon ($project) {
132 $b .= _('Anonymous Darcs Access');
135 $b .= _('This project\'s Darcs repository can be checked out through anonymous access with the following command.');
137 $b .= $this->getInstructionForDarcs($project, false);
142 function getInstructionsForRW ($project) {
144 $b .= sprintf(_('Developer %s Access via SSH'), 'Darcs');
147 $b .= sprintf(_('Only project developers can access the %s tree via this method.'), 'Darcs');
149 $b .= _('SSH must be installed on your client machine.');
151 $b .= _('Substitute <i>developername</i> with the proper values.');
153 $b .= _('Enter your site password when prompted.');
155 $b .= $this->getInstructionForDarcs($project, true);
159 function getSnapshotPara ($project) {
162 $filename = $project->getUnixName().'-scm-latest.tar'.util_get_compressed_file_extension();
163 if (file_exists(forge_get_config('scm_snapshots_path').'/'.$filename)) {
165 $b .= util_make_link ("/snapshots.php?group_id=".$project->getID(),
166 _('Download the nightly snapshot')
173 function getBrowserLinkBlock ($project) {
175 $b = $HTML->boxMiddle(sprintf(_('%s Repository Browser'), 'Darcs'));
177 $b .= sprintf(_("Browsing the %s tree gives you a view into the current status of this project's code."), 'Darcs');
179 $b .= _('You may also view the complete histories of any file in the repository.');
181 $repo_names = $this->getRepositories($project);
182 if (count($repo_names) > 0)
184 foreach ($repo_names as $repo_name)
187 $b .= util_make_link ("/scm/browser.php?group_id=".$project->getID()."&repo_name=".$repo_name,
188 sprintf(_('Browse %s Repository'), 'Darcs') .' ' . $repo_name);
194 $b .= '<p>'._('No repositories to browse').'</p>';
199 function getStatsBlock ($project) {
203 $result = db_query_params('SELECT u.realname, u.user_name, u.user_id, sum(commits) as commits, sum(adds) as adds, sum(adds+commits) as combined FROM stats_cvs_user s, users u WHERE group_id=$1 AND s.user_id=u.user_id AND (commits>0 OR adds >0) GROUP BY u.user_id, realname, user_name, u.user_id ORDER BY combined DESC, realname',
204 array ($project->getID()));
206 if (db_numrows($result) > 0) {
207 $b .= $HTML->boxMiddle(_('Repository Statistics'));
209 $tableHeaders = array(
214 $b .= $HTML->listTableTop($tableHeaders);
217 $total = array('adds' => 0, 'commits' => 0);
219 while($data = db_fetch_array($result)) {
220 $b .= '<tr '. $HTML->boxGetAltRowStyle($i) .'>';
221 $b .= '<td width="50%">' ;
222 $b .= util_make_link_u ($data['user_name'], $data['user_id'], $data['realname']) ;
223 $b .= '</td><td width="25%" align="right">'.$data['adds']. '</td>'.
224 '<td width="25%" align="right">'.$data['commits'].'</td></tr>';
225 $total['adds'] += $data['adds'];
226 $total['commits'] += $data['commits'];
229 $b .= '<tr '. $HTML->boxGetAltRowStyle($i) .'>';
230 $b .= '<td width="50%"><strong>'._('Total').':</strong></td>'.
231 '<td width="25%" align="right"><strong>'.$total['adds']. '</strong></td>'.
232 '<td width="25%" align="right"><strong>'.$total['commits'].'</strong></td>';
234 $b .= $HTML->listTableBottom();
240 function printBrowserPage ($params) {
241 $project = $this->checkParams ($params) ;
246 if ($project->usesPlugin ($this->name)) {
247 if ($this->browserDisplayable ($project)) {
248 print '<iframe src="'.
249 util_make_url ("/plugins/scmdarcs/cgi-bin/darcsweb.cgi?r=".
250 $project->getUnixName()). '/' . $params['repo_name'] .
251 '" frameborder="0" width=100% height=700></iframe>' ;
256 function createOrUpdateRepo ($params) {
257 $project = $this->checkParams ($params) ;
262 if (! $project->usesPlugin ($this->name)) {
266 $toprepo = $this->getRootRepositories($project);
267 $unix_group = 'scm_' . $project->getUnixName() ;
269 system("chmod g+ws $toprepo");
271 $result = db_query_params(
272 "SELECT repo_name, clone_repo_name FROM plugin_scmdarcs_create_repos WHERE group_id=$1",
273 array($project->getID()));
276 echo "Error while retrieving darcs repository to create\n";
280 while ($res = db_fetch_array($result))
282 $repo = $toprepo . '/' . $res['repo_name'];
284 if ($res['clone_repo_name'] != '')
286 $clone_repo = $toprepo . '/' . $res['clone_repo_name'];
288 if (!is_dir ($repo."/_darcs")) {
289 system ("mkdir -p '$repo'") ;
290 system ("cd $repo ; darcs init >/dev/null") ;
293 system ("darcs fetch '$clone_repo'") ;
295 system ("find $repo -type d | xargs chmod g+s") ;
297 $result1 = db_query_params(
298 "DELETE FROM plugin_scmdarcs_create_repos WHERE group_id=$1 AND repo_name=$2",
299 array($project->getID(), $res['repo_name']));
302 echo "Cannot remove scheduling of darcs repository creation ".$res['repo_name']."\n";
308 foreach ($this->getRepositories($project) as $repo_name)
310 $repo = $toprepo . '/' . $repo_name ;
312 system ("chgrp -R $unix_group $repo") ;
313 if ($project->enableAnonSCM()) {
314 system ("chmod -R g+wX,o+rX-w $repo") ;
316 system ("chmod -R g+wX,o-rwx $repo") ;
321 function darcswebRepository ($project, $repo_name, $repo_url, $repo_dir) {
322 $classname = preg_replace ('/\W/', '_', 'repo_' . $repo_name) ;
323 return ("class $classname:\n"
324 ."\trepodir = '$repo_dir'\n"
325 ."\treponame = '$repo_name'\n"
326 ."\t".'repodesc = """Repository ' . $repo_name . ' of '.$project->getPublicName().'"""'."\n"
327 ."\trepourl = '" . util_make_url ('/anonscm/darcs/' . $repo_url) . "'\n"
328 ."\trepoprojurl = '" . util_make_url ('/projects/' . $repo_url) . "'\n"
329 ."\trepoencoding = 'utf8'\n"
334 function updateRepositoryList ($params) {
335 $groups = $this->getGroups () ;
337 foreach ($groups as $project) {
338 if ($this->browserDisplayable ($project)) {
343 $fname = '/etc/gforge/plugins/scmdarcs/config.py' ;
345 $f = fopen ($fname.'.new', 'w') ;
347 fwrite ($f, "class base:\n"
348 ."\tdarcslogo = '".util_make_url ('/plugins/scmdarcs/darcsweb/darcs.png')."'\n"
349 ."\tdarcsfav = '".util_make_url ('/plugins/scmdarcs/darcsweb/minidarcs.png')."'\n"
350 ."\tcssfile = '".util_make_url ('/plugins/scmdarcs/darcsweb/style.css')."'\n"
353 foreach ($list as $project) {
354 $unix_name = $project->getUnixName();
355 $toprepo = $this->getRootRepositories($project);
356 $repo_names = $this->getRepositories($project);
357 foreach ($repo_names as $repo_name) {
358 if ($repo_name == $unix_name)
360 # Default repository name, we create a default entry for it
362 $this->darcswebRepository($project,
364 "$unix_name/$repo_name",
365 "$toprepo/$repo_name"));
368 $this->darcswebRepository($project,
369 "$unix_name/$repo_name",
370 "$unix_name/$repo_name",
371 "$toprepo/$repo_name"));
375 chmod ($fname.'.new', 0644) ;
376 rename ($fname.'.new', $fname) ;
379 function generateSnapshots ($params) {
382 $project = $this->checkParams ($params) ;
387 $group_name = $project->getUnixName() ;
389 $tarball = forge_get_config('scm_tarballs_path').'/'.$group_name.'-scmroot.tar'.util_get_compressed_file_extension();
391 if (! $project->usesPlugin ($this->name)) {
395 if (! $project->enableAnonSCM()) {
401 $toprepo = forge_get_config('repos_path', 'scmdarcs') ;
402 $repo = $this->getRootRepositories($project);
404 if (!is_dir ($repo)) {
409 $tmp = trim (`mktemp -d`) ;
413 $today = date ('Y-m-d') ;
414 $dir = $project->getUnixName ()."-$today" ;
415 system ("mkdir -p $tmp/$dir") ;
416 system ("cd $tmp ; darcs $repo $dir > /dev/null 2>&1") ;
417 system ("tar cCf $tmp - $dir |".forge_get_config('compression_method')."> $tmp/snapshot") ;
418 chmod ("$tmp/snapshot", 0644) ;
419 copy ("$tmp/snapshot", $snapshot) ;
420 unlink ("$tmp/snapshot") ;
421 system ("rm -rf $tmp/$dir") ;
423 system ("tar cCf $toprepo - ".$project->getUnixName() ."|".forge_get_config('compression_method')."> $tmp/tarball") ;
424 chmod ("$tmp/tarball", 0644) ;
425 copy ("$tmp/tarball", $tarball) ;
426 unlink ("$tmp/tarball") ;
427 system ("rm -rf $tmp") ;
430 function gatherStats ($params) {
431 global $adds, $deletes, $updates, $commits,
432 $usr_adds, $usr_deletes, $usr_updates;
434 $project = $this->checkParams ($params) ;
439 if (! $project->usesPlugin ($this->name)) {
443 if ($params['mode'] == 'day') {
446 $year = $params ['year'] ;
447 $month = $params ['month'] ;
448 $day = $params ['day'] ;
449 $month_string = sprintf( "%04d%02d", $year, $month );
450 $start_time = gmmktime( 0, 0, 0, $month, $day, $year);
451 $end_time = $start_time + 86400;
456 $usr_adds = array () ;
457 $usr_updates = array () ;
458 $usr_deletes = array ();
460 $toprepo = $this->getRootRepositories($project) ;
461 $from_date = date("c", $start_time);
462 $to_date = date("c", $end_time);
464 // cleaning stats_cvs_* table for the current day
465 $res = db_query_params ('DELETE FROM stats_cvs_group WHERE month=$1 AND day=$2 AND group_id=$3',
466 array ($month_string,
468 $project->getID())) ;
470 echo "Error while cleaning stats_cvs_group\n" ;
475 $res = db_query_params ('DELETE FROM stats_cvs_user WHERE month=$1 AND day=$2 AND group_id=$3',
476 array ($month_string,
478 $project->getID())) ;
480 echo "Error while cleaning stats_cvs_user\n" ;
485 foreach ($this->getRepositories($project) as $repo_name)
487 $repo = $toprepo . '/' . $repo_name;
488 if (!is_dir ($repo) || !is_dir ("$repo/_darcs")) {
489 echo "No repository $repo\n" ;
498 $pipe = popen("darcs changes --repodir='$repo' "
499 ."--match 'date \"between $from_date and $to_date\"' "
502 $xml_parser = xml_parser_create();
503 xml_set_element_handler($xml_parser, "DarcsPluginStartElement", "DarcsPluginEndElement");
505 // Analyzing history stream
506 while (!feof($pipe) &&
507 $data = fgets ($pipe, 4096)) {
509 if (!xml_parse ($xml_parser, $data, feof ($pipe))) {
510 debug("Unable to parse XML with error " .
511 xml_error_string(xml_get_error_code($xml_parser)) .
513 xml_get_current_line_number($xml_parser));
520 xml_parser_free ($xml_parser);
523 // inserting group results in stats_cvs_groups
525 if (!db_query_params ('INSERT INTO stats_cvs_group (month,day,group_id,checkouts,commits,adds) VALUES ($1,$2,$3,$4,$5,$6)',
526 array ($month_string,
532 echo "Error while inserting into stats_cvs_group\n" ;
537 // build map for email -> login
539 $email_login = array();
540 $email_login_fn = $repo."/_darcs/email-login.txt";
541 if (!file_exists($email_login_fn))
543 $email_login_fn = $repo."/.email-login.txt";
545 if (!file_exists($email_login_fn))
547 unset($email_login_fn);
550 if (isset($email_login_fn))
552 $fh = fopen($email_login_fn, 'r');
555 $a = explode(" ", fgets($fh));
558 $email_login[$a[0]] = rtrim($a[1]);
564 // building the user list
565 $user_list = array_unique( array_merge( array_keys( $usr_adds ), array_keys( $usr_updates ) ) );
567 foreach ( $user_list as $user ) {
568 // trying to get user id from darcs user name
570 $tmp_email = explode("<", $id, 2);
571 if (isset($tmp_email[1]))
573 $tmp_email = explode(">", $tmp_email[1]);
576 if (isset($email_login[$id]))
578 $id = $email_login[$id];
581 $u = &user_get_object_by_name ($id) ;
583 $user_id = $u->getID();
588 if (!db_query_params ('INSERT INTO stats_cvs_user (month,day,group_id,user_id,commits,adds) VALUES ($1,$2,$3,$4,$5,$6)',
589 array ($month_string,
593 isset ($usr_updates[$user]) ? $usr_updates[$user] : 0,
594 isset ($usr_adds[$user]) ? $usr_adds[$user] : 0))) {
595 echo "Error while inserting into stats_cvs_user\n" ;
605 function printAdminPage ($params) {
606 parent::printAdminPage($params);
608 $project = $this->checkParams($params);
614 if ($project->usesPlugin($this->name))
616 $result = db_query_params(
617 "SELECT repo_name FROM plugin_scmdarcs_create_repos WHERE group_id=$1",
618 array($project->getID()));
619 if ($result && db_numrows($result) > 0)
622 while ($res = db_fetch_array($result))
624 array_push($nm, $res['repo_name']);
626 print '<p><strong>'._('Repository to be created: ') . '</strong>' .
627 implode(_(', '), $nm) . '</p>';
630 print '<p><strong>'._('Create new repository:') . '</strong></p>';
631 print '<p>'._('Repository name')._(': ');
632 print '<input type="string" name="scm_create_repo_name" size=16 maxlength=128 /></p>';
633 print '<p>'._('Clone')._(': ').
634 '<select name="scm_clone_repo_name">';
635 print '<option value=""><none></option>';
636 foreach ($this->getRepositories($project) as $repo_name)
638 print '<option value="'.$repo_name.'">'.$repo_name.'</option>';
640 print '</select></p>';
644 function adminUpdate ($params) {
645 parent::adminUpdate($params);
647 $project = $this->checkParams($params);
653 $new_repo_name = $params['scm_create_repo_name'];
654 $clone_repo_name = $params['scm_clone_repo_name'];
655 if ($new_repo_name != '')
657 $repo_names = $this->getRepositories($project);
658 if (in_array($new_repo_name, $repo_names))
660 html_error_top(_("Repository $new_repo_name already exists"));
664 if ($clone_repo_name != '' && !in_array($clone_repo_name, $repo_names))
666 html_error_top(_("Clone repository $clone_repo_name doesn't exist"));
669 if ($clone_repo_name == '<none>')
671 $clone_repo_name = '';
674 if (!preg_match('/^[\w][-_\w\d\.]+$/', $new_repo_name))
676 html_error_top("Invalid repository name $new_repo_name");
681 if (!db_query_params ('INSERT INTO plugin_scmdarcs_create_repos (group_id,repo_name,clone_repo_name)
683 array($project->getID(), $new_repo_name, $clone_repo_name)))
685 html_error_top("SQL error while scheduling new repository $new_repo_name");
691 html_feedback_top(_("Repository $new_repo_name schedule for creation"));
697 function DarcsPluginStartElement($parser, $name, $attrs) {
698 global $last_user, $commits,
699 $adds, $updates, $deletes,
700 $usr_adds, $usr_updates, $usr_deletes;
703 $last_user = $attrs['AUTHOR'];
704 if (!array_key_exists($last_user, $usr_deletes))
706 $usr_deletes[$last_user] = 0;
708 if (!array_key_exists($last_user, $usr_updates))
710 $usr_updates[$last_user] = 0;
712 if (!array_key_exists($last_user, $usr_adds))
714 $usr_adds[$last_user] = 0;
719 case "REMOVE_DIRECTORY":
722 $usr_deletes[$last_user]++;
729 $usr_updates[$last_user]++;
733 case "ADD_DIRECTORY":
736 $usr_adds[$last_user]++;
742 function DarcsPluginEndElement ($parser, $name) {
746 // c-file-style: "bsd"