]> git.madduck.net Git - code/myrepos.git/blob - mr

madduck's git repository

Every one of the projects in this repository is available at the canonical URL git://git.madduck.net/madduck/pub/<projectpath> — see each project's metadata for the exact URL.

All patches and comments are welcome. Please squash your changes to logical commits before using git-format-patch and git-send-email to patches@git.madduck.net. If you'd read over the Git project's submission guidelines and adhered to them, I'd be especially grateful.

SSH access, as well as push access can be individually arranged.

If you use my repositories frequently, consider adding the following snippet to ~/.gitconfig and using the third clone URL listed for each project:

[url "git://git.madduck.net/madduck/"]
  insteadOf = madduck:

improve description
[code/myrepos.git] / mr
1 #!/usr/bin/perl
2
3 =head1 NAME
4
5 mr - a Multiple Repository management tool
6
7 =head1 SYNOPSIS
8
9 B<mr> [options] checkout
10
11 B<mr> [options] update
12
13 B<mr> [options] status
14
15 B<mr> [options] commit [-m "message"]
16
17 B<mr> [options] diff
18
19 B<mr> [options] log
20
21 B<mr> [options] register [repository]
22
23 B<mr> [options] config section [parameter=[value] ...]
24
25 B<mr> [options] action [params ...]
26
27 =head1 DESCRIPTION
28
29 B<mr> is a Multiple Repository management tool. It
30 can checkout, update, or perform other actions on
31 a set of repositories as if they were one combined respository. It
32 supports any combination of subversion, git, cvs, and bzr repositories, 
33 and support for other revision control systems can easily be added.
34
35 B<mr> cds into and operates on all registered repsitories at or below your
36 working directory. Or, if you are in a subdirectory of a repository that
37 contains no other registered repositories, it will stay in that directory,
38 and work on only that repository,
39
40 The predefined commands should be fairly familiar to users of any revision
41 control system:
42
43 =over 4
44
45 =item checkout (or co)
46
47 Checks out any repositories that are not already checked out.
48
49 =item update
50
51 Updates each repository from its configured remote repository.
52
53 If a repository isn't checked out yet, it will first check it out.
54
55 =item status
56
57 Displays a status report for each repository, showing what
58 uncommitted changes are present in the repository.
59
60 =item commit (or ci)
61
62 Commits changes to each repository. (By default, changes are pushed to the
63 remote repository too, when using distributed systems like git.)
64
65 The optional -m parameter allows specifying a commit message.
66
67 =item diff
68
69 Show a diff of uncommitted changes.
70
71 =item log
72
73 Show the commit log.
74
75 =item list (or ls)
76
77 List the repositories that mr will act on.
78
79 =item register
80
81 Register an existing repository in the mrconfig file. By default, the
82 epository in the current directory is registered, or you can specify a
83 directory to register.
84
85 =item config
86
87 Adds, modifies, removes, or prints a value from the mrconfig file. The next
88 parameter is the name of the section the value is in. To add or modify
89 values, use one or more instances of "parameter=value". Use "parameter=" to
90 remove a parameter. Use just "parameter" to get the value of a parameter.
91
92 For example, to add (or edit) a repository in src/foo:
93
94   mr config src/foo checkout="svn co svn://example.com/foo/trunk foo"
95
96 To show the command that mr uses to update the repository in src/foo:
97
98   mr config src/foo update
99
100 =item help
101
102 Displays this help.
103
104 =back
105
106 Actions can be abbreviated to any unambiguous subsctring, so
107 "mr st" is equivilant to "mr status", and "mr up" is equivilant to "mr
108 update"
109
110 Additional parameters can be passed to most commands, and are passed on
111 unchanged to the underlying revision control system. This is mostly useful
112 if the repositories mr will act on all use the same revision control
113 system.
114
115 =head1 OPTIONS
116
117 =over 4
118
119 =item -d directory
120
121 Specifies the topmost directory that B<mr> should work in. The default is
122 the current working directory.
123
124 =item -c mrconfig
125
126 Use the specified mrconfig file, instead of looking for one in your home
127 directory.
128
129 =item -v
130
131 Be verbose.
132
133 =back
134
135 =head1 FILES
136
137 B<mr> is configured by .mrconfig files. It starts by reading the .mrconfig
138 file in your home directory, and this can in turn chain load .mrconfig files
139 from repositories.
140
141 Here is an example .mrconfig file:
142
143   [src]
144   checkout = svn co svn://svn.example.com/src/trunk src
145   chain = true
146
147   [src/linux-2.6]
148   checkout = git clone git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux-2.6.git &&
149         cd linux-2.6 &&
150         git checkout -b mybranch origin/master
151
152 The .mrconfig file uses a variant of the INI file format. Lines starting with
153 "#" are comments. Values can be continued to the following line by
154 indenting the line with whitespace.
155
156 The "DEFAULT" section allows setting default values for the sections that
157 come after it.
158
159 The "ALIAS" section allows adding aliases for actions. Each parameter
160 is an alias, and its value is the action to use.
161
162 All other sections add repositories. The section header specifies the
163 directory where the repository is located. This is relative to the directory
164 that contains the mrconfig file, but you can also choose to use absolute
165 paths.
166
167 Within a section, each parameter defines a shell command to run to handle a
168 given action. mr contains default handlers for the "update", "status", and
169 "commit" actions, so normally you only need to specify what to do for
170 "checkout".
171
172 Note that these shell commands are run in a "set -e" shell
173 environment, where any additional parameters you pass are available in
174 "$@". The "checkout" command is run in the parent of the repository
175 directory, since the repository isn't checked out yet. All other commands
176 are run inside the repository, though not necessarily at the top of it.
177 The "MR_REPO" environment variable is set to the path to the top of the
178 repository.
179
180 A few parameters have special meanings:
181
182 =over 4
183
184 =item skip
185
186 If the "skip" parameter is set and its command returns nonzero, then B<mr>
187 will skip acting on that repository. The command is passed the action
188 name in $1.
189
190 Here are two examples. The first skips the repo unless
191 mr is run by joey. The second uses the hours_since function
192 (included in mr's built-in library) to skip updating the repo unless it's
193 been at least 12 hours since the last update.
194
195   skip = test $(whoami) != joey
196   skip = [ "$1" = update ] && [ $(hours_since "$1") -lt 12 ]
197
198 =item chain
199
200 If the "chain" parameter is set and its command returns nonzero, then B<mr>
201 will try to load a .mrconfig file from the root of the repository. (You
202 should avoid chaining from repositories with untrusted committers.)
203
204 =item deleted
205
206 If the "deleted" parameter is set and its command returns nonzero, then
207 B<mr> will treat the repository as deleted. It won't ever actually delete
208 the repository, but it will warn if it sees the repsoitory's directory.
209 This is useful when one mrconfig file is shared amoung multiple machines,
210 to keep track of and remember to delete old repositories.
211
212 =item lib
213
214 The "lib" parameter can specify some shell code that will be run before each
215 command, this can be a useful way to define shell functions for other commands
216 to use.
217
218 =back
219
220 =head1 AUTHOR
221
222 Copyright 2007 Joey Hess <joey@kitenet.net>
223
224 Licensed under the GNU GPL version 2 or higher.
225
226 http://kitenet.net/~joey/code/mr/
227
228 =cut
229
230 use warnings;
231 use strict;
232 use Getopt::Long;
233 use Cwd qw(getcwd abs_path);
234
235 my $directory=getcwd();
236 my $config="$ENV{HOME}/.mrconfig";
237 my $verbose=0;
238 my %config;
239 my %knownactions;
240 my %alias;
241
242 Getopt::Long::Configure("no_permute");
243 my $result=GetOptions(
244         "d|directory=s" => sub { $directory=abs_path($_[1]) },
245         "c|config=s" => \$config,
246         "verbose" => \$verbose,
247 );
248 if (! $result || @ARGV < 1) {
249         die("Usage: mr [-d directory] action [params ...]\n".
250             "(Use mr help for man page.)\n");
251
252 }
253
254 loadconfig(\*DATA);
255 loadconfig($config);
256 #use Data::Dumper;
257 #print Dumper(\%config);
258
259 eval {
260         use FindBin qw($Bin $Script);
261         $ENV{MR_PATH}=$Bin."/".$Script;
262 };
263
264 # alias expansion and command stemming
265 my $action=shift @ARGV;
266 if (exists $alias{$action}) {
267         $action=$alias{$action};
268 }
269 if (! exists $knownactions{$action}) {
270         my @matches = grep { /^\Q$action\E/ }
271                 keys %knownactions, keys %alias;
272         if (@matches == 1) {
273                 $action=$matches[0];
274         }
275         elsif (@matches == 0) {
276                 die "mr: unknown action \"$action\" (known actions: ".
277                         join(", ", sort keys %knownactions).")\n";
278         }
279         else {
280                 die "mr: ambiguous action \"$action\" (matches: ".
281                         join(", ", @matches).")\n";
282         }
283 }
284
285 if ($action eq 'help') {
286         exec($config{''}{DEFAULT}{$action}) || die "exec: $!";
287 }
288 elsif ($action eq 'config') {
289         if (@ARGV < 2) {
290                 die "mr config: not enough parameters\n";
291         }
292         my $section=shift;
293         if ($section=~/^\//) {
294                 # try to convert to a path relative to $config's dir
295                 my ($dir)=$config=~/^(.*\/)[^\/]+$/;
296                 if ($section=~/^\Q$dir\E(.*)/) {
297                         $section=$1;
298                 }
299         }
300         my %changefields;
301         foreach (@ARGV) {
302                 if (/^([^=]+)=(.*)$/) {
303                         $changefields{$1}=$2;
304                 }
305                 else {
306                         my $found=0;
307                         foreach my $topdir (sort keys %config) {
308                                 if (exists $config{$topdir}{$section} &&
309                                     exists $config{$topdir}{$section}{$_}) {
310                                         print $config{$topdir}{$section}{$_}."\n";
311                                         $found=1;
312                                 }
313                         }
314                         if (! $found) {
315                                 die "mr $action: $section $_ not set\n";
316                         }
317                 }
318         }
319         modifyconfig($config, $section, %changefields) if %changefields;
320         exit 0;
321 }
322 elsif ($action eq 'register') {
323         my $command="set -e; ".$config{''}{DEFAULT}{lib}."\n".
324                 "my_action(){ $config{''}{DEFAULT}{$action}\n }; my_action ".
325                 join(" ", map { s/\//\/\//g; s/"/\"/g; '"'.$_.'"' } @ARGV);
326         print STDERR "mr $action: running >>$command<<\n" if $verbose;
327         exec($command) || die "exec: $!";
328 }
329
330 # work out what repos to act on
331 my @repos;
332 my $nochdir=0;
333 foreach my $topdir (sort keys %config) {
334         foreach my $subdir (sort keys %{$config{$topdir}}) {
335                 next if $subdir eq 'DEFAULT';
336                 my $dir=($subdir =~/^\//) ? $subdir : $topdir.$subdir;
337                 my $d=$directory;
338                 $dir.="/" unless $dir=~/\/$/;
339                 $d.="/" unless $d=~/\/$/;
340                 next if $dir ne $directory && $dir !~ /^\Q$directory\E/;
341                 push @repos, [$dir, $topdir, $subdir];
342         }
343 }
344 if (! @repos) {
345         # fallback to find a leaf repo
346         LEAF: foreach my $topdir (reverse sort keys %config) {
347                 foreach my $subdir (reverse sort keys %{$config{$topdir}}) {
348                         next if $subdir eq 'DEFAULT';
349                         my $dir=($subdir =~/^\//) ? $subdir : $topdir.$subdir;
350                         my $d=$directory;
351                         $dir.="/" unless $dir=~/\/$/;
352                         $d.="/" unless $d=~/\/$/;
353                         if ($d=~/^\Q$dir\E/) {
354                                 push @repos, [$dir, $topdir, $subdir];
355                                 last LEAF;
356                         }
357                 }
358         }
359         $nochdir=1;
360 }
361
362 my (@failed, @successful, @skipped);
363 foreach my $repo (@repos) {
364         action($action, @$repo);
365 }
366
367 sub action {
368         my ($action, $dir, $topdir, $subdir) = @_;
369         
370         my $lib= exists $config{$topdir}{$subdir}{lib} ?
371                         $config{$topdir}{$subdir}{lib}."\n" : "";
372
373         if (exists $config{$topdir}{$subdir}{deleted}) {
374                 if (! -d $dir) {
375                         return;
376                 }
377                 else {
378                         my $test="set -e;".$lib.$config{$topdir}{$subdir}{deleted};
379                         print "mr $action: running deleted test >>$test<<\n" if $verbose;
380                         my $ret=system($test);
381                         if ($ret >> 8 == 0) {
382                                 print STDERR "mr error: $dir should be deleted yet still exists\n\n";
383                                 push @failed, $dir;
384                                 return;
385                         }
386                 }
387         }
388
389         if ($action eq 'checkout') {
390                 if (-d $dir) {
391                         print "mr $action: $dir already exists, skipping checkout\n" if $verbose;
392                         push @skipped, $dir;
393                         return;
394                 }
395                 $dir=~s/^(.*)\/[^\/]+\/?$/$1/;
396         }
397         elsif ($action eq 'update') {
398                 if (! -d $dir) {
399                         return action("checkout", $dir, $topdir, $subdir);
400                 }
401         }
402         
403         $ENV{MR_REPO}=$dir;
404
405         if (exists $config{$topdir}{$subdir}{skip}) {
406                 my $test="set -e;".$lib.
407                         "my_action(){ $config{$topdir}{$subdir}{skip}\n }; my_action '$action'";
408                 print "mr $action: running skip test >>$test<<\n" if $verbose;
409                 my $ret=system($test);
410                 if ($ret >> 8 == 0) {
411                         print "mr $action: $dir skipped per config file\n" if $verbose;
412                         push @skipped, $dir;
413                         return;
414                 }
415         }
416         
417         if (! $nochdir && ! chdir($dir)) {
418                 print STDERR "mr $action: failed to chdir to $dir: $!\n";
419                 push @failed, $dir;
420         }
421         elsif (! exists $config{$topdir}{$subdir}{$action}) {
422                 print STDERR "mr $action: no defined $action command for $topdir$subdir, skipping\n";
423                 push @skipped, $dir;
424         }
425         else {
426                 if (! $nochdir) {
427                         print "mr $action: $topdir$subdir\n";
428                 }
429                 else {
430                         print "mr $action: $topdir$subdir (in subdir $directory)\n";
431                 }
432                 my $command="set -e; ".$lib.
433                         "my_action(){ $config{$topdir}{$subdir}{$action}\n }; my_action ".
434                         join(" ", map { s/\//\/\//g; s/"/\"/g; '"'.$_.'"' } @ARGV);
435                 print STDERR "mr $action: running >>$command<<\n" if $verbose;
436                 my $ret=system($command);
437                 if ($ret != 0) {
438                         print STDERR "mr $action: failed ($ret)\n" if $verbose;
439                         push @failed, $dir;
440                         if ($ret >> 8 != 0) {
441                                 print STDERR "mr $action: command failed\n";
442                         }
443                         elsif ($ret != 0) {
444                                 print STDERR "mr $action: command died ($ret)\n";
445                         }
446                 }
447                 else {
448                         push @successful, $dir;
449                 }
450
451                 print "\n";
452         }
453 }
454
455 sub showstat {
456         my $count=shift;
457         my $singular=shift;
458         my $plural=shift;
459         if ($count) {
460                 return "$count ".($count > 1 ? $plural : $singular);
461         }
462         return;
463 }
464 if (! @successful && ! @failed && ! @skipped) {
465         die "mr $action: no repositories found to work on\n";
466 }
467 print "mr $action: finished (".join("; ",
468         showstat($#successful+1, "successful", "successful"),
469         showstat($#failed+1, "failed", "failed"),
470         showstat($#skipped+1, "skipped", "skipped"),
471 ).")\n";
472 if (@failed) {
473         exit 1;
474 }
475 elsif (! @successful && @skipped) {
476         exit 1;
477 }
478 exit 0;
479
480 my %loaded;
481 sub loadconfig {
482         my $f=shift;
483
484         my @toload;
485
486         my $in;
487         my $dir;
488         if (ref $f eq 'GLOB') {
489                 $dir="";
490                 $in=$f; 
491         }
492         else {
493                 if (! -e $f) {
494                         return;
495                 }
496
497                 my $absf=abs_path($f);
498                 if ($loaded{$absf}) {
499                         return;
500                 }
501                 $loaded{$absf}=1;
502
503                 ($dir)=$f=~/^(.*\/)[^\/]+$/;
504                 if (! defined $dir) {
505                         $dir=".";
506                 }
507                 $dir=abs_path($dir)."/";
508
509                 # copy in defaults from first parent
510                 my $parent=$dir;
511                 while ($parent=~s/^(.*)\/[^\/]+\/?$/$1/) {
512                         if (exists $config{$parent} &&
513                             exists $config{$parent}{DEFAULT}) {
514                                 $config{$dir}{DEFAULT}={ %{$config{$parent}{DEFAULT}} };
515                                 last;
516                         }
517                 }
518                 
519                 print "mr: loading config $f\n" if $verbose;
520                 open($in, "<", $f) || die "mr: open $f: $!\n";
521         }
522         my @lines=<$in>;
523         close $in;
524
525         my $section;
526         while (@lines) {
527                 $_=shift @lines;
528                 chomp;
529                 next if /^\s*\#/ || /^\s*$/;
530                 if (/^\[([^\]]*)\]\s*$/) {
531                         $section=$1;
532                 }
533                 elsif (/^(\w+)\s*=\s*(.*)/) {
534                         my $parameter=$1;
535                         my $value=$2;
536
537                         # continued value
538                         while (@lines && $lines[0]=~/^\s(.+)/) {
539                                 shift(@lines);
540                                 $value.="\n$1";
541                                 chomp $value;
542                         }
543
544                         if (! defined $section) {
545                                 die "$f line $.: parameter ($parameter) not in section\n";
546                         }
547                         if ($section ne 'ALIAS' &&
548                             ! exists $config{$dir}{$section} &&
549                             exists $config{$dir}{DEFAULT}) {
550                                 # copy in defaults
551                                 $config{$dir}{$section}={ %{$config{$dir}{DEFAULT}} };
552                         }
553                         if ($section eq 'ALIAS') {
554                                 $alias{$parameter}=$value;
555                         }
556                         elsif ($parameter eq 'lib') {
557                                 $config{$dir}{$section}{lib}.=$value."\n";
558                         }
559                         else {
560                                 $config{$dir}{$section}{$parameter}=$value;
561                                 $knownactions{$parameter}=1;
562                                 if ($parameter eq 'chain' &&
563                                     length $dir && $section ne "DEFAULT" &&
564                                     -e $dir.$section."/.mrconfig" &&
565                                     system($value) >> 8 == 0) {
566                                         push @toload, $dir.$section."/.mrconfig";
567                                 }
568                         }
569                 }
570                 else {
571                         die "$f line $.: parse error\n";
572                 }
573         }
574
575         foreach (@toload) {
576                 loadconfig($_);
577         }
578 }
579
580 sub modifyconfig {
581         my $f=shift;
582         # the section to modify or add
583         my $targetsection=shift;
584         # fields to change in the section
585         # To remove a field, set its value to "".
586         my %changefields=@_;
587
588         my @lines;
589         my @out;
590
591         if (-e $f) {
592                 open(my $in, "<", $f) || die "mr: open $f: $!\n";
593                 @lines=<$in>;
594                 close $in;
595         }
596
597         my $formatfield=sub {
598                 my $field=shift;
599                 my @value=split(/\n/, shift);
600
601                 return "$field = ".shift(@value)."\n".
602                         join("", map { "\t$_\n" } @value);
603         };
604         my $addfields=sub {
605                 my @blanks;
606                 while ($out[$#out] =~ /^\s*$/) {
607                         unshift @blanks, pop @out;
608                 }
609                 foreach my $field (sort keys %changefields) {
610                         if (length $changefields{$field}) {
611                                 push @out, "$field = $changefields{$field}\n";
612                                 delete $changefields{$field};
613                         }
614                 }
615                 push @out, @blanks;
616         };
617
618         my $section;
619         while (@lines) {
620                 $_=shift(@lines);
621
622                 if (/^\s*\#/ || /^\s*$/) {
623                         push @out, $_;
624                 }
625                 elsif (/^\[([^\]]*)\]\s*$/) {
626                         if (defined $section && 
627                             $section eq $targetsection) {
628                                 $addfields->();
629                         }
630
631                         $section=$1;
632
633                         push @out, $_;
634                 }
635                 elsif (/^(\w+)\s*=\s(.*)/) {
636                         my $parameter=$1;
637                         my $value=$2;
638
639                         # continued value
640                         while (@lines && $lines[0]=~/^\s(.+)/) {
641                                 shift(@lines);
642                                 $value.="\n$1";
643                                 chomp $value;
644                         }
645
646                         if ($section eq $targetsection) {
647                                 if (exists $changefields{$parameter}) {
648                                         if (length $changefields{$parameter}) {
649                                                 $value=$changefields{$parameter};
650                                         }
651                                         delete $changefields{$parameter};
652                                 }
653                         }
654
655                         push @out, $formatfield->($parameter, $value);
656                 }
657         }
658
659         if (defined $section && 
660             $section eq $targetsection) {
661                 $addfields->();
662         }
663         elsif (%changefields) {
664                 push @out, "\n[$targetsection]\n";
665                 foreach my $field (sort keys %changefields) {
666                         if (length $changefields{$field}) {
667                                 push @out, $formatfield->($field, $changefields{$field});
668                         }
669                 }
670         }
671
672         open(my $out, ">", $f) || die "mr: write $f: $!\n";
673         print $out @out;
674         close $out;     
675 }
676
677 # Finally, some useful actions that mr knows about by default.
678 # These can be overridden in ~/.mrconfig.
679 __DATA__
680 [ALIAS]
681 co = checkout
682 ci = commit
683 ls = list
684
685 [DEFAULT]
686 lib =
687         error() {
688                 echo "mr: $@" >&2
689                 exit 1
690         }
691         hours_since() {
692                 for dir in .git .svn .bzr CVS; do
693                         if [ -e "$MR_REPO/$dir" ]; then
694                                 flagfile="$MR_REPO/$dir/.mr_last$1"
695                                 break
696                         fi
697                 done
698                 if [ -z "$flagfile" ]; then
699                         error "cannot determine flag filename"
700                 fi
701                 perl -wle 'print -f shift() ? int((-M _) * 24) : 9999' "$flagfile"
702                 touch "$flagfile"
703         }
704
705 update =
706         if [ -d "$MR_REPO"/.svn ]; then
707                 svn update "$@"
708         elif [ -d "$MR_REPO"/.git ]; then
709                 git pull origin master "$@"
710         elif [ -d "$MR_REPO"/.bzr ]; then
711                 bzr merge "$@"
712         elif [ -d "$MR_REPO"/CVS ]; then
713                 cvs update "$@"
714         else
715                 error "unknown repo type"
716         fi
717 status =
718         if [ -d "$MR_REPO"/.svn ]; then
719                 svn status "$@"
720         elif [ -d "$MR_REPO"/.git ]; then
721                 git status "$@" || true
722         elif [ -d "$MR_REPO"/.bzr ]; then
723                 bzr status "$@"
724         elif [ -d "$MR_REPO"/CVS ]; then
725                 cvs status "$@"
726         else
727                 error "unknown repo type"
728         fi
729 commit =
730         if [ -d "$MR_REPO"/.svn ]; then
731                 svn commit "$@"
732         elif [ -d "$MR_REPO"/.git ]; then
733                 git commit -a "$@" && git push --all
734         elif [ -d "$MR_REPO"/.bzr ]; then
735                 bzr commit "$@" && bzr push
736         elif [ -d "$MR_REPO"/CVS ]; then
737                 cvs commit "$@"
738         else
739                 error "unknown repo type"
740         fi
741 diff =
742         if [ -d "$MR_REPO"/.svn ]; then
743                 svn diff "$@"
744         elif [ -d "$MR_REPO"/.git ]; then
745                 git diff "$@"
746         elif [ -d "$MR_REPO"/.bzr ]; then
747                 bzr diff "$@"
748         elif [ -d "$MR_REPO"/CVS ]; then
749                 cvs diff "$@"
750         else
751                 error "unknown repo type"
752         fi
753 log =
754         if [ -d "$MR_REPO"/.svn ]; then
755                 svn log"$@"
756         elif [ -d "$MR_REPO"/.git ]; then
757                 git log "$@"
758         elif [ -d "$MR_REPO"/.bzr ]; then
759                 bzr log "$@"
760         elif [ -d "$MR_REPO"/CVS ]; then
761                 cvs log "$@"
762         else
763                 error "unknown repo type"
764         fi
765 register =
766         if [ -n "$1" ]; then
767                 cd "$1"
768         fi
769         basedir="$(basename $(pwd))"
770         if [ -d .svn ]; then
771                 url=$(LANG=C svn info . | grep -i ^URL: | cut -d ' ' -f 2)
772                 if [ -z "$url" ]; then
773                         error "cannot determine svn url"
774                 fi
775                 echo "Registering svn url: $url"
776                 mr config "$(pwd)" checkout="svn co $url $basedir"
777         elif [ -d .git ]; then
778                 url=$(LANG=C git-config --get remote.origin.url)
779                 if [ -z "$url" ]; then
780                         error "cannot determine git url"
781                 fi
782                 echo "Registering git url: $url"
783                 mr config "$(pwd)" checkout="git clone $url $basedir"
784         elif [ -d .bzr ]; then
785                 url=$(cat .bzr/branch/parent)
786                 if [ -z "$url" ]; then
787                         error "cannot determine bzr url"
788                 fi
789                 echo "Registering bzr url: $url"
790                 mr config "$(pwd)" checkout="bzr clone $url $basedir"
791         else
792                 error "unable to register this repo type"
793         fi
794 help =
795         if [ ! -e "$MR_PATH" ]; then
796                 error "cannot find program path"
797         fi
798         (pod2man -c mr "$MR_PATH" | man -l -) || error "pod2man or man failed"
799 list = true
800 config = 
801
802 ed = echo "A horse is a horse, of course, of course.."
803 T = echo "I pity the fool."