3 * FusionForge Bazaar plugin
5 * Copyright 2009, Roland Mas
6 * Copyright 2013-2014, 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 require_once $gfcommon.'include/plugins_utils.php';
27 forge_define_config_item ('default_server', 'scmbzr', forge_get_config ('scm_host')) ;
28 forge_define_config_item ('repos_path', 'scmbzr', forge_get_config('chroot').'/scmrepos/bzr') ;
30 class BzrPlugin extends SCMPlugin {
31 function BzrPlugin () {
34 $this->name = 'scmbzr';
35 $this->text = _('Bazaar');
37 _("This plugin contains the Bazaar subsystem of FusionForge. It allows each
38 FusionForge project to have its own Bazaar repository, and gives some control
39 over it to the project's administrator.");
40 $this->hooks[] = 'scm_generate_snapshots' ;
41 $this->hooks[] = 'scm_browser_page';
42 $this->hooks[] = 'scm_update_repolist' ;
43 $this->hooks[] = 'scm_gather_stats' ;
45 $this->main_branch_names = array () ;
46 $this->main_branch_names[] = 'trunk' ;
47 $this->main_branch_names[] = 'master' ;
48 $this->main_branch_names[] = 'main' ;
49 $this->main_branch_names[] = 'head' ;
50 $this->main_branch_names[] = 'HEAD' ;
55 function getDefaultServer() {
56 return forge_get_config('default_server', 'scmbzr') ;
59 function printShortStats ($params) {
60 $project = $this->checkParams ($params) ;
65 if ($project->usesPlugin($this->name) && forge_check_perm('scm', $project->getID(), 'read')) {
66 $result = db_query_params('SELECT sum(updates) AS updates, sum(adds) AS adds FROM stats_cvs_group WHERE group_id=$1',
67 array ($project->getID())) ;
68 $commit_num = db_result($result,0,'updates');
69 $add_num = db_result($result,0,'adds');
76 echo ' (Bazaar: '.sprintf(_('<strong>%1$s</strong> updates, <strong>%2$s</strong> adds'), number_format($commit_num, 0), number_format($add_num, 0)).")";
80 function getBlurb () {
82 . sprintf(_('Documentation for %1$s is available at <a href="%2$s">%2$s</a>.'),
84 'http://bazaar-vcs.org/Documentation')
88 function getInstructionsForAnon ($project) {
90 $b .= _('Anonymous Bazaar Access');
93 $b .= _('This project\'s Bazaar repository can be checked out through anonymous access with the following command.');
96 $b .= '<tt>bzr checkout '.util_make_url ('/anonscm/bzr/'.$project->getUnixName().'/').'</tt><br />';
101 function getInstructionsForRW ($project) {
103 if (session_loggedin()) {
104 $u = user_get_object(user_getid()) ;
105 $d = $u->getUnixName() ;
107 $b .= sprintf(_('Developer %s Access via SSH'), 'Bazaar');
110 $b .= sprintf(_('Only project developers can access the %s tree via this method.'), 'Bazaar');
112 $b .= _('SSH must be installed on your client machine.');
114 $b .= _('Enter your site password when prompted.');
116 $b .= '<p><tt>bzr checkout bzr+ssh://'.$d.'@' . $this->getBoxForProject($project) . forge_get_config('repos_path', 'scmbzr') .'/'. $project->getUnixName().'/'._('branchname').'</tt></p>' ;
119 $b .= sprintf(_('Developer %s Access via SSH'), 'Bazaar');
122 $b .= sprintf(_('Only project developers can access the %s tree via this method.'), 'Bazaar');
124 $b .= _('SSH must be installed on your client machine.');
126 $b .= _('Substitute <em>developername</em> with the proper value.');
128 $b .= _('Enter your site password when prompted.');
130 $b .= '<p><tt>bzr checkout bzr+ssh://<i>'._('developername').'</i>@' . $this->getBoxForProject($project) . forge_get_config('repos_path', 'scmbzr') .'/'. $project->getUnixName().'/'._('branchname').'</tt></p>' ;
135 function getSnapshotPara ($project) {
139 function getBrowserLinkBlock ($project) {
141 $b = $HTML->boxMiddle(sprintf(_('%s Repository Browser'), 'Bazaar'));
143 $b .= sprintf(_("Browsing the %s tree gives you a view into the current status of this project's code."), 'Bazaar');
145 $b .= _('You may also view the complete histories of any file in the repository.');
148 $b .= util_make_link ("/scm/browser.php?group_id=".$project->getID(),
149 sprintf(_('Browse %s Repository'), 'Bazaar')
155 function getStatsBlock ($project) {
159 function printBrowserPage ($params) {
161 $project = $this->checkParams ($params) ;
166 if ($project->usesPlugin ($this->name)) {
167 if ($this->browserDisplayable ($project)) {
168 htmlIframe('/scm/loggerhead/'.$project->getUnixName(),array('id'=>'scmbzr_iframe'));
173 function createOrUpdateRepo ($params) {
174 $project = $this->checkParams ($params) ;
179 if (! $project->usesPlugin ($this->name)) {
183 $project_name = $project->getUnixName();
185 $repo = forge_get_config('repos_path', 'scmbzr') . '/' . $project->getUnixName() ;
186 $unix_group = 'scm_' . $project_name ;
188 $repo_exists = false ;
189 if (is_dir ($repo)) {
190 $pipe = popen ("bzr info $repo 2>/dev/null", "r") ;
191 $line = fgets ($pipe) ;
194 if (preg_match ("/^Shared repository/", $line) != 0
195 || preg_match ("/^Repository branch/", $line) != 0) {
196 $repo_exists = true ;
201 $tmp_repo = util_mkdtemp('.bzr', $project_name);
202 if ($tmp_repo == false) {
206 system ("bzr init-repo --no-trees $tmp_repo >/dev/null") ;
207 system ("find $tmp_repo/.bzr -type d | xargs chmod g+s") ;
208 system ("chmod -R g+rwX,o+rX-w $tmp_repo/.bzr") ;
209 system ("chgrp -R $unix_group $tmp_repo/.bzr") ;
211 system ("mkdir -p $repo") ;
212 system ("chgrp $unix_group $repo") ;
213 system ("chmod g+ws $repo") ;
214 system ("mv $tmp_repo/.bzr $repo/.bzr");
218 if ($project->enableAnonSCM()) {
219 system ("chmod o+rX-w $repo") ;
221 system ("chmod o-rwx $repo") ;
225 function updateRepositoryList ($params) {
226 $groups = $this->getGroups () ;
228 $dir = forge_get_config('data_path').'/plugins/scmbzr/public-repositories' ;
231 mkdir ($dir, 0644, true);
234 $oldlist = array () ;
235 $dh = opendir ($dir) ;
236 while (($file = readdir($dh)) !== false) {
237 if ($file != '.' && $file != '..') {
244 $newlist = array () ;
245 foreach ($groups as $project) {
246 if ($this->browserDisplayable ($project)) {
247 $newlist[] = $project->getUnixName() ;
252 $dellist = array () ;
253 $createlist = array () ;
255 while (count ($oldlist) > 0 && count ($newlist) > 0) {
259 $createlist[] = array_shift ($newlist) ;
261 $dellist[] = array_shift ($oldlist) ;
263 array_shift ($newlist) ;
264 array_shift ($oldlist) ;
267 $dellist = array_merge ($dellist, $oldlist) ;
268 $createlist = array_merge ($createlist, $newlist) ;
270 foreach ($dellist as $del) {
271 unlink ($dir . '/' . $del) ;
273 foreach ($createlist as $create) {
274 symlink (forge_get_config('repos_path', 'scmbzr') . '/' . $create, $dir . '/' . $create) ;
278 function gatherStats ($params) {
279 $project = $this->checkParams ($params) ;
284 if (! $project->usesPlugin ($this->name)) {
288 if ($params['mode'] == 'day') {
291 $year = $params ['year'] ;
292 $month = $params ['month'] ;
293 $day = $params ['day'] ;
294 $month_string = sprintf( "%04d%02d", $year, $month );
295 $start_time = gmmktime( 0, 0, 0, $month, $day, $year);
296 $end_time = $start_time + 86400;
298 $date = sprintf ("%04d-%02d-%02d", $year, $month, $day);
302 $usr_adds = array () ;
303 $usr_updates = array () ;
304 $usr_deletes = array () ;
305 $usr_commits = array () ;
307 $toprepo = forge_get_config('repos_path', 'scmbzr') ;
308 $repo = $toprepo . '/' . $project->getUnixName() ;
310 $branch = $this->findMainBranch ($project) ;
317 $pipe = popen ("bzr log file://$repo/$branch --long --verbose 2> /dev/null", 'r' ) ;
319 // cleaning stats_cvs_* table for the current day
320 $res = db_query_params ('DELETE FROM stats_cvs_group WHERE month=$1 AND day=$2 AND group_id=$3',
321 array ($month_string,
323 $project->getID())) ;
325 echo "Error while cleaning stats_cvs_group\n" ;
330 $res = db_query_params ('DELETE FROM stats_cvs_user WHERE month=$1 AND day=$2 AND group_id=$3',
331 array ($month_string,
333 $project->getID())) ;
335 echo "Error while cleaning stats_cvs_user\n" ;
340 // Analyzing history stream
341 $sep = '------------------------------------------------------------' ;
350 while (! feof ($pipe) &&
351 $line = rtrim (fgets ($pipe))) {
353 if ($curdate == $date) {
354 $adds = $adds + $curadds ;
355 $updates = $updates + $curupdates ;
357 if ($curdate != '' && $curdate < $date) {
367 } elseif (preg_match( '/^revno: ([0-9]+)$/', $line, $matches)) {
368 $currev = $matches[1] ;
369 } elseif (preg_match( '/^committer: (.*)$/', $line, $matches)) {
370 $curuser = $matches[1] ;
371 } elseif (preg_match( '/^timestamp: ... (\d\d\d\d-\d\d-\d\d)/', $line, $matches)) {
372 $curdate = $matches[1] ;
373 } elseif (preg_match( '/^modified:/', $line, $matches)) {
374 $state = 'modified' ;
375 } elseif (preg_match( '/^renamed:/', $line, $matches)) {
377 } elseif (preg_match( '/^removed:/', $line, $matches)) {
379 } elseif (preg_match( '/^added/', $line, $matches)) {
396 if ($curdate == $date) {
397 $adds = $adds + $curadds ;
398 $updates = $updates + $curupdates ;
399 $deletes = $deletes + $curdeletes ;
400 $commits = $commits + $curcommits ;
403 // inserting group results in stats_cvs_groups
404 if ($updates > 0 || $adds > 0 || $deletes > 0 || $commits > 0) {
405 if (!db_query_params ('INSERT INTO stats_cvs_group (month,day,group_id,checkouts,commits,adds,updates,deletes) VALUES ($1,$2,$3,$4,$5,$6)',
406 array ($month_string,
414 echo "Error while inserting into stats_cvs_group\n" ;
420 // building the user list
421 $user_list = array_unique( array_merge( array_keys( $usr_adds ), array_keys( $usr_updates ), array_keys( $usr_commits ) ) );
423 foreach ( $user_list as $user ) {
424 // trying to get user id from user name
425 $u = user_get_object_by_name ($user) ;
427 $user_id = $u->getID();
432 $uc = $usr_commits[$user] ? $usr_commits[$user] : 0 ;
433 $uu = $usr_updates[$user] ? $usr_updates[$user] : 0 ;
434 $ua = $usr_adds[$user] ? $usr_adds[$user] : 0 ;
435 $ud = $usr_deletes[$user] ? $usr_deletes[$user] : 0 ;
436 if ($uu > 0 || $ua > 0 || $uc > 0 || $ud > 0) {
437 if (!db_query_params ('INSERT INTO stats_cvs_user (month,day,group_id,user_id,commits,adds,updates,deletes) VALUES ($1,$2,$3,$4,$5,$6)',
438 array ($month_string,
446 echo "Error while inserting into stats_cvs_user\n" ;
456 function findMainBranch ($project) {
457 $toprepo = forge_get_config('repos_path', 'scmbzr') ;
458 $repo = $toprepo . '/' . $project->getUnixName() ;
462 foreach ($this->main_branch_names as $bname) {
463 system ("bzr ls file://$repo/$bname > /dev/null 2>&1", $code) ;
472 function generateSnapshots ($params) {
473 $us = forge_get_config('use_scm_snapshots') ;
474 $ut = forge_get_config('use_scm_tarballs') ;
479 $project = $this->checkParams ($params) ;
484 $group_name = $project->getUnixName() ;
486 $snapshot = forge_get_config('scm_snapshots_path').'/'.$group_name.'-scm-latest.tar'.util_get_compressed_file_extension();
487 $tarball = forge_get_config('scm_tarballs_path').'/'.$group_name.'-scmroot.tar'.util_get_compressed_file_extension();
489 if (! $project->usesPlugin ($this->name)) {
493 if (! $project->enableAnonSCM()) {
494 if (file_exists ($snapshot)) unlink ($snapshot) ;
495 if (file_exists ($tarball)) unlink ($tarball) ;
499 $toprepo = forge_get_config('repos_path', 'scmbzr') ;
500 $repo = $toprepo . '/' . $project->getUnixName() ;
502 if (!is_dir ($repo) || !is_file ("$repo/format")) {
503 if (file_exists ($snapshot)) unlink ($snapshot) ;
504 if (file_exists ($tarball)) unlink ($tarball) ;
508 $tmp = trim (`mktemp -d`) ;
513 $today = date ('Y-m-d') ;
514 $branch = $this->findMainBranch ($project) ;
516 system ("bzr export --root=$group_name-scm-$today --format=tar - $repo/$bname |".forge_get_config('compression_method')."> $tmp/snapshot") ;
517 chmod ("$tmp/snapshot", 0644) ;
518 copy ("$tmp/snapshot", $snapshot) ;
519 unlink ("$tmp/snapshot") ;
520 system ("rm -rf $tmp/$dir") ;
527 system ("tar cCf $toprepo - ".$project->getUnixName() ."|".forge_get_config('compression_method')."> $tmp/tarball") ;
528 chmod ("$tmp/tarball", 0644) ;
529 copy ("$tmp/tarball", $tarball) ;
530 unlink ("$tmp/tarball") ;
531 system ("rm -rf $tmp") ;
538 // c-file-style: "bsd"