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

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