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.
7 mr - a Multiple Repository management tool
11 B<mr> [options] checkout
13 B<mr> [options] update
15 B<mr> [options] status
17 B<mr> [options] commit [-m "message"]
19 B<mr> [options] record [-m "message"]
25 B<mr> [options] register [repository]
27 B<mr> [options] config section ["parameter=[value]" ...]
29 B<mr> [options] action [params ...]
31 B<mr> [options] [online|offline]
33 B<mr> [options] remember action [params ...]
37 B<mr> is a Multiple Repository management tool. It can checkout, update, or
38 perform other actions on a set of repositories as if they were one combined
39 repository. It supports any combination of subversion, git, cvs, mecurial,
40 bzr and darcs repositories, and support for other revision control systems can
43 B<mr> cds into and operates on all registered repositories at or below your
44 working directory. Or, if you are in a subdirectory of a repository that
45 contains no other registered repositories, it will stay in that directory,
46 and work on only that repository,
48 These predefined commands should be fairly familiar to users of any revision
53 =item checkout (or co)
55 Checks out any repositories that are not already checked out.
59 Updates each repository from its configured remote repository.
61 If a repository isn't checked out yet, it will first check it out.
65 Displays a status report for each repository, showing what
66 uncommitted changes are present in the repository.
70 Commits changes to each repository. (By default, changes are pushed to the
71 remote repository too, when using distributed systems like git. If you
72 don't like this default, you can change it in your .mrconfig, or use record
75 The optional -m parameter allows specifying a commit message.
79 Records changes to the local repository, but does not push them to the
80 remote repository. Only supported for distributed revision control systems.
82 The optional -m parameter allows specifying a commit message.
86 Show a diff of uncommitted changes.
94 These commands are also available:
100 List the repositories that mr will act on.
104 Register an existing repository in a mrconfig file. By default, the
105 repository in the current directory is registered, or you can specify a
106 directory to register.
108 The mrconfig file that is modified is chosen by either the -c option, or by
109 looking for the closest known one at or below the current directory.
113 Adds, modifies, removes, or prints a value from a mrconfig file. The next
114 parameter is the name of the section the value is in. To add or modify
115 values, use one or more instances of "parameter=value". Use "parameter=" to
116 remove a parameter. Use just "parameter" to get the value of a parameter.
118 For example, to add (or edit) a repository in src/foo:
120 mr config src/foo checkout="svn co svn://example.com/foo/trunk foo"
122 To show the command that mr uses to update the repository in src/foo:
124 mr config src/foo update
126 To see the built-in library of shell functions contained in mr:
128 mr config DEFAULT lib
130 The ~/.mrconfig file is used by default. To use a different config file,
135 Advises mr that it is in offline mode. Any commands that fail in
136 offline mode will be remembered, and retried when mr is told it's online.
140 Advices mr that it is in online mode again. Commands that failed while in
141 offline mode will be re-run.
145 Remember a command, to be run later when mr re-enters online mode. This
146 implicitly puts mr into offline mode. The command can be any regular mr
147 command. This is useful when you know that a command will fail due to being
148 offline, and so don't want to run it right now at all, but just remember
149 to run it when you go back online.
157 Actions can be abbreviated to any unambiguous substring, so
158 "mr st" is equivalent to "mr status", and "mr up" is equivalent to "mr
161 Additional parameters can be passed to most commands, and are passed on
162 unchanged to the underlying revision control system. This is mostly useful
163 if the repositories mr will act on all use the same revision control
172 Specifies the topmost directory that B<mr> should work in. The default is
173 the current working directory.
177 Use the specified mrconfig file. The default is B<~/.mrconfig>
189 Expand the statistics line displayed at the end to include information
190 about exactly which repositories failed and were skipped, if any.
194 Interactive mode. If a repository fails to be processed, a subshell will be
195 started which you can use to resolve or investigate the problem. Exit the
196 subshell to continue the mr run.
200 If no number if specified, just operate on the repository for the current
201 directory, do not recurse into deeper repositories.
203 If a number is specified, will recurse into repositories at most that many
204 subdirectories deep. For example, with -n 2 it would recurse into ./src/foo,
205 but not ./src/packages/bar.
209 Run the specified number of jobs in parallel, or an unlimited number of jobs
210 with no number specified. This can greatly speed up operations such as updates.
211 It is not recommended for interactive operations.
213 Note that running more than 10 jobs at a time is likely to run afoul of
214 ssh connection limits. Running between 3 and 5 jobs at a time will yeild
215 a good speedup in updates without loading the machine too much.
221 The ~/.mrlog file contains commands that mr has remembered to run later,
222 due to being offline. You can delete or edit this file to remove commands,
223 or even to add other commands for 'mr online' to run. If the file is
224 present, mr assumes it is in offline mode.
226 B<mr> is configured by .mrconfig files. It starts by reading the .mrconfig
227 file in your home directory, and this can in turn chain load .mrconfig files
230 Here is an example .mrconfig file:
233 checkout = svn co svn://svn.example.com/src/trunk src
237 checkout = git clone git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux-2.6.git &&
239 git checkout -b mybranch origin/master
241 The .mrconfig file uses a variant of the INI file format. Lines starting with
242 "#" are comments. Values can be continued to the following line by
243 indenting the line with whitespace.
245 The "DEFAULT" section allows setting default values for the sections that
248 The "ALIAS" section allows adding aliases for actions. Each parameter
249 is an alias, and its value is the action to use.
251 All other sections add repositories. The section header specifies the
252 directory where the repository is located. This is relative to the directory
253 that contains the mrconfig file, but you can also choose to use absolute
254 paths. (Note that you can use environment variables in section names; they
255 will be passed through the shell for expansion. For example,
256 "[$HOSTNAME]", or "[${HOSTNAME}foo]")
258 Within a section, each parameter defines a shell command to run to handle a
259 given action. mr contains default handlers for "update", "status",
260 "commit", and other standard actions. Normally you only need to specify what
261 to do for "checkout".
263 Note that these shell commands are run in a "set -e" shell
264 environment, where any additional parameters you pass are available in
265 "$@". The "checkout" command is run in the parent of the repository
266 directory, since the repository isn't checked out yet. All other commands
267 are run inside the repository, though not necessarily at the top of it.
269 The "MR_REPO" environment variable is set to the path to the top of the
270 repository. (For the "register" action, "MR_REPO" is instead set to the
271 basename of the directory that should be created when checking the
274 The "MR_CONFIG" environment variable is set to the .mrconfig file
275 that defines the repo being acted on, or, if the repo is not yet in a config
276 file, the .mrconfig file that should be modified to register the repo.
278 A few parameters have special meanings:
284 If the "skip" parameter is set and its command returns true, then B<mr>
285 will skip acting on that repository. The command is passed the action
288 Here are two examples. The first skips the repo unless
289 mr is run by joey. The second uses the hours_since function
290 (included in mr's built-in library) to skip updating the repo unless it's
291 been at least 12 hours since the last update.
293 skip = test `whoami` != joey
294 skip = [ "$1" = update ] && ! hours_since "$1" 12
298 The "order" parameter can be used to override the default ordering of
299 repositories. The default order value is 10. Use smaller values to make
300 repositories be processed earlier, and larger values to make repositories
303 Note that if a repository is located in a subdirectory of another
304 repository, ordering it to be processed earlier is not recommended.
308 If the "chain" parameter is set and its command returns true, then B<mr>
309 will try to load a .mrconfig file from the root of the repository. (You
310 should avoid chaining from repositories with untrusted committers.)
314 If the "include" parameter is set, its command is ran, and should output
315 additional mrconfig file content. The content is included as if it were
316 part of the including file.
318 Unlike all other parameters, this parameter does not need to be placed
323 The "lib" parameter can specify some shell code that will be run before each
324 command, this can be a useful way to define shell functions for other commands
329 When looking for a command to run for a given action, mr first looks for
330 a parameter with the same name as the action. If that is not found, it
331 looks for a parameter named "rcs_action" (substituting in the name of the
332 revision control system and the action). The name of the revision control
333 system is itself determined by running each defined "rcs_test" action,
336 Internally, mr has settings for "git_update", "svn_update", etc. To change
337 the action that is performed for a given revision control system, you can
338 override these rcs specific actions. To add a new revision control system,
339 you can just add rcs specific actions for it.
343 Copyright 2007 Joey Hess <joey@kitenet.net>
345 Licensed under the GNU GPL version 2 or higher.
347 http://kitenet.net/~joey/code/mr/
356 use Cwd qw(getcwd abs_path);
358 # things that can happen when mr runs a command
367 my $config_overridden=0;
375 my $directory=getcwd();
376 $ENV{MR_CONFIG}="$ENV{HOME}/.mrconfig";
383 my (@ok, @failed, @skipped);
389 my ($action, $dir, $topdir, $subdir) = @_;
391 if (exists $rcs{$dir}) {
396 foreach my $rcs_test (
398 length $a <=> length $b
401 } grep { /_test$/ } keys %{$config{$topdir}{$subdir}}) {
402 my ($rcs)=$rcs_test=~/(.*)_test/;
403 $test="my_$rcs_test() {\n$config{$topdir}{$subdir}{$rcs_test}\n}\n".$test;
404 $test.="if my_$rcs_test; then echo $rcs; fi\n";
406 $test=$config{$topdir}{$subdir}{lib}."\n".$test
407 if exists $config{$topdir}{$subdir}{lib};
409 print "mr $action: running rcs test >>$test<<\n" if $verbose;
414 print STDERR "mr $action: found multiple possible repository types ($rcs) for $topdir$subdir\n";
418 return $rcs{$dir}=undef;
421 return $rcs{$dir}=$rcs;
425 sub findcommand { #{{{
426 my ($action, $dir, $topdir, $subdir, $is_checkout) = @_;
428 if (exists $config{$topdir}{$subdir}{$action}) {
429 return $config{$topdir}{$subdir}{$action};
436 my $rcs=rcs_test(@_);
439 exists $config{$topdir}{$subdir}{$rcs."_".$action}) {
440 return $config{$topdir}{$subdir}{$rcs."_".$action};
448 my ($action, $dir, $topdir, $subdir) = @_;
450 $ENV{MR_CONFIG}=$configfiles{$topdir};
451 my $lib=exists $config{$topdir}{$subdir}{lib} ?
452 $config{$topdir}{$subdir}{lib}."\n" : "";
453 my $is_checkout=($action eq 'checkout');
459 print "mr $action: $dir already exists, skipping checkout\n" if $verbose;
463 $dir=~s/^(.*)\/[^\/]+\/?$/$1/;
465 elsif ($action =~ /update/) {
467 return action("checkout", $dir, $topdir, $subdir);
471 my $skiptest=findcommand("skip", $dir, $topdir, $subdir, $is_checkout);
472 my $command=findcommand($action, $dir, $topdir, $subdir, $is_checkout);
474 if (defined $skiptest) {
475 my $test="set -e;".$lib.
476 "my_action(){ $skiptest\n }; my_action '$action'";
477 print "mr $action: running skip test >>$test<<\n" if $verbose;
478 my $ret=system($test);
480 if (($? & 127) == 2) {
481 print STDERR "mr $action: interrupted\n";
485 print STDERR "mr $action: skip test received signal ".($? & 127)."\n";
489 if ($ret >> 8 == 0) {
490 print "mr $action: $dir skipped per config file\n" if $verbose;
495 if ($is_checkout && ! -d $dir) {
496 print "mr $action: creating parent directory $dir\n" if $verbose;
497 system("mkdir", "-p", $dir);
500 if (! $no_chdir && ! chdir($dir)) {
501 print STDERR "mr $action: failed to chdir to $dir: $!\n";
504 elsif (! defined $command) {
505 my $rcs=rcs_test(@_);
506 if (! defined $rcs) {
507 print STDERR "mr $action: unknown repository type and no defined $action command for $topdir$subdir\n";
511 print STDERR "mr $action: no defined action for $rcs repository $topdir$subdir, skipping\n";
517 print "mr $action: $topdir$subdir\n" unless $quiet;
521 $s=~s/^\Q$topdir$subdir\E\/?//;
522 print "mr $action: $topdir$subdir (in subdir $s)\n" unless $quiet;
524 $command="set -e; ".$lib.
525 "my_action(){ $command\n }; my_action ".
526 join(" ", map { s/\//\/\//g; s/"/\"/g; '"'.$_.'"' } @ARGV);
527 print "mr $action: running >>$command<<\n" if $verbose;
528 my $ret=system($command);
530 if (($? & 127) == 2) {
531 print STDERR "mr $action: interrupted\n";
535 print STDERR "mr $action: received signal ".($? & 127)."\n";
538 print STDERR "mr $action: failed ($ret)\n" if $verbose;
539 if ($ret >> 8 != 0) {
540 print STDERR "mr $action: command failed\n";
541 if (-e "$ENV{HOME}/.mrlog" && $action ne 'remember') {
542 # recreate original command line to
543 # remember, and avoid recursing
544 @ARGV=('-n', $action, @ARGV);
545 action("remember", $dir, $topdir, $subdir);
549 print STDERR "mr $action: command died ($ret)\n";
554 if ($action eq 'checkout' && ! -d $dir) {
555 print STDERR "mr $action: $dir missing after checkout\n";;
564 # run actions on multiple repos, in parallel
574 while (@fhs or @repos) {
575 while ((!$jobs || $running < $jobs) && @repos) {
577 my $repo = shift @repos;
578 pipe(my $outfh, CHILD_STDOUT);
579 pipe(my $errfh, CHILD_STDERR);
581 unless ($pid = fork) {
582 die "mr $action: cannot fork: $!" unless defined $pid;
583 open(STDOUT, ">&CHILD_STDOUT") || die "mr $action cannot reopen stdout: $!";
584 open(STDERR, ">&CHILD_STDERR") || die "mr $action cannot reopen stderr: $!";
589 exit action($action, @$repo);
593 push @active, [$pid, $repo];
594 push @fhs, [$outfh, $errfh];
597 my ($rin, $rout) = ('','');
599 foreach my $fh (@fhs) {
600 next unless defined $fh;
601 vec($rin, fileno($fh->[0]), 1) = 1 if defined $fh->[0];
602 vec($rin, fileno($fh->[1]), 1) = 1 if defined $fh->[1];
604 $nfound = select($rout=$rin, undef, undef, 1);
605 foreach my $channel (0, 1) {
606 foreach my $i (0..$#fhs) {
607 next unless defined $fhs[$i];
608 my $fh = $fhs[$i][$channel];
609 next unless defined $fh;
610 if (vec($rout, fileno($fh), 1) == 1) {
612 if (sysread($fh, $r, 1024) == 0) {
614 $fhs[$i][$channel] = undef;
615 if (! defined $fhs[$i][0] &&
616 ! defined $fhs[$i][1]) {
617 waitpid($active[$i][0], 0);
618 print STDOUT $out[$i][0];
619 print STDERR $out[$i][1];
620 record($active[$i][1], $? >> 8);
622 splice(@active, $i, 1);
627 $out[$i][$channel] .= $r;
635 my $dir=shift()->[0];
642 elsif ($ret == FAILED) {
644 chdir($dir) unless $no_chdir;
645 print STDERR "mr: Starting interactive shell. Exit shell to continue.\n";
646 system((getpwuid($<))[8]);
651 elsif ($ret == SKIPPED) {
654 elsif ($ret == ABORT) {
658 die "unknown exit status $ret";
664 if (! @ok && ! @failed && ! @skipped) {
665 die "mr $action: no repositories found to work on\n";
667 print "mr $action: finished (".join("; ",
668 showstat($#ok+1, "ok", "ok"),
669 showstat($#failed+1, "failed", "failed"),
670 showstat($#skipped+1, "skipped", "skipped"),
671 ).")\n" unless $quiet;
674 print "mr $action: (skipped: ".join(" ", @skipped).")\n" unless $quiet;
677 print STDERR "mr $action: (failed: ".join(" ", @failed).")\n";
687 return "$count ".($count > 1 ? $plural : $singular);
692 # an ordered list of repos
695 foreach my $topdir (sort keys %config) {
696 foreach my $subdir (sort keys %{$config{$topdir}}) {
700 order => $config{$topdir}{$subdir}{order},
705 $a->{order} <=> $b->{order}
707 $a->{topdir} cmp $b->{topdir}
709 $a->{subdir} cmp $b->{subdir}
713 # figure out which repos to act on
714 sub selectrepos { #{{{
716 foreach my $repo (repolist()) {
717 my $topdir=$repo->{topdir};
718 my $subdir=$repo->{subdir};
720 next if $subdir eq 'DEFAULT';
721 my $dir=($subdir =~/^\//) ? $subdir : $topdir.$subdir;
723 $dir.="/" unless $dir=~/\/$/;
724 $d.="/" unless $d=~/\/$/;
725 next if $dir ne $d && $dir !~ /^\Q$d\E/;
726 if (defined $max_depth) {
727 my @a=split('/', $dir);
728 my @b=split('/', $d);
729 do { } while (@a && @b && shift(@a) eq shift(@b));
730 next if @a > $max_depth || @b > $max_depth;
732 push @repos, [$dir, $topdir, $subdir];
735 # fallback to find a leaf repo
736 foreach my $repo (reverse repolist()) {
737 my $topdir=$repo->{topdir};
738 my $subdir=$repo->{subdir};
740 next if $subdir eq 'DEFAULT';
741 my $dir=($subdir =~/^\//) ? $subdir : $topdir.$subdir;
743 $dir.="/" unless $dir=~/\/$/;
744 $d.="/" unless $d=~/\/$/;
745 if ($d=~/^\Q$dir\E/) {
746 push @repos, [$dir, $topdir, $subdir];
768 sub loadconfig { #{{{
775 if (ref $f eq 'GLOB') {
784 my $absf=abs_path($f);
785 if ($loaded{$absf}) {
790 ($dir)=$f=~/^(.*\/)[^\/]+$/;
791 if (! defined $dir) {
794 $dir=abs_path($dir)."/";
796 if (! exists $configfiles{$dir}) {
797 $configfiles{$dir}=$f;
800 # copy in defaults from first parent
802 while ($parent=~s/^(.*\/)[^\/]+\/?$/$1/) {
803 if ($parent eq '/') {
806 if (exists $config{$parent} &&
807 exists $config{$parent}{DEFAULT}) {
808 $config{$dir}{DEFAULT}={ %{$config{$parent}{DEFAULT}} };
813 print "mr: loading config $f\n" if $verbose;
814 open($in, "<", $f) || die "mr: open $f: $!\n";
825 next if /^\s*\#/ || /^\s*$/;
826 if (/^\[([^\]]*)\]\s*$/) {
827 $section=expandenv($1);
829 elsif (/^(\w+)\s*=\s*(.*)/) {
834 while (@lines && $lines[0]=~/^\s(.+)/) {
841 if ($parameter eq "include") {
842 print "mr: including output of \"$value\"\n" if $verbose;
843 unshift @lines, `$value`;
847 if (! defined $section) {
848 die "$f line $.: parameter ($parameter) not in section\n";
850 if ($section ne 'ALIAS' &&
851 ! exists $config{$dir}{$section} &&
852 exists $config{$dir}{DEFAULT}) {
854 $config{$dir}{$section}={ %{$config{$dir}{DEFAULT}} };
856 if ($section eq 'ALIAS') {
857 $alias{$parameter}=$value;
859 elsif ($parameter eq 'lib') {
860 $config{$dir}{$section}{lib}.=$value."\n";
863 $config{$dir}{$section}{$parameter}=$value;
864 if ($parameter =~ /.*_(.*)/) {
868 $knownactions{$parameter}=1;
870 if ($parameter eq 'chain' &&
871 length $dir && $section ne "DEFAULT" &&
872 -e $dir.$section."/.mrconfig") {
873 my $ret=system($value);
875 if (($? & 127) == 2) {
876 print STDERR "mr: chain test interrupted\n";
880 print STDERR "mr: chain test received signal ".($? & 127)."\n";
884 push @toload, $dir.$section."/.mrconfig";
890 die "$f line $line: parse error\n";
899 sub modifyconfig { #{{{
901 # the section to modify or add
902 my $targetsection=shift;
903 # fields to change in the section
904 # To remove a field, set its value to "".
911 open(my $in, "<", $f) || die "mr: open $f: $!\n";
916 my $formatfield=sub {
918 my @value=split(/\n/, shift);
920 return "$field = ".shift(@value)."\n".
921 join("", map { "\t$_\n" } @value);
925 while ($out[$#out] =~ /^\s*$/) {
926 unshift @blanks, pop @out;
928 foreach my $field (sort keys %changefields) {
929 if (length $changefields{$field}) {
930 push @out, "$field = $changefields{$field}\n";
931 delete $changefields{$field};
941 if (/^\s*\#/ || /^\s*$/) {
944 elsif (/^\[([^\]]*)\]\s*$/) {
945 if (defined $section &&
946 $section eq $targetsection) {
950 $section=expandenv($1);
954 elsif (/^(\w+)\s*=\s(.*)/) {
959 while (@lines && $lines[0]=~/^\s(.+)/) {
965 if ($section eq $targetsection) {
966 if (exists $changefields{$parameter}) {
967 if (length $changefields{$parameter}) {
968 $value=$changefields{$parameter};
970 delete $changefields{$parameter};
974 push @out, $formatfield->($parameter, $value);
978 if (defined $section &&
979 $section eq $targetsection) {
982 elsif (%changefields) {
983 push @out, "\n[$targetsection]\n";
984 foreach my $field (sort keys %changefields) {
985 if (length $changefields{$field}) {
986 push @out, $formatfield->($field, $changefields{$field});
991 open(my $out, ">", $f) || die "mr: write $f: $!\n";
999 # actions that do not operate on all repos
1000 if ($action eq 'help') {
1003 elsif ($action eq 'config') {
1006 elsif ($action eq 'register') {
1009 elsif ($action eq 'remember' ||
1010 $action eq 'offline' ||
1011 $action eq 'online') {
1012 my @repos=selectrepos;
1013 action($action, @{$repos[0]}) if @repos;
1017 if (!$jobs || $jobs > 1) {
1018 mrs($action, selectrepos());
1021 foreach my $repo (selectrepos()) {
1022 record($repo, action($action, @$repo));
1028 exec($config{''}{DEFAULT}{help}) || die "exec: $!";
1033 die "mr config: not enough parameters\n";
1036 if ($section=~/^\//) {
1037 # try to convert to a path relative to the config file
1038 my ($dir)=$ENV{MR_CONFIG}=~/^(.*\/)[^\/]+$/;
1039 $dir=abs_path($dir);
1040 $dir.="/" unless $dir=~/\/$/;
1041 if ($section=~/^\Q$dir\E(.*)/) {
1047 if (/^([^=]+)=(.*)$/) {
1048 $changefields{$1}=$2;
1052 foreach my $topdir (sort keys %config) {
1053 if (exists $config{$topdir}{$section} &&
1054 exists $config{$topdir}{$section}{$_}) {
1055 print $config{$topdir}{$section}{$_}."\n";
1057 last if $section eq 'DEFAULT';
1061 die "mr config: $section $_ not set\n";
1065 modifyconfig($ENV{MR_CONFIG}, $section, %changefields) if %changefields;
1070 if ($config_overridden) {
1071 # Find the directory that the specified config file is
1073 ($directory)=abs_path($ENV{MR_CONFIG})=~/^(.*\/)[^\/]+$/;
1076 # Find the closest known mrconfig file to the current
1078 $directory.="/" unless $directory=~/\/$/;
1080 foreach my $topdir (reverse sort keys %config) {
1081 next unless length $topdir;
1082 if ($directory=~/^\Q$topdir\E/) {
1083 $ENV{MR_CONFIG}=$configfiles{$topdir};
1089 if (! $foundconfig) {
1090 $directory=""; # no config file, use builtin
1094 my $subdir=shift @ARGV;
1095 if (! chdir($subdir)) {
1096 print STDERR "mr register: failed to chdir to $subdir: $!\n";
1100 $ENV{MR_REPO}=getcwd();
1101 my $command=findcommand("register", $ENV{MR_REPO}, $directory, 'DEFAULT', 0);
1102 if (! defined $command) {
1103 die "mr register: unknown repository type\n";
1106 $ENV{MR_REPO}=~s/.*\/(.*)/$1/;
1107 $command="set -e; ".$config{$directory}{DEFAULT}{lib}."\n".
1108 "my_action(){ $command\n }; my_action ".
1109 join(" ", map { s/\//\/\//g; s/"/\"/g; '"'.$_.'"' } @ARGV);
1110 print "mr register: running >>$command<<\n" if $verbose;
1111 exec($command) || die "exec: $!";
1114 # alias expansion and command stemming
1115 sub expandaction { #{{{
1117 if (exists $alias{$action}) {
1118 $action=$alias{$action};
1120 if (! exists $knownactions{$action}) {
1121 my @matches = grep { /^\Q$action\E/ }
1122 keys %knownactions, keys %alias;
1123 if (@matches == 1) {
1124 $action=$matches[0];
1126 elsif (@matches == 0) {
1127 die "mr: unknown action \"$action\" (known actions: ".
1128 join(", ", sort keys %knownactions).")\n";
1131 die "mr: ambiguous action \"$action\" (matches: ".
1132 join(", ", @matches).")\n";
1140 Getopt::Long::Configure("bundling", "no_permute");
1141 my $result=GetOptions(
1142 "d|directory=s" => sub { $directory=abs_path($_[1]) },
1143 "c|config=s" => sub { $ENV{MR_CONFIG}=$_[1]; $config_overridden=1 },
1144 "v|verbose" => \$verbose,
1145 "q|quiet" => \$quiet,
1146 "s|stats" => \$stats,
1147 "i|interactive" => \$interactive,
1148 "n|no-recurse:i" => \$max_depth,
1149 "j|jobs:i" => \$jobs,
1151 if (! $result || @ARGV < 1) {
1152 die("Usage: mr [-d directory] action [params ...]\n".
1153 "(Use mr help for man page.)\n");
1156 $ENV{MR_SWITCHES}="";
1157 foreach my $option (@saved) {
1158 last if $option eq $ARGV[0];
1159 $ENV{MR_SWITCHES}.="$option ";
1165 print STDERR "mr: interrupted\n";
1169 # This can happen if it's run in a directory that was removed
1170 # or other strangeness.
1171 if (! defined $directory) {
1172 die("mr: failed to determine working directory\n");
1174 # Make sure MR_CONFIG is an absolute path, but don't use abs_path since
1175 # the config file might be a symlink to elsewhere, and the directory it's
1176 # in is significant.
1177 if ($ENV{MR_CONFIG} !~ /^\//) {
1178 $ENV{MR_CONFIG}=getcwd()."/".$ENV{MR_CONFIG};
1180 # Try to set MR_PATH to the path to the program.
1182 use FindBin qw($Bin $Script);
1183 $ENV{MR_PATH}=$Bin."/".$Script;
1192 loadconfig($ENV{MR_CONFIG});
1193 #use Data::Dumper; print Dumper(\%config);
1195 my $action=expandaction(shift @ARGV);
1202 elsif (! @ok && @skipped) {
1210 # Finally, some useful actions that mr knows about by default.
1211 # These can be overridden in ~/.mrconfig.
1227 echo "mr (warning): $@" >&2
1233 if [ -z "$1" ] || [ -z "$2" ]; then
1234 error "mr: usage: hours_since action num"
1236 for dir in .git .svn .bzr CVS .hg _darcs; do
1237 if [ -e "$MR_REPO/$dir" ]; then
1238 flagfile="$MR_REPO/$dir/.mr_last$1"
1242 if [ -z "$flagfile" ]; then
1243 error "cannot determine flag filename"
1245 delta=`perl -wle 'print -f shift() ? int((-M _) * 24) : 9999' "$flagfile"`
1246 if [ "$delta" -lt "$2" ]; then
1254 svn_test = test -d "$MR_REPO"/.svn
1255 git_test = test -d "$MR_REPO"/.git
1256 bzr_test = test -d "$MR_REPO"/.bzr
1257 cvs_test = test -d "$MR_REPO"/CVS
1258 hg_test = test -d "$MR_REPO"/.hg
1259 darcs_test = test -d "$MR_REPO"/_darcs
1261 test -d "$MR_REPO"/refs/heads && test -d "$MR_REPO"/refs/tags &&
1262 test -d "$MR_REPO"/objects && test -f "$MR_REPO"/config &&
1263 test "`GIT_CONFIG="$MR_REPO"/config git config --get core.bare`" = true
1265 svn_update = svn update "$@"
1266 git_update = git pull "$@"
1267 bzr_update = bzr merge "$@"
1268 cvs_update = cvs update "$@"
1269 hg_update = hg pull "$@" && hg update "$@"
1270 darcs_update = darcs pull -a "$@"
1272 svn_status = svn status "$@"
1273 git_status = git status "$@" || true
1274 bzr_status = bzr status "$@"
1275 cvs_status = cvs status "$@"
1276 hg_status = hg status "$@"
1277 darcs_status = darcs whatsnew -ls "$@" || true
1279 svn_commit = svn commit "$@"
1280 git_commit = git commit -a "$@" && git push --all
1281 bzr_commit = bzr commit "$@" && bzr push
1282 cvs_commit = cvs commit "$@"
1283 hg_commit = hg commit -m "$@" && hg push
1284 darcs_commit = darcs record -a -m "$@" && darcs push -a
1286 git_record = git commit -a "$@"
1287 bzr_record = bzr commit "$@"
1288 hg_record = hg commit -m "$@"
1289 darcs_record = darcs record -a -m "$@"
1291 svn_diff = svn diff "$@"
1292 git_diff = git diff "$@"
1293 bzr_diff = bzr diff "$@"
1294 cvs_diff = cvs diff "$@"
1295 hg_diff = hg diff "$@"
1296 darcs_diff = darcs diff -u "$@"
1298 svn_log = svn log "$@"
1299 git_log = git log "$@"
1300 bzr_log = bzr log "$@"
1301 cvs_log = cvs log "$@"
1302 hg_log = hg log "$@"
1303 darcs_log = darcs changes "$@"
1304 git_bare_log = git log "$@"
1307 url=`LC_ALL=C svn info . | grep -i '^URL:' | cut -d ' ' -f 2`
1308 if [ -z "$url" ]; then
1309 error "cannot determine svn url"
1311 echo "Registering svn url: $url in $MR_CONFIG"
1312 mr -c "$MR_CONFIG" config "`pwd`" checkout="svn co '$url' '$MR_REPO'"
1314 url="`LC_ALL=C git config --get remote.origin.url`" || true
1315 if [ -z "$url" ]; then
1316 error "cannot determine git url"
1318 echo "Registering git url: $url in $MR_CONFIG"
1319 mr -c "$MR_CONFIG" config "`pwd`" checkout="git clone '$url' '$MR_REPO'"
1321 url="`LC_ALL=C bzr info . | egrep -i 'checkout of branch|parent branch' | awk '{print $NF}'`"
1322 if [ -z "$url" ]; then
1323 error "cannot determine bzr url"
1325 echo "Registering bzr url: $url in $MR_CONFIG"
1326 mr -c "$MR_CONFIG" config "`pwd`" checkout="bzr clone '$url' '$MR_REPO'"
1328 repo=`cat CVS/Repository`
1330 if [ -z "$root" ]; then
1331 error "cannot determine cvs root"
1333 echo "Registering cvs repository $repo at root $root"
1334 mr -c "$MR_CONFIG" config "`pwd`" checkout="cvs -d '$root' co -d '$MR_REPO' '$repo'"
1336 url=`hg showconfig paths.default`
1337 echo "Registering mercurial repo url: $url in $MR_CONFIG"
1338 mr -c "$MR_CONFIG" config "`pwd`" checkout="hg clone '$url' '$MR_REPO'"
1340 url=`cat _darcs/prefs/defaultrepo`
1341 echo "Registering darcs repository $url in $MR_CONFIG"
1342 mr -c "$MR_CONFIG" config "`pwd`" checkout="darcs get '$url' '$MR_REPO'"
1344 url="`LC_ALL=C GIT_CONFIG=config git config --get remote.origin.url`" || true
1345 if [ -z "$url" ]; then
1346 error "cannot determine git url"
1348 echo "Registering git url: $url in $MR_CONFIG"
1349 mr -c "$MR_CONFIG" config "`pwd`" checkout="git clone --bare '$url' '$MR_REPO'"
1352 if [ ! -e "$MR_PATH" ]; then
1353 error "cannot find program path"
1355 tmp=$(mktemp -t mr.XXXXXXXXXX) || error "mktemp failed"
1356 trap "rm -f $tmp" exit
1357 pod2man -c mr "$MR_PATH" > "$tmp" || error "pod2man failed"
1358 man -l "$tmp" || error "man failed"
1363 if [ -s ~/.mrlog ]; then
1364 info "running offline commands"
1365 mv -f ~/.mrlog ~/.mrlog.old
1366 if ! sh ~/.mrlog.old; then
1367 error "offline commands failed; left in ~/.mrlog.old"
1371 info "no offline commands to run"
1375 info "offline mode enabled"
1377 info "remembering command: 'mr $@'"
1378 command="mr -d '$(pwd)' $MR_SWITCHES"
1380 command="$command '$w'"
1382 if [ ! -e ~/.mrlog ] || ! grep -q -F "$command" ~/.mrlog; then
1383 echo "$command" >> ~/.mrlog
1386 ed = echo "A horse is a horse, of course, of course.."
1387 T = echo "I pity the fool."
1388 right = echo "Not found."
1391 # vim:sw=8:sts=0:ts=8:noet