]> 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:

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