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>
184 Search in the current directory, and its parent directories and use
185 the first B<.mrconfig> found, instead of the default B<~/.mrconfig>.
197 Expand the statistics line displayed at the end to include information
198 about exactly which repositories failed and were skipped, if any.
202 Interactive mode. If a repository fails to be processed, a subshell will be
203 started which you can use to resolve or investigate the problem. Exit the
204 subshell to continue the mr run.
208 If no number if specified, just operate on the repository for the current
209 directory, do not recurse into deeper repositories.
211 If a number is specified, will recurse into repositories at most that many
212 subdirectories deep. For example, with -n 2 it would recurse into ./src/foo,
213 but not ./src/packages/bar.
217 Run the specified number of jobs in parallel, or an unlimited number of jobs
218 with no number specified. This can greatly speed up operations such as updates.
219 It is not recommended for interactive operations.
221 Note that running more than 10 jobs at a time is likely to run afoul of
222 ssh connection limits. Running between 3 and 5 jobs at a time will yield
223 a good speedup in updates without loading the machine too much.
229 B<mr> is configured by .mrconfig files. It starts by reading the .mrconfig
230 file in your home directory, and this can in turn chain load .mrconfig files
233 Here is an example .mrconfig file:
236 checkout = svn co svn://svn.example.com/src/trunk src
240 checkout = git clone git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux-2.6.git &&
242 git checkout -b mybranch origin/master
244 The .mrconfig file uses a variant of the INI file format. Lines starting with
245 "#" are comments. Values can be continued to the following line by
246 indenting the line with whitespace.
248 The "DEFAULT" section allows setting default values for the sections that
251 The "ALIAS" section allows adding aliases for actions. Each parameter
252 is an alias, and its value is the action to use.
254 All other sections add repositories. The section header specifies the
255 directory where the repository is located. This is relative to the directory
256 that contains the mrconfig file, but you can also choose to use absolute
257 paths. (Note that you can use environment variables in section names; they
258 will be passed through the shell for expansion. For example,
259 "[$HOSTNAME]", or "[${HOSTNAME}foo]")
261 Within a section, each parameter defines a shell command to run to handle a
262 given action. mr contains default handlers for "update", "status",
263 "commit", and other standard actions. Normally you only need to specify what
264 to do for "checkout".
266 Note that these shell commands are run in a "set -e" shell
267 environment, where any additional parameters you pass are available in
268 "$@". The "checkout" command is run in the parent of the repository
269 directory, since the repository isn't checked out yet. All other commands
270 are run inside the repository, though not necessarily at the top of it.
272 The "MR_REPO" environment variable is set to the path to the top of the
273 repository. (For the "register" action, "MR_REPO" is instead set to the
274 basename of the directory that should be created when checking the
277 The "MR_CONFIG" environment variable is set to the .mrconfig file
278 that defines the repo being acted on, or, if the repo is not yet in a config
279 file, the .mrconfig file that should be modified to register the repo.
281 A few parameters have special meanings:
287 If the "skip" parameter is set and its command returns true, then B<mr>
288 will skip acting on that repository. The command is passed the action
291 Here are two examples. The first skips the repo unless
292 mr is run by joey. The second uses the hours_since function
293 (included in mr's built-in library) to skip updating the repo unless it's
294 been at least 12 hours since the last update.
296 skip = test `whoami` != joey
297 skip = [ "$1" = update ] && ! hours_since "$1" 12
301 The "order" parameter can be used to override the default ordering of
302 repositories. The default order value is 10. Use smaller values to make
303 repositories be processed earlier, and larger values to make repositories
306 Note that if a repository is located in a subdirectory of another
307 repository, ordering it to be processed earlier is not recommended.
311 If the "chain" parameter is set and its command returns true, then B<mr>
312 will try to load a .mrconfig file from the root of the repository. (You
313 should avoid chaining from repositories with untrusted committers.)
317 If the "include" parameter is set, its command is ran, and should output
318 additional mrconfig file content. The content is included as if it were
319 part of the including file.
321 Unlike all other parameters, this parameter does not need to be placed
326 The "lib" parameter can specify some shell code that will be run before each
327 command, this can be a useful way to define shell functions for other commands
332 When looking for a command to run for a given action, mr first looks for
333 a parameter with the same name as the action. If that is not found, it
334 looks for a parameter named "rcs_action" (substituting in the name of the
335 revision control system and the action). The name of the revision control
336 system is itself determined by running each defined "rcs_test" action,
339 Internally, mr has settings for "git_update", "svn_update", etc. To change
340 the action that is performed for a given revision control system, you can
341 override these rcs specific actions. To add a new revision control system,
342 you can just add rcs specific actions for it.
344 The ~/.mrlog file contains commands that mr has remembered to run later,
345 due to being offline. You can delete or edit this file to remove commands,
346 or even to add other commands for 'mr online' to run. If the file is
347 present, mr assumes it is in offline mode.
351 mr can be extended to support things such as unison and git-svn. Some
352 files providing such extensions are available in /usr/share/mr/. See
353 the documentation in the files for details about using them.
357 Copyright 2007 Joey Hess <joey@kitenet.net>
359 Licensed under the GNU GPL version 2 or higher.
361 http://kitenet.net/~joey/code/mr/
368 use Cwd qw(getcwd abs_path);
370 # things that can happen when mr runs a command
379 my $config_overridden=0;
387 my $directory=getcwd();
388 $ENV{MR_CONFIG}="$ENV{HOME}/.mrconfig";
395 my (@ok, @failed, @skipped);
401 my ($action, $dir, $topdir, $subdir) = @_;
403 if (exists $rcs{$dir}) {
408 foreach my $rcs_test (
410 length $a <=> length $b
413 } grep { /_test$/ } keys %{$config{$topdir}{$subdir}}) {
414 my ($rcs)=$rcs_test=~/(.*)_test/;
415 $test="my_$rcs_test() {\n$config{$topdir}{$subdir}{$rcs_test}\n}\n".$test;
416 $test.="if my_$rcs_test; then echo $rcs; fi\n";
418 $test=$config{$topdir}{$subdir}{lib}."\n".$test
419 if exists $config{$topdir}{$subdir}{lib};
421 print "mr $action: running rcs test >>$test<<\n" if $verbose;
426 print STDERR "mr $action: found multiple possible repository types ($rcs) for $topdir$subdir\n";
430 return $rcs{$dir}=undef;
433 return $rcs{$dir}=$rcs;
438 my ($action, $dir, $topdir, $subdir, $is_checkout) = @_;
440 if (exists $config{$topdir}{$subdir}{$action}) {
441 return $config{$topdir}{$subdir}{$action};
448 my $rcs=rcs_test(@_);
451 exists $config{$topdir}{$subdir}{$rcs."_".$action}) {
452 return $config{$topdir}{$subdir}{$rcs."_".$action};
460 my ($action, $dir, $topdir, $subdir) = @_;
462 $ENV{MR_CONFIG}=$configfiles{$topdir};
463 my $lib=exists $config{$topdir}{$subdir}{lib} ?
464 $config{$topdir}{$subdir}{lib}."\n" : "";
465 my $is_checkout=($action eq 'checkout');
471 print "mr $action: $dir already exists, skipping checkout\n" if $verbose;
475 $dir=~s/^(.*)\/[^\/]+\/?$/$1/;
477 elsif ($action =~ /update/) {
479 return action("checkout", $dir, $topdir, $subdir);
483 my $skiptest=findcommand("skip", $dir, $topdir, $subdir, $is_checkout);
484 my $command=findcommand($action, $dir, $topdir, $subdir, $is_checkout);
486 if (defined $skiptest) {
487 my $test="set -e;".$lib.
488 "my_action(){ $skiptest\n }; my_action '$action'";
489 print "mr $action: running skip test >>$test<<\n" if $verbose;
490 my $ret=system($test);
492 if (($? & 127) == 2) {
493 print STDERR "mr $action: interrupted\n";
497 print STDERR "mr $action: skip test received signal ".($? & 127)."\n";
501 if ($ret >> 8 == 0) {
502 print "mr $action: $dir skipped per config file\n" if $verbose;
507 if ($is_checkout && ! -d $dir) {
508 print "mr $action: creating parent directory $dir\n" if $verbose;
509 system("mkdir", "-p", $dir);
512 if (! $no_chdir && ! chdir($dir)) {
513 print STDERR "mr $action: failed to chdir to $dir: $!\n";
516 elsif (! defined $command) {
517 my $rcs=rcs_test(@_);
518 if (! defined $rcs) {
519 print STDERR "mr $action: unknown repository type and no defined $action command for $topdir$subdir\n";
523 print STDERR "mr $action: no defined action for $rcs repository $topdir$subdir, skipping\n";
529 print "mr $action: $topdir$subdir\n" unless $quiet;
533 $s=~s/^\Q$topdir$subdir\E\/?//;
534 print "mr $action: $topdir$subdir (in subdir $s)\n" unless $quiet;
536 $command="set -e; ".$lib.
537 "my_action(){ $command\n }; my_action ".
538 join(" ", map { s/\//\/\//g; s/"/\"/g; '"'.$_.'"' } @ARGV);
539 print "mr $action: running >>$command<<\n" if $verbose;
540 my $ret=system($command);
542 if (($? & 127) == 2) {
543 print STDERR "mr $action: interrupted\n";
547 print STDERR "mr $action: received signal ".($? & 127)."\n";
550 print STDERR "mr $action: failed ($ret)\n" if $verbose;
551 if ($ret >> 8 != 0) {
552 print STDERR "mr $action: command failed\n";
553 if (-e "$ENV{HOME}/.mrlog" && $action ne 'remember') {
554 # recreate original command line to
555 # remember, and avoid recursing
557 @ARGV=('-n', $action, @orig);
558 action("remember", $dir, $topdir, $subdir);
563 print STDERR "mr $action: command died ($ret)\n";
568 if ($action eq 'checkout' && ! -d $dir) {
569 print STDERR "mr $action: $dir missing after checkout\n";;
578 # run actions on multiple repos, in parallel
588 while (@fhs or @repos) {
589 while ((!$jobs || $running < $jobs) && @repos) {
591 my $repo = shift @repos;
592 pipe(my $outfh, CHILD_STDOUT);
593 pipe(my $errfh, CHILD_STDERR);
595 unless ($pid = fork) {
596 die "mr $action: cannot fork: $!" unless defined $pid;
597 open(STDOUT, ">&CHILD_STDOUT") || die "mr $action cannot reopen stdout: $!";
598 open(STDERR, ">&CHILD_STDERR") || die "mr $action cannot reopen stderr: $!";
603 exit action($action, @$repo);
607 push @active, [$pid, $repo];
608 push @fhs, [$outfh, $errfh];
611 my ($rin, $rout) = ('','');
613 foreach my $fh (@fhs) {
614 next unless defined $fh;
615 vec($rin, fileno($fh->[0]), 1) = 1 if defined $fh->[0];
616 vec($rin, fileno($fh->[1]), 1) = 1 if defined $fh->[1];
618 $nfound = select($rout=$rin, undef, undef, 1);
619 foreach my $channel (0, 1) {
620 foreach my $i (0..$#fhs) {
621 next unless defined $fhs[$i];
622 my $fh = $fhs[$i][$channel];
623 next unless defined $fh;
624 if (vec($rout, fileno($fh), 1) == 1) {
626 if (sysread($fh, $r, 1024) == 0) {
628 $fhs[$i][$channel] = undef;
629 if (! defined $fhs[$i][0] &&
630 ! defined $fhs[$i][1]) {
631 waitpid($active[$i][0], 0);
632 print STDOUT $out[$i][0];
633 print STDERR $out[$i][1];
634 record($active[$i][1], $? >> 8);
636 splice(@active, $i, 1);
641 $out[$i][$channel] .= $r;
649 my $dir=shift()->[0];
656 elsif ($ret == FAILED) {
658 chdir($dir) unless $no_chdir;
659 print STDERR "mr: Starting interactive shell. Exit shell to continue.\n";
660 system((getpwuid($<))[8]);
665 elsif ($ret == SKIPPED) {
668 elsif ($ret == ABORT) {
672 die "unknown exit status $ret";
678 if (! @ok && ! @failed && ! @skipped) {
679 die "mr $action: no repositories found to work on\n";
681 print "mr $action: finished (".join("; ",
682 showstat($#ok+1, "ok", "ok"),
683 showstat($#failed+1, "failed", "failed"),
684 showstat($#skipped+1, "skipped", "skipped"),
685 ).")\n" unless $quiet;
688 print "mr $action: (skipped: ".join(" ", @skipped).")\n" unless $quiet;
691 print STDERR "mr $action: (failed: ".join(" ", @failed).")\n";
701 return "$count ".($count > 1 ? $plural : $singular);
706 # an ordered list of repos
709 foreach my $topdir (sort keys %config) {
710 foreach my $subdir (sort keys %{$config{$topdir}}) {
714 order => $config{$topdir}{$subdir}{order},
719 $a->{order} <=> $b->{order}
721 $a->{topdir} cmp $b->{topdir}
723 $a->{subdir} cmp $b->{subdir}
727 # figure out which repos to act on
730 foreach my $repo (repolist()) {
731 my $topdir=$repo->{topdir};
732 my $subdir=$repo->{subdir};
734 next if $subdir eq 'DEFAULT';
735 my $dir=($subdir =~/^\//) ? $subdir : $topdir.$subdir;
737 $dir.="/" unless $dir=~/\/$/;
738 $d.="/" unless $d=~/\/$/;
739 next if $dir ne $d && $dir !~ /^\Q$d\E/;
740 if (defined $max_depth) {
741 my @a=split('/', $dir);
742 my @b=split('/', $d);
743 do { } while (@a && @b && shift(@a) eq shift(@b));
744 next if @a > $max_depth || @b > $max_depth;
746 push @repos, [$dir, $topdir, $subdir];
749 # fallback to find a leaf repo
750 foreach my $repo (reverse repolist()) {
751 my $topdir=$repo->{topdir};
752 my $subdir=$repo->{subdir};
754 next if $subdir eq 'DEFAULT';
755 my $dir=($subdir =~/^\//) ? $subdir : $topdir.$subdir;
757 $dir.="/" unless $dir=~/\/$/;
758 $d.="/" unless $d=~/\/$/;
759 if ($d=~/^\Q$dir\E/) {
760 push @repos, [$dir, $topdir, $subdir];
789 if (ref $f eq 'GLOB') {
798 my $absf=abs_path($f);
799 if ($loaded{$absf}) {
804 ($dir)=$f=~/^(.*\/)[^\/]+$/;
805 if (! defined $dir) {
808 $dir=abs_path($dir)."/";
810 if (! exists $configfiles{$dir}) {
811 $configfiles{$dir}=$f;
814 # copy in defaults from first parent
816 while ($parent=~s/^(.*\/)[^\/]+\/?$/$1/) {
817 if ($parent eq '/') {
820 if (exists $config{$parent} &&
821 exists $config{$parent}{DEFAULT}) {
822 $config{$dir}{DEFAULT}={ %{$config{$parent}{DEFAULT}} };
827 print "mr: loading config $f\n" if $verbose;
828 open($in, "<", $f) || die "mr: open $f: $!\n";
839 next if /^\s*\#/ || /^\s*$/;
840 if (/^\[([^\]]*)\]\s*$/) {
841 $section=expandenv($1);
843 elsif (/^(\w+)\s*=\s*(.*)/) {
848 while (@lines && $lines[0]=~/^\s(.+)/) {
855 if ($parameter eq "include") {
856 print "mr: including output of \"$value\"\n" if $verbose;
857 unshift @lines, `$value`;
859 print STDERR "mr: include command exited nonzero ($?)\n";
864 if (! defined $section) {
865 die "$f line $.: parameter ($parameter) not in section\n";
867 if ($section ne 'ALIAS' &&
868 ! exists $config{$dir}{$section} &&
869 exists $config{$dir}{DEFAULT}) {
871 $config{$dir}{$section}={ %{$config{$dir}{DEFAULT}} };
873 if ($section eq 'ALIAS') {
874 $alias{$parameter}=$value;
876 elsif ($parameter eq 'lib') {
877 $config{$dir}{$section}{lib}.=$value."\n";
880 $config{$dir}{$section}{$parameter}=$value;
881 if ($parameter =~ /.*_(.*)/) {
885 $knownactions{$parameter}=1;
887 if ($parameter eq 'chain' &&
888 length $dir && $section ne "DEFAULT" &&
889 -e $dir.$section."/.mrconfig") {
890 my $ret=system($value);
892 if (($? & 127) == 2) {
893 print STDERR "mr: chain test interrupted\n";
897 print STDERR "mr: chain test received signal ".($? & 127)."\n";
901 push @toload, $dir.$section."/.mrconfig";
907 die "$f line $line: parse error\n";
918 # the section to modify or add
919 my $targetsection=shift;
920 # fields to change in the section
921 # To remove a field, set its value to "".
928 open(my $in, "<", $f) || die "mr: open $f: $!\n";
933 my $formatfield=sub {
935 my @value=split(/\n/, shift);
937 return "$field = ".shift(@value)."\n".
938 join("", map { "\t$_\n" } @value);
942 while ($out[$#out] =~ /^\s*$/) {
943 unshift @blanks, pop @out;
945 foreach my $field (sort keys %changefields) {
946 if (length $changefields{$field}) {
947 push @out, "$field = $changefields{$field}\n";
948 delete $changefields{$field};
958 if (/^\s*\#/ || /^\s*$/) {
961 elsif (/^\[([^\]]*)\]\s*$/) {
962 if (defined $section &&
963 $section eq $targetsection) {
967 $section=expandenv($1);
971 elsif (/^(\w+)\s*=\s(.*)/) {
976 while (@lines && $lines[0]=~/^\s(.+)/) {
982 if ($section eq $targetsection) {
983 if (exists $changefields{$parameter}) {
984 if (length $changefields{$parameter}) {
985 $value=$changefields{$parameter};
987 delete $changefields{$parameter};
991 push @out, $formatfield->($parameter, $value);
995 if (defined $section &&
996 $section eq $targetsection) {
999 elsif (%changefields) {
1000 push @out, "\n[$targetsection]\n";
1001 foreach my $field (sort keys %changefields) {
1002 if (length $changefields{$field}) {
1003 push @out, $formatfield->($field, $changefields{$field});
1008 open(my $out, ">", $f) || die "mr: write $f: $!\n";
1016 # actions that do not operate on all repos
1017 if ($action eq 'help') {
1020 elsif ($action eq 'config') {
1023 elsif ($action eq 'register') {
1026 elsif ($action eq 'remember' ||
1027 $action eq 'offline' ||
1028 $action eq 'online') {
1029 my @repos=selectrepos;
1030 action($action, @{$repos[0]}) if @repos;
1034 if (!$jobs || $jobs > 1) {
1035 mrs($action, selectrepos());
1038 foreach my $repo (selectrepos()) {
1039 record($repo, action($action, @$repo));
1045 exec($config{''}{DEFAULT}{help}) || die "exec: $!";
1050 die "mr config: not enough parameters\n";
1053 if ($section=~/^\//) {
1054 # try to convert to a path relative to the config file
1055 my ($dir)=$ENV{MR_CONFIG}=~/^(.*\/)[^\/]+$/;
1056 $dir=abs_path($dir);
1057 $dir.="/" unless $dir=~/\/$/;
1058 if ($section=~/^\Q$dir\E(.*)/) {
1064 if (/^([^=]+)=(.*)$/) {
1065 $changefields{$1}=$2;
1069 foreach my $topdir (sort keys %config) {
1070 if (exists $config{$topdir}{$section} &&
1071 exists $config{$topdir}{$section}{$_}) {
1072 print $config{$topdir}{$section}{$_}."\n";
1074 last if $section eq 'DEFAULT';
1078 die "mr config: $section $_ not set\n";
1082 modifyconfig($ENV{MR_CONFIG}, $section, %changefields) if %changefields;
1087 if ($config_overridden) {
1088 # Find the directory that the specified config file is
1090 ($directory)=abs_path($ENV{MR_CONFIG})=~/^(.*\/)[^\/]+$/;
1093 # Find the closest known mrconfig file to the current
1095 $directory.="/" unless $directory=~/\/$/;
1097 foreach my $topdir (reverse sort keys %config) {
1098 next unless length $topdir;
1099 if ($directory=~/^\Q$topdir\E/) {
1100 $ENV{MR_CONFIG}=$configfiles{$topdir};
1106 if (! $foundconfig) {
1107 $directory=""; # no config file, use builtin
1111 my $subdir=shift @ARGV;
1112 if (! chdir($subdir)) {
1113 print STDERR "mr register: failed to chdir to $subdir: $!\n";
1117 $ENV{MR_REPO}=getcwd();
1118 my $command=findcommand("register", $ENV{MR_REPO}, $directory, 'DEFAULT', 0);
1119 if (! defined $command) {
1120 die "mr register: unknown repository type\n";
1123 $ENV{MR_REPO}=~s/.*\/(.*)/$1/;
1124 $command="set -e; ".$config{$directory}{DEFAULT}{lib}."\n".
1125 "my_action(){ $command\n }; my_action ".
1126 join(" ", map { s/\//\/\//g; s/"/\"/g; '"'.$_.'"' } @ARGV);
1127 print "mr register: running >>$command<<\n" if $verbose;
1128 exec($command) || die "exec: $!";
1131 # alias expansion and command stemming
1134 if (exists $alias{$action}) {
1135 $action=$alias{$action};
1137 if (! exists $knownactions{$action}) {
1138 my @matches = grep { /^\Q$action\E/ }
1139 keys %knownactions, keys %alias;
1140 if (@matches == 1) {
1141 $action=$matches[0];
1143 elsif (@matches == 0) {
1144 die "mr: unknown action \"$action\" (known actions: ".
1145 join(", ", sort keys %knownactions).")\n";
1148 die "mr: ambiguous action \"$action\" (matches: ".
1149 join(", ", @matches).")\n";
1155 sub find_nearest_mrconfig {
1157 while (length $dir) {
1158 if (-e "$dir/.mrconfig") {
1159 return "$dir/.mrconfig";
1161 $dir=~s/\/[^\/]*$//;
1163 die "no .mrconfig found in path\n";
1168 Getopt::Long::Configure("bundling", "no_permute");
1169 my $result=GetOptions(
1170 "d|directory=s" => sub { $directory=abs_path($_[1]) },
1171 "c|config=s" => sub { $ENV{MR_CONFIG}=$_[1]; $config_overridden=1 },
1172 "p|path" => sub { $ENV{MR_CONFIG}=find_nearest_mrconfig(); $config_overridden=1 },
1173 "v|verbose" => \$verbose,
1174 "q|quiet" => \$quiet,
1175 "s|stats" => \$stats,
1176 "i|interactive" => \$interactive,
1177 "n|no-recurse:i" => \$max_depth,
1178 "j|jobs:i" => \$jobs,
1180 if (! $result || @ARGV < 1) {
1181 die("Usage: mr [-d directory] action [params ...]\n".
1182 "(Use mr help for man page.)\n");
1185 $ENV{MR_SWITCHES}="";
1186 foreach my $option (@saved) {
1187 last if $option eq $ARGV[0];
1188 $ENV{MR_SWITCHES}.="$option ";
1194 print STDERR "mr: interrupted\n";
1198 # This can happen if it's run in a directory that was removed
1199 # or other strangeness.
1200 if (! defined $directory) {
1201 die("mr: failed to determine working directory\n");
1203 # Make sure MR_CONFIG is an absolute path, but don't use abs_path since
1204 # the config file might be a symlink to elsewhere, and the directory it's
1205 # in is significant.
1206 if ($ENV{MR_CONFIG} !~ /^\//) {
1207 $ENV{MR_CONFIG}=getcwd()."/".$ENV{MR_CONFIG};
1209 # Try to set MR_PATH to the path to the program.
1211 use FindBin qw($Bin $Script);
1212 $ENV{MR_PATH}=$Bin."/".$Script;
1221 loadconfig($ENV{MR_CONFIG});
1222 #use Data::Dumper; print Dumper(\%config);
1224 my $action=expandaction(shift @ARGV);
1231 elsif (! @ok && @skipped) {
1239 # Finally, some useful actions that mr knows about by default.
1240 # These can be overridden in ~/.mrconfig.
1255 echo "mr (warning): $@" >&2
1261 if [ -z "$1" ] || [ -z "$2" ]; then
1262 error "mr: usage: hours_since action num"
1264 for dir in .git .svn .bzr CVS .hg _darcs; do
1265 if [ -e "$MR_REPO/$dir" ]; then
1266 flagfile="$MR_REPO/$dir/.mr_last$1"
1270 if [ -z "$flagfile" ]; then
1271 error "cannot determine flag filename"
1273 delta=`perl -wle 'print -f shift() ? int((-M _) * 24) : 9999' "$flagfile"`
1274 if [ "$delta" -lt "$2" ]; then
1282 svn_test = test -d "$MR_REPO"/.svn
1283 git_test = test -d "$MR_REPO"/.git
1284 bzr_test = test -d "$MR_REPO"/.bzr
1285 cvs_test = test -d "$MR_REPO"/CVS
1286 hg_test = test -d "$MR_REPO"/.hg
1287 darcs_test = test -d "$MR_REPO"/_darcs
1289 test -d "$MR_REPO"/refs/heads && test -d "$MR_REPO"/refs/tags &&
1290 test -d "$MR_REPO"/objects && test -f "$MR_REPO"/config &&
1291 test "`GIT_CONFIG="$MR_REPO"/config git config --get core.bare`" = true
1293 svn_update = svn update "$@"
1294 git_update = git pull "$@"
1295 bzr_update = bzr merge --pull "$@"
1296 cvs_update = cvs update "$@"
1297 hg_update = hg pull "$@" && hg update "$@"
1298 darcs_update = darcs pull -a "$@"
1300 svn_status = svn status "$@"
1301 git_status = git status "$@" || true
1302 bzr_status = bzr status "$@"
1303 cvs_status = cvs status "$@"
1304 hg_status = hg status "$@"
1305 darcs_status = darcs whatsnew -ls "$@" || true
1307 svn_commit = svn commit "$@"
1308 git_commit = git commit -a "$@" && git push --all
1309 bzr_commit = bzr commit "$@" && bzr push
1310 cvs_commit = cvs commit "$@"
1311 hg_commit = hg commit -m "$@" && hg push
1312 darcs_commit = darcs record -a -m "$@" && darcs push -a
1314 git_record = git commit -a "$@"
1315 bzr_record = bzr commit "$@"
1316 hg_record = hg commit -m "$@"
1317 darcs_record = darcs record -a -m "$@"
1320 git_push = git push "$@"
1321 bzr_push = bzr push "$@"
1323 hg_push = hg push "$@"
1324 darcs_push = darcs push -a "$@"
1326 svn_diff = svn diff "$@"
1327 git_diff = git diff "$@"
1328 bzr_diff = bzr diff "$@"
1329 cvs_diff = cvs diff "$@"
1330 hg_diff = hg diff "$@"
1331 darcs_diff = darcs diff -u "$@"
1333 svn_log = svn log "$@"
1334 git_log = git log "$@"
1335 bzr_log = bzr log "$@"
1336 cvs_log = cvs log "$@"
1337 hg_log = hg log "$@"
1338 darcs_log = darcs changes "$@"
1339 git_bare_log = git log "$@"
1342 url=`LC_ALL=C svn info . | grep -i '^URL:' | cut -d ' ' -f 2`
1343 if [ -z "$url" ]; then
1344 error "cannot determine svn url"
1346 echo "Registering svn url: $url in $MR_CONFIG"
1347 mr -c "$MR_CONFIG" config "`pwd`" checkout="svn co '$url' '$MR_REPO'"
1349 url="`LC_ALL=C git config --get remote.origin.url`" || true
1350 if [ -z "$url" ]; then
1351 error "cannot determine git url"
1353 echo "Registering git url: $url in $MR_CONFIG"
1354 mr -c "$MR_CONFIG" config "`pwd`" checkout="git clone '$url' '$MR_REPO'"
1356 url="`LC_ALL=C bzr info . | egrep -i 'checkout of branch|parent branch' | awk '{print $NF}'`"
1357 if [ -z "$url" ]; then
1358 error "cannot determine bzr url"
1360 echo "Registering bzr url: $url in $MR_CONFIG"
1361 mr -c "$MR_CONFIG" config "`pwd`" checkout="bzr clone '$url' '$MR_REPO'"
1363 repo=`cat CVS/Repository`
1365 if [ -z "$root" ]; then
1366 error "cannot determine cvs root"
1368 echo "Registering cvs repository $repo at root $root"
1369 mr -c "$MR_CONFIG" config "`pwd`" checkout="cvs -d '$root' co -d '$MR_REPO' '$repo'"
1371 url=`hg showconfig paths.default`
1372 echo "Registering mercurial repo url: $url in $MR_CONFIG"
1373 mr -c "$MR_CONFIG" config "`pwd`" checkout="hg clone '$url' '$MR_REPO'"
1375 url=`cat _darcs/prefs/defaultrepo`
1376 echo "Registering darcs repository $url in $MR_CONFIG"
1377 mr -c "$MR_CONFIG" config "`pwd`" checkout="darcs get '$url' '$MR_REPO'"
1379 url="`LC_ALL=C GIT_CONFIG=config git config --get remote.origin.url`" || true
1380 if [ -z "$url" ]; then
1381 error "cannot determine git url"
1383 echo "Registering git url: $url in $MR_CONFIG"
1384 mr -c "$MR_CONFIG" config "`pwd`" checkout="git clone --bare '$url' '$MR_REPO'"
1387 if [ ! -e "$MR_PATH" ]; then
1388 error "cannot find program path"
1390 tmp=$(mktemp -t mr.XXXXXXXXXX) || error "mktemp failed"
1391 trap "rm -f $tmp" exit
1392 pod2man -c mr "$MR_PATH" > "$tmp" || error "pod2man failed"
1393 man -l "$tmp" || error "man failed"
1398 if [ -s ~/.mrlog ]; then
1399 info "running offline commands"
1400 mv -f ~/.mrlog ~/.mrlog.old
1401 if ! sh -e ~/.mrlog.old; then
1402 error "offline command failed; left in ~/.mrlog.old"
1406 info "no offline commands to run"
1411 info "offline mode enabled"
1413 info "remembering command: 'mr $@'"
1414 command="mr -d '$(pwd)' $MR_SWITCHES"
1416 command="$command '$w'"
1418 if [ ! -e ~/.mrlog ] || ! grep -q -F "$command" ~/.mrlog; then
1419 echo "$command" >> ~/.mrlog
1422 ed = echo "A horse is a horse, of course, of course.."
1423 T = echo "I pity the fool."
1424 right = echo "Not found."
1426 # vim:sw=8:sts=0:ts=8:noet