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

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