2 /** FusionForge Mercurial (Hg) plugin
4 * Copyright 2009, Roland Mas
5 * Copyright 2012, Denise Patzker
6 * Copyright 2012, 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', 'scmhg', forge_get_config('web_host'));
26 forge_define_config_item('repos_path', 'scmhg', forge_get_config('chroot').'/scmrepos/hg');
28 class HgPlugin extends SCMPlugin {
31 $this->name = 'scmhg';
32 $this->text = 'Mercurial';
33 $this->_addHook('scm_browser_page');
34 $this->_addHook('scm_update_repolist');
35 $this->_addHook('scm_generate_snapshots');
36 $this->_addHook('scm_gather_stats');
41 * getPluginDescription - display the description of this plugin in pluginman admin page
43 * @return string the description
45 function getPluginDescription() {
46 return _('Use Mercuial as Source Code Management tool. Offer DAV or SSH access.');
49 function getDefaultServer() {
50 return forge_get_config('default_server', 'scmhg');
54 return '<p>' . _('Documentation for Mercurial is available at <a href="http://hgbook.red-bean.com/">http://hgbook.red-bean.com/</a> . ')._(' Another short Introduction can be found at <a href="http://hginit.com/">http://hginit.com/</a>').'</p>';
57 function getInstructionsForAnon($project) {
59 $b .= _('Anonymous Mercurial Access');
62 if (forge_get_config('use_dav', 'scmhg')) {
63 $protocol = forge_get_config('use_ssl', 'scmhg')? 'https' : 'http';
65 $b .= 'This project\'s Mercurial repository can be checked out through anonymous access with the following command.';
68 $b .= '<tt>hg clone '.$protocol.'://'.forge_get_config('anonhg_login', 'scmhg').'@' . $this->getBoxForProject($project) . '/'. 'hg' .'/'. $project->getUnixName() .'/'.'</tt><br />';
69 $b .= _('The password is ').forge_get_config('anonhg_password', 'scmhg').'<br/>';
72 $b .= '<p class="warning">'._('Please contact forge administrator, scmhg plugin is not correctly configured');
78 function getInstructionsForRW($project) {
79 $protocol = forge_get_config('use_ssl', 'scmhg')? 'https' : 'http';
80 if (session_loggedin()) {
81 $u = user_get_object(user_getid());
82 $d = $u->getUnixName();
84 if (forge_get_config('use_ssh', 'scmhg')) {
86 $b .= _('Developer Mercurial Access via SSH');
89 $b .= _('Only project developers can access the Mercurial tree via this method. SSH must be installed on your client machine. Enter your site password when prompted.');
91 // Warning : the ssh uri MUST be this form : ssh://username@scmbox//path/reponame
92 // HAVE YOU SEEN THE // starting the path ? Keep in mind the double /
93 $b .= '<p><tt>hg clone ssh://'.$d.'@' . $this->getBoxForProject($project) .'/'. forge_get_config('repos_path', 'scmhg') .'/'. $project->getUnixName().'</tt></p>';
95 if (forge_get_config('use_dav', 'scmhg')) {
97 $b .= _('Developer Mercurial Access via HTTP');
100 $b .= _('Only project developers can access the Mercurial tree via this method. Enter your site password when prompted.');
102 $b .= '<p><tt>hg clone '.$protocol.'://<i>'. $d .'</i>@' . $this->getBoxForProject($project) .'/hg/'. $project->getUnixName() . '</tt></p>';
105 if (forge_get_config('use_ssh', 'scmhg')) {
106 $d = '<i>developername</i>';
108 $b .= _('Developer Mercurial Access via SSH');
111 $b .= _('Only project developers can access the Mercurial tree via this method. SSH must be installed on your client machine. Substitute <i>developername</i> with the proper value. Enter your site password when prompted.');
113 // Warning : the ssh uri MUST be this form : ssh://username@scmbox//path/reponame
114 // HAVE YOU SEEN THE // starting the path ? Keep in mind the double /
115 $b .= '<p><tt>hg clone ssh://'.$d.'@' . $this->getBoxForProject($project) .'/'. forge_get_config('repos_path', 'scmhg') .'/'. $project->getUnixName().'</tt></p>';
118 $b .= _('Developer Mercurial Access via HTTP');
121 $b .= _('Only project developers can access the Mercurial tree via this method. Enter your site password when prompted.');
123 $b .= '<p><tt>hg clone '.$protocol.'://<i>'. _('developername') .'</i>@' . $this->getBoxForProject($project) .'/hg/'. $project->getUnixName() . '</tt></p>';
129 function getSnapshotPara($project) {
133 function getBrowserLinkBlock($project) {
135 $b = $HTML->boxMiddle(_('Hg Repository Browser'));
137 $b .= _('Browsing the Mercurial tree gives you a view into the current status of this project\'s code. You may also view the complete histories of any file in the repository.');
140 $b .= util_make_link("/scm/browser.php?group_id=".$project->getID(), _('Browse Hg Repository'));
145 function getStatsBlock($project) {
149 function printShortStats($params) {
150 $project = $this->checkParams($params);
154 if ($project->usesPlugin($this->name)) {
155 $result = db_query_params('SELECT sum(commits) AS commits, sum(adds) AS adds FROM stats_cvs_group WHERE group_id=$1',
156 array ($project->getID())) ;
157 $commit_num = db_result($result,0,'commits');
158 $add_num = db_result($result,0,'adds');
165 echo ' (Mercurial: '.sprintf(_('<strong>%1$s</strong> commits, <strong>%2$s</strong> adds'), number_format($commit_num, 0), number_format($add_num, 0)).")";
169 function printBrowserPage($params) {
170 $project = $this->checkParams($params);
174 if ($project->usesPlugin($this->name)) {
175 if ($this->browserDisplayable($project)) {
176 print '<iframe src="'.util_make_url('/plugins/scmhg/cgi-bin/'.$project->getUnixName().'.cgi?p='.$project->getUnixName()).'" frameborder="0" width=100% height=700></iframe>';
181 function createOrUpdateRepo($params) {
182 $project = $this->checkParams($params);
186 if (!$project->usesPlugin($this->name)) {
190 $repo = forge_get_config('repos_path', 'scmhg') . '/' . $project->getUnixName();
191 if (forge_get_config('use_ssh', 'scmhg')) {
192 $unix_group = 'scm_' . $project->getUnixName();
194 if (forge_get_config('use_dav', 'scmhg')) {
195 $unix_group = forge_get_config('apache_group');
196 $unix_user = forge_get_config('apache_user');
199 system("mkdir -p $repo");
200 /** per project configuration for http **/
201 if (forge_get_config('use_dav', 'scmhg')) {
202 //get template hgweb.cgi
203 $hgweb = forge_get_config('source_path').'/plugins/scmhg/www/cgi-bin/hgweb.cgi';
204 $project_hgweb = forge_get_config('source_path').'/www/plugins/scmhg/cgi-bin/'.$project->getUnixName().'.cgi';
205 if (!is_file($project_hgweb)) {
206 $lines = file($hgweb);
208 foreach ($lines as $line) {
209 if (preg_match("/\Aapplication = hgweb/",$line)) {
210 //link per project hgweb.cgi to the project repository
211 $repo_config .= "application = hgweb(\"".$repo."\",\"".$project->getUnixName()."\")\n";
213 $repo_config .= $line;
216 $f = fopen($project_hgweb, 'w');
217 fwrite($f, $repo_config);
219 system("chown $unix_user:$unix_group $project_hgweb");
220 system("chmod u+x $project_hgweb");
223 if (!is_dir("$repo/.hg")) {
224 system("hg init $repo");
225 $f = fopen("$repo/.hg/hgrc",'w');
227 $conf .= "baseurl = /hg";
228 $conf .= "\ndescription = ".$project->getUnixName();
229 $conf .= "\nstyle = paper";
230 $conf .= "\nallow_push = *"; //every user ( see apache configuration) is allowed to push
231 $conf .= "\nallow_read = *"; // every user is allowed to clone and pull
234 system("chgrp -R $unix_group $repo");
235 system("chmod 770 $repo" );
236 system("find $repo -type d | xargs chmod g+s" );
237 system("chmod 660 $repo/.hg/hgrc");
240 if ($project->enableAnonSCM()) {
241 system("chmod -R g+wX,o+rX-w $repo");
243 system("chmod -R g+wX,o-rwx $repo");
247 function updateRepositoryList($params) {
248 $groups = $this->getGroups();
249 if (!forge_get_config('use_dav', 'scmhg')) {
253 $unix_group = forge_get_config('apache_group');
254 $unix_user = forge_get_config('apache_user');
257 foreach ($groups as $project) {
258 if ( !$project->isActive()) {
261 if ( !$project->usesSCM()) {
265 $read = ""; /*pull,clone*/
266 $path = forge_get_config('repos_path', 'scmhg').'/'.$project->getUnixName().'/.hg';
269 $users = $project->getMembers();
270 $pname = $project->getUnixName();
271 foreach ($users as $user) {
272 if (forge_check_perm_for_user ($user,
282 $push .= $user->getUnixName();
283 $read .= $user->getUnixName();
286 $hgusers[$user->getID()] = $user;
287 }elseif (forge_check_perm_for_user ($user,
294 $read .= $user->getUnixName();
296 $hgusers[$user->getID()] = $user;
300 if ($project->enableAnonSCM()) {
304 /*make new hgrc file*/
305 if (is_file($path.'/hgrc')) {
306 $hgrc_val = parse_ini_file($path.'/hgrc', true);
307 if (isset ($hgrc_val['web'])) {
308 $hgrc_val['web']['allow_read'] = $read;
309 $hgrc_val['web']['allow_push'] = $push;
311 if (isset ($hgrc_val['notify']['test'])) {
312 /* Set the value again, because parse_ini_file() converts boolean values to "" or "1" .
313 This would break the hgrc file.*/
314 $hgrc_val['notify']['test'] = 'false';
316 if (isset ($hgrc_val['notify']['template'])) {
317 /*Set value again, because special character are not escaped*/
318 $hgrc_val['notify']['template'] = '"\ndetails: {webroot}/rev/{node|short}\nchangeset: {rev}:{node|short}\nuser: {author}\ndate: {date|date}\ndescription:\n{desc}\n"';
321 foreach ($hgrc_val as $section => $sub) {
322 $hgrc .= '['.$section."]\n";
323 foreach ($sub as $prop => $value) {
324 $hgrc .= "$prop = $value\n";
325 if ($value == end($sub)) {
332 $hgrc .= "baseurl = /hg";
333 $hgrc .= "\ndescription = ".$project->getUnixName();
334 $hgrc .= "\nstyle = paper";
335 $hgrc .= "\nallow_read = ".$read;
336 $hgrc .= "\nallow_push = ".$push;
339 $f = fopen($path.'/hgrc.new', 'w');
342 rename($path.'/hgrc.new', $path.'/hgrc');
343 system("chown $unix_user:$unix_group $path/hgrc");
344 system("chmod 660 $path/hgrc");
347 foreach ($hgusers as $user_id => $user) {
348 $password_data .= $user->getUnixName().':'.$user->getUnixPasswd()."\n";
350 $password_data .= forge_get_config('anonhg_login', 'scmhg').":".htpasswd_apr1_md5(forge_get_config('anonhg_password', 'scmhg'))."\n";
352 $fname = forge_get_config('data_path').'/hgroot-authfile';
353 $f = fopen($fname.'.new', 'w');
354 fwrite($f, $password_data);
356 chmod($fname.'.new', 0644);
357 rename($fname.'.new', $fname);
360 function generateSnapshots($params) {
361 $project = $this->checkParams($params);
365 if (! $project->usesPlugin ($this->name)) {
369 $group_name = $project->getUnixName();
370 $tarball = forge_get_config('scm_tarballs_path').'/'.$group_name.'-scmroot.tar'.util_get_compressed_file_extension();
372 if (!$project->enableAnonSCM()) {
373 if (is_file($tarball)) {
379 $toprepo = forge_get_config('repos_path', 'scmhg');
380 $repo = $toprepo . '/' . $project->getUnixName();
382 if (!is_dir($repo)) {
383 if (is_file($tarball)) {
389 $tmp = trim(`mktemp -d`);
393 system("tar cCf $toprepo - ".$project->getUnixName() ."|".forge_get_config('compression_method')."> $tmp/tarball") ;
394 chmod("$tmp/tarball", 0644);
395 copy("$tmp/tarball", $tarball);
396 unlink("$tmp/tarball");
397 system("rm -rf $tmp");
400 function gatherStats($params) {
401 global $last_user, $usr_adds, $usr_deletes, $usr_updates, $updates, $adds;
403 $project = $this->checkParams($params);
407 if (! $project->usesPlugin($this->name)) {
411 if ($params['mode'] == 'day') {
413 $year = $params['year'];
414 $month = $params['month'];
415 $day = $params['day'];
416 $month_string = sprintf("%04d%02d", $year, $month );
417 $start_time = gmmktime(0, 0, 0, $month, $day, $year);
418 $end_time = $start_time + 86400;
420 $usr_updates = array();
421 $usr_deletes = array();
424 $repo = forge_get_config('repos_path', 'scmhg') . '/' . $project->getUnixName();
425 if (!is_dir($repo) || !is_dir("$repo/.hg")) {
426 // echo "No repository\n";
430 // cleaning stats_cvs_* table for the current day
431 $res = db_query_params('DELETE FROM stats_cvs_group WHERE month = $1 AND day = $2 AND group_id = $3',
436 echo "Error while cleaning stats_cvs_group\n";
440 //switch into scm_repository and take a look at the log informations
441 $cdir = chdir($repo);
443 //show customised log informations
444 $pipe = popen("hg log --style fflog.tmpl -d '$start_time 0 to $end_time 0'", 'r');
446 while (!feof($pipe) && $line = fgets ($pipe)) {
447 //determine between author line and file informations
448 if (preg_match("/(\A[AMD]) .*/", $line, $matches)) {
449 if ($last_user == "") continue;
450 switch ($matches[1]) {
452 $usr_adds[$last_user]++;
456 $usr_updates[$last_user]++;
460 $usr_deletes[$last_user]++;
464 $last_user = $this->getUser($line);
469 // inserting group results in stats_cvs_groups
470 if ($updates > 0 || $adds > 0) {
471 if (!db_query_params('INSERT INTO stats_cvs_group (month, day, group_id, checkouts, commits, adds) VALUES ($1, $2, $3, $4, $5, $6)',
478 echo "Error while inserting into stats_cvs_group\n";
484 // building the user list
485 $user_list = array_unique(array_merge(array_keys($usr_adds), array_keys($usr_updates)));
486 foreach ( $user_list as $user ) {
487 $uu = $usr_updates[$user] ? $usr_updates[$user] : 0;
488 $ua = $usr_adds[$user] ? $usr_adds[$user] : 0;
489 if ($uu > 0 || $ua > 0) {
490 if (!db_query_params('INSERT INTO stats_cvs_user (month, day, group_id, user_id, commits,adds) VALUES ($1, $2, $3, $4, $5, $6)',
497 echo "Error while inserting into stats_cvs_user\n";
507 function scm_add_repo(&$params) {
508 $project = $this->checkParams($params);
512 if (! $project->usesPlugin ($this->name)) {
516 if (!isset($params['repo_name'])) {
520 if ($params['repo_name'] == $project->getUnixName()) {
521 $params['error_msg'] = sprintf(_('A repository %s already exists'), $params['repo_name']);
525 if (! util_is_valid_repository_name($params['repo_name'])) {
526 $params['error_msg'] = _('This repository name is not valid');
530 $result = db_query_params('SELECT count(*) AS count FROM plugin_scmhg_repos WHERE group_id=$1 AND repo_name = $2',
531 array ($params['group_id'], $params['repo_name']));
533 $params['error_msg'] = db_error();
536 if (db_result($result, 0, 'count')) {
537 $params['error_msg'] = sprintf(_('A repository %s already exists'), $params['repo_name']);
543 if (isset($params['clone'])) {
544 $clone = $params['clone'];
545 // Verify if repository used for the clone exists?
546 $description = sprintf(_('Clone of %s repository'), $params['clone']);
548 if (isset($params['description'])) {
549 $description = $params['description'];
552 $result = db_query_params ('INSERT INTO plugin_scmhg_repos (group_id, repo_name, description, clone) VALUES ($1, $2, $3, $4)',
553 array ($params['group_id'], $params['repo_name'], $description, $clone));
555 $params['error_msg'] = db_error();
559 plugin_hook ("scm_admin_update", $params);
563 function scm_admin_buttons(&$params) {
564 $project = $this->checkParams($params);
568 if (! $project->usesPlugin ($this->name)) {
575 '/scm/admin/?group_id='.$params['group_id'].'&form_create_repo=1',
577 array('icon' => html_image('ic/scm_repo_add.png'))
581 function scm_admin_form(&$params) {
582 $project = $this->checkParams($params);
586 if (! $project->usesPlugin ($this->name)) {
590 $project_name = $project->getUnixName();
592 $select_repo = '<select name="frontpage">' . "\n";//array($project->getPublicName());
593 $result = db_query_params('SELECT repo_name FROM plugin_scmhg_repos WHERE group_id=$1',
594 array ($params['group_id']));
596 //$params['error_msg'] = db_error();
599 $select_repo = '<select name="clone">' . "\n";
600 $select_repo .= '<option value="">'._('None').'</option>' . "\n";
601 $select_repo .= '<option value="'.$project_name.'">'.$project_name.'</option>' . "\n";
602 while($data = db_fetch_array($result)) {
603 $select_repo .= '<option value="'.$data['repo_name'].'">'.$data['repo_name'].'</option>' . "\n";
605 $select_repo .= '</select>' . "\n";
607 session_require_perm('project_admin', $params['group_id']);
609 $adminheadertitle = sprintf(_('Create SCM repository for project %1$s'), $project_name);
610 project_admin_header(array('title'=>$adminheadertitle, 'group'=>$params['group_id']));
613 <form name="form_create_repo"
614 action="<?php echo getStringFromServer('PHP_SELF'); ?>" method="post">
615 <input type="hidden" name="group_id" value="<?php echo $params['group_id'] ?>" />
616 <input type="hidden" name="create_repository" value="1" />
617 <p><strong><?php echo _('Repository name:') ?></strong><?php echo utils_requiredField(); ?><br />
618 <input type="text" required="required" size="20" name="repo_name" value="" /></p>
619 <p><strong><?php echo _('Description')._(':'); ?></strong><br />
620 <input type="text" size="60" name="description" value="" /></p>
621 <p><strong><?php echo _('Cloned from:') ?></strong><br />
622 <?php echo $select_repo ?></p>
623 <input type="submit" name="cancel" value="<?php echo _('Cancel') ?>" />
624 <input type="submit" name="submit" value="<?php echo _('Submit') ?>" />
629 project_admin_footer(array());
637 // c-file-style: "bsd"