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 B<mr> is configured by .mrconfig files, which list the repositories. It
49 starts by reading the .mrconfig file in your home directory, and this can
50 in turn chain load .mrconfig files from repositories.
52 These predefined commands should be fairly familiar to users of any revision
57 =item checkout (or co)
59 Checks out any repositories that are not already checked out.
63 Updates each repository from its configured remote repository.
65 If a repository isn't checked out yet, it will first check it out.
69 Displays a status report for each repository, showing what
70 uncommitted changes are present in the repository.
74 Commits changes to each repository. (By default, changes are pushed to the
75 remote repository too, when using distributed systems like git. If you
76 don't like this default, you can change it in your .mrconfig, or use record
79 The optional -m parameter allows specifying a commit message.
83 Records changes to the local repository, but does not push them to the
84 remote repository. Only supported for distributed revision control systems.
86 The optional -m parameter allows specifying a commit message.
90 Pushes committed local changes to the remote repository. A no-op for
91 centralized revision control systems.
95 Show a diff of uncommitted changes.
103 These commands are also available:
109 Causes mr to download the url, save it to a .mrconfig file in the
110 current directory, and then check out all repositories listed in it.
114 List the repositories that mr will act on.
118 Register an existing repository in a mrconfig file. By default, the
119 repository in the current directory is registered, or you can specify a
120 directory to register.
122 The mrconfig file that is modified is chosen by either the -c option, or by
123 looking for the closest known one at or below the current directory.
127 Adds, modifies, removes, or prints a value from a mrconfig file. The next
128 parameter is the name of the section the value is in. To add or modify
129 values, use one or more instances of "parameter=value". Use "parameter=" to
130 remove a parameter. Use just "parameter" to get the value of a parameter.
132 For example, to add (or edit) a repository in src/foo:
134 mr config src/foo checkout="svn co svn://example.com/foo/trunk foo"
136 To show the command that mr uses to update the repository in src/foo:
138 mr config src/foo update
140 To see the built-in library of shell functions contained in mr:
142 mr config DEFAULT lib
144 The ~/.mrconfig file is used by default. To use a different config file,
149 Advises mr that it is in offline mode. Any commands that fail in
150 offline mode will be remembered, and retried when mr is told it's online.
154 Advices mr that it is in online mode again. Commands that failed while in
155 offline mode will be re-run.
159 Remember a command, to be run later when mr re-enters online mode. This
160 implicitly puts mr into offline mode. The command can be any regular mr
161 command. This is useful when you know that a command will fail due to being
162 offline, and so don't want to run it right now at all, but just remember
163 to run it when you go back online.
171 Actions can be abbreviated to any unambiguous substring, so
172 "mr st" is equivalent to "mr status", and "mr up" is equivalent to "mr
175 Additional parameters can be passed to most commands, and are passed on
176 unchanged to the underlying revision control system. This is mostly useful
177 if the repositories mr will act on all use the same revision control
186 Specifies the topmost directory that B<mr> should work in. The default is
187 the current working directory.
191 Use the specified mrconfig file. The default is B<~/.mrconfig>
195 Search in the current directory, and its parent directories and use
196 the first B<.mrconfig> found, instead of the default B<~/.mrconfig>.
208 Expand the statistics line displayed at the end to include information
209 about exactly which repositories failed and were skipped, if any.
213 Interactive mode. If a repository fails to be processed, a subshell will be
214 started which you can use to resolve or investigate the problem. Exit the
215 subshell to continue the mr run.
219 If no number if specified, just operate on the repository for the current
220 directory, do not recurse into deeper repositories.
222 If a number is specified, will recurse into repositories at most that many
223 subdirectories deep. For example, with -n 2 it would recurse into ./src/foo,
224 but not ./src/packages/bar.
228 Run the specified number of jobs in parallel, or an unlimited number of jobs
229 with no number specified. This can greatly speed up operations such as updates.
230 It is not recommended for interactive operations.
232 Note that running more than 10 jobs at a time is likely to run afoul of
233 ssh connection limits. Running between 3 and 5 jobs at a time will yield
234 a good speedup in updates without loading the machine too much.
238 Trust all mrconfig files even if they are not listed in ~/.mrtrust.
243 =head1 "MRCONFIG FILES"
245 Here is an example .mrconfig file:
248 checkout = svn co svn://svn.example.com/src/trunk src
252 checkout = git clone git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux-2.6.git &&
254 git checkout -b mybranch origin/master
256 The .mrconfig file uses a variant of the INI file format. Lines starting with
257 "#" are comments. Values can be continued to the following line by
258 indenting the line with whitespace.
260 The "DEFAULT" section allows setting default values for the sections that
263 The "ALIAS" section allows adding aliases for actions. Each parameter
264 is an alias, and its value is the action to use.
266 All other sections add repositories. The section header specifies the
267 directory where the repository is located. This is relative to the directory
268 that contains the mrconfig file, but you can also choose to use absolute
269 paths. (Note that you can use environment variables in section names; they
270 will be passed through the shell for expansion. For example,
271 "[$HOSTNAME]", or "[${HOSTNAME}foo]")
273 Within a section, each parameter defines a shell command to run to handle a
274 given action. mr contains default handlers for "update", "status",
275 "commit", and other standard actions. Normally you only need to specify what
276 to do for "checkout".
278 Note that these shell commands are run in a "set -e" shell
279 environment, where any additional parameters you pass are available in
280 "$@". The "checkout" command is run in the parent of the repository
281 directory, since the repository isn't checked out yet. All other commands
282 are run inside the repository, though not necessarily at the top of it.
284 The "MR_REPO" environment variable is set to the path to the top of the
285 repository. (For the "register" action, "MR_REPO" is instead set to the
286 basename of the directory that should be created when checking the
289 The "MR_CONFIG" environment variable is set to the .mrconfig file
290 that defines the repo being acted on, or, if the repo is not yet in a config
291 file, the .mrconfig file that should be modified to register the repo.
293 A few parameters have special meanings:
299 If the "skip" parameter is set and its command returns true, then B<mr>
300 will skip acting on that repository. The command is passed the action
303 Here are two examples. The first skips the repo unless
304 mr is run by joey. The second uses the hours_since function
305 (included in mr's built-in library) to skip updating the repo unless it's
306 been at least 12 hours since the last update.
308 skip = test `whoami` != joey
309 skip = [ "$1" = update ] && ! hours_since "$1" 12
313 The "order" parameter can be used to override the default ordering of
314 repositories. The default order value is 10. Use smaller values to make
315 repositories be processed earlier, and larger values to make repositories
318 Note that if a repository is located in a subdirectory of another
319 repository, ordering it to be processed earlier is not recommended.
323 If the "chain" parameter is set and its command returns true, then B<mr>
324 will try to load a .mrconfig file from the root of the repository.
328 If the "include" parameter is set, its command is ran, and should output
329 additional mrconfig file content. The content is included as if it were
330 part of the including file.
332 Unlike all other parameters, this parameter does not need to be placed
337 The "lib" parameter can specify some shell code that will be run before each
338 command, this can be a useful way to define shell functions for other commands
343 When looking for a command to run for a given action, mr first looks for
344 a parameter with the same name as the action. If that is not found, it
345 looks for a parameter named "rcs_action" (substituting in the name of the
346 revision control system and the action). The name of the revision control
347 system is itself determined by running each defined "rcs_test" action,
350 Internally, mr has settings for "git_update", "svn_update", etc. To change
351 the action that is performed for a given revision control system, you can
352 override these rcs specific actions. To add a new revision control system,
353 you can just add rcs specific actions for it.
355 The ~/.mrlog file contains commands that mr has remembered to run later,
356 due to being offline. You can delete or edit this file to remove commands,
357 or even to add other commands for 'mr online' to run. If the file is
358 present, mr assumes it is in offline mode.
360 =head "UNTRUSTED MRCONFIG FILES"
362 Since mrconfig files can contain arbitrary shell commands, they can do
363 anything. This flexability is good, but it also allows a malicious mrconfig
364 file to delete your whole home directory. Such a file might be contained
365 inside a repository that your main ~/.mrconfig checks out and chains to. To
366 avoid worries about evil commands in a mrconfig file, mr
367 has the ability to read mrconfig files in untrusted mode. Such files are
368 limited to running only known safe commands (like "git clone") in a
369 carefully checked manner.
371 By default, mr trusts all mrconfig files. (This default will change in a
372 future release!) But if you have a ~/.mrtrust file, mr will only trust
373 mrconfig files that are listed within it. (One file per line.) All other
374 files will be treated as untrusted.
378 mr can be extended to support things such as unison and git-svn. Some
379 files providing such extensions are available in /usr/share/mr/. See
380 the documentation in the files for details about using them.
384 Copyright 2007-2009 Joey Hess <joey@kitenet.net>
386 Licensed under the GNU GPL version 2 or higher.
388 http://kitenet.net/~joey/code/mr/
395 use Cwd qw(getcwd abs_path);
397 # things that can happen when mr runs a command
406 my $config_overridden=0;
415 my $directory=getcwd();
416 $ENV{MR_CONFIG}="$ENV{HOME}/.mrconfig";
423 my (@ok, @failed, @skipped);
429 my ($action, $dir, $topdir, $subdir) = @_;
431 if (exists $rcs{$dir}) {
436 foreach my $rcs_test (
438 length $a <=> length $b
441 } grep { /_test$/ } keys %{$config{$topdir}{$subdir}}) {
442 my ($rcs)=$rcs_test=~/(.*)_test/;
443 $test="my_$rcs_test() {\n$config{$topdir}{$subdir}{$rcs_test}\n}\n".$test;
444 $test.="if my_$rcs_test; then echo $rcs; fi\n";
446 $test=$config{$topdir}{$subdir}{lib}."\n".$test
447 if exists $config{$topdir}{$subdir}{lib};
449 print "mr $action: running rcs test >>$test<<\n" if $verbose;
454 print STDERR "mr $action: found multiple possible repository types ($rcs) for $topdir$subdir\n";
458 return $rcs{$dir}=undef;
461 return $rcs{$dir}=$rcs;
466 my ($action, $dir, $topdir, $subdir, $is_checkout) = @_;
468 if (exists $config{$topdir}{$subdir}{$action}) {
469 return $config{$topdir}{$subdir}{$action};
476 my $rcs=rcs_test(@_);
479 exists $config{$topdir}{$subdir}{$rcs."_".$action}) {
480 return $config{$topdir}{$subdir}{$rcs."_".$action};
488 my ($action, $dir, $topdir, $subdir) = @_;
490 $ENV{MR_CONFIG}=$configfiles{$topdir};
491 my $lib=exists $config{$topdir}{$subdir}{lib} ?
492 $config{$topdir}{$subdir}{lib}."\n" : "";
493 my $is_checkout=($action eq 'checkout');
499 print "mr $action: $dir already exists, skipping checkout\n" if $verbose;
503 $dir=~s/^(.*)\/[^\/]+\/?$/$1/;
505 elsif ($action =~ /update/) {
507 return action("checkout", $dir, $topdir, $subdir);
511 my $skiptest=findcommand("skip", $dir, $topdir, $subdir, $is_checkout);
512 my $command=findcommand($action, $dir, $topdir, $subdir, $is_checkout);
514 if (defined $skiptest) {
515 my $test="set -e;".$lib.
516 "my_action(){ $skiptest\n }; my_action '$action'";
517 print "mr $action: running skip test >>$test<<\n" if $verbose;
518 my $ret=system($test);
520 if (($? & 127) == 2) {
521 print STDERR "mr $action: interrupted\n";
525 print STDERR "mr $action: skip test received signal ".($? & 127)."\n";
529 if ($ret >> 8 == 0) {
530 print "mr $action: $dir skipped per config file\n" if $verbose;
535 if ($is_checkout && ! -d $dir) {
536 print "mr $action: creating parent directory $dir\n" if $verbose;
537 system("mkdir", "-p", $dir);
540 if (! $no_chdir && ! chdir($dir)) {
541 print STDERR "mr $action: failed to chdir to $dir: $!\n";
544 elsif (! defined $command) {
545 my $rcs=rcs_test(@_);
546 if (! defined $rcs) {
547 print STDERR "mr $action: unknown repository type and no defined $action command for $topdir$subdir\n";
551 print STDERR "mr $action: no defined action for $rcs repository $topdir$subdir, skipping\n";
557 print "mr $action: $topdir$subdir\n" unless $quiet;
561 $s=~s/^\Q$topdir$subdir\E\/?//;
562 print "mr $action: $topdir$subdir (in subdir $s)\n" unless $quiet;
564 $command="set -e; ".$lib.
565 "my_action(){ $command\n }; my_action ".
566 join(" ", map { s/\//\/\//g; s/"/\"/g; '"'.$_.'"' } @ARGV);
567 print "mr $action: running >>$command<<\n" if $verbose;
568 my $ret=system($command);
570 if (($? & 127) == 2) {
571 print STDERR "mr $action: interrupted\n";
575 print STDERR "mr $action: received signal ".($? & 127)."\n";
578 print STDERR "mr $action: failed ($ret)\n" if $verbose;
579 if ($ret >> 8 != 0) {
580 print STDERR "mr $action: command failed\n";
581 if (-e "$ENV{HOME}/.mrlog" && $action ne 'remember') {
582 # recreate original command line to
583 # remember, and avoid recursing
585 @ARGV=('-n', $action, @orig);
586 action("remember", $dir, $topdir, $subdir);
591 print STDERR "mr $action: command died ($ret)\n";
596 if ($action eq 'checkout' && ! -d $dir) {
597 print STDERR "mr $action: $dir missing after checkout\n";;
606 # run actions on multiple repos, in parallel
616 while (@fhs or @repos) {
617 while ((!$jobs || $running < $jobs) && @repos) {
619 my $repo = shift @repos;
620 pipe(my $outfh, CHILD_STDOUT);
621 pipe(my $errfh, CHILD_STDERR);
623 unless ($pid = fork) {
624 die "mr $action: cannot fork: $!" unless defined $pid;
625 open(STDOUT, ">&CHILD_STDOUT") || die "mr $action cannot reopen stdout: $!";
626 open(STDERR, ">&CHILD_STDERR") || die "mr $action cannot reopen stderr: $!";
631 exit action($action, @$repo);
635 push @active, [$pid, $repo];
636 push @fhs, [$outfh, $errfh];
639 my ($rin, $rout) = ('','');
641 foreach my $fh (@fhs) {
642 next unless defined $fh;
643 vec($rin, fileno($fh->[0]), 1) = 1 if defined $fh->[0];
644 vec($rin, fileno($fh->[1]), 1) = 1 if defined $fh->[1];
646 $nfound = select($rout=$rin, undef, undef, 1);
647 foreach my $channel (0, 1) {
648 foreach my $i (0..$#fhs) {
649 next unless defined $fhs[$i];
650 my $fh = $fhs[$i][$channel];
651 next unless defined $fh;
652 if (vec($rout, fileno($fh), 1) == 1) {
654 if (sysread($fh, $r, 1024) == 0) {
656 $fhs[$i][$channel] = undef;
657 if (! defined $fhs[$i][0] &&
658 ! defined $fhs[$i][1]) {
659 waitpid($active[$i][0], 0);
660 print STDOUT $out[$i][0];
661 print STDERR $out[$i][1];
662 record($active[$i][1], $? >> 8);
664 splice(@active, $i, 1);
669 $out[$i][$channel] .= $r;
677 my $dir=shift()->[0];
684 elsif ($ret == FAILED) {
686 chdir($dir) unless $no_chdir;
687 print STDERR "mr: Starting interactive shell. Exit shell to continue.\n";
688 system((getpwuid($<))[8]);
693 elsif ($ret == SKIPPED) {
696 elsif ($ret == ABORT) {
700 die "unknown exit status $ret";
706 if (! @ok && ! @failed && ! @skipped) {
707 die "mr $action: no repositories found to work on\n";
709 print "mr $action: finished (".join("; ",
710 showstat($#ok+1, "ok", "ok"),
711 showstat($#failed+1, "failed", "failed"),
712 showstat($#skipped+1, "skipped", "skipped"),
713 ).")\n" unless $quiet;
716 print "mr $action: (skipped: ".join(" ", @skipped).")\n" unless $quiet;
719 print STDERR "mr $action: (failed: ".join(" ", @failed).")\n";
729 return "$count ".($count > 1 ? $plural : $singular);
734 # an ordered list of repos
737 foreach my $topdir (sort keys %config) {
738 foreach my $subdir (sort keys %{$config{$topdir}}) {
742 order => $config{$topdir}{$subdir}{order},
747 $a->{order} <=> $b->{order}
749 $a->{topdir} cmp $b->{topdir}
751 $a->{subdir} cmp $b->{subdir}
755 # figure out which repos to act on
758 foreach my $repo (repolist()) {
759 my $topdir=$repo->{topdir};
760 my $subdir=$repo->{subdir};
762 next if $subdir eq 'DEFAULT';
763 my $dir=($subdir =~/^\//) ? $subdir : $topdir.$subdir;
765 $dir.="/" unless $dir=~/\/$/;
766 $d.="/" unless $d=~/\/$/;
767 next if $dir ne $d && $dir !~ /^\Q$d\E/;
768 if (defined $max_depth) {
769 my @a=split('/', $dir);
770 my @b=split('/', $d);
771 do { } while (@a && @b && shift(@a) eq shift(@b));
772 next if @a > $max_depth || @b > $max_depth;
774 push @repos, [$dir, $topdir, $subdir];
777 # fallback to find a leaf repo
778 foreach my $repo (reverse repolist()) {
779 my $topdir=$repo->{topdir};
780 my $subdir=$repo->{subdir};
782 next if $subdir eq 'DEFAULT';
783 my $dir=($subdir =~/^\//) ? $subdir : $topdir.$subdir;
785 $dir.="/" unless $dir=~/\/$/;
786 $d.="/" unless $d=~/\/$/;
787 if ($d=~/^\Q$dir\E/) {
788 push @repos, [$dir, $topdir, $subdir];
810 sub is_trusted_config {
811 my $config=shift; # must be abs_pathed already
813 # We always trust ~/.mrconfig.
814 return 1 if $config eq abs_path("$ENV{HOME}/.mrconfig");
816 return 1 if $trust_all;
818 my $trustfile=$ENV{HOME}."/.mrtrust";
820 if (! -e $trustfile) {
821 print "mr: Assuming $config is trusted.\n";
822 print "mr: For better security, you are encouraged to create ~/.mrtrust\n";
823 print "mr: and list all trusted mrconfig files in it.\n";
828 $trusted{"$ENV{HOME}/.mrconfig"}=1;
829 open (TRUST, "<", $trustfile) || die "$trustfile: $!";
832 s/^~\//$ENV{HOME}\//;
833 $trusted{abs_path($_)}=1;
838 return $trusted{$config};
842 sub is_trusted_repo {
845 # Tightly limit what is allowed in a repo name.
846 # No ../, no absolute paths, and no unusual filenames
847 # that might try to escape to the shell.
848 return $repo =~ /^[-_.+\/A-Za-z0-9]+$/ &&
849 $repo !~ /\.\./ && $repo !~ /^\//;
852 sub is_trusted_checkout {
855 # To determine if the command is safe, compare it with the
856 # *_trusted_checkout config settings. Those settings are
857 # templates for allowed commands, so make sure that each word
858 # of the command matches the corresponding word of the template.
861 foreach my $word (split(' ', $command)) {
863 if ($word=~/^'(.*)'$/) {
866 elsif ($word=~/^"(.*)"$/) {
873 foreach my $key (grep { /_trusted_checkout$/ }
874 keys %{$config{''}{DEFAULT}}) {
875 my @twords=split(' ', $config{''}{DEFAULT}{$key});
876 next if @words > @twords;
880 for (my $c=0; $c < @twords && $match; $c++) {
881 if ($twords[$c] eq '$url') {
882 # Match all the typical characters found in
883 # urls, plus @ which svn can use. Note
884 # that the "url" might also be a local
887 defined $words[$c] &&
888 $words[$c] =~ /^[-_.+:@\/A-Za-z0-9]+$/
892 elsif ($twords[$c] eq '$repo') {
893 # If a repo is not specified, assume it
894 # will be the last path component of the
895 # url, or something derived from it, and
897 if (! defined $words[$c] && defined $url) {
898 ($words[$c])=$url=~/\/([^\/]+)\/?$/;
902 defined $words[$c] &&
903 is_trusted_repo($words[$c])
906 elsif (defined $words[$c] && $twords[$c] eq $words[$c]) {
928 if (ref $f eq 'GLOB') {
938 my $absf=abs_path($f);
939 if ($loaded{$absf}) {
944 $trusted=is_trusted_config($absf);
946 ($dir)=$f=~/^(.*\/)[^\/]+$/;
947 if (! defined $dir) {
950 $dir=abs_path($dir)."/";
952 if (! exists $configfiles{$dir}) {
953 $configfiles{$dir}=$f;
956 # copy in defaults from first parent
958 while ($parent=~s/^(.*\/)[^\/]+\/?$/$1/) {
959 if ($parent eq '/') {
962 if (exists $config{$parent} &&
963 exists $config{$parent}{DEFAULT}) {
964 $config{$dir}{DEFAULT}={ %{$config{$parent}{DEFAULT}} };
969 print "mr: loading config $f\n" if $verbose;
970 open($in, "<", $f) || die "mr: open $f: $!\n";
981 next if /^\s*\#/ || /^\s*$/;
982 if (/^\[([^\]]*)\]\s*$/) {
986 if (! is_trusted_repo($section) ||
987 $section eq 'ALIAS' ||
988 $section eq 'DEFAULT') {
989 die "mr: illegal section \"[$section]\" in untrusted $f line $line\n";
992 $section=expandenv($section) if $trusted;
994 elsif (/^(\w+)\s*=\s*(.*)/) {
999 while (@lines && $lines[0]=~/^\s(.+)/) {
1007 # Untrusted files can only contain checkout
1009 if ($parameter ne 'checkout') {
1010 die "mr: illegal setting \"$parameter=$value\" in untrusted $f line $line\n";
1012 if (! is_trusted_checkout($value)) {
1013 die "mr: illegal checkout command \"$value\" in untrusted $f line $line\n";
1017 if ($parameter eq "include") {
1018 print "mr: including output of \"$value\"\n" if $verbose;
1019 unshift @lines, `$value`;
1021 print STDERR "mr: include command exited nonzero ($?)\n";
1026 if (! defined $section) {
1027 die "$f line $.: parameter ($parameter) not in section\n";
1029 if ($section ne 'ALIAS' &&
1030 ! exists $config{$dir}{$section} &&
1031 exists $config{$dir}{DEFAULT}) {
1033 $config{$dir}{$section}={ %{$config{$dir}{DEFAULT}} };
1035 if ($section eq 'ALIAS') {
1036 $alias{$parameter}=$value;
1038 elsif ($parameter eq 'lib') {
1039 $config{$dir}{$section}{lib}.=$value."\n";
1042 $config{$dir}{$section}{$parameter}=$value;
1043 if ($parameter =~ /.*_(.*)/) {
1044 $knownactions{$1}=1;
1047 $knownactions{$parameter}=1;
1049 if ($parameter eq 'chain' &&
1050 length $dir && $section ne "DEFAULT" &&
1051 -e $dir.$section."/.mrconfig") {
1052 my $ret=system($value);
1054 if (($? & 127) == 2) {
1055 print STDERR "mr: chain test interrupted\n";
1059 print STDERR "mr: chain test received signal ".($? & 127)."\n";
1063 push @toload, $dir.$section."/.mrconfig";
1069 die "$f line $line: parse error\n";
1080 # the section to modify or add
1081 my $targetsection=shift;
1082 # fields to change in the section
1083 # To remove a field, set its value to "".
1084 my %changefields=@_;
1090 open(my $in, "<", $f) || die "mr: open $f: $!\n";
1095 my $formatfield=sub {
1097 my @value=split(/\n/, shift);
1099 return "$field = ".shift(@value)."\n".
1100 join("", map { "\t$_\n" } @value);
1104 while ($out[$#out] =~ /^\s*$/) {
1105 unshift @blanks, pop @out;
1107 foreach my $field (sort keys %changefields) {
1108 if (length $changefields{$field}) {
1109 push @out, "$field = $changefields{$field}\n";
1110 delete $changefields{$field};
1120 if (/^\s*\#/ || /^\s*$/) {
1123 elsif (/^\[([^\]]*)\]\s*$/) {
1124 if (defined $section &&
1125 $section eq $targetsection) {
1129 $section=expandenv($1);
1133 elsif (/^(\w+)\s*=\s(.*)/) {
1138 while (@lines && $lines[0]=~/^\s(.+)/) {
1144 if ($section eq $targetsection) {
1145 if (exists $changefields{$parameter}) {
1146 if (length $changefields{$parameter}) {
1147 $value=$changefields{$parameter};
1149 delete $changefields{$parameter};
1153 push @out, $formatfield->($parameter, $value);
1157 if (defined $section &&
1158 $section eq $targetsection) {
1161 elsif (%changefields) {
1162 push @out, "\n[$targetsection]\n";
1163 foreach my $field (sort keys %changefields) {
1164 if (length $changefields{$field}) {
1165 push @out, $formatfield->($field, $changefields{$field});
1170 open(my $out, ">", $f) || die "mr: write $f: $!\n";
1178 # actions that do not operate on all repos
1179 if ($action eq 'help') {
1182 elsif ($action eq 'config') {
1185 elsif ($action eq 'register') {
1188 elsif ($action eq 'bootstrap') {
1191 elsif ($action eq 'remember' ||
1192 $action eq 'offline' ||
1193 $action eq 'online') {
1194 my @repos=selectrepos;
1195 action($action, @{$repos[0]}) if @repos;
1199 if (!$jobs || $jobs > 1) {
1200 mrs($action, selectrepos());
1203 foreach my $repo (selectrepos()) {
1204 record($repo, action($action, @$repo));
1210 exec($config{''}{DEFAULT}{help}) || die "exec: $!";
1215 die "mr config: not enough parameters\n";
1218 if ($section=~/^\//) {
1219 # try to convert to a path relative to the config file
1220 my ($dir)=$ENV{MR_CONFIG}=~/^(.*\/)[^\/]+$/;
1221 $dir=abs_path($dir);
1222 $dir.="/" unless $dir=~/\/$/;
1223 if ($section=~/^\Q$dir\E(.*)/) {
1229 if (/^([^=]+)=(.*)$/) {
1230 $changefields{$1}=$2;
1234 foreach my $topdir (sort keys %config) {
1235 if (exists $config{$topdir}{$section} &&
1236 exists $config{$topdir}{$section}{$_}) {
1237 print $config{$topdir}{$section}{$_}."\n";
1239 last if $section eq 'DEFAULT';
1243 die "mr config: $section $_ not set\n";
1247 modifyconfig($ENV{MR_CONFIG}, $section, %changefields) if %changefields;
1252 if ($config_overridden) {
1253 # Find the directory that the specified config file is
1255 ($directory)=abs_path($ENV{MR_CONFIG})=~/^(.*\/)[^\/]+$/;
1258 # Find the closest known mrconfig file to the current
1260 $directory.="/" unless $directory=~/\/$/;
1262 foreach my $topdir (reverse sort keys %config) {
1263 next unless length $topdir;
1264 if ($directory=~/^\Q$topdir\E/) {
1265 $ENV{MR_CONFIG}=$configfiles{$topdir};
1271 if (! $foundconfig) {
1272 $directory=""; # no config file, use builtin
1276 my $subdir=shift @ARGV;
1277 if (! chdir($subdir)) {
1278 print STDERR "mr register: failed to chdir to $subdir: $!\n";
1282 $ENV{MR_REPO}=getcwd();
1283 my $command=findcommand("register", $ENV{MR_REPO}, $directory, 'DEFAULT', 0);
1284 if (! defined $command) {
1285 die "mr register: unknown repository type\n";
1288 $ENV{MR_REPO}=~s/.*\/(.*)/$1/;
1289 $command="set -e; ".$config{$directory}{DEFAULT}{lib}."\n".
1290 "my_action(){ $command\n }; my_action ".
1291 join(" ", map { s/\//\/\//g; s/"/\"/g; '"'.$_.'"' } @ARGV);
1292 print "mr register: running >>$command<<\n" if $verbose;
1293 exec($command) || die "exec: $!";
1297 my $url=shift @ARGV;
1299 if (! defined $url || ! length $url) {
1300 die "mr: bootstrap requires url\n";
1303 if (-e ".mrconfig") {
1304 die "mr: .mrconfig file already exists, not overwriting with $url\n";
1307 if (system("curl", "-s", $url, "-o", ".mrconfig") != 0) {
1308 die "mr: download of $url failed\n";
1311 exec("mr $ENV{MR_SWITCHES} -c .mrconfig checkout");
1312 die "failed to run mr checkout";
1315 # alias expansion and command stemming
1318 if (exists $alias{$action}) {
1319 $action=$alias{$action};
1321 if (! exists $knownactions{$action}) {
1322 my @matches = grep { /^\Q$action\E/ }
1323 keys %knownactions, keys %alias;
1324 if (@matches == 1) {
1325 $action=$matches[0];
1327 elsif (@matches == 0) {
1328 die "mr: unknown action \"$action\" (known actions: ".
1329 join(", ", sort keys %knownactions).")\n";
1332 die "mr: ambiguous action \"$action\" (matches: ".
1333 join(", ", @matches).")\n";
1339 sub find_nearest_mrconfig {
1341 while (length $dir) {
1342 if (-e "$dir/.mrconfig") {
1343 return "$dir/.mrconfig";
1345 $dir=~s/\/[^\/]*$//;
1347 die "no .mrconfig found in path\n";
1352 Getopt::Long::Configure("bundling", "no_permute");
1353 my $result=GetOptions(
1354 "d|directory=s" => sub { $directory=abs_path($_[1]) },
1355 "c|config=s" => sub { $ENV{MR_CONFIG}=$_[1]; $config_overridden=1 },
1356 "p|path" => sub { $ENV{MR_CONFIG}=find_nearest_mrconfig(); $config_overridden=1 },
1357 "v|verbose" => \$verbose,
1358 "q|quiet" => \$quiet,
1359 "s|stats" => \$stats,
1360 "i|interactive" => \$interactive,
1361 "n|no-recurse:i" => \$max_depth,
1362 "j|jobs:i" => \$jobs,
1363 "t|trust-all" => \$trust_all,
1365 if (! $result || @ARGV < 1) {
1366 die("Usage: mr [options] action [params ...]\n".
1367 "(Use mr help for man page.)\n");
1370 $ENV{MR_SWITCHES}="";
1371 foreach my $option (@saved) {
1372 last if $option eq $ARGV[0];
1373 $ENV{MR_SWITCHES}.="$option ";
1379 print STDERR "mr: interrupted\n";
1383 # This can happen if it's run in a directory that was removed
1384 # or other strangeness.
1385 if (! defined $directory) {
1386 die("mr: failed to determine working directory\n");
1388 # Make sure MR_CONFIG is an absolute path, but don't use abs_path since
1389 # the config file might be a symlink to elsewhere, and the directory it's
1390 # in is significant.
1391 if ($ENV{MR_CONFIG} !~ /^\//) {
1392 $ENV{MR_CONFIG}=getcwd()."/".$ENV{MR_CONFIG};
1394 # Try to set MR_PATH to the path to the program.
1396 use FindBin qw($Bin $Script);
1397 $ENV{MR_PATH}=$Bin."/".$Script;
1406 loadconfig($ENV{MR_CONFIG});
1407 #use Data::Dumper; print Dumper(\%config);
1409 my $action=expandaction(shift @ARGV);
1416 elsif (! @ok && @skipped) {
1424 # Finally, some useful actions that mr knows about by default.
1425 # These can be overridden in ~/.mrconfig.
1440 echo "mr (warning): $@" >&2
1446 if [ -z "$1" ] || [ -z "$2" ]; then
1447 error "mr: usage: hours_since action num"
1449 for dir in .git .svn .bzr CVS .hg _darcs; do
1450 if [ -e "$MR_REPO/$dir" ]; then
1451 flagfile="$MR_REPO/$dir/.mr_last$1"
1455 if [ -z "$flagfile" ]; then
1456 error "cannot determine flag filename"
1458 delta=`perl -wle 'print -f shift() ? int((-M _) * 24) : 9999' "$flagfile"`
1459 if [ "$delta" -lt "$2" ]; then
1467 svn_test = test -d "$MR_REPO"/.svn
1468 git_test = test -d "$MR_REPO"/.git
1469 bzr_test = test -d "$MR_REPO"/.bzr
1470 cvs_test = test -d "$MR_REPO"/CVS
1471 hg_test = test -d "$MR_REPO"/.hg
1472 darcs_test = test -d "$MR_REPO"/_darcs
1474 test -d "$MR_REPO"/refs/heads && test -d "$MR_REPO"/refs/tags &&
1475 test -d "$MR_REPO"/objects && test -f "$MR_REPO"/config &&
1476 test "`GIT_CONFIG="$MR_REPO"/config git config --get core.bare`" = true
1478 svn_update = svn update "$@"
1479 git_update = git pull "$@"
1480 bzr_update = bzr merge --pull "$@"
1481 cvs_update = cvs update "$@"
1482 hg_update = hg pull "$@" && hg update "$@"
1483 darcs_update = darcs pull -a "$@"
1485 svn_status = svn status "$@"
1486 git_status = git status "$@" || true
1487 bzr_status = bzr status "$@"
1488 cvs_status = cvs status "$@"
1489 hg_status = hg status "$@"
1490 darcs_status = darcs whatsnew -ls "$@" || true
1492 svn_commit = svn commit "$@"
1493 git_commit = git commit -a "$@" && git push --all
1494 bzr_commit = bzr commit "$@" && bzr push
1495 cvs_commit = cvs commit "$@"
1496 hg_commit = hg commit -m "$@" && hg push
1497 darcs_commit = darcs record -a -m "$@" && darcs push -a
1499 git_record = git commit -a "$@"
1500 bzr_record = bzr commit "$@"
1501 hg_record = hg commit -m "$@"
1502 darcs_record = darcs record -a -m "$@"
1505 git_push = git push "$@"
1506 bzr_push = bzr push "$@"
1508 hg_push = hg push "$@"
1509 darcs_push = darcs push -a "$@"
1511 svn_diff = svn diff "$@"
1512 git_diff = git diff "$@"
1513 bzr_diff = bzr diff "$@"
1514 cvs_diff = cvs diff "$@"
1515 hg_diff = hg diff "$@"
1516 darcs_diff = darcs diff -u "$@"
1518 svn_log = svn log "$@"
1519 git_log = git log "$@"
1520 bzr_log = bzr log "$@"
1521 cvs_log = cvs log "$@"
1522 hg_log = hg log "$@"
1523 darcs_log = darcs changes "$@"
1524 git_bare_log = git log "$@"
1527 url=`LC_ALL=C svn info . | grep -i '^URL:' | cut -d ' ' -f 2`
1528 if [ -z "$url" ]; then
1529 error "cannot determine svn url"
1531 echo "Registering svn url: $url in $MR_CONFIG"
1532 mr -c "$MR_CONFIG" config "`pwd`" checkout="svn co '$url' '$MR_REPO'"
1534 url="`LC_ALL=C git config --get remote.origin.url`" || true
1535 if [ -z "$url" ]; then
1536 error "cannot determine git url"
1538 echo "Registering git url: $url in $MR_CONFIG"
1539 mr -c "$MR_CONFIG" config "`pwd`" checkout="git clone '$url' '$MR_REPO'"
1541 url="`LC_ALL=C bzr info . | egrep -i 'checkout of branch|parent branch' | awk '{print $NF}'`"
1542 if [ -z "$url" ]; then
1543 error "cannot determine bzr url"
1545 echo "Registering bzr url: $url in $MR_CONFIG"
1546 mr -c "$MR_CONFIG" config "`pwd`" checkout="bzr clone '$url' '$MR_REPO'"
1548 repo=`cat CVS/Repository`
1550 if [ -z "$root" ]; then
1551 error "cannot determine cvs root"
1553 echo "Registering cvs repository $repo at root $root"
1554 mr -c "$MR_CONFIG" config "`pwd`" checkout="cvs -d '$root' co -d '$MR_REPO' '$repo'"
1556 url=`hg showconfig paths.default`
1557 echo "Registering mercurial repo url: $url in $MR_CONFIG"
1558 mr -c "$MR_CONFIG" config "`pwd`" checkout="hg clone '$url' '$MR_REPO'"
1560 url=`cat _darcs/prefs/defaultrepo`
1561 echo "Registering darcs repository $url in $MR_CONFIG"
1562 mr -c "$MR_CONFIG" config "`pwd`" checkout="darcs get '$url' '$MR_REPO'"
1564 url="`LC_ALL=C GIT_CONFIG=config git config --get remote.origin.url`" || true
1565 if [ -z "$url" ]; then
1566 error "cannot determine git url"
1568 echo "Registering git url: $url in $MR_CONFIG"
1569 mr -c "$MR_CONFIG" config "`pwd`" checkout="git clone --bare '$url' '$MR_REPO'"
1571 svn_trusted_checkout = svn co $url $repo
1572 svn_alt_trusted_checkout = svn checkout $url $repo
1573 git_trusted_checkout = git clone $url $repo
1574 bzr_trusted_checkout = bzr clone $url $repo
1576 hg_trusted_checkout = hg clone $url $repo
1577 darcs_trusted_checkout = darcs get $url $repo
1578 git_bare_trusted_checkout = git clone --bare $url $repo
1581 if [ ! -e "$MR_PATH" ]; then
1582 error "cannot find program path"
1584 tmp=$(mktemp -t mr.XXXXXXXXXX) || error "mktemp failed"
1585 trap "rm -f $tmp" exit
1586 pod2man -c mr "$MR_PATH" > "$tmp" || error "pod2man failed"
1587 man -l "$tmp" || error "man failed"
1593 if [ -s ~/.mrlog ]; then
1594 info "running offline commands"
1595 mv -f ~/.mrlog ~/.mrlog.old
1596 if ! sh -e ~/.mrlog.old; then
1597 error "offline command failed; left in ~/.mrlog.old"
1601 info "no offline commands to run"
1606 info "offline mode enabled"
1608 info "remembering command: 'mr $@'"
1609 command="mr -d '$(pwd)' $MR_SWITCHES"
1611 command="$command '$w'"
1613 if [ ! -e ~/.mrlog ] || ! grep -q -F "$command" ~/.mrlog; then
1614 echo "$command" >> ~/.mrlog
1617 ed = echo "A horse is a horse, of course, of course.."
1618 T = echo "I pity the fool."
1619 right = echo "Not found."
1621 # vim:sw=8:sts=0:ts=8:noet