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.
5 mr - a Multiple Repository management tool
9 B<mr> [options] checkout
11 B<mr> [options] update
13 B<mr> [options] status
15 B<mr> [options] commit [-m "message"]
17 B<mr> [options] record [-m "message"]
23 B<mr> [options] register [repository]
25 B<mr> [options] config section ["parameter=[value]" ...]
27 B<mr> [options] action [params ...]
29 B<mr> [options] [online|offline]
31 B<mr> [options] remember action [params ...]
35 B<mr> is a Multiple Repository management tool. It can checkout, update, or
36 perform other actions on a set of repositories as if they were one combined
37 repository. It supports any combination of subversion, git, cvs, mecurial,
38 bzr and darcs repositories, and support for other revision control systems can
41 B<mr> cds into and operates on all registered repositories at or below your
42 working directory. Or, if you are in a subdirectory of a repository that
43 contains no other registered repositories, it will stay in that directory,
44 and work on only that repository,
46 These predefined commands should be fairly familiar to users of any revision
51 =item checkout (or co)
53 Checks out any repositories that are not already checked out.
57 Updates each repository from its configured remote repository.
59 If a repository isn't checked out yet, it will first check it out.
63 Displays a status report for each repository, showing what
64 uncommitted changes are present in the repository.
68 Commits changes to each repository. (By default, changes are pushed to the
69 remote repository too, when using distributed systems like git. If you
70 don't like this default, you can change it in your .mrconfig, or use record
73 The optional -m parameter allows specifying a commit message.
77 Records changes to the local repository, but does not push them to the
78 remote repository. Only supported for distributed revision control systems.
80 The optional -m parameter allows specifying a commit message.
84 Pushes committed local changes to the remote repository. A no-op for
85 centralized revision control systems.
89 Show a diff of uncommitted changes.
97 These commands are also available:
103 List the repositories that mr will act on.
107 Register an existing repository in a mrconfig file. By default, the
108 repository in the current directory is registered, or you can specify a
109 directory to register.
111 The mrconfig file that is modified is chosen by either the -c option, or by
112 looking for the closest known one at or below the current directory.
116 Adds, modifies, removes, or prints a value from a mrconfig file. The next
117 parameter is the name of the section the value is in. To add or modify
118 values, use one or more instances of "parameter=value". Use "parameter=" to
119 remove a parameter. Use just "parameter" to get the value of a parameter.
121 For example, to add (or edit) a repository in src/foo:
123 mr config src/foo checkout="svn co svn://example.com/foo/trunk foo"
125 To show the command that mr uses to update the repository in src/foo:
127 mr config src/foo update
129 To see the built-in library of shell functions contained in mr:
131 mr config DEFAULT lib
133 The ~/.mrconfig file is used by default. To use a different config file,
138 Advises mr that it is in offline mode. Any commands that fail in
139 offline mode will be remembered, and retried when mr is told it's online.
143 Advices mr that it is in online mode again. Commands that failed while in
144 offline mode will be re-run.
148 Remember a command, to be run later when mr re-enters online mode. This
149 implicitly puts mr into offline mode. The command can be any regular mr
150 command. This is useful when you know that a command will fail due to being
151 offline, and so don't want to run it right now at all, but just remember
152 to run it when you go back online.
160 Actions can be abbreviated to any unambiguous substring, so
161 "mr st" is equivalent to "mr status", and "mr up" is equivalent to "mr
164 Additional parameters can be passed to most commands, and are passed on
165 unchanged to the underlying revision control system. This is mostly useful
166 if the repositories mr will act on all use the same revision control
175 Specifies the topmost directory that B<mr> should work in. The default is
176 the current working directory.
180 Use the specified mrconfig file. The default is B<~/.mrconfig>
192 Expand the statistics line displayed at the end to include information
193 about exactly which repositories failed and were skipped, if any.
197 Interactive mode. If a repository fails to be processed, a subshell will be
198 started which you can use to resolve or investigate the problem. Exit the
199 subshell to continue the mr run.
203 If no number if specified, just operate on the repository for the current
204 directory, do not recurse into deeper repositories.
206 If a number is specified, will recurse into repositories at most that many
207 subdirectories deep. For example, with -n 2 it would recurse into ./src/foo,
208 but not ./src/packages/bar.
212 Run the specified number of jobs in parallel, or an unlimited number of jobs
213 with no number specified. This can greatly speed up operations such as updates.
214 It is not recommended for interactive operations.
216 Note that running more than 10 jobs at a time is likely to run afoul of
217 ssh connection limits. Running between 3 and 5 jobs at a time will yield
218 a good speedup in updates without loading the machine too much.
224 B<mr> is configured by .mrconfig files. It starts by reading the .mrconfig
225 file in your home directory, and this can in turn chain load .mrconfig files
228 Here is an example .mrconfig file:
231 checkout = svn co svn://svn.example.com/src/trunk src
235 checkout = git clone git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux-2.6.git &&
237 git checkout -b mybranch origin/master
239 The .mrconfig file uses a variant of the INI file format. Lines starting with
240 "#" are comments. Values can be continued to the following line by
241 indenting the line with whitespace.
243 The "DEFAULT" section allows setting default values for the sections that
246 The "ALIAS" section allows adding aliases for actions. Each parameter
247 is an alias, and its value is the action to use.
249 All other sections add repositories. The section header specifies the
250 directory where the repository is located. This is relative to the directory
251 that contains the mrconfig file, but you can also choose to use absolute
252 paths. (Note that you can use environment variables in section names; they
253 will be passed through the shell for expansion. For example,
254 "[$HOSTNAME]", or "[${HOSTNAME}foo]")
256 Within a section, each parameter defines a shell command to run to handle a
257 given action. mr contains default handlers for "update", "status",
258 "commit", and other standard actions. Normally you only need to specify what
259 to do for "checkout".
261 Note that these shell commands are run in a "set -e" shell
262 environment, where any additional parameters you pass are available in
263 "$@". The "checkout" command is run in the parent of the repository
264 directory, since the repository isn't checked out yet. All other commands
265 are run inside the repository, though not necessarily at the top of it.
267 The "MR_REPO" environment variable is set to the path to the top of the
268 repository. (For the "register" action, "MR_REPO" is instead set to the
269 basename of the directory that should be created when checking the
272 The "MR_CONFIG" environment variable is set to the .mrconfig file
273 that defines the repo being acted on, or, if the repo is not yet in a config
274 file, the .mrconfig file that should be modified to register the repo.
276 A few parameters have special meanings:
282 If the "skip" parameter is set and its command returns true, then B<mr>
283 will skip acting on that repository. The command is passed the action
286 Here are two examples. The first skips the repo unless
287 mr is run by joey. The second uses the hours_since function
288 (included in mr's built-in library) to skip updating the repo unless it's
289 been at least 12 hours since the last update.
291 skip = test `whoami` != joey
292 skip = [ "$1" = update ] && ! hours_since "$1" 12
296 The "order" parameter can be used to override the default ordering of
297 repositories. The default order value is 10. Use smaller values to make
298 repositories be processed earlier, and larger values to make repositories
301 Note that if a repository is located in a subdirectory of another
302 repository, ordering it to be processed earlier is not recommended.
306 If the "chain" parameter is set and its command returns true, then B<mr>
307 will try to load a .mrconfig file from the root of the repository. (You
308 should avoid chaining from repositories with untrusted committers.)
312 If the "include" parameter is set, its command is ran, and should output
313 additional mrconfig file content. The content is included as if it were
314 part of the including file.
316 Unlike all other parameters, this parameter does not need to be placed
321 The "lib" parameter can specify some shell code that will be run before each
322 command, this can be a useful way to define shell functions for other commands
327 When looking for a command to run for a given action, mr first looks for
328 a parameter with the same name as the action. If that is not found, it
329 looks for a parameter named "rcs_action" (substituting in the name of the
330 revision control system and the action). The name of the revision control
331 system is itself determined by running each defined "rcs_test" action,
334 Internally, mr has settings for "git_update", "svn_update", etc. To change
335 the action that is performed for a given revision control system, you can
336 override these rcs specific actions. To add a new revision control system,
337 you can just add rcs specific actions for it.
339 The ~/.mrlog file contains commands that mr has remembered to run later,
340 due to being offline. You can delete or edit this file to remove commands,
341 or even to add other commands for 'mr online' to run. If the file is
342 present, mr assumes it is in offline mode.
346 mr can be extended to support things such as unison and git-svn. Some
347 files providing such extensions are available in /usr/share/mr/. See
348 the documentation in the files for details about using them.
352 Copyright 2007 Joey Hess <joey@kitenet.net>
354 Licensed under the GNU GPL version 2 or higher.
356 http://kitenet.net/~joey/code/mr/
363 use Cwd qw(getcwd abs_path);
365 # things that can happen when mr runs a command
374 my $config_overridden=0;
382 my $directory=getcwd();
383 $ENV{MR_CONFIG}="$ENV{HOME}/.mrconfig";
390 my (@ok, @failed, @skipped);
396 my ($action, $dir, $topdir, $subdir) = @_;
398 if (exists $rcs{$dir}) {
403 foreach my $rcs_test (
405 length $a <=> length $b
408 } grep { /_test$/ } keys %{$config{$topdir}{$subdir}}) {
409 my ($rcs)=$rcs_test=~/(.*)_test/;
410 $test="my_$rcs_test() {\n$config{$topdir}{$subdir}{$rcs_test}\n}\n".$test;
411 $test.="if my_$rcs_test; then echo $rcs; fi\n";
413 $test=$config{$topdir}{$subdir}{lib}."\n".$test
414 if exists $config{$topdir}{$subdir}{lib};
416 print "mr $action: running rcs test >>$test<<\n" if $verbose;
421 print STDERR "mr $action: found multiple possible repository types ($rcs) for $topdir$subdir\n";
425 return $rcs{$dir}=undef;
428 return $rcs{$dir}=$rcs;
433 my ($action, $dir, $topdir, $subdir, $is_checkout) = @_;
435 if (exists $config{$topdir}{$subdir}{$action}) {
436 return $config{$topdir}{$subdir}{$action};
443 my $rcs=rcs_test(@_);
446 exists $config{$topdir}{$subdir}{$rcs."_".$action}) {
447 return $config{$topdir}{$subdir}{$rcs."_".$action};
455 my ($action, $dir, $topdir, $subdir) = @_;
457 $ENV{MR_CONFIG}=$configfiles{$topdir};
458 my $lib=exists $config{$topdir}{$subdir}{lib} ?
459 $config{$topdir}{$subdir}{lib}."\n" : "";
460 my $is_checkout=($action eq 'checkout');
466 print "mr $action: $dir already exists, skipping checkout\n" if $verbose;
470 $dir=~s/^(.*)\/[^\/]+\/?$/$1/;
472 elsif ($action =~ /update/) {
474 return action("checkout", $dir, $topdir, $subdir);
478 my $skiptest=findcommand("skip", $dir, $topdir, $subdir, $is_checkout);
479 my $command=findcommand($action, $dir, $topdir, $subdir, $is_checkout);
481 if (defined $skiptest) {
482 my $test="set -e;".$lib.
483 "my_action(){ $skiptest\n }; my_action '$action'";
484 print "mr $action: running skip test >>$test<<\n" if $verbose;
485 my $ret=system($test);
487 if (($? & 127) == 2) {
488 print STDERR "mr $action: interrupted\n";
492 print STDERR "mr $action: skip test received signal ".($? & 127)."\n";
496 if ($ret >> 8 == 0) {
497 print "mr $action: $dir skipped per config file\n" if $verbose;
502 if ($is_checkout && ! -d $dir) {
503 print "mr $action: creating parent directory $dir\n" if $verbose;
504 system("mkdir", "-p", $dir);
507 if (! $no_chdir && ! chdir($dir)) {
508 print STDERR "mr $action: failed to chdir to $dir: $!\n";
511 elsif (! defined $command) {
512 my $rcs=rcs_test(@_);
513 if (! defined $rcs) {
514 print STDERR "mr $action: unknown repository type and no defined $action command for $topdir$subdir\n";
518 print STDERR "mr $action: no defined action for $rcs repository $topdir$subdir, skipping\n";
524 print "mr $action: $topdir$subdir\n" unless $quiet;
528 $s=~s/^\Q$topdir$subdir\E\/?//;
529 print "mr $action: $topdir$subdir (in subdir $s)\n" unless $quiet;
531 $command="set -e; ".$lib.
532 "my_action(){ $command\n }; my_action ".
533 join(" ", map { s/\//\/\//g; s/"/\"/g; '"'.$_.'"' } @ARGV);
534 print "mr $action: running >>$command<<\n" if $verbose;
535 my $ret=system($command);
537 if (($? & 127) == 2) {
538 print STDERR "mr $action: interrupted\n";
542 print STDERR "mr $action: received signal ".($? & 127)."\n";
545 print STDERR "mr $action: failed ($ret)\n" if $verbose;
546 if ($ret >> 8 != 0) {
547 print STDERR "mr $action: command failed\n";
548 if (-e "$ENV{HOME}/.mrlog" && $action ne 'remember') {
549 # recreate original command line to
550 # remember, and avoid recursing
552 @ARGV=('-n', $action, @orig);
553 action("remember", $dir, $topdir, $subdir);
558 print STDERR "mr $action: command died ($ret)\n";
563 if ($action eq 'checkout' && ! -d $dir) {
564 print STDERR "mr $action: $dir missing after checkout\n";;
573 # run actions on multiple repos, in parallel
583 while (@fhs or @repos) {
584 while ((!$jobs || $running < $jobs) && @repos) {
586 my $repo = shift @repos;
587 pipe(my $outfh, CHILD_STDOUT);
588 pipe(my $errfh, CHILD_STDERR);
590 unless ($pid = fork) {
591 die "mr $action: cannot fork: $!" unless defined $pid;
592 open(STDOUT, ">&CHILD_STDOUT") || die "mr $action cannot reopen stdout: $!";
593 open(STDERR, ">&CHILD_STDERR") || die "mr $action cannot reopen stderr: $!";
598 exit action($action, @$repo);
602 push @active, [$pid, $repo];
603 push @fhs, [$outfh, $errfh];
606 my ($rin, $rout) = ('','');
608 foreach my $fh (@fhs) {
609 next unless defined $fh;
610 vec($rin, fileno($fh->[0]), 1) = 1 if defined $fh->[0];
611 vec($rin, fileno($fh->[1]), 1) = 1 if defined $fh->[1];
613 $nfound = select($rout=$rin, undef, undef, 1);
614 foreach my $channel (0, 1) {
615 foreach my $i (0..$#fhs) {
616 next unless defined $fhs[$i];
617 my $fh = $fhs[$i][$channel];
618 next unless defined $fh;
619 if (vec($rout, fileno($fh), 1) == 1) {
621 if (sysread($fh, $r, 1024) == 0) {
623 $fhs[$i][$channel] = undef;
624 if (! defined $fhs[$i][0] &&
625 ! defined $fhs[$i][1]) {
626 waitpid($active[$i][0], 0);
627 print STDOUT $out[$i][0];
628 print STDERR $out[$i][1];
629 record($active[$i][1], $? >> 8);
631 splice(@active, $i, 1);
636 $out[$i][$channel] .= $r;
644 my $dir=shift()->[0];
651 elsif ($ret == FAILED) {
653 chdir($dir) unless $no_chdir;
654 print STDERR "mr: Starting interactive shell. Exit shell to continue.\n";
655 system((getpwuid($<))[8]);
660 elsif ($ret == SKIPPED) {
663 elsif ($ret == ABORT) {
667 die "unknown exit status $ret";
673 if (! @ok && ! @failed && ! @skipped) {
674 die "mr $action: no repositories found to work on\n";
676 print "mr $action: finished (".join("; ",
677 showstat($#ok+1, "ok", "ok"),
678 showstat($#failed+1, "failed", "failed"),
679 showstat($#skipped+1, "skipped", "skipped"),
680 ).")\n" unless $quiet;
683 print "mr $action: (skipped: ".join(" ", @skipped).")\n" unless $quiet;
686 print STDERR "mr $action: (failed: ".join(" ", @failed).")\n";
696 return "$count ".($count > 1 ? $plural : $singular);
701 # an ordered list of repos
704 foreach my $topdir (sort keys %config) {
705 foreach my $subdir (sort keys %{$config{$topdir}}) {
709 order => $config{$topdir}{$subdir}{order},
714 $a->{order} <=> $b->{order}
716 $a->{topdir} cmp $b->{topdir}
718 $a->{subdir} cmp $b->{subdir}
722 # figure out which repos to act on
725 foreach my $repo (repolist()) {
726 my $topdir=$repo->{topdir};
727 my $subdir=$repo->{subdir};
729 next if $subdir eq 'DEFAULT';
730 my $dir=($subdir =~/^\//) ? $subdir : $topdir.$subdir;
732 $dir.="/" unless $dir=~/\/$/;
733 $d.="/" unless $d=~/\/$/;
734 next if $dir ne $d && $dir !~ /^\Q$d\E/;
735 if (defined $max_depth) {
736 my @a=split('/', $dir);
737 my @b=split('/', $d);
738 do { } while (@a && @b && shift(@a) eq shift(@b));
739 next if @a > $max_depth || @b > $max_depth;
741 push @repos, [$dir, $topdir, $subdir];
744 # fallback to find a leaf repo
745 foreach my $repo (reverse repolist()) {
746 my $topdir=$repo->{topdir};
747 my $subdir=$repo->{subdir};
749 next if $subdir eq 'DEFAULT';
750 my $dir=($subdir =~/^\//) ? $subdir : $topdir.$subdir;
752 $dir.="/" unless $dir=~/\/$/;
753 $d.="/" unless $d=~/\/$/;
754 if ($d=~/^\Q$dir\E/) {
755 push @repos, [$dir, $topdir, $subdir];
784 if (ref $f eq 'GLOB') {
793 my $absf=abs_path($f);
794 if ($loaded{$absf}) {
799 ($dir)=$f=~/^(.*\/)[^\/]+$/;
800 if (! defined $dir) {
803 $dir=abs_path($dir)."/";
805 if (! exists $configfiles{$dir}) {
806 $configfiles{$dir}=$f;
809 # copy in defaults from first parent
811 while ($parent=~s/^(.*\/)[^\/]+\/?$/$1/) {
812 if ($parent eq '/') {
815 if (exists $config{$parent} &&
816 exists $config{$parent}{DEFAULT}) {
817 $config{$dir}{DEFAULT}={ %{$config{$parent}{DEFAULT}} };
822 print "mr: loading config $f\n" if $verbose;
823 open($in, "<", $f) || die "mr: open $f: $!\n";
834 next if /^\s*\#/ || /^\s*$/;
835 if (/^\[([^\]]*)\]\s*$/) {
836 $section=expandenv($1);
838 elsif (/^(\w+)\s*=\s*(.*)/) {
843 while (@lines && $lines[0]=~/^\s(.+)/) {
850 if ($parameter eq "include") {
851 print "mr: including output of \"$value\"\n" if $verbose;
852 unshift @lines, `$value`;
854 print STDERR "mr: include command exited nonzero ($?)\n";
859 if (! defined $section) {
860 die "$f line $.: parameter ($parameter) not in section\n";
862 if ($section ne 'ALIAS' &&
863 ! exists $config{$dir}{$section} &&
864 exists $config{$dir}{DEFAULT}) {
866 $config{$dir}{$section}={ %{$config{$dir}{DEFAULT}} };
868 if ($section eq 'ALIAS') {
869 $alias{$parameter}=$value;
871 elsif ($parameter eq 'lib') {
872 $config{$dir}{$section}{lib}.=$value."\n";
875 $config{$dir}{$section}{$parameter}=$value;
876 if ($parameter =~ /.*_(.*)/) {
880 $knownactions{$parameter}=1;
882 if ($parameter eq 'chain' &&
883 length $dir && $section ne "DEFAULT" &&
884 -e $dir.$section."/.mrconfig") {
885 my $ret=system($value);
887 if (($? & 127) == 2) {
888 print STDERR "mr: chain test interrupted\n";
892 print STDERR "mr: chain test received signal ".($? & 127)."\n";
896 push @toload, $dir.$section."/.mrconfig";
902 die "$f line $line: parse error\n";
913 # the section to modify or add
914 my $targetsection=shift;
915 # fields to change in the section
916 # To remove a field, set its value to "".
923 open(my $in, "<", $f) || die "mr: open $f: $!\n";
928 my $formatfield=sub {
930 my @value=split(/\n/, shift);
932 return "$field = ".shift(@value)."\n".
933 join("", map { "\t$_\n" } @value);
937 while ($out[$#out] =~ /^\s*$/) {
938 unshift @blanks, pop @out;
940 foreach my $field (sort keys %changefields) {
941 if (length $changefields{$field}) {
942 push @out, "$field = $changefields{$field}\n";
943 delete $changefields{$field};
953 if (/^\s*\#/ || /^\s*$/) {
956 elsif (/^\[([^\]]*)\]\s*$/) {
957 if (defined $section &&
958 $section eq $targetsection) {
962 $section=expandenv($1);
966 elsif (/^(\w+)\s*=\s(.*)/) {
971 while (@lines && $lines[0]=~/^\s(.+)/) {
977 if ($section eq $targetsection) {
978 if (exists $changefields{$parameter}) {
979 if (length $changefields{$parameter}) {
980 $value=$changefields{$parameter};
982 delete $changefields{$parameter};
986 push @out, $formatfield->($parameter, $value);
990 if (defined $section &&
991 $section eq $targetsection) {
994 elsif (%changefields) {
995 push @out, "\n[$targetsection]\n";
996 foreach my $field (sort keys %changefields) {
997 if (length $changefields{$field}) {
998 push @out, $formatfield->($field, $changefields{$field});
1003 open(my $out, ">", $f) || die "mr: write $f: $!\n";
1011 # actions that do not operate on all repos
1012 if ($action eq 'help') {
1015 elsif ($action eq 'config') {
1018 elsif ($action eq 'register') {
1021 elsif ($action eq 'remember' ||
1022 $action eq 'offline' ||
1023 $action eq 'online') {
1024 my @repos=selectrepos;
1025 action($action, @{$repos[0]}) if @repos;
1029 if (!$jobs || $jobs > 1) {
1030 mrs($action, selectrepos());
1033 foreach my $repo (selectrepos()) {
1034 record($repo, action($action, @$repo));
1040 exec($config{''}{DEFAULT}{help}) || die "exec: $!";
1045 die "mr config: not enough parameters\n";
1048 if ($section=~/^\//) {
1049 # try to convert to a path relative to the config file
1050 my ($dir)=$ENV{MR_CONFIG}=~/^(.*\/)[^\/]+$/;
1051 $dir=abs_path($dir);
1052 $dir.="/" unless $dir=~/\/$/;
1053 if ($section=~/^\Q$dir\E(.*)/) {
1059 if (/^([^=]+)=(.*)$/) {
1060 $changefields{$1}=$2;
1064 foreach my $topdir (sort keys %config) {
1065 if (exists $config{$topdir}{$section} &&
1066 exists $config{$topdir}{$section}{$_}) {
1067 print $config{$topdir}{$section}{$_}."\n";
1069 last if $section eq 'DEFAULT';
1073 die "mr config: $section $_ not set\n";
1077 modifyconfig($ENV{MR_CONFIG}, $section, %changefields) if %changefields;
1082 if ($config_overridden) {
1083 # Find the directory that the specified config file is
1085 ($directory)=abs_path($ENV{MR_CONFIG})=~/^(.*\/)[^\/]+$/;
1088 # Find the closest known mrconfig file to the current
1090 $directory.="/" unless $directory=~/\/$/;
1092 foreach my $topdir (reverse sort keys %config) {
1093 next unless length $topdir;
1094 if ($directory=~/^\Q$topdir\E/) {
1095 $ENV{MR_CONFIG}=$configfiles{$topdir};
1101 if (! $foundconfig) {
1102 $directory=""; # no config file, use builtin
1106 my $subdir=shift @ARGV;
1107 if (! chdir($subdir)) {
1108 print STDERR "mr register: failed to chdir to $subdir: $!\n";
1112 $ENV{MR_REPO}=getcwd();
1113 my $command=findcommand("register", $ENV{MR_REPO}, $directory, 'DEFAULT', 0);
1114 if (! defined $command) {
1115 die "mr register: unknown repository type\n";
1118 $ENV{MR_REPO}=~s/.*\/(.*)/$1/;
1119 $command="set -e; ".$config{$directory}{DEFAULT}{lib}."\n".
1120 "my_action(){ $command\n }; my_action ".
1121 join(" ", map { s/\//\/\//g; s/"/\"/g; '"'.$_.'"' } @ARGV);
1122 print "mr register: running >>$command<<\n" if $verbose;
1123 exec($command) || die "exec: $!";
1126 # alias expansion and command stemming
1129 if (exists $alias{$action}) {
1130 $action=$alias{$action};
1132 if (! exists $knownactions{$action}) {
1133 my @matches = grep { /^\Q$action\E/ }
1134 keys %knownactions, keys %alias;
1135 if (@matches == 1) {
1136 $action=$matches[0];
1138 elsif (@matches == 0) {
1139 die "mr: unknown action \"$action\" (known actions: ".
1140 join(", ", sort keys %knownactions).")\n";
1143 die "mr: ambiguous action \"$action\" (matches: ".
1144 join(", ", @matches).")\n";
1152 Getopt::Long::Configure("bundling", "no_permute");
1153 my $result=GetOptions(
1154 "d|directory=s" => sub { $directory=abs_path($_[1]) },
1155 "c|config=s" => sub { $ENV{MR_CONFIG}=$_[1]; $config_overridden=1 },
1156 "v|verbose" => \$verbose,
1157 "q|quiet" => \$quiet,
1158 "s|stats" => \$stats,
1159 "i|interactive" => \$interactive,
1160 "n|no-recurse:i" => \$max_depth,
1161 "j|jobs:i" => \$jobs,
1163 if (! $result || @ARGV < 1) {
1164 die("Usage: mr [-d directory] action [params ...]\n".
1165 "(Use mr help for man page.)\n");
1168 $ENV{MR_SWITCHES}="";
1169 foreach my $option (@saved) {
1170 last if $option eq $ARGV[0];
1171 $ENV{MR_SWITCHES}.="$option ";
1177 print STDERR "mr: interrupted\n";
1181 # This can happen if it's run in a directory that was removed
1182 # or other strangeness.
1183 if (! defined $directory) {
1184 die("mr: failed to determine working directory\n");
1186 # Make sure MR_CONFIG is an absolute path, but don't use abs_path since
1187 # the config file might be a symlink to elsewhere, and the directory it's
1188 # in is significant.
1189 if ($ENV{MR_CONFIG} !~ /^\//) {
1190 $ENV{MR_CONFIG}=getcwd()."/".$ENV{MR_CONFIG};
1192 # Try to set MR_PATH to the path to the program.
1194 use FindBin qw($Bin $Script);
1195 $ENV{MR_PATH}=$Bin."/".$Script;
1204 loadconfig($ENV{MR_CONFIG});
1205 #use Data::Dumper; print Dumper(\%config);
1207 my $action=expandaction(shift @ARGV);
1214 elsif (! @ok && @skipped) {
1222 # Finally, some useful actions that mr knows about by default.
1223 # These can be overridden in ~/.mrconfig.
1238 echo "mr (warning): $@" >&2
1244 if [ -z "$1" ] || [ -z "$2" ]; then
1245 error "mr: usage: hours_since action num"
1247 for dir in .git .svn .bzr CVS .hg _darcs; do
1248 if [ -e "$MR_REPO/$dir" ]; then
1249 flagfile="$MR_REPO/$dir/.mr_last$1"
1253 if [ -z "$flagfile" ]; then
1254 error "cannot determine flag filename"
1256 delta=`perl -wle 'print -f shift() ? int((-M _) * 24) : 9999' "$flagfile"`
1257 if [ "$delta" -lt "$2" ]; then
1265 svn_test = test -d "$MR_REPO"/.svn
1266 git_test = test -d "$MR_REPO"/.git
1267 bzr_test = test -d "$MR_REPO"/.bzr
1268 cvs_test = test -d "$MR_REPO"/CVS
1269 hg_test = test -d "$MR_REPO"/.hg
1270 darcs_test = test -d "$MR_REPO"/_darcs
1272 test -d "$MR_REPO"/refs/heads && test -d "$MR_REPO"/refs/tags &&
1273 test -d "$MR_REPO"/objects && test -f "$MR_REPO"/config &&
1274 test "`GIT_CONFIG="$MR_REPO"/config git config --get core.bare`" = true
1276 svn_update = svn update "$@"
1277 git_update = git pull "$@"
1278 bzr_update = bzr merge "$@"
1279 cvs_update = cvs update "$@"
1280 hg_update = hg pull "$@" && hg update "$@"
1281 darcs_update = darcs pull -a "$@"
1283 svn_status = svn status "$@"
1284 git_status = git status "$@" || true
1285 bzr_status = bzr status "$@"
1286 cvs_status = cvs status "$@"
1287 hg_status = hg status "$@"
1288 darcs_status = darcs whatsnew -ls "$@" || true
1290 svn_commit = svn commit "$@"
1291 git_commit = git commit -a "$@" && git push --all
1292 bzr_commit = bzr commit "$@" && bzr push
1293 cvs_commit = cvs commit "$@"
1294 hg_commit = hg commit -m "$@" && hg push
1295 darcs_commit = darcs record -a -m "$@" && darcs push -a
1297 git_record = git commit -a "$@"
1298 bzr_record = bzr commit "$@"
1299 hg_record = hg commit -m "$@"
1300 darcs_record = darcs record -a -m "$@"
1303 git_push = git push "$@"
1304 bzr_push = bzr push "$@"
1306 hg_push = hg push "$@"
1307 darcs_push = darcs push -a "$@"
1309 svn_diff = svn diff "$@"
1310 git_diff = git diff "$@"
1311 bzr_diff = bzr diff "$@"
1312 cvs_diff = cvs diff "$@"
1313 hg_diff = hg diff "$@"
1314 darcs_diff = darcs diff -u "$@"
1316 svn_log = svn log "$@"
1317 git_log = git log "$@"
1318 bzr_log = bzr log "$@"
1319 cvs_log = cvs log "$@"
1320 hg_log = hg log "$@"
1321 darcs_log = darcs changes "$@"
1322 git_bare_log = git log "$@"
1325 url=`LC_ALL=C svn info . | grep -i '^URL:' | cut -d ' ' -f 2`
1326 if [ -z "$url" ]; then
1327 error "cannot determine svn url"
1329 echo "Registering svn url: $url in $MR_CONFIG"
1330 mr -c "$MR_CONFIG" config "`pwd`" checkout="svn co '$url' '$MR_REPO'"
1332 url="`LC_ALL=C git config --get remote.origin.url`" || true
1333 if [ -z "$url" ]; then
1334 error "cannot determine git url"
1336 echo "Registering git url: $url in $MR_CONFIG"
1337 mr -c "$MR_CONFIG" config "`pwd`" checkout="git clone '$url' '$MR_REPO'"
1339 url="`LC_ALL=C bzr info . | egrep -i 'checkout of branch|parent branch' | awk '{print $NF}'`"
1340 if [ -z "$url" ]; then
1341 error "cannot determine bzr url"
1343 echo "Registering bzr url: $url in $MR_CONFIG"
1344 mr -c "$MR_CONFIG" config "`pwd`" checkout="bzr clone '$url' '$MR_REPO'"
1346 repo=`cat CVS/Repository`
1348 if [ -z "$root" ]; then
1349 error "cannot determine cvs root"
1351 echo "Registering cvs repository $repo at root $root"
1352 mr -c "$MR_CONFIG" config "`pwd`" checkout="cvs -d '$root' co -d '$MR_REPO' '$repo'"
1354 url=`hg showconfig paths.default`
1355 echo "Registering mercurial repo url: $url in $MR_CONFIG"
1356 mr -c "$MR_CONFIG" config "`pwd`" checkout="hg clone '$url' '$MR_REPO'"
1358 url=`cat _darcs/prefs/defaultrepo`
1359 echo "Registering darcs repository $url in $MR_CONFIG"
1360 mr -c "$MR_CONFIG" config "`pwd`" checkout="darcs get '$url' '$MR_REPO'"
1362 url="`LC_ALL=C GIT_CONFIG=config git config --get remote.origin.url`" || true
1363 if [ -z "$url" ]; then
1364 error "cannot determine git url"
1366 echo "Registering git url: $url in $MR_CONFIG"
1367 mr -c "$MR_CONFIG" config "`pwd`" checkout="git clone --bare '$url' '$MR_REPO'"
1370 if [ ! -e "$MR_PATH" ]; then
1371 error "cannot find program path"
1373 tmp=$(mktemp -t mr.XXXXXXXXXX) || error "mktemp failed"
1374 trap "rm -f $tmp" exit
1375 pod2man -c mr "$MR_PATH" > "$tmp" || error "pod2man failed"
1376 man -l "$tmp" || error "man failed"
1381 if [ -s ~/.mrlog ]; then
1382 info "running offline commands"
1383 mv -f ~/.mrlog ~/.mrlog.old
1384 if ! sh -e ~/.mrlog.old; then
1385 error "offline command failed; left in ~/.mrlog.old"
1389 info "no offline commands to run"
1394 info "offline mode enabled"
1396 info "remembering command: 'mr $@'"
1397 command="mr -d '$(pwd)' $MR_SWITCHES"
1399 command="$command '$w'"
1401 if [ ! -e ~/.mrlog ] || ! grep -q -F "$command" ~/.mrlog; then
1402 echo "$command" >> ~/.mrlog
1405 ed = echo "A horse is a horse, of course, of course.."
1406 T = echo "I pity the fool."
1407 right = echo "Not found."
1409 # vim:sw=8:sts=0:ts=8:noet