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] bootstrap url
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 Pushes committed local changes to the remote repository. A no-op for
87 centralized revision control systems.
91 Show a diff of uncommitted changes.
99 These commands are also available:
105 Causes mr to download the url, save it to a .mrconfig file in the
106 current directory, and then check out all repositories listed in it.
108 (Please only do this if you have reason to trust the url, since
109 mrconfig files can contain arbitrary commands!)
113 List the repositories that mr will act on.
117 Register an existing repository in a mrconfig file. By default, the
118 repository in the current directory is registered, or you can specify a
119 directory to register.
121 The mrconfig file that is modified is chosen by either the -c option, or by
122 looking for the closest known one at or below the current directory.
126 Adds, modifies, removes, or prints a value from a mrconfig file. The next
127 parameter is the name of the section the value is in. To add or modify
128 values, use one or more instances of "parameter=value". Use "parameter=" to
129 remove a parameter. Use just "parameter" to get the value of a parameter.
131 For example, to add (or edit) a repository in src/foo:
133 mr config src/foo checkout="svn co svn://example.com/foo/trunk foo"
135 To show the command that mr uses to update the repository in src/foo:
137 mr config src/foo update
139 To see the built-in library of shell functions contained in mr:
141 mr config DEFAULT lib
143 The ~/.mrconfig file is used by default. To use a different config file,
148 Advises mr that it is in offline mode. Any commands that fail in
149 offline mode will be remembered, and retried when mr is told it's online.
153 Advices mr that it is in online mode again. Commands that failed while in
154 offline mode will be re-run.
158 Remember a command, to be run later when mr re-enters online mode. This
159 implicitly puts mr into offline mode. The command can be any regular mr
160 command. This is useful when you know that a command will fail due to being
161 offline, and so don't want to run it right now at all, but just remember
162 to run it when you go back online.
170 Actions can be abbreviated to any unambiguous substring, so
171 "mr st" is equivalent to "mr status", and "mr up" is equivalent to "mr
174 Additional parameters can be passed to most commands, and are passed on
175 unchanged to the underlying revision control system. This is mostly useful
176 if the repositories mr will act on all use the same revision control
185 Specifies the topmost directory that B<mr> should work in. The default is
186 the current working directory.
190 Use the specified mrconfig file. The default is B<~/.mrconfig>
194 Search in the current directory, and its parent directories and use
195 the first B<.mrconfig> found, instead of the default B<~/.mrconfig>.
207 Expand the statistics line displayed at the end to include information
208 about exactly which repositories failed and were skipped, if any.
212 Interactive mode. If a repository fails to be processed, a subshell will be
213 started which you can use to resolve or investigate the problem. Exit the
214 subshell to continue the mr run.
218 If no number if specified, just operate on the repository for the current
219 directory, do not recurse into deeper repositories.
221 If a number is specified, will recurse into repositories at most that many
222 subdirectories deep. For example, with -n 2 it would recurse into ./src/foo,
223 but not ./src/packages/bar.
227 Run the specified number of jobs in parallel, or an unlimited number of jobs
228 with no number specified. This can greatly speed up operations such as updates.
229 It is not recommended for interactive operations.
231 Note that running more than 10 jobs at a time is likely to run afoul of
232 ssh connection limits. Running between 3 and 5 jobs at a time will yield
233 a good speedup in updates without loading the machine too much.
239 B<mr> is configured by .mrconfig files. It starts by reading the .mrconfig
240 file in your home directory, and this can in turn chain load .mrconfig files
243 Here is an example .mrconfig file:
246 checkout = svn co svn://svn.example.com/src/trunk src
250 checkout = git clone git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux-2.6.git &&
252 git checkout -b mybranch origin/master
254 The .mrconfig file uses a variant of the INI file format. Lines starting with
255 "#" are comments. Values can be continued to the following line by
256 indenting the line with whitespace.
258 The "DEFAULT" section allows setting default values for the sections that
261 The "ALIAS" section allows adding aliases for actions. Each parameter
262 is an alias, and its value is the action to use.
264 All other sections add repositories. The section header specifies the
265 directory where the repository is located. This is relative to the directory
266 that contains the mrconfig file, but you can also choose to use absolute
267 paths. (Note that you can use environment variables in section names; they
268 will be passed through the shell for expansion. For example,
269 "[$HOSTNAME]", or "[${HOSTNAME}foo]")
271 Within a section, each parameter defines a shell command to run to handle a
272 given action. mr contains default handlers for "update", "status",
273 "commit", and other standard actions. Normally you only need to specify what
274 to do for "checkout".
276 Note that these shell commands are run in a "set -e" shell
277 environment, where any additional parameters you pass are available in
278 "$@". The "checkout" command is run in the parent of the repository
279 directory, since the repository isn't checked out yet. All other commands
280 are run inside the repository, though not necessarily at the top of it.
282 The "MR_REPO" environment variable is set to the path to the top of the
283 repository. (For the "register" action, "MR_REPO" is instead set to the
284 basename of the directory that should be created when checking the
287 The "MR_CONFIG" environment variable is set to the .mrconfig file
288 that defines the repo being acted on, or, if the repo is not yet in a config
289 file, the .mrconfig file that should be modified to register the repo.
291 A few parameters have special meanings:
297 If the "skip" parameter is set and its command returns true, then B<mr>
298 will skip acting on that repository. The command is passed the action
301 Here are two examples. The first skips the repo unless
302 mr is run by joey. The second uses the hours_since function
303 (included in mr's built-in library) to skip updating the repo unless it's
304 been at least 12 hours since the last update.
306 skip = test `whoami` != joey
307 skip = [ "$1" = update ] && ! hours_since "$1" 12
311 The "order" parameter can be used to override the default ordering of
312 repositories. The default order value is 10. Use smaller values to make
313 repositories be processed earlier, and larger values to make repositories
316 Note that if a repository is located in a subdirectory of another
317 repository, ordering it to be processed earlier is not recommended.
321 If the "chain" parameter is set and its command returns true, then B<mr>
322 will try to load a .mrconfig file from the root of the repository. (You
323 should avoid chaining from repositories with untrusted committers.)
327 If the "include" parameter is set, its command is ran, and should output
328 additional mrconfig file content. The content is included as if it were
329 part of the including file.
331 Unlike all other parameters, this parameter does not need to be placed
336 The "lib" parameter can specify some shell code that will be run before each
337 command, this can be a useful way to define shell functions for other commands
342 When looking for a command to run for a given action, mr first looks for
343 a parameter with the same name as the action. If that is not found, it
344 looks for a parameter named "rcs_action" (substituting in the name of the
345 revision control system and the action). The name of the revision control
346 system is itself determined by running each defined "rcs_test" action,
349 Internally, mr has settings for "git_update", "svn_update", etc. To change
350 the action that is performed for a given revision control system, you can
351 override these rcs specific actions. To add a new revision control system,
352 you can just add rcs specific actions for it.
354 The ~/.mrlog file contains commands that mr has remembered to run later,
355 due to being offline. You can delete or edit this file to remove commands,
356 or even to add other commands for 'mr online' to run. If the file is
357 present, mr assumes it is in offline mode.
361 mr can be extended to support things such as unison and git-svn. Some
362 files providing such extensions are available in /usr/share/mr/. See
363 the documentation in the files for details about using them.
367 Copyright 2007-2009 Joey Hess <joey@kitenet.net>
369 Licensed under the GNU GPL version 2 or higher.
371 http://kitenet.net/~joey/code/mr/
378 use Cwd qw(getcwd abs_path);
380 # things that can happen when mr runs a command
389 my $config_overridden=0;
397 my $directory=getcwd();
398 $ENV{MR_CONFIG}="$ENV{HOME}/.mrconfig";
405 my (@ok, @failed, @skipped);
411 my ($action, $dir, $topdir, $subdir) = @_;
413 if (exists $rcs{$dir}) {
418 foreach my $rcs_test (
420 length $a <=> length $b
423 } grep { /_test$/ } keys %{$config{$topdir}{$subdir}}) {
424 my ($rcs)=$rcs_test=~/(.*)_test/;
425 $test="my_$rcs_test() {\n$config{$topdir}{$subdir}{$rcs_test}\n}\n".$test;
426 $test.="if my_$rcs_test; then echo $rcs; fi\n";
428 $test=$config{$topdir}{$subdir}{lib}."\n".$test
429 if exists $config{$topdir}{$subdir}{lib};
431 print "mr $action: running rcs test >>$test<<\n" if $verbose;
436 print STDERR "mr $action: found multiple possible repository types ($rcs) for $topdir$subdir\n";
440 return $rcs{$dir}=undef;
443 return $rcs{$dir}=$rcs;
448 my ($action, $dir, $topdir, $subdir, $is_checkout) = @_;
450 if (exists $config{$topdir}{$subdir}{$action}) {
451 return $config{$topdir}{$subdir}{$action};
458 my $rcs=rcs_test(@_);
461 exists $config{$topdir}{$subdir}{$rcs."_".$action}) {
462 return $config{$topdir}{$subdir}{$rcs."_".$action};
470 my ($action, $dir, $topdir, $subdir) = @_;
472 $ENV{MR_CONFIG}=$configfiles{$topdir};
473 my $lib=exists $config{$topdir}{$subdir}{lib} ?
474 $config{$topdir}{$subdir}{lib}."\n" : "";
475 my $is_checkout=($action eq 'checkout');
481 print "mr $action: $dir already exists, skipping checkout\n" if $verbose;
485 $dir=~s/^(.*)\/[^\/]+\/?$/$1/;
487 elsif ($action =~ /update/) {
489 return action("checkout", $dir, $topdir, $subdir);
493 my $skiptest=findcommand("skip", $dir, $topdir, $subdir, $is_checkout);
494 my $command=findcommand($action, $dir, $topdir, $subdir, $is_checkout);
496 if (defined $skiptest) {
497 my $test="set -e;".$lib.
498 "my_action(){ $skiptest\n }; my_action '$action'";
499 print "mr $action: running skip test >>$test<<\n" if $verbose;
500 my $ret=system($test);
502 if (($? & 127) == 2) {
503 print STDERR "mr $action: interrupted\n";
507 print STDERR "mr $action: skip test received signal ".($? & 127)."\n";
511 if ($ret >> 8 == 0) {
512 print "mr $action: $dir skipped per config file\n" if $verbose;
517 if ($is_checkout && ! -d $dir) {
518 print "mr $action: creating parent directory $dir\n" if $verbose;
519 system("mkdir", "-p", $dir);
522 if (! $no_chdir && ! chdir($dir)) {
523 print STDERR "mr $action: failed to chdir to $dir: $!\n";
526 elsif (! defined $command) {
527 my $rcs=rcs_test(@_);
528 if (! defined $rcs) {
529 print STDERR "mr $action: unknown repository type and no defined $action command for $topdir$subdir\n";
533 print STDERR "mr $action: no defined action for $rcs repository $topdir$subdir, skipping\n";
539 print "mr $action: $topdir$subdir\n" unless $quiet;
543 $s=~s/^\Q$topdir$subdir\E\/?//;
544 print "mr $action: $topdir$subdir (in subdir $s)\n" unless $quiet;
546 $command="set -e; ".$lib.
547 "my_action(){ $command\n }; my_action ".
548 join(" ", map { s/\//\/\//g; s/"/\"/g; '"'.$_.'"' } @ARGV);
549 print "mr $action: running >>$command<<\n" if $verbose;
550 my $ret=system($command);
552 if (($? & 127) == 2) {
553 print STDERR "mr $action: interrupted\n";
557 print STDERR "mr $action: received signal ".($? & 127)."\n";
560 print STDERR "mr $action: failed ($ret)\n" if $verbose;
561 if ($ret >> 8 != 0) {
562 print STDERR "mr $action: command failed\n";
563 if (-e "$ENV{HOME}/.mrlog" && $action ne 'remember') {
564 # recreate original command line to
565 # remember, and avoid recursing
567 @ARGV=('-n', $action, @orig);
568 action("remember", $dir, $topdir, $subdir);
573 print STDERR "mr $action: command died ($ret)\n";
578 if ($action eq 'checkout' && ! -d $dir) {
579 print STDERR "mr $action: $dir missing after checkout\n";;
588 # run actions on multiple repos, in parallel
598 while (@fhs or @repos) {
599 while ((!$jobs || $running < $jobs) && @repos) {
601 my $repo = shift @repos;
602 pipe(my $outfh, CHILD_STDOUT);
603 pipe(my $errfh, CHILD_STDERR);
605 unless ($pid = fork) {
606 die "mr $action: cannot fork: $!" unless defined $pid;
607 open(STDOUT, ">&CHILD_STDOUT") || die "mr $action cannot reopen stdout: $!";
608 open(STDERR, ">&CHILD_STDERR") || die "mr $action cannot reopen stderr: $!";
613 exit action($action, @$repo);
617 push @active, [$pid, $repo];
618 push @fhs, [$outfh, $errfh];
621 my ($rin, $rout) = ('','');
623 foreach my $fh (@fhs) {
624 next unless defined $fh;
625 vec($rin, fileno($fh->[0]), 1) = 1 if defined $fh->[0];
626 vec($rin, fileno($fh->[1]), 1) = 1 if defined $fh->[1];
628 $nfound = select($rout=$rin, undef, undef, 1);
629 foreach my $channel (0, 1) {
630 foreach my $i (0..$#fhs) {
631 next unless defined $fhs[$i];
632 my $fh = $fhs[$i][$channel];
633 next unless defined $fh;
634 if (vec($rout, fileno($fh), 1) == 1) {
636 if (sysread($fh, $r, 1024) == 0) {
638 $fhs[$i][$channel] = undef;
639 if (! defined $fhs[$i][0] &&
640 ! defined $fhs[$i][1]) {
641 waitpid($active[$i][0], 0);
642 print STDOUT $out[$i][0];
643 print STDERR $out[$i][1];
644 record($active[$i][1], $? >> 8);
646 splice(@active, $i, 1);
651 $out[$i][$channel] .= $r;
659 my $dir=shift()->[0];
666 elsif ($ret == FAILED) {
668 chdir($dir) unless $no_chdir;
669 print STDERR "mr: Starting interactive shell. Exit shell to continue.\n";
670 system((getpwuid($<))[8]);
675 elsif ($ret == SKIPPED) {
678 elsif ($ret == ABORT) {
682 die "unknown exit status $ret";
688 if (! @ok && ! @failed && ! @skipped) {
689 die "mr $action: no repositories found to work on\n";
691 print "mr $action: finished (".join("; ",
692 showstat($#ok+1, "ok", "ok"),
693 showstat($#failed+1, "failed", "failed"),
694 showstat($#skipped+1, "skipped", "skipped"),
695 ).")\n" unless $quiet;
698 print "mr $action: (skipped: ".join(" ", @skipped).")\n" unless $quiet;
701 print STDERR "mr $action: (failed: ".join(" ", @failed).")\n";
711 return "$count ".($count > 1 ? $plural : $singular);
716 # an ordered list of repos
719 foreach my $topdir (sort keys %config) {
720 foreach my $subdir (sort keys %{$config{$topdir}}) {
724 order => $config{$topdir}{$subdir}{order},
729 $a->{order} <=> $b->{order}
731 $a->{topdir} cmp $b->{topdir}
733 $a->{subdir} cmp $b->{subdir}
737 # figure out which repos to act on
740 foreach my $repo (repolist()) {
741 my $topdir=$repo->{topdir};
742 my $subdir=$repo->{subdir};
744 next if $subdir eq 'DEFAULT';
745 my $dir=($subdir =~/^\//) ? $subdir : $topdir.$subdir;
747 $dir.="/" unless $dir=~/\/$/;
748 $d.="/" unless $d=~/\/$/;
749 next if $dir ne $d && $dir !~ /^\Q$d\E/;
750 if (defined $max_depth) {
751 my @a=split('/', $dir);
752 my @b=split('/', $d);
753 do { } while (@a && @b && shift(@a) eq shift(@b));
754 next if @a > $max_depth || @b > $max_depth;
756 push @repos, [$dir, $topdir, $subdir];
759 # fallback to find a leaf repo
760 foreach my $repo (reverse repolist()) {
761 my $topdir=$repo->{topdir};
762 my $subdir=$repo->{subdir};
764 next if $subdir eq 'DEFAULT';
765 my $dir=($subdir =~/^\//) ? $subdir : $topdir.$subdir;
767 $dir.="/" unless $dir=~/\/$/;
768 $d.="/" unless $d=~/\/$/;
769 if ($d=~/^\Q$dir\E/) {
770 push @repos, [$dir, $topdir, $subdir];
799 if (ref $f eq 'GLOB') {
808 my $absf=abs_path($f);
809 if ($loaded{$absf}) {
814 ($dir)=$f=~/^(.*\/)[^\/]+$/;
815 if (! defined $dir) {
818 $dir=abs_path($dir)."/";
820 if (! exists $configfiles{$dir}) {
821 $configfiles{$dir}=$f;
824 # copy in defaults from first parent
826 while ($parent=~s/^(.*\/)[^\/]+\/?$/$1/) {
827 if ($parent eq '/') {
830 if (exists $config{$parent} &&
831 exists $config{$parent}{DEFAULT}) {
832 $config{$dir}{DEFAULT}={ %{$config{$parent}{DEFAULT}} };
837 print "mr: loading config $f\n" if $verbose;
838 open($in, "<", $f) || die "mr: open $f: $!\n";
849 next if /^\s*\#/ || /^\s*$/;
850 if (/^\[([^\]]*)\]\s*$/) {
851 $section=expandenv($1);
853 elsif (/^(\w+)\s*=\s*(.*)/) {
858 while (@lines && $lines[0]=~/^\s(.+)/) {
865 if ($parameter eq "include") {
866 print "mr: including output of \"$value\"\n" if $verbose;
867 unshift @lines, `$value`;
869 print STDERR "mr: include command exited nonzero ($?)\n";
874 if (! defined $section) {
875 die "$f line $.: parameter ($parameter) not in section\n";
877 if ($section ne 'ALIAS' &&
878 ! exists $config{$dir}{$section} &&
879 exists $config{$dir}{DEFAULT}) {
881 $config{$dir}{$section}={ %{$config{$dir}{DEFAULT}} };
883 if ($section eq 'ALIAS') {
884 $alias{$parameter}=$value;
886 elsif ($parameter eq 'lib') {
887 $config{$dir}{$section}{lib}.=$value."\n";
890 $config{$dir}{$section}{$parameter}=$value;
891 if ($parameter =~ /.*_(.*)/) {
895 $knownactions{$parameter}=1;
897 if ($parameter eq 'chain' &&
898 length $dir && $section ne "DEFAULT" &&
899 -e $dir.$section."/.mrconfig") {
900 my $ret=system($value);
902 if (($? & 127) == 2) {
903 print STDERR "mr: chain test interrupted\n";
907 print STDERR "mr: chain test received signal ".($? & 127)."\n";
911 push @toload, $dir.$section."/.mrconfig";
917 die "$f line $line: parse error\n";
928 # the section to modify or add
929 my $targetsection=shift;
930 # fields to change in the section
931 # To remove a field, set its value to "".
938 open(my $in, "<", $f) || die "mr: open $f: $!\n";
943 my $formatfield=sub {
945 my @value=split(/\n/, shift);
947 return "$field = ".shift(@value)."\n".
948 join("", map { "\t$_\n" } @value);
952 while ($out[$#out] =~ /^\s*$/) {
953 unshift @blanks, pop @out;
955 foreach my $field (sort keys %changefields) {
956 if (length $changefields{$field}) {
957 push @out, "$field = $changefields{$field}\n";
958 delete $changefields{$field};
968 if (/^\s*\#/ || /^\s*$/) {
971 elsif (/^\[([^\]]*)\]\s*$/) {
972 if (defined $section &&
973 $section eq $targetsection) {
977 $section=expandenv($1);
981 elsif (/^(\w+)\s*=\s(.*)/) {
986 while (@lines && $lines[0]=~/^\s(.+)/) {
992 if ($section eq $targetsection) {
993 if (exists $changefields{$parameter}) {
994 if (length $changefields{$parameter}) {
995 $value=$changefields{$parameter};
997 delete $changefields{$parameter};
1001 push @out, $formatfield->($parameter, $value);
1005 if (defined $section &&
1006 $section eq $targetsection) {
1009 elsif (%changefields) {
1010 push @out, "\n[$targetsection]\n";
1011 foreach my $field (sort keys %changefields) {
1012 if (length $changefields{$field}) {
1013 push @out, $formatfield->($field, $changefields{$field});
1018 open(my $out, ">", $f) || die "mr: write $f: $!\n";
1026 # actions that do not operate on all repos
1027 if ($action eq 'help') {
1030 elsif ($action eq 'config') {
1033 elsif ($action eq 'register') {
1036 elsif ($action eq 'bootstrap') {
1039 elsif ($action eq 'remember' ||
1040 $action eq 'offline' ||
1041 $action eq 'online') {
1042 my @repos=selectrepos;
1043 action($action, @{$repos[0]}) if @repos;
1047 if (!$jobs || $jobs > 1) {
1048 mrs($action, selectrepos());
1051 foreach my $repo (selectrepos()) {
1052 record($repo, action($action, @$repo));
1058 exec($config{''}{DEFAULT}{help}) || die "exec: $!";
1063 die "mr config: not enough parameters\n";
1066 if ($section=~/^\//) {
1067 # try to convert to a path relative to the config file
1068 my ($dir)=$ENV{MR_CONFIG}=~/^(.*\/)[^\/]+$/;
1069 $dir=abs_path($dir);
1070 $dir.="/" unless $dir=~/\/$/;
1071 if ($section=~/^\Q$dir\E(.*)/) {
1077 if (/^([^=]+)=(.*)$/) {
1078 $changefields{$1}=$2;
1082 foreach my $topdir (sort keys %config) {
1083 if (exists $config{$topdir}{$section} &&
1084 exists $config{$topdir}{$section}{$_}) {
1085 print $config{$topdir}{$section}{$_}."\n";
1087 last if $section eq 'DEFAULT';
1091 die "mr config: $section $_ not set\n";
1095 modifyconfig($ENV{MR_CONFIG}, $section, %changefields) if %changefields;
1100 if ($config_overridden) {
1101 # Find the directory that the specified config file is
1103 ($directory)=abs_path($ENV{MR_CONFIG})=~/^(.*\/)[^\/]+$/;
1106 # Find the closest known mrconfig file to the current
1108 $directory.="/" unless $directory=~/\/$/;
1110 foreach my $topdir (reverse sort keys %config) {
1111 next unless length $topdir;
1112 if ($directory=~/^\Q$topdir\E/) {
1113 $ENV{MR_CONFIG}=$configfiles{$topdir};
1119 if (! $foundconfig) {
1120 $directory=""; # no config file, use builtin
1124 my $subdir=shift @ARGV;
1125 if (! chdir($subdir)) {
1126 print STDERR "mr register: failed to chdir to $subdir: $!\n";
1130 $ENV{MR_REPO}=getcwd();
1131 my $command=findcommand("register", $ENV{MR_REPO}, $directory, 'DEFAULT', 0);
1132 if (! defined $command) {
1133 die "mr register: unknown repository type\n";
1136 $ENV{MR_REPO}=~s/.*\/(.*)/$1/;
1137 $command="set -e; ".$config{$directory}{DEFAULT}{lib}."\n".
1138 "my_action(){ $command\n }; my_action ".
1139 join(" ", map { s/\//\/\//g; s/"/\"/g; '"'.$_.'"' } @ARGV);
1140 print "mr register: running >>$command<<\n" if $verbose;
1141 exec($command) || die "exec: $!";
1145 my $url=shift @ARGV;
1147 if (! defined $url || ! length $url) {
1148 die "mr: bootstrap requires url\n";
1151 if (-e ".mrconfig") {
1152 die "mr: .mrconfig file already exists, not overwriting with $url\n";
1155 if (system("curl", "-s", $url, "-o", ".mrconfig") != 0) {
1156 die "mr: download of $url failed\n";
1159 exec("mr $ENV{MR_SWITCHES} -c .mrconfig checkout");
1160 die "failed to run mr checkout";
1163 # alias expansion and command stemming
1166 if (exists $alias{$action}) {
1167 $action=$alias{$action};
1169 if (! exists $knownactions{$action}) {
1170 my @matches = grep { /^\Q$action\E/ }
1171 keys %knownactions, keys %alias;
1172 if (@matches == 1) {
1173 $action=$matches[0];
1175 elsif (@matches == 0) {
1176 die "mr: unknown action \"$action\" (known actions: ".
1177 join(", ", sort keys %knownactions).")\n";
1180 die "mr: ambiguous action \"$action\" (matches: ".
1181 join(", ", @matches).")\n";
1187 sub find_nearest_mrconfig {
1189 while (length $dir) {
1190 if (-e "$dir/.mrconfig") {
1191 return "$dir/.mrconfig";
1193 $dir=~s/\/[^\/]*$//;
1195 die "no .mrconfig found in path\n";
1200 Getopt::Long::Configure("bundling", "no_permute");
1201 my $result=GetOptions(
1202 "d|directory=s" => sub { $directory=abs_path($_[1]) },
1203 "c|config=s" => sub { $ENV{MR_CONFIG}=$_[1]; $config_overridden=1 },
1204 "p|path" => sub { $ENV{MR_CONFIG}=find_nearest_mrconfig(); $config_overridden=1 },
1205 "v|verbose" => \$verbose,
1206 "q|quiet" => \$quiet,
1207 "s|stats" => \$stats,
1208 "i|interactive" => \$interactive,
1209 "n|no-recurse:i" => \$max_depth,
1210 "j|jobs:i" => \$jobs,
1212 if (! $result || @ARGV < 1) {
1213 die("Usage: mr [-d directory] action [params ...]\n".
1214 "(Use mr help for man page.)\n");
1217 $ENV{MR_SWITCHES}="";
1218 foreach my $option (@saved) {
1219 last if $option eq $ARGV[0];
1220 $ENV{MR_SWITCHES}.="$option ";
1226 print STDERR "mr: interrupted\n";
1230 # This can happen if it's run in a directory that was removed
1231 # or other strangeness.
1232 if (! defined $directory) {
1233 die("mr: failed to determine working directory\n");
1235 # Make sure MR_CONFIG is an absolute path, but don't use abs_path since
1236 # the config file might be a symlink to elsewhere, and the directory it's
1237 # in is significant.
1238 if ($ENV{MR_CONFIG} !~ /^\//) {
1239 $ENV{MR_CONFIG}=getcwd()."/".$ENV{MR_CONFIG};
1241 # Try to set MR_PATH to the path to the program.
1243 use FindBin qw($Bin $Script);
1244 $ENV{MR_PATH}=$Bin."/".$Script;
1253 loadconfig($ENV{MR_CONFIG});
1254 #use Data::Dumper; print Dumper(\%config);
1256 my $action=expandaction(shift @ARGV);
1263 elsif (! @ok && @skipped) {
1271 # Finally, some useful actions that mr knows about by default.
1272 # These can be overridden in ~/.mrconfig.
1287 echo "mr (warning): $@" >&2
1293 if [ -z "$1" ] || [ -z "$2" ]; then
1294 error "mr: usage: hours_since action num"
1296 for dir in .git .svn .bzr CVS .hg _darcs; do
1297 if [ -e "$MR_REPO/$dir" ]; then
1298 flagfile="$MR_REPO/$dir/.mr_last$1"
1302 if [ -z "$flagfile" ]; then
1303 error "cannot determine flag filename"
1305 delta=`perl -wle 'print -f shift() ? int((-M _) * 24) : 9999' "$flagfile"`
1306 if [ "$delta" -lt "$2" ]; then
1314 svn_test = test -d "$MR_REPO"/.svn
1315 git_test = test -d "$MR_REPO"/.git
1316 bzr_test = test -d "$MR_REPO"/.bzr
1317 cvs_test = test -d "$MR_REPO"/CVS
1318 hg_test = test -d "$MR_REPO"/.hg
1319 darcs_test = test -d "$MR_REPO"/_darcs
1321 test -d "$MR_REPO"/refs/heads && test -d "$MR_REPO"/refs/tags &&
1322 test -d "$MR_REPO"/objects && test -f "$MR_REPO"/config &&
1323 test "`GIT_CONFIG="$MR_REPO"/config git config --get core.bare`" = true
1325 svn_update = svn update "$@"
1326 git_update = git pull "$@"
1327 bzr_update = bzr merge --pull "$@"
1328 cvs_update = cvs update "$@"
1329 hg_update = hg pull "$@" && hg update "$@"
1330 darcs_update = darcs pull -a "$@"
1332 svn_status = svn status "$@"
1333 git_status = git status "$@" || true
1334 bzr_status = bzr status "$@"
1335 cvs_status = cvs status "$@"
1336 hg_status = hg status "$@"
1337 darcs_status = darcs whatsnew -ls "$@" || true
1339 svn_commit = svn commit "$@"
1340 git_commit = git commit -a "$@" && git push --all
1341 bzr_commit = bzr commit "$@" && bzr push
1342 cvs_commit = cvs commit "$@"
1343 hg_commit = hg commit -m "$@" && hg push
1344 darcs_commit = darcs record -a -m "$@" && darcs push -a
1346 git_record = git commit -a "$@"
1347 bzr_record = bzr commit "$@"
1348 hg_record = hg commit -m "$@"
1349 darcs_record = darcs record -a -m "$@"
1352 git_push = git push "$@"
1353 bzr_push = bzr push "$@"
1355 hg_push = hg push "$@"
1356 darcs_push = darcs push -a "$@"
1358 svn_diff = svn diff "$@"
1359 git_diff = git diff "$@"
1360 bzr_diff = bzr diff "$@"
1361 cvs_diff = cvs diff "$@"
1362 hg_diff = hg diff "$@"
1363 darcs_diff = darcs diff -u "$@"
1365 svn_log = svn log "$@"
1366 git_log = git log "$@"
1367 bzr_log = bzr log "$@"
1368 cvs_log = cvs log "$@"
1369 hg_log = hg log "$@"
1370 darcs_log = darcs changes "$@"
1371 git_bare_log = git log "$@"
1374 url=`LC_ALL=C svn info . | grep -i '^URL:' | cut -d ' ' -f 2`
1375 if [ -z "$url" ]; then
1376 error "cannot determine svn url"
1378 echo "Registering svn url: $url in $MR_CONFIG"
1379 mr -c "$MR_CONFIG" config "`pwd`" checkout="svn co '$url' '$MR_REPO'"
1381 url="`LC_ALL=C git config --get remote.origin.url`" || true
1382 if [ -z "$url" ]; then
1383 error "cannot determine git url"
1385 echo "Registering git url: $url in $MR_CONFIG"
1386 mr -c "$MR_CONFIG" config "`pwd`" checkout="git clone '$url' '$MR_REPO'"
1388 url="`LC_ALL=C bzr info . | egrep -i 'checkout of branch|parent branch' | awk '{print $NF}'`"
1389 if [ -z "$url" ]; then
1390 error "cannot determine bzr url"
1392 echo "Registering bzr url: $url in $MR_CONFIG"
1393 mr -c "$MR_CONFIG" config "`pwd`" checkout="bzr clone '$url' '$MR_REPO'"
1395 repo=`cat CVS/Repository`
1397 if [ -z "$root" ]; then
1398 error "cannot determine cvs root"
1400 echo "Registering cvs repository $repo at root $root"
1401 mr -c "$MR_CONFIG" config "`pwd`" checkout="cvs -d '$root' co -d '$MR_REPO' '$repo'"
1403 url=`hg showconfig paths.default`
1404 echo "Registering mercurial repo url: $url in $MR_CONFIG"
1405 mr -c "$MR_CONFIG" config "`pwd`" checkout="hg clone '$url' '$MR_REPO'"
1407 url=`cat _darcs/prefs/defaultrepo`
1408 echo "Registering darcs repository $url in $MR_CONFIG"
1409 mr -c "$MR_CONFIG" config "`pwd`" checkout="darcs get '$url' '$MR_REPO'"
1411 url="`LC_ALL=C GIT_CONFIG=config git config --get remote.origin.url`" || true
1412 if [ -z "$url" ]; then
1413 error "cannot determine git url"
1415 echo "Registering git url: $url in $MR_CONFIG"
1416 mr -c "$MR_CONFIG" config "`pwd`" checkout="git clone --bare '$url' '$MR_REPO'"
1419 if [ ! -e "$MR_PATH" ]; then
1420 error "cannot find program path"
1422 tmp=$(mktemp -t mr.XXXXXXXXXX) || error "mktemp failed"
1423 trap "rm -f $tmp" exit
1424 pod2man -c mr "$MR_PATH" > "$tmp" || error "pod2man failed"
1425 man -l "$tmp" || error "man failed"
1431 if [ -s ~/.mrlog ]; then
1432 info "running offline commands"
1433 mv -f ~/.mrlog ~/.mrlog.old
1434 if ! sh -e ~/.mrlog.old; then
1435 error "offline command failed; left in ~/.mrlog.old"
1439 info "no offline commands to run"
1444 info "offline mode enabled"
1446 info "remembering command: 'mr $@'"
1447 command="mr -d '$(pwd)' $MR_SWITCHES"
1449 command="$command '$w'"
1451 if [ ! -e ~/.mrlog ] || ! grep -q -F "$command" ~/.mrlog; then
1452 echo "$command" >> ~/.mrlog
1455 ed = echo "A horse is a horse, of course, of course.."
1456 T = echo "I pity the fool."
1457 right = echo "Not found."
1459 # vim:sw=8:sts=0:ts=8:noet