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 The ~/.mrlog file contains commands that mr has remembered to run later,
225 due to being offline. You can delete or edit this file to remove commands,
226 or even to add other commands for 'mr online' to run. If the file is
227 present, mr assumes it is in offline mode.
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.
346 Copyright 2007 Joey Hess <joey@kitenet.net>
348 Licensed under the GNU GPL version 2 or higher.
350 http://kitenet.net/~joey/code/mr/
357 use Cwd qw(getcwd abs_path);
359 # things that can happen when mr runs a command
368 my $config_overridden=0;
376 my $directory=getcwd();
377 $ENV{MR_CONFIG}="$ENV{HOME}/.mrconfig";
384 my (@ok, @failed, @skipped);
390 my ($action, $dir, $topdir, $subdir) = @_;
392 if (exists $rcs{$dir}) {
397 foreach my $rcs_test (
399 length $a <=> length $b
402 } grep { /_test$/ } keys %{$config{$topdir}{$subdir}}) {
403 my ($rcs)=$rcs_test=~/(.*)_test/;
404 $test="my_$rcs_test() {\n$config{$topdir}{$subdir}{$rcs_test}\n}\n".$test;
405 $test.="if my_$rcs_test; then echo $rcs; fi\n";
407 $test=$config{$topdir}{$subdir}{lib}."\n".$test
408 if exists $config{$topdir}{$subdir}{lib};
410 print "mr $action: running rcs test >>$test<<\n" if $verbose;
415 print STDERR "mr $action: found multiple possible repository types ($rcs) for $dir\n";
419 return $rcs{$dir}=undef;
422 return $rcs{$dir}=$rcs;
427 my ($action, $dir, $topdir, $subdir, $is_checkout) = @_;
429 if (exists $config{$topdir}{$subdir}{$action}) {
430 return $config{$topdir}{$subdir}{$action};
437 my $rcs=rcs_test(@_);
440 exists $config{$topdir}{$subdir}{$rcs."_".$action}) {
441 return $config{$topdir}{$subdir}{$rcs."_".$action};
449 my ($action, $dir, $topdir, $subdir) = @_;
451 $ENV{MR_CONFIG}=$configfiles{$topdir};
452 my $lib=exists $config{$topdir}{$subdir}{lib} ?
453 $config{$topdir}{$subdir}{lib}."\n" : "";
454 my $is_checkout=($action eq 'checkout');
460 print "mr $action: $dir already exists, skipping checkout\n" if $verbose;
464 $dir=~s/^(.*)\/[^\/]+\/?$/$1/;
466 elsif ($action =~ /update/) {
468 return action("checkout", $dir, $topdir, $subdir);
472 my $skiptest=findcommand("skip", $dir, $topdir, $subdir, $is_checkout);
473 my $command=findcommand($action, $dir, $topdir, $subdir, $is_checkout);
475 if (defined $skiptest) {
476 my $test="set -e;".$lib.
477 "my_action(){ $skiptest\n }; my_action '$action'";
478 print "mr $action: running skip test >>$test<<\n" if $verbose;
479 my $ret=system($test);
481 if (($? & 127) == 2) {
482 print STDERR "mr $action: interrupted\n";
486 print STDERR "mr $action: skip test received signal ".($? & 127)."\n";
490 if ($ret >> 8 == 0) {
491 print "mr $action: $dir skipped per config file\n" if $verbose;
496 if ($is_checkout && ! -d $dir) {
497 print "mr $action: creating parent directory $dir\n" if $verbose;
498 system("mkdir", "-p", $dir);
501 if (! $no_chdir && ! chdir($dir)) {
502 print STDERR "mr $action: failed to chdir to $dir: $!\n";
505 elsif (! defined $command) {
506 my $rcs=rcs_test(@_);
507 if (! defined $rcs) {
508 print STDERR "mr $action: unknown repository type and no defined $action command for $dir\n";
512 print STDERR "mr $action: no defined action for $rcs repository $dir, skipping\n";
518 print "mr $action: $dir\n" unless $quiet;
522 $s=~s/^\Q$dir\E\/?//;
523 print "mr $action: $dir (in subdir $s)\n" unless $quiet;
525 $command="set -e; ".$lib.
526 "my_action(){ $command\n }; my_action ".
527 join(" ", map { s/\//\/\//g; s/"/\"/g; '"'.$_.'"' } @ARGV);
528 print "mr $action: running >>$command<<\n" if $verbose;
529 my $ret=system($command);
531 if (($? & 127) == 2) {
532 print STDERR "mr $action: interrupted\n";
536 print STDERR "mr $action: received signal ".($? & 127)."\n";
539 print STDERR "mr $action: failed ($ret)\n" if $verbose;
540 if ($ret >> 8 != 0) {
541 print STDERR "mr $action: command failed\n";
542 if (-e "$ENV{HOME}/.mrlog" && $action ne 'remember') {
543 # recreate original command line to
544 # remember, and avoid recursing
546 @ARGV=('-n', $action, @orig);
547 action("remember", $dir, $topdir, $subdir);
552 print STDERR "mr $action: command died ($ret)\n";
557 if ($action eq 'checkout' && ! -d $dir) {
558 print STDERR "mr $action: $dir missing after checkout\n";;
567 # run actions on multiple repos, in parallel
577 while (@fhs or @repos) {
578 while ((!$jobs || $running < $jobs) && @repos) {
580 my $repo = shift @repos;
581 pipe(my $outfh, CHILD_STDOUT);
582 pipe(my $errfh, CHILD_STDERR);
584 unless ($pid = fork) {
585 die "mr $action: cannot fork: $!" unless defined $pid;
586 open(STDOUT, ">&CHILD_STDOUT") || die "mr $action cannot reopen stdout: $!";
587 open(STDERR, ">&CHILD_STDERR") || die "mr $action cannot reopen stderr: $!";
592 exit action($action, @$repo);
596 push @active, [$pid, $repo];
597 push @fhs, [$outfh, $errfh];
600 my ($rin, $rout) = ('','');
602 foreach my $fh (@fhs) {
603 next unless defined $fh;
604 vec($rin, fileno($fh->[0]), 1) = 1 if defined $fh->[0];
605 vec($rin, fileno($fh->[1]), 1) = 1 if defined $fh->[1];
607 $nfound = select($rout=$rin, undef, undef, 1);
608 foreach my $channel (0, 1) {
609 foreach my $i (0..$#fhs) {
610 next unless defined $fhs[$i];
611 my $fh = $fhs[$i][$channel];
612 next unless defined $fh;
613 if (vec($rout, fileno($fh), 1) == 1) {
615 if (sysread($fh, $r, 1024) == 0) {
617 $fhs[$i][$channel] = undef;
618 if (! defined $fhs[$i][0] &&
619 ! defined $fhs[$i][1]) {
620 waitpid($active[$i][0], 0);
621 print STDOUT $out[$i][0];
622 print STDERR $out[$i][1];
623 record($active[$i][1], $? >> 8);
625 splice(@active, $i, 1);
630 $out[$i][$channel] .= $r;
638 my $dir=shift()->[0];
645 elsif ($ret == FAILED) {
647 chdir($dir) unless $no_chdir;
648 print STDERR "mr: Starting interactive shell. Exit shell to continue.\n";
649 system((getpwuid($<))[8]);
654 elsif ($ret == SKIPPED) {
657 elsif ($ret == ABORT) {
661 die "unknown exit status $ret";
667 if (! @ok && ! @failed && ! @skipped) {
668 die "mr $action: no repositories found to work on\n";
670 print "mr $action: finished (".join("; ",
671 showstat($#ok+1, "ok", "ok"),
672 showstat($#failed+1, "failed", "failed"),
673 showstat($#skipped+1, "skipped", "skipped"),
674 ).")\n" unless $quiet;
677 print "mr $action: (skipped: ".join(" ", @skipped).")\n" unless $quiet;
680 print STDERR "mr $action: (failed: ".join(" ", @failed).")\n";
690 return "$count ".($count > 1 ? $plural : $singular);
695 # an ordered list of repos
698 foreach my $topdir (sort keys %config) {
699 foreach my $subdir (sort keys %{$config{$topdir}}) {
703 order => $config{$topdir}{$subdir}{order},
708 $a->{order} <=> $b->{order}
710 $a->{topdir} cmp $b->{topdir}
712 $a->{subdir} cmp $b->{subdir}
716 # figure out which repos to act on
719 foreach my $repo (repolist()) {
720 my $topdir=$repo->{topdir};
721 my $subdir=$repo->{subdir};
723 next if $subdir eq 'DEFAULT';
724 my $dir=($subdir =~/^\//) ? $subdir : $topdir.$subdir;
726 $dir.="/" unless $dir=~/\/$/;
727 $d.="/" unless $d=~/\/$/;
728 next if $dir ne $d && $dir !~ /^\Q$d\E/;
729 if (defined $max_depth) {
730 my @a=split('/', $dir);
731 my @b=split('/', $d);
732 do { } while (@a && @b && shift(@a) eq shift(@b));
733 next if @a > $max_depth || @b > $max_depth;
735 push @repos, [$dir, $topdir, $subdir];
738 # fallback to find a leaf repo
739 foreach my $repo (reverse repolist()) {
740 my $topdir=$repo->{topdir};
741 my $subdir=$repo->{subdir};
743 next if $subdir eq 'DEFAULT';
744 my $dir=($subdir =~/^\//) ? $subdir : $topdir.$subdir;
746 $dir.="/" unless $dir=~/\/$/;
747 $d.="/" unless $d=~/\/$/;
748 if ($d=~/^\Q$dir\E/) {
749 push @repos, [$dir, $topdir, $subdir];
778 if (ref $f eq 'GLOB') {
787 my $absf=abs_path($f);
788 if ($loaded{$absf}) {
793 ($dir)=$f=~/^(.*\/)[^\/]+$/;
794 if (! defined $dir) {
797 $dir=abs_path($dir)."/";
799 if (! exists $configfiles{$dir}) {
800 $configfiles{$dir}=$f;
803 # copy in defaults from first parent
805 while ($parent=~s/^(.*\/)[^\/]+\/?$/$1/) {
806 if ($parent eq '/') {
809 if (exists $config{$parent} &&
810 exists $config{$parent}{DEFAULT}) {
811 $config{$dir}{DEFAULT}={ %{$config{$parent}{DEFAULT}} };
816 print "mr: loading config $f\n" if $verbose;
817 open($in, "<", $f) || die "mr: open $f: $!\n";
828 next if /^\s*\#/ || /^\s*$/;
829 if (/^\[([^\]]*)\]\s*$/) {
830 $section=expandenv($1);
832 elsif (/^(\w+)\s*=\s*(.*)/) {
837 while (@lines && $lines[0]=~/^\s(.+)/) {
844 if ($parameter eq "include") {
845 print "mr: including output of \"$value\"\n" if $verbose;
846 unshift @lines, `$value`;
848 print STDERR "mr: include command exited nonzero ($?)\n";
853 if (! defined $section) {
854 die "$f line $.: parameter ($parameter) not in section\n";
856 if ($section ne 'ALIAS' &&
857 ! exists $config{$dir}{$section} &&
858 exists $config{$dir}{DEFAULT}) {
860 $config{$dir}{$section}={ %{$config{$dir}{DEFAULT}} };
862 if ($section eq 'ALIAS') {
863 $alias{$parameter}=$value;
865 elsif ($parameter eq 'lib') {
866 $config{$dir}{$section}{lib}.=$value."\n";
869 $config{$dir}{$section}{$parameter}=$value;
870 if ($parameter =~ /.*_(.*)/) {
874 $knownactions{$parameter}=1;
876 if ($parameter eq 'chain' &&
877 length $dir && $section ne "DEFAULT" &&
878 -e $dir.$section."/.mrconfig") {
879 my $ret=system($value);
881 if (($? & 127) == 2) {
882 print STDERR "mr: chain test interrupted\n";
886 print STDERR "mr: chain test received signal ".($? & 127)."\n";
890 push @toload, $dir.$section."/.mrconfig";
896 die "$f line $line: parse error\n";
907 # the section to modify or add
908 my $targetsection=shift;
909 # fields to change in the section
910 # To remove a field, set its value to "".
917 open(my $in, "<", $f) || die "mr: open $f: $!\n";
922 my $formatfield=sub {
924 my @value=split(/\n/, shift);
926 return "$field = ".shift(@value)."\n".
927 join("", map { "\t$_\n" } @value);
931 while ($out[$#out] =~ /^\s*$/) {
932 unshift @blanks, pop @out;
934 foreach my $field (sort keys %changefields) {
935 if (length $changefields{$field}) {
936 push @out, "$field = $changefields{$field}\n";
937 delete $changefields{$field};
947 if (/^\s*\#/ || /^\s*$/) {
950 elsif (/^\[([^\]]*)\]\s*$/) {
951 if (defined $section &&
952 $section eq $targetsection) {
956 $section=expandenv($1);
960 elsif (/^(\w+)\s*=\s(.*)/) {
965 while (@lines && $lines[0]=~/^\s(.+)/) {
971 if ($section eq $targetsection) {
972 if (exists $changefields{$parameter}) {
973 if (length $changefields{$parameter}) {
974 $value=$changefields{$parameter};
976 delete $changefields{$parameter};
980 push @out, $formatfield->($parameter, $value);
984 if (defined $section &&
985 $section eq $targetsection) {
988 elsif (%changefields) {
989 push @out, "\n[$targetsection]\n";
990 foreach my $field (sort keys %changefields) {
991 if (length $changefields{$field}) {
992 push @out, $formatfield->($field, $changefields{$field});
997 open(my $out, ">", $f) || die "mr: write $f: $!\n";
1005 # actions that do not operate on all repos
1006 if ($action eq 'help') {
1009 elsif ($action eq 'config') {
1012 elsif ($action eq 'register') {
1015 elsif ($action eq 'remember' ||
1016 $action eq 'offline' ||
1017 $action eq 'online') {
1018 my @repos=selectrepos;
1019 action($action, @{$repos[0]}) if @repos;
1023 if (!$jobs || $jobs > 1) {
1024 mrs($action, selectrepos());
1027 foreach my $repo (selectrepos()) {
1028 record($repo, action($action, @$repo));
1034 exec($config{''}{DEFAULT}{help}) || die "exec: $!";
1039 die "mr config: not enough parameters\n";
1042 if ($section=~/^\//) {
1043 # try to convert to a path relative to the config file
1044 my ($dir)=$ENV{MR_CONFIG}=~/^(.*\/)[^\/]+$/;
1045 $dir=abs_path($dir);
1046 $dir.="/" unless $dir=~/\/$/;
1047 if ($section=~/^\Q$dir\E(.*)/) {
1053 if (/^([^=]+)=(.*)$/) {
1054 $changefields{$1}=$2;
1058 foreach my $topdir (sort keys %config) {
1059 if (exists $config{$topdir}{$section} &&
1060 exists $config{$topdir}{$section}{$_}) {
1061 print $config{$topdir}{$section}{$_}."\n";
1063 last if $section eq 'DEFAULT';
1067 die "mr config: $section $_ not set\n";
1071 modifyconfig($ENV{MR_CONFIG}, $section, %changefields) if %changefields;
1076 if ($config_overridden) {
1077 # Find the directory that the specified config file is
1079 ($directory)=abs_path($ENV{MR_CONFIG})=~/^(.*\/)[^\/]+$/;
1082 # Find the closest known mrconfig file to the current
1084 $directory.="/" unless $directory=~/\/$/;
1086 foreach my $topdir (reverse sort keys %config) {
1087 next unless length $topdir;
1088 if ($directory=~/^\Q$topdir\E/) {
1089 $ENV{MR_CONFIG}=$configfiles{$topdir};
1095 if (! $foundconfig) {
1096 $directory=""; # no config file, use builtin
1100 my $subdir=shift @ARGV;
1101 if (! chdir($subdir)) {
1102 print STDERR "mr register: failed to chdir to $subdir: $!\n";
1106 $ENV{MR_REPO}=getcwd();
1107 my $command=findcommand("register", $ENV{MR_REPO}, $directory, 'DEFAULT', 0);
1108 if (! defined $command) {
1109 die "mr register: unknown repository type\n";
1112 $ENV{MR_REPO}=~s/.*\/(.*)/$1/;
1113 $command="set -e; ".$config{$directory}{DEFAULT}{lib}."\n".
1114 "my_action(){ $command\n }; my_action ".
1115 join(" ", map { s/\//\/\//g; s/"/\"/g; '"'.$_.'"' } @ARGV);
1116 print "mr register: running >>$command<<\n" if $verbose;
1117 exec($command) || die "exec: $!";
1120 # alias expansion and command stemming
1123 if (exists $alias{$action}) {
1124 $action=$alias{$action};
1126 if (! exists $knownactions{$action}) {
1127 my @matches = grep { /^\Q$action\E/ }
1128 keys %knownactions, keys %alias;
1129 if (@matches == 1) {
1130 $action=$matches[0];
1132 elsif (@matches == 0) {
1133 die "mr: unknown action \"$action\" (known actions: ".
1134 join(", ", sort keys %knownactions).")\n";
1137 die "mr: ambiguous action \"$action\" (matches: ".
1138 join(", ", @matches).")\n";
1146 Getopt::Long::Configure("bundling", "no_permute");
1147 my $result=GetOptions(
1148 "d|directory=s" => sub { $directory=abs_path($_[1]) },
1149 "c|config=s" => sub { $ENV{MR_CONFIG}=$_[1]; $config_overridden=1 },
1150 "v|verbose" => \$verbose,
1151 "q|quiet" => \$quiet,
1152 "s|stats" => \$stats,
1153 "i|interactive" => \$interactive,
1154 "n|no-recurse:i" => \$max_depth,
1155 "j|jobs:i" => \$jobs,
1157 if (! $result || @ARGV < 1) {
1158 die("Usage: mr [-d directory] action [params ...]\n".
1159 "(Use mr help for man page.)\n");
1162 $ENV{MR_SWITCHES}="";
1163 foreach my $option (@saved) {
1164 last if $option eq $ARGV[0];
1165 $ENV{MR_SWITCHES}.="$option ";
1171 print STDERR "mr: interrupted\n";
1175 # This can happen if it's run in a directory that was removed
1176 # or other strangeness.
1177 if (! defined $directory) {
1178 die("mr: failed to determine working directory\n");
1180 # Make sure MR_CONFIG is an absolute path, but don't use abs_path since
1181 # the config file might be a symlink to elsewhere, and the directory it's
1182 # in is significant.
1183 if ($ENV{MR_CONFIG} !~ /^\//) {
1184 $ENV{MR_CONFIG}=getcwd()."/".$ENV{MR_CONFIG};
1186 # Try to set MR_PATH to the path to the program.
1188 use FindBin qw($Bin $Script);
1189 $ENV{MR_PATH}=$Bin."/".$Script;
1198 loadconfig($ENV{MR_CONFIG});
1199 #use Data::Dumper; print Dumper(\%config);
1201 my $action=expandaction(shift @ARGV);
1208 elsif (! @ok && @skipped) {
1216 # Finally, some useful actions that mr knows about by default.
1217 # These can be overridden in ~/.mrconfig.
1232 echo "mr (warning): $@" >&2
1238 if [ -z "$1" ] || [ -z "$2" ]; then
1239 error "mr: usage: hours_since action num"
1241 for dir in .git .svn .bzr CVS .hg _darcs; do
1242 if [ -e "$MR_REPO/$dir" ]; then
1243 flagfile="$MR_REPO/$dir/.mr_last$1"
1247 if [ -z "$flagfile" ]; then
1248 error "cannot determine flag filename"
1250 delta=`perl -wle 'print -f shift() ? int((-M _) * 24) : 9999' "$flagfile"`
1251 if [ "$delta" -lt "$2" ]; then
1259 svn_test = test -d "$MR_REPO"/.svn
1260 git_test = test -d "$MR_REPO"/.git
1261 bzr_test = test -d "$MR_REPO"/.bzr
1262 cvs_test = test -d "$MR_REPO"/CVS
1263 hg_test = test -d "$MR_REPO"/.hg
1264 darcs_test = test -d "$MR_REPO"/_darcs
1266 test -d "$MR_REPO"/refs/heads && test -d "$MR_REPO"/refs/tags &&
1267 test -d "$MR_REPO"/objects && test -f "$MR_REPO"/config &&
1268 test "`GIT_CONFIG="$MR_REPO"/config git config --get core.bare`" = true
1270 svn_update = svn update "$@"
1271 git_update = git pull "$@"
1272 bzr_update = bzr merge "$@"
1273 cvs_update = cvs update "$@"
1274 hg_update = hg pull "$@" && hg update "$@"
1275 darcs_update = darcs pull -a "$@"
1277 svn_status = svn status "$@"
1278 git_status = git status "$@" || true
1279 bzr_status = bzr status "$@"
1280 cvs_status = cvs status "$@"
1281 hg_status = hg status "$@"
1282 darcs_status = darcs whatsnew -ls "$@" || true
1284 svn_commit = svn commit "$@"
1285 git_commit = git commit -a "$@" && git push --all
1286 bzr_commit = bzr commit "$@" && bzr push
1287 cvs_commit = cvs commit "$@"
1288 hg_commit = hg commit -m "$@" && hg push
1289 darcs_commit = darcs record -a -m "$@" && darcs push -a
1291 git_record = git commit -a "$@"
1292 bzr_record = bzr commit "$@"
1293 hg_record = hg commit -m "$@"
1294 darcs_record = darcs record -a -m "$@"
1297 git_push = git push "$@"
1298 bzr_push = bzr push "$@"
1300 hg_push = hg push "$@"
1301 darcs_push = darcs push -a "$@"
1303 svn_diff = svn diff "$@"
1304 git_diff = git diff "$@"
1305 bzr_diff = bzr diff "$@"
1306 cvs_diff = cvs diff "$@"
1307 hg_diff = hg diff "$@"
1308 darcs_diff = darcs diff -u "$@"
1310 svn_log = svn log "$@"
1311 git_log = git log "$@"
1312 bzr_log = bzr log "$@"
1313 cvs_log = cvs log "$@"
1314 hg_log = hg log "$@"
1315 darcs_log = darcs changes "$@"
1316 git_bare_log = git log "$@"
1319 url=`LC_ALL=C svn info . | grep -i '^URL:' | cut -d ' ' -f 2`
1320 if [ -z "$url" ]; then
1321 error "cannot determine svn url"
1323 echo "Registering svn url: $url in $MR_CONFIG"
1324 mr -c "$MR_CONFIG" config "`pwd`" checkout="svn co '$url' '$MR_REPO'"
1326 url="`LC_ALL=C git config --get remote.origin.url`" || true
1327 if [ -z "$url" ]; then
1328 error "cannot determine git url"
1330 echo "Registering git url: $url in $MR_CONFIG"
1331 mr -c "$MR_CONFIG" config "`pwd`" checkout="git clone '$url' '$MR_REPO'"
1333 url="`LC_ALL=C bzr info . | egrep -i 'checkout of branch|parent branch' | awk '{print $NF}'`"
1334 if [ -z "$url" ]; then
1335 error "cannot determine bzr url"
1337 echo "Registering bzr url: $url in $MR_CONFIG"
1338 mr -c "$MR_CONFIG" config "`pwd`" checkout="bzr clone '$url' '$MR_REPO'"
1340 repo=`cat CVS/Repository`
1342 if [ -z "$root" ]; then
1343 error "cannot determine cvs root"
1345 echo "Registering cvs repository $repo at root $root"
1346 mr -c "$MR_CONFIG" config "`pwd`" checkout="cvs -d '$root' co -d '$MR_REPO' '$repo'"
1348 url=`hg showconfig paths.default`
1349 echo "Registering mercurial repo url: $url in $MR_CONFIG"
1350 mr -c "$MR_CONFIG" config "`pwd`" checkout="hg clone '$url' '$MR_REPO'"
1352 url=`cat _darcs/prefs/defaultrepo`
1353 echo "Registering darcs repository $url in $MR_CONFIG"
1354 mr -c "$MR_CONFIG" config "`pwd`" checkout="darcs get '$url' '$MR_REPO'"
1356 url="`LC_ALL=C GIT_CONFIG=config git config --get remote.origin.url`" || true
1357 if [ -z "$url" ]; then
1358 error "cannot determine git url"
1360 echo "Registering git url: $url in $MR_CONFIG"
1361 mr -c "$MR_CONFIG" config "`pwd`" checkout="git clone --bare '$url' '$MR_REPO'"
1364 if [ ! -e "$MR_PATH" ]; then
1365 error "cannot find program path"
1367 tmp=$(mktemp -t mr.XXXXXXXXXX) || error "mktemp failed"
1368 trap "rm -f $tmp" exit
1369 pod2man -c mr "$MR_PATH" > "$tmp" || error "pod2man failed"
1370 man -l "$tmp" || error "man failed"
1375 if [ -s ~/.mrlog ]; then
1376 info "running offline commands"
1377 mv -f ~/.mrlog ~/.mrlog.old
1378 if ! sh -e ~/.mrlog.old; then
1379 error "offline command failed; left in ~/.mrlog.old"
1383 info "no offline commands to run"
1388 info "offline mode enabled"
1390 info "remembering command: 'mr $@'"
1391 command="mr -d '$(pwd)' $MR_SWITCHES"
1393 command="$command '$w'"
1395 if [ ! -e ~/.mrlog ] || ! grep -q -F "$command" ~/.mrlog; then
1396 echo "$command" >> ~/.mrlog
1399 ed = echo "A horse is a horse, of course, of course.."
1400 T = echo "I pity the fool."
1401 right = echo "Not found."
1403 # vim:sw=8:sts=0:ts=8:noet