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 @ARGV=($action, @ARGV);
543 action("remember", $dir, $topdir, $subdir);
547 print STDERR "mr $action: command died ($ret)\n";
552 if ($action eq 'checkout' && ! -d $dir) {
553 print STDERR "mr $action: $dir missing after checkout\n";;
562 # run actions on multiple repos, in parallel
572 while (@fhs or @repos) {
573 while ((!$jobs || $running < $jobs) && @repos) {
575 my $repo = shift @repos;
576 pipe(my $outfh, CHILD_STDOUT);
577 pipe(my $errfh, CHILD_STDERR);
579 unless ($pid = fork) {
580 die "mr $action: cannot fork: $!" unless defined $pid;
581 open(STDOUT, ">&CHILD_STDOUT") || die "mr $action cannot reopen stdout: $!";
582 open(STDERR, ">&CHILD_STDERR") || die "mr $action cannot reopen stderr: $!";
587 exit action($action, @$repo);
591 push @active, [$pid, $repo];
592 push @fhs, [$outfh, $errfh];
595 my ($rin, $rout) = ('','');
597 foreach my $fh (@fhs) {
598 next unless defined $fh;
599 vec($rin, fileno($fh->[0]), 1) = 1 if defined $fh->[0];
600 vec($rin, fileno($fh->[1]), 1) = 1 if defined $fh->[1];
602 $nfound = select($rout=$rin, undef, undef, 1);
603 foreach my $channel (0, 1) {
604 foreach my $i (0..$#fhs) {
605 next unless defined $fhs[$i];
606 my $fh = $fhs[$i][$channel];
607 next unless defined $fh;
608 if (vec($rout, fileno($fh), 1) == 1) {
610 if (sysread($fh, $r, 1024) == 0) {
612 $fhs[$i][$channel] = undef;
613 if (! defined $fhs[$i][0] &&
614 ! defined $fhs[$i][1]) {
615 waitpid($active[$i][0], 0);
616 print STDOUT $out[$i][0];
617 print STDERR $out[$i][1];
618 record($active[$i][1], $? >> 8);
620 splice(@active, $i, 1);
625 $out[$i][$channel] .= $r;
633 my $dir=shift()->[0];
640 elsif ($ret == FAILED) {
642 chdir($dir) unless $no_chdir;
643 print STDERR "mr: Starting interactive shell. Exit shell to continue.\n";
644 system((getpwuid($<))[8]);
649 elsif ($ret == SKIPPED) {
652 elsif ($ret == ABORT) {
656 die "unknown exit status $ret";
662 if (! @ok && ! @failed && ! @skipped) {
663 die "mr $action: no repositories found to work on\n";
665 print "mr $action: finished (".join("; ",
666 showstat($#ok+1, "ok", "ok"),
667 showstat($#failed+1, "failed", "failed"),
668 showstat($#skipped+1, "skipped", "skipped"),
669 ).")\n" unless $quiet;
672 print "mr $action: (skipped: ".join(" ", @skipped).")\n" unless $quiet;
675 print STDERR "mr $action: (failed: ".join(" ", @failed).")\n";
685 return "$count ".($count > 1 ? $plural : $singular);
690 # an ordered list of repos
693 foreach my $topdir (sort keys %config) {
694 foreach my $subdir (sort keys %{$config{$topdir}}) {
698 order => $config{$topdir}{$subdir}{order},
703 $a->{order} <=> $b->{order}
705 $a->{topdir} cmp $b->{topdir}
707 $a->{subdir} cmp $b->{subdir}
711 # figure out which repos to act on
712 sub selectrepos { #{{{
714 foreach my $repo (repolist()) {
715 my $topdir=$repo->{topdir};
716 my $subdir=$repo->{subdir};
718 next if $subdir eq 'DEFAULT';
719 my $dir=($subdir =~/^\//) ? $subdir : $topdir.$subdir;
721 $dir.="/" unless $dir=~/\/$/;
722 $d.="/" unless $d=~/\/$/;
723 next if $dir ne $d && $dir !~ /^\Q$d\E/;
724 if (defined $max_depth) {
725 my @a=split('/', $dir);
726 my @b=split('/', $d);
727 do { } while (@a && @b && shift(@a) eq shift(@b));
728 next if @a > $max_depth || @b > $max_depth;
730 push @repos, [$dir, $topdir, $subdir];
733 # fallback to find a leaf repo
734 foreach my $repo (reverse repolist()) {
735 my $topdir=$repo->{topdir};
736 my $subdir=$repo->{subdir};
738 next if $subdir eq 'DEFAULT';
739 my $dir=($subdir =~/^\//) ? $subdir : $topdir.$subdir;
741 $dir.="/" unless $dir=~/\/$/;
742 $d.="/" unless $d=~/\/$/;
743 if ($d=~/^\Q$dir\E/) {
744 push @repos, [$dir, $topdir, $subdir];
766 sub loadconfig { #{{{
773 if (ref $f eq 'GLOB') {
782 my $absf=abs_path($f);
783 if ($loaded{$absf}) {
788 ($dir)=$f=~/^(.*\/)[^\/]+$/;
789 if (! defined $dir) {
792 $dir=abs_path($dir)."/";
794 if (! exists $configfiles{$dir}) {
795 $configfiles{$dir}=$f;
798 # copy in defaults from first parent
800 while ($parent=~s/^(.*\/)[^\/]+\/?$/$1/) {
801 if ($parent eq '/') {
804 if (exists $config{$parent} &&
805 exists $config{$parent}{DEFAULT}) {
806 $config{$dir}{DEFAULT}={ %{$config{$parent}{DEFAULT}} };
811 print "mr: loading config $f\n" if $verbose;
812 open($in, "<", $f) || die "mr: open $f: $!\n";
823 next if /^\s*\#/ || /^\s*$/;
824 if (/^\[([^\]]*)\]\s*$/) {
825 $section=expandenv($1);
827 elsif (/^(\w+)\s*=\s*(.*)/) {
832 while (@lines && $lines[0]=~/^\s(.+)/) {
839 if ($parameter eq "include") {
840 print "mr: including output of \"$value\"\n" if $verbose;
841 unshift @lines, `$value`;
845 if (! defined $section) {
846 die "$f line $.: parameter ($parameter) not in section\n";
848 if ($section ne 'ALIAS' &&
849 ! exists $config{$dir}{$section} &&
850 exists $config{$dir}{DEFAULT}) {
852 $config{$dir}{$section}={ %{$config{$dir}{DEFAULT}} };
854 if ($section eq 'ALIAS') {
855 $alias{$parameter}=$value;
857 elsif ($parameter eq 'lib') {
858 $config{$dir}{$section}{lib}.=$value."\n";
861 $config{$dir}{$section}{$parameter}=$value;
862 if ($parameter =~ /.*_(.*)/) {
866 $knownactions{$parameter}=1;
868 if ($parameter eq 'chain' &&
869 length $dir && $section ne "DEFAULT" &&
870 -e $dir.$section."/.mrconfig") {
871 my $ret=system($value);
873 if (($? & 127) == 2) {
874 print STDERR "mr: chain test interrupted\n";
878 print STDERR "mr: chain test received signal ".($? & 127)."\n";
882 push @toload, $dir.$section."/.mrconfig";
888 die "$f line $line: parse error\n";
897 sub modifyconfig { #{{{
899 # the section to modify or add
900 my $targetsection=shift;
901 # fields to change in the section
902 # To remove a field, set its value to "".
909 open(my $in, "<", $f) || die "mr: open $f: $!\n";
914 my $formatfield=sub {
916 my @value=split(/\n/, shift);
918 return "$field = ".shift(@value)."\n".
919 join("", map { "\t$_\n" } @value);
923 while ($out[$#out] =~ /^\s*$/) {
924 unshift @blanks, pop @out;
926 foreach my $field (sort keys %changefields) {
927 if (length $changefields{$field}) {
928 push @out, "$field = $changefields{$field}\n";
929 delete $changefields{$field};
939 if (/^\s*\#/ || /^\s*$/) {
942 elsif (/^\[([^\]]*)\]\s*$/) {
943 if (defined $section &&
944 $section eq $targetsection) {
948 $section=expandenv($1);
952 elsif (/^(\w+)\s*=\s(.*)/) {
957 while (@lines && $lines[0]=~/^\s(.+)/) {
963 if ($section eq $targetsection) {
964 if (exists $changefields{$parameter}) {
965 if (length $changefields{$parameter}) {
966 $value=$changefields{$parameter};
968 delete $changefields{$parameter};
972 push @out, $formatfield->($parameter, $value);
976 if (defined $section &&
977 $section eq $targetsection) {
980 elsif (%changefields) {
981 push @out, "\n[$targetsection]\n";
982 foreach my $field (sort keys %changefields) {
983 if (length $changefields{$field}) {
984 push @out, $formatfield->($field, $changefields{$field});
989 open(my $out, ">", $f) || die "mr: write $f: $!\n";
997 # actions that do not operate on all repos
998 if ($action eq 'help') {
1001 elsif ($action eq 'config') {
1004 elsif ($action eq 'register') {
1007 elsif ($action eq 'remember') {
1008 my @repos=selectrepos;
1009 action($action, @{$repos[0]}) if @repos;
1013 if (!$jobs || $jobs > 1) {
1014 mrs($action, selectrepos());
1017 foreach my $repo (selectrepos()) {
1018 record($repo, action($action, @$repo));
1024 exec($config{''}{DEFAULT}{help}) || die "exec: $!";
1029 die "mr config: not enough parameters\n";
1032 if ($section=~/^\//) {
1033 # try to convert to a path relative to the config file
1034 my ($dir)=$ENV{MR_CONFIG}=~/^(.*\/)[^\/]+$/;
1035 $dir=abs_path($dir);
1036 $dir.="/" unless $dir=~/\/$/;
1037 if ($section=~/^\Q$dir\E(.*)/) {
1043 if (/^([^=]+)=(.*)$/) {
1044 $changefields{$1}=$2;
1048 foreach my $topdir (sort keys %config) {
1049 if (exists $config{$topdir}{$section} &&
1050 exists $config{$topdir}{$section}{$_}) {
1051 print $config{$topdir}{$section}{$_}."\n";
1053 last if $section eq 'DEFAULT';
1057 die "mr config: $section $_ not set\n";
1061 modifyconfig($ENV{MR_CONFIG}, $section, %changefields) if %changefields;
1066 if ($config_overridden) {
1067 # Find the directory that the specified config file is
1069 ($directory)=abs_path($ENV{MR_CONFIG})=~/^(.*\/)[^\/]+$/;
1072 # Find the closest known mrconfig file to the current
1074 $directory.="/" unless $directory=~/\/$/;
1076 foreach my $topdir (reverse sort keys %config) {
1077 next unless length $topdir;
1078 if ($directory=~/^\Q$topdir\E/) {
1079 $ENV{MR_CONFIG}=$configfiles{$topdir};
1085 if (! $foundconfig) {
1086 $directory=""; # no config file, use builtin
1090 my $subdir=shift @ARGV;
1091 if (! chdir($subdir)) {
1092 print STDERR "mr register: failed to chdir to $subdir: $!\n";
1096 $ENV{MR_REPO}=getcwd();
1097 my $command=findcommand("register", $ENV{MR_REPO}, $directory, 'DEFAULT', 0);
1098 if (! defined $command) {
1099 die "mr register: unknown repository type\n";
1102 $ENV{MR_REPO}=~s/.*\/(.*)/$1/;
1103 $command="set -e; ".$config{$directory}{DEFAULT}{lib}."\n".
1104 "my_action(){ $command\n }; my_action ".
1105 join(" ", map { s/\//\/\//g; s/"/\"/g; '"'.$_.'"' } @ARGV);
1106 print "mr register: running >>$command<<\n" if $verbose;
1107 exec($command) || die "exec: $!";
1110 # alias expansion and command stemming
1111 sub expandaction { #{{{
1113 if (exists $alias{$action}) {
1114 $action=$alias{$action};
1116 if (! exists $knownactions{$action}) {
1117 my @matches = grep { /^\Q$action\E/ }
1118 keys %knownactions, keys %alias;
1119 if (@matches == 1) {
1120 $action=$matches[0];
1122 elsif (@matches == 0) {
1123 die "mr: unknown action \"$action\" (known actions: ".
1124 join(", ", sort keys %knownactions).")\n";
1127 die "mr: ambiguous action \"$action\" (matches: ".
1128 join(", ", @matches).")\n";
1135 Getopt::Long::Configure("bundling", "no_permute");
1136 my $result=GetOptions(
1137 "d|directory=s" => sub { $directory=abs_path($_[1]) },
1138 "c|config=s" => sub { $ENV{MR_CONFIG}=$_[1]; $config_overridden=1 },
1139 "v|verbose" => \$verbose,
1140 "q|quiet" => \$quiet,
1141 "s|stats" => \$stats,
1142 "i|interactive" => \$interactive,
1143 "n|no-recurse:i" => \$max_depth,
1144 "j|jobs:i" => \$jobs,
1146 if (! $result || @ARGV < 1) {
1147 die("Usage: mr [-d directory] action [params ...]\n".
1148 "(Use mr help for man page.)\n");
1154 print STDERR "mr: interrupted\n";
1158 # This can happen if it's run in a directory that was removed
1159 # or other strangeness.
1160 if (! defined $directory) {
1161 die("mr: failed to determine working directory\n");
1163 # Make sure MR_CONFIG is an absolute path, but don't use abs_path since
1164 # the config file might be a symlink to elsewhere, and the directory it's
1165 # in is significant.
1166 if ($ENV{MR_CONFIG} !~ /^\//) {
1167 $ENV{MR_CONFIG}=getcwd()."/".$ENV{MR_CONFIG};
1169 # Try to set MR_PATH to the path to the program.
1171 use FindBin qw($Bin $Script);
1172 $ENV{MR_PATH}=$Bin."/".$Script;
1180 loadconfig($ENV{MR_CONFIG});
1181 #use Data::Dumper; print Dumper(\%config);
1183 my $action=expandaction(shift @ARGV);
1190 elsif (! @ok && @skipped) {
1198 # Finally, some useful actions that mr knows about by default.
1199 # These can be overridden in ~/.mrconfig.
1215 echo "mr (warning): $@" >&2
1221 if [ -z "$1" ] || [ -z "$2" ]; then
1222 error "mr: usage: hours_since action num"
1224 for dir in .git .svn .bzr CVS .hg _darcs; do
1225 if [ -e "$MR_REPO/$dir" ]; then
1226 flagfile="$MR_REPO/$dir/.mr_last$1"
1230 if [ -z "$flagfile" ]; then
1231 error "cannot determine flag filename"
1233 delta=`perl -wle 'print -f shift() ? int((-M _) * 24) : 9999' "$flagfile"`
1234 if [ "$delta" -lt "$2" ]; then
1242 svn_test = test -d "$MR_REPO"/.svn
1243 git_test = test -d "$MR_REPO"/.git
1244 bzr_test = test -d "$MR_REPO"/.bzr
1245 cvs_test = test -d "$MR_REPO"/CVS
1246 hg_test = test -d "$MR_REPO"/.hg
1247 darcs_test = test -d "$MR_REPO"/_darcs
1249 test -d "$MR_REPO"/refs/heads && test -d "$MR_REPO"/refs/tags &&
1250 test -d "$MR_REPO"/objects && test -f "$MR_REPO"/config &&
1251 test "`GIT_CONFIG="$MR_REPO"/config git config --get core.bare`" = true
1253 svn_update = svn update "$@"
1254 git_update = git pull "$@"
1255 bzr_update = bzr merge "$@"
1256 cvs_update = cvs update "$@"
1257 hg_update = hg pull "$@" && hg update "$@"
1258 darcs_update = darcs pull -a "$@"
1260 svn_status = svn status "$@"
1261 git_status = git status "$@" || true
1262 bzr_status = bzr status "$@"
1263 cvs_status = cvs status "$@"
1264 hg_status = hg status "$@"
1265 darcs_status = darcs whatsnew -ls "$@" || true
1267 svn_commit = svn commit "$@"
1268 git_commit = git commit -a "$@" && git push --all
1269 bzr_commit = bzr commit "$@" && bzr push
1270 cvs_commit = cvs commit "$@"
1271 hg_commit = hg commit -m "$@" && hg push
1272 darcs_commit = darcs record -a -m "$@" && darcs push -a
1274 git_record = git commit -a "$@"
1275 bzr_record = bzr commit "$@"
1276 hg_record = hg commit -m "$@"
1277 darcs_record = darcs record -a -m "$@"
1279 svn_diff = svn diff "$@"
1280 git_diff = git diff "$@"
1281 bzr_diff = bzr diff "$@"
1282 cvs_diff = cvs diff "$@"
1283 hg_diff = hg diff "$@"
1284 darcs_diff = darcs diff -u "$@"
1286 svn_log = svn log "$@"
1287 git_log = git log "$@"
1288 bzr_log = bzr log "$@"
1289 cvs_log = cvs log "$@"
1290 hg_log = hg log "$@"
1291 darcs_log = darcs changes "$@"
1292 git_bare_log = git log "$@"
1295 url=`LC_ALL=C svn info . | grep -i '^URL:' | cut -d ' ' -f 2`
1296 if [ -z "$url" ]; then
1297 error "cannot determine svn url"
1299 echo "Registering svn url: $url in $MR_CONFIG"
1300 mr -c "$MR_CONFIG" config "`pwd`" checkout="svn co '$url' '$MR_REPO'"
1302 url="`LC_ALL=C git config --get remote.origin.url`" || true
1303 if [ -z "$url" ]; then
1304 error "cannot determine git url"
1306 echo "Registering git url: $url in $MR_CONFIG"
1307 mr -c "$MR_CONFIG" config "`pwd`" checkout="git clone '$url' '$MR_REPO'"
1309 url="`LC_ALL=C bzr info . | egrep -i 'checkout of branch|parent branch' | awk '{print $NF}'`"
1310 if [ -z "$url" ]; then
1311 error "cannot determine bzr url"
1313 echo "Registering bzr url: $url in $MR_CONFIG"
1314 mr -c "$MR_CONFIG" config "`pwd`" checkout="bzr clone '$url' '$MR_REPO'"
1316 repo=`cat CVS/Repository`
1318 if [ -z "$root" ]; then
1319 error "cannot determine cvs root"
1321 echo "Registering cvs repository $repo at root $root"
1322 mr -c "$MR_CONFIG" config "`pwd`" checkout="cvs -d '$root' co -d '$MR_REPO' '$repo'"
1324 url=`hg showconfig paths.default`
1325 echo "Registering mercurial repo url: $url in $MR_CONFIG"
1326 mr -c "$MR_CONFIG" config "`pwd`" checkout="hg clone '$url' '$MR_REPO'"
1328 url=`cat _darcs/prefs/defaultrepo`
1329 echo "Registering darcs repository $url in $MR_CONFIG"
1330 mr -c "$MR_CONFIG" config "`pwd`" checkout="darcs get '$url' '$MR_REPO'"
1332 url="`LC_ALL=C GIT_CONFIG=config 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 --bare '$url' '$MR_REPO'"
1340 if [ ! -e "$MR_PATH" ]; then
1341 error "cannot find program path"
1343 tmp=$(mktemp -t mr.XXXXXXXXXX) || error "mktemp failed"
1344 trap "rm -f $tmp" exit
1345 pod2man -c mr "$MR_PATH" > "$tmp" || error "pod2man failed"
1346 man -l "$tmp" || error "man failed"
1351 if [ -e ~/.mrlog ]; then
1352 info "running offline commands"
1353 mv -f ~/.mrlog ~/.mrlog.old
1354 if ! sh ~/.mrlog.old; then
1355 error "offline commands failed; left in ~/.mrlog.old"
1359 info "no offline commands to run"
1363 info "offline mode enabled"
1365 info "remembering command: 'mr $@'"
1366 command="mr -d '$(pwd)'"
1368 command="$command '$w'"
1370 if [ ! -e ~/.mrlog ] || ! grep -q -F "$command" ~/.mrlog; then
1371 echo "$command" >> ~/.mrlog
1374 ed = echo "A horse is a horse, of course, of course.."
1375 T = echo "I pity the fool."
1376 right = echo "Not found."
1379 # vim:sw=8:sts=0:ts=8:noet