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 =item --directory directory
188 Specifies the topmost directory that B<mr> should work in. The default is
189 the current working directory.
193 =item --config mrconfig
195 Use the specified mrconfig file. The default is B<~/.mrconfig>
201 Search in the current directory, and its parent directories and use
202 the first B<.mrconfig> found, instead of the default B<~/.mrconfig>.
220 Expand the statistics line displayed at the end to include information
221 about exactly which repositories failed and were skipped, if any.
227 Interactive mode. If a repository fails to be processed, a subshell will be
228 started which you can use to resolve or investigate the problem. Exit the
229 subshell to continue the mr run.
233 =item --no-recurse [number]
235 If no number if specified, just operate on the repository for the current
236 directory, do not recurse into deeper repositories.
238 If a number is specified, will recurse into repositories at most that many
239 subdirectories deep. For example, with -n 2 it would recurse into ./src/foo,
240 but not ./src/packages/bar.
244 =item --jobs [number]
246 Run the specified number of jobs in parallel, or an unlimited number of jobs
247 with no number specified. This can greatly speed up operations such as updates.
248 It is not recommended for interactive operations.
250 Note that running more than 10 jobs at a time is likely to run afoul of
251 ssh connection limits. Running between 3 and 5 jobs at a time will yield
252 a good speedup in updates without loading the machine too much.
258 Trust all mrconfig files even if they are not listed in ~/.mrtrust.
263 =head1 "MRCONFIG FILES"
265 Here is an example .mrconfig file:
268 checkout = svn co svn://svn.example.com/src/trunk src
272 checkout = git clone git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux-2.6.git &&
274 git checkout -b mybranch origin/master
276 The .mrconfig file uses a variant of the INI file format. Lines starting with
277 "#" are comments. Values can be continued to the following line by
278 indenting the line with whitespace.
280 The "DEFAULT" section allows setting default values for the sections that
283 The "ALIAS" section allows adding aliases for actions. Each parameter
284 is an alias, and its value is the action to use.
286 All other sections add repositories. The section header specifies the
287 directory where the repository is located. This is relative to the directory
288 that contains the mrconfig file, but you can also choose to use absolute
289 paths. (Note that you can use environment variables in section names; they
290 will be passed through the shell for expansion. For example,
291 "[$HOSTNAME]", or "[${HOSTNAME}foo]")
293 Within a section, each parameter defines a shell command to run to handle a
294 given action. mr contains default handlers for "update", "status",
295 "commit", and other standard actions. Normally you only need to specify what
296 to do for "checkout".
298 Note that these shell commands are run in a "set -e" shell
299 environment, where any additional parameters you pass are available in
300 "$@". The "checkout" command is run in the parent of the repository
301 directory, since the repository isn't checked out yet. All other commands
302 are run inside the repository, though not necessarily at the top of it.
304 The "MR_REPO" environment variable is set to the path to the top of the
305 repository. (For the "register" action, "MR_REPO" is instead set to the
306 basename of the directory that should be created when checking the
309 The "MR_CONFIG" environment variable is set to the .mrconfig file
310 that defines the repo being acted on, or, if the repo is not yet in a config
311 file, the .mrconfig file that should be modified to register the repo.
313 A few parameters have special meanings:
319 If the "skip" parameter is set and its command returns true, then B<mr>
320 will skip acting on that repository. The command is passed the action
323 Here are two examples. The first skips the repo unless
324 mr is run by joey. The second uses the hours_since function
325 (included in mr's built-in library) to skip updating the repo unless it's
326 been at least 12 hours since the last update.
328 skip = test `whoami` != joey
329 skip = [ "$1" = update ] && ! hours_since "$1" 12
333 The "order" parameter can be used to override the default ordering of
334 repositories. The default order value is 10. Use smaller values to make
335 repositories be processed earlier, and larger values to make repositories
338 Note that if a repository is located in a subdirectory of another
339 repository, ordering it to be processed earlier is not recommended.
343 If the "chain" parameter is set and its command returns true, then B<mr>
344 will try to load a .mrconfig file from the root of the repository.
348 If the "include" parameter is set, its command is ran, and should output
349 additional mrconfig file content. The content is included as if it were
350 part of the including file.
352 Unlike all other parameters, this parameter does not need to be placed
357 The "lib" parameter can specify some shell code that will be run before each
358 command, this can be a useful way to define shell functions for other commands
363 When looking for a command to run for a given action, mr first looks for
364 a parameter with the same name as the action. If that is not found, it
365 looks for a parameter named "rcs_action" (substituting in the name of the
366 revision control system and the action). The name of the revision control
367 system is itself determined by running each defined "rcs_test" action,
370 Internally, mr has settings for "git_update", "svn_update", etc. To change
371 the action that is performed for a given revision control system, you can
372 override these rcs specific actions. To add a new revision control system,
373 you can just add rcs specific actions for it.
375 The ~/.mrlog file contains commands that mr has remembered to run later,
376 due to being offline. You can delete or edit this file to remove commands,
377 or even to add other commands for 'mr online' to run. If the file is
378 present, mr assumes it is in offline mode.
380 =head "UNTRUSTED MRCONFIG FILES"
382 Since mrconfig files can contain arbitrary shell commands, they can do
383 anything. This flexability is good, but it also allows a malicious mrconfig
384 file to delete your whole home directory. Such a file might be contained
385 inside a repository that your main ~/.mrconfig checks out and chains to. To
386 avoid worries about evil commands in a mrconfig file, mr
387 has the ability to read mrconfig files in untrusted mode. Such files are
388 limited to running only known safe commands (like "git clone") in a
389 carefully checked manner.
391 By default, mr trusts all mrconfig files. (This default will change in a
392 future release!) But if you have a ~/.mrtrust file, mr will only trust
393 mrconfig files that are listed within it. (One file per line.) All other
394 files will be treated as untrusted.
398 mr can be extended to support things such as unison and git-svn. Some
399 files providing such extensions are available in /usr/share/mr/. See
400 the documentation in the files for details about using them.
404 Copyright 2007-2009 Joey Hess <joey@kitenet.net>
406 Licensed under the GNU GPL version 2 or higher.
408 http://kitenet.net/~joey/code/mr/
415 use Cwd qw(getcwd abs_path);
417 # things that can happen when mr runs a command
426 my $config_overridden=0;
435 my $directory=getcwd();
436 $ENV{MR_CONFIG}="$ENV{HOME}/.mrconfig";
443 my (@ok, @failed, @skipped);
449 my ($action, $dir, $topdir, $subdir) = @_;
451 if (exists $rcs{$dir}) {
456 foreach my $rcs_test (
458 length $a <=> length $b
461 } grep { /_test$/ } keys %{$config{$topdir}{$subdir}}) {
462 my ($rcs)=$rcs_test=~/(.*)_test/;
463 $test="my_$rcs_test() {\n$config{$topdir}{$subdir}{$rcs_test}\n}\n".$test;
464 $test.="if my_$rcs_test; then echo $rcs; fi\n";
466 $test=$config{$topdir}{$subdir}{lib}."\n".$test
467 if exists $config{$topdir}{$subdir}{lib};
469 print "mr $action: running rcs test >>$test<<\n" if $verbose;
474 print STDERR "mr $action: found multiple possible repository types ($rcs) for $topdir$subdir\n";
478 return $rcs{$dir}=undef;
481 return $rcs{$dir}=$rcs;
486 my ($action, $dir, $topdir, $subdir, $is_checkout) = @_;
488 if (exists $config{$topdir}{$subdir}{$action}) {
489 return $config{$topdir}{$subdir}{$action};
496 my $rcs=rcs_test(@_);
499 exists $config{$topdir}{$subdir}{$rcs."_".$action}) {
500 return $config{$topdir}{$subdir}{$rcs."_".$action};
508 my ($action, $dir, $topdir, $subdir) = @_;
510 $ENV{MR_CONFIG}=$configfiles{$topdir};
511 my $lib=exists $config{$topdir}{$subdir}{lib} ?
512 $config{$topdir}{$subdir}{lib}."\n" : "";
513 my $is_checkout=($action eq 'checkout');
519 print "mr $action: $dir already exists, skipping checkout\n" if $verbose;
523 $dir=~s/^(.*)\/[^\/]+\/?$/$1/;
525 elsif ($action =~ /update/) {
527 return action("checkout", $dir, $topdir, $subdir);
531 my $skiptest=findcommand("skip", $dir, $topdir, $subdir, $is_checkout);
532 my $command=findcommand($action, $dir, $topdir, $subdir, $is_checkout);
534 if (defined $skiptest) {
535 my $test="set -e;".$lib.
536 "my_action(){ $skiptest\n }; my_action '$action'";
537 print "mr $action: running skip test >>$test<<\n" if $verbose;
538 my $ret=system($test);
540 if (($? & 127) == 2) {
541 print STDERR "mr $action: interrupted\n";
545 print STDERR "mr $action: skip test received signal ".($? & 127)."\n";
549 if ($ret >> 8 == 0) {
550 print "mr $action: $dir skipped per config file\n" if $verbose;
555 if ($is_checkout && ! -d $dir) {
556 print "mr $action: creating parent directory $dir\n" if $verbose;
557 system("mkdir", "-p", $dir);
560 if (! $no_chdir && ! chdir($dir)) {
561 print STDERR "mr $action: failed to chdir to $dir: $!\n";
564 elsif (! defined $command) {
565 my $rcs=rcs_test(@_);
566 if (! defined $rcs) {
567 print STDERR "mr $action: unknown repository type and no defined $action command for $topdir$subdir\n";
571 print STDERR "mr $action: no defined action for $rcs repository $topdir$subdir, skipping\n";
577 print "mr $action: $topdir$subdir\n" unless $quiet;
581 $s=~s/^\Q$topdir$subdir\E\/?//;
582 print "mr $action: $topdir$subdir (in subdir $s)\n" unless $quiet;
584 $command="set -e; ".$lib.
585 "my_action(){ $command\n }; my_action ".
586 join(" ", map { s/\//\/\//g; s/"/\"/g; '"'.$_.'"' } @ARGV);
587 print "mr $action: running >>$command<<\n" if $verbose;
588 my $ret=system($command);
590 if (($? & 127) == 2) {
591 print STDERR "mr $action: interrupted\n";
595 print STDERR "mr $action: received signal ".($? & 127)."\n";
598 print STDERR "mr $action: failed ($ret)\n" if $verbose;
599 if ($ret >> 8 != 0) {
600 print STDERR "mr $action: command failed\n";
601 if (-e "$ENV{HOME}/.mrlog" && $action ne 'remember') {
602 # recreate original command line to
603 # remember, and avoid recursing
605 @ARGV=('-n', $action, @orig);
606 action("remember", $dir, $topdir, $subdir);
611 print STDERR "mr $action: command died ($ret)\n";
616 if ($action eq 'checkout' && ! -d $dir) {
617 print STDERR "mr $action: $dir missing after checkout\n";;
626 # run actions on multiple repos, in parallel
636 while (@fhs or @repos) {
637 while ((!$jobs || $running < $jobs) && @repos) {
639 my $repo = shift @repos;
640 pipe(my $outfh, CHILD_STDOUT);
641 pipe(my $errfh, CHILD_STDERR);
643 unless ($pid = fork) {
644 die "mr $action: cannot fork: $!" unless defined $pid;
645 open(STDOUT, ">&CHILD_STDOUT") || die "mr $action cannot reopen stdout: $!";
646 open(STDERR, ">&CHILD_STDERR") || die "mr $action cannot reopen stderr: $!";
651 exit action($action, @$repo);
655 push @active, [$pid, $repo];
656 push @fhs, [$outfh, $errfh];
659 my ($rin, $rout) = ('','');
661 foreach my $fh (@fhs) {
662 next unless defined $fh;
663 vec($rin, fileno($fh->[0]), 1) = 1 if defined $fh->[0];
664 vec($rin, fileno($fh->[1]), 1) = 1 if defined $fh->[1];
666 $nfound = select($rout=$rin, undef, undef, 1);
667 foreach my $channel (0, 1) {
668 foreach my $i (0..$#fhs) {
669 next unless defined $fhs[$i];
670 my $fh = $fhs[$i][$channel];
671 next unless defined $fh;
672 if (vec($rout, fileno($fh), 1) == 1) {
674 if (sysread($fh, $r, 1024) == 0) {
676 $fhs[$i][$channel] = undef;
677 if (! defined $fhs[$i][0] &&
678 ! defined $fhs[$i][1]) {
679 waitpid($active[$i][0], 0);
680 print STDOUT $out[$i][0];
681 print STDERR $out[$i][1];
682 record($active[$i][1], $? >> 8);
684 splice(@active, $i, 1);
689 $out[$i][$channel] .= $r;
697 my $dir=shift()->[0];
704 elsif ($ret == FAILED) {
706 chdir($dir) unless $no_chdir;
707 print STDERR "mr: Starting interactive shell. Exit shell to continue.\n";
708 system((getpwuid($<))[8]);
713 elsif ($ret == SKIPPED) {
716 elsif ($ret == ABORT) {
720 die "unknown exit status $ret";
726 if (! @ok && ! @failed && ! @skipped) {
727 die "mr $action: no repositories found to work on\n";
729 print "mr $action: finished (".join("; ",
730 showstat($#ok+1, "ok", "ok"),
731 showstat($#failed+1, "failed", "failed"),
732 showstat($#skipped+1, "skipped", "skipped"),
733 ).")\n" unless $quiet;
736 print "mr $action: (skipped: ".join(" ", @skipped).")\n" unless $quiet;
739 print STDERR "mr $action: (failed: ".join(" ", @failed).")\n";
749 return "$count ".($count > 1 ? $plural : $singular);
754 # an ordered list of repos
757 foreach my $topdir (sort keys %config) {
758 foreach my $subdir (sort keys %{$config{$topdir}}) {
762 order => $config{$topdir}{$subdir}{order},
767 $a->{order} <=> $b->{order}
769 $a->{topdir} cmp $b->{topdir}
771 $a->{subdir} cmp $b->{subdir}
775 # figure out which repos to act on
778 foreach my $repo (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 next if $dir ne $d && $dir !~ /^\Q$d\E/;
788 if (defined $max_depth) {
789 my @a=split('/', $dir);
790 my @b=split('/', $d);
791 do { } while (@a && @b && shift(@a) eq shift(@b));
792 next if @a > $max_depth || @b > $max_depth;
794 push @repos, [$dir, $topdir, $subdir];
797 # fallback to find a leaf repo
798 foreach my $repo (reverse repolist()) {
799 my $topdir=$repo->{topdir};
800 my $subdir=$repo->{subdir};
802 next if $subdir eq 'DEFAULT';
803 my $dir=($subdir =~/^\//) ? $subdir : $topdir.$subdir;
805 $dir.="/" unless $dir=~/\/$/;
806 $d.="/" unless $d=~/\/$/;
807 if ($d=~/^\Q$dir\E/) {
808 push @repos, [$dir, $topdir, $subdir];
830 sub is_trusted_config {
831 my $config=shift; # must be abs_pathed already
833 # We always trust ~/.mrconfig.
834 return 1 if $config eq abs_path("$ENV{HOME}/.mrconfig");
836 return 1 if $trust_all;
838 my $trustfile=$ENV{HOME}."/.mrtrust";
840 if (! -e $trustfile) {
841 print "mr: Assuming $config is trusted.\n";
842 print "mr: For better security, you are encouraged to create ~/.mrtrust\n";
843 print "mr: and list all trusted mrconfig files in it.\n";
848 $trusted{"$ENV{HOME}/.mrconfig"}=1;
849 open (TRUST, "<", $trustfile) || die "$trustfile: $!";
852 s/^~\//$ENV{HOME}\//;
853 $trusted{abs_path($_)}=1;
858 return $trusted{$config};
862 sub is_trusted_repo {
865 # Tightly limit what is allowed in a repo name.
866 # No ../, no absolute paths, and no unusual filenames
867 # that might try to escape to the shell.
868 return $repo =~ /^[-_.+\/A-Za-z0-9]+$/ &&
869 $repo !~ /\.\./ && $repo !~ /^\//;
872 sub is_trusted_checkout {
875 # To determine if the command is safe, compare it with the
876 # *_trusted_checkout config settings. Those settings are
877 # templates for allowed commands, so make sure that each word
878 # of the command matches the corresponding word of the template.
881 foreach my $word (split(' ', $command)) {
883 if ($word=~/^'(.*)'$/) {
886 elsif ($word=~/^"(.*)"$/) {
893 foreach my $key (grep { /_trusted_checkout$/ }
894 keys %{$config{''}{DEFAULT}}) {
895 my @twords=split(' ', $config{''}{DEFAULT}{$key});
896 next if @words > @twords;
900 for (my $c=0; $c < @twords && $match; $c++) {
901 if ($twords[$c] eq '$url') {
902 # Match all the typical characters found in
903 # urls, plus @ which svn can use. Note
904 # that the "url" might also be a local
907 defined $words[$c] &&
908 $words[$c] =~ /^[-_.+:@\/A-Za-z0-9]+$/
912 elsif ($twords[$c] eq '$repo') {
913 # If a repo is not specified, assume it
914 # will be the last path component of the
915 # url, or something derived from it, and
917 if (! defined $words[$c] && defined $url) {
918 ($words[$c])=$url=~/\/([^\/]+)\/?$/;
922 defined $words[$c] &&
923 is_trusted_repo($words[$c])
926 elsif (defined $words[$c] && $twords[$c] eq $words[$c]) {
948 if (ref $f eq 'GLOB') {
958 my $absf=abs_path($f);
959 if ($loaded{$absf}) {
964 $trusted=is_trusted_config($absf);
966 ($dir)=$f=~/^(.*\/)[^\/]+$/;
967 if (! defined $dir) {
970 $dir=abs_path($dir)."/";
972 if (! exists $configfiles{$dir}) {
973 $configfiles{$dir}=$f;
976 # copy in defaults from first parent
978 while ($parent=~s/^(.*\/)[^\/]+\/?$/$1/) {
979 if ($parent eq '/') {
982 if (exists $config{$parent} &&
983 exists $config{$parent}{DEFAULT}) {
984 $config{$dir}{DEFAULT}={ %{$config{$parent}{DEFAULT}} };
989 print "mr: loading config $f\n" if $verbose;
990 open($in, "<", $f) || die "mr: open $f: $!\n";
1001 next if /^\s*\#/ || /^\s*$/;
1002 if (/^\[([^\]]*)\]\s*$/) {
1006 if (! is_trusted_repo($section) ||
1007 $section eq 'ALIAS' ||
1008 $section eq 'DEFAULT') {
1009 die "mr: illegal section \"[$section]\" in untrusted $f line $line\n";
1012 $section=expandenv($section) if $trusted;
1014 elsif (/^(\w+)\s*=\s*(.*)/) {
1019 while (@lines && $lines[0]=~/^\s(.+)/) {
1027 # Untrusted files can only contain checkout
1029 if ($parameter ne 'checkout') {
1030 die "mr: illegal setting \"$parameter=$value\" in untrusted $f line $line\n";
1032 if (! is_trusted_checkout($value)) {
1033 die "mr: illegal checkout command \"$value\" in untrusted $f line $line\n";
1037 if ($parameter eq "include") {
1038 print "mr: including output of \"$value\"\n" if $verbose;
1039 unshift @lines, `$value`;
1041 print STDERR "mr: include command exited nonzero ($?)\n";
1046 if (! defined $section) {
1047 die "$f line $.: parameter ($parameter) not in section\n";
1049 if ($section ne 'ALIAS' &&
1050 ! exists $config{$dir}{$section} &&
1051 exists $config{$dir}{DEFAULT}) {
1053 $config{$dir}{$section}={ %{$config{$dir}{DEFAULT}} };
1055 if ($section eq 'ALIAS') {
1056 $alias{$parameter}=$value;
1058 elsif ($parameter eq 'lib') {
1059 $config{$dir}{$section}{lib}.=$value."\n";
1062 $config{$dir}{$section}{$parameter}=$value;
1063 if ($parameter =~ /.*_(.*)/) {
1064 $knownactions{$1}=1;
1067 $knownactions{$parameter}=1;
1069 if ($parameter eq 'chain' &&
1070 length $dir && $section ne "DEFAULT" &&
1071 -e $dir.$section."/.mrconfig") {
1072 my $ret=system($value);
1074 if (($? & 127) == 2) {
1075 print STDERR "mr: chain test interrupted\n";
1079 print STDERR "mr: chain test received signal ".($? & 127)."\n";
1083 push @toload, $dir.$section."/.mrconfig";
1089 die "$f line $line: parse error\n";
1100 # the section to modify or add
1101 my $targetsection=shift;
1102 # fields to change in the section
1103 # To remove a field, set its value to "".
1104 my %changefields=@_;
1110 open(my $in, "<", $f) || die "mr: open $f: $!\n";
1115 my $formatfield=sub {
1117 my @value=split(/\n/, shift);
1119 return "$field = ".shift(@value)."\n".
1120 join("", map { "\t$_\n" } @value);
1124 while ($out[$#out] =~ /^\s*$/) {
1125 unshift @blanks, pop @out;
1127 foreach my $field (sort keys %changefields) {
1128 if (length $changefields{$field}) {
1129 push @out, "$field = $changefields{$field}\n";
1130 delete $changefields{$field};
1140 if (/^\s*\#/ || /^\s*$/) {
1143 elsif (/^\[([^\]]*)\]\s*$/) {
1144 if (defined $section &&
1145 $section eq $targetsection) {
1149 $section=expandenv($1);
1153 elsif (/^(\w+)\s*=\s(.*)/) {
1158 while (@lines && $lines[0]=~/^\s(.+)/) {
1164 if ($section eq $targetsection) {
1165 if (exists $changefields{$parameter}) {
1166 if (length $changefields{$parameter}) {
1167 $value=$changefields{$parameter};
1169 delete $changefields{$parameter};
1173 push @out, $formatfield->($parameter, $value);
1177 if (defined $section &&
1178 $section eq $targetsection) {
1181 elsif (%changefields) {
1182 push @out, "\n[$targetsection]\n";
1183 foreach my $field (sort keys %changefields) {
1184 if (length $changefields{$field}) {
1185 push @out, $formatfield->($field, $changefields{$field});
1190 open(my $out, ">", $f) || die "mr: write $f: $!\n";
1198 # actions that do not operate on all repos
1199 if ($action eq 'help') {
1202 elsif ($action eq 'config') {
1205 elsif ($action eq 'register') {
1208 elsif ($action eq 'bootstrap') {
1211 elsif ($action eq 'remember' ||
1212 $action eq 'offline' ||
1213 $action eq 'online') {
1214 my @repos=selectrepos;
1215 action($action, @{$repos[0]}) if @repos;
1219 if (!$jobs || $jobs > 1) {
1220 mrs($action, selectrepos());
1223 foreach my $repo (selectrepos()) {
1224 record($repo, action($action, @$repo));
1230 exec($config{''}{DEFAULT}{help}) || die "exec: $!";
1235 die "mr config: not enough parameters\n";
1238 if ($section=~/^\//) {
1239 # try to convert to a path relative to the config file
1240 my ($dir)=$ENV{MR_CONFIG}=~/^(.*\/)[^\/]+$/;
1241 $dir=abs_path($dir);
1242 $dir.="/" unless $dir=~/\/$/;
1243 if ($section=~/^\Q$dir\E(.*)/) {
1249 if (/^([^=]+)=(.*)$/) {
1250 $changefields{$1}=$2;
1254 foreach my $topdir (sort keys %config) {
1255 if (exists $config{$topdir}{$section} &&
1256 exists $config{$topdir}{$section}{$_}) {
1257 print $config{$topdir}{$section}{$_}."\n";
1259 last if $section eq 'DEFAULT';
1263 die "mr config: $section $_ not set\n";
1267 modifyconfig($ENV{MR_CONFIG}, $section, %changefields) if %changefields;
1272 if ($config_overridden) {
1273 # Find the directory that the specified config file is
1275 ($directory)=abs_path($ENV{MR_CONFIG})=~/^(.*\/)[^\/]+$/;
1278 # Find the closest known mrconfig file to the current
1280 $directory.="/" unless $directory=~/\/$/;
1282 foreach my $topdir (reverse sort keys %config) {
1283 next unless length $topdir;
1284 if ($directory=~/^\Q$topdir\E/) {
1285 $ENV{MR_CONFIG}=$configfiles{$topdir};
1291 if (! $foundconfig) {
1292 $directory=""; # no config file, use builtin
1296 my $subdir=shift @ARGV;
1297 if (! chdir($subdir)) {
1298 print STDERR "mr register: failed to chdir to $subdir: $!\n";
1302 $ENV{MR_REPO}=getcwd();
1303 my $command=findcommand("register", $ENV{MR_REPO}, $directory, 'DEFAULT', 0);
1304 if (! defined $command) {
1305 die "mr register: unknown repository type\n";
1308 $ENV{MR_REPO}=~s/.*\/(.*)/$1/;
1309 $command="set -e; ".$config{$directory}{DEFAULT}{lib}."\n".
1310 "my_action(){ $command\n }; my_action ".
1311 join(" ", map { s/\//\/\//g; s/"/\"/g; '"'.$_.'"' } @ARGV);
1312 print "mr register: running >>$command<<\n" if $verbose;
1313 exec($command) || die "exec: $!";
1317 my $url=shift @ARGV;
1319 if (! defined $url || ! length $url) {
1320 die "mr: bootstrap requires url\n";
1323 if (-e ".mrconfig") {
1324 die "mr: .mrconfig file already exists, not overwriting with $url\n";
1327 if (system("curl", "-s", $url, "-o", ".mrconfig") != 0) {
1328 die "mr: download of $url failed\n";
1331 exec("mr $ENV{MR_SWITCHES} -c .mrconfig checkout");
1332 die "failed to run mr checkout";
1335 # alias expansion and command stemming
1338 if (exists $alias{$action}) {
1339 $action=$alias{$action};
1341 if (! exists $knownactions{$action}) {
1342 my @matches = grep { /^\Q$action\E/ }
1343 keys %knownactions, keys %alias;
1344 if (@matches == 1) {
1345 $action=$matches[0];
1347 elsif (@matches == 0) {
1348 die "mr: unknown action \"$action\" (known actions: ".
1349 join(", ", sort keys %knownactions).")\n";
1352 die "mr: ambiguous action \"$action\" (matches: ".
1353 join(", ", @matches).")\n";
1359 sub find_nearest_mrconfig {
1361 while (length $dir) {
1362 if (-e "$dir/.mrconfig") {
1363 return "$dir/.mrconfig";
1365 $dir=~s/\/[^\/]*$//;
1367 die "no .mrconfig found in path\n";
1372 Getopt::Long::Configure("bundling", "no_permute");
1373 my $result=GetOptions(
1374 "d|directory=s" => sub { $directory=abs_path($_[1]) },
1375 "c|config=s" => sub { $ENV{MR_CONFIG}=$_[1]; $config_overridden=1 },
1376 "p|path" => sub { $ENV{MR_CONFIG}=find_nearest_mrconfig(); $config_overridden=1 },
1377 "v|verbose" => \$verbose,
1378 "q|quiet" => \$quiet,
1379 "s|stats" => \$stats,
1380 "i|interactive" => \$interactive,
1381 "n|no-recurse:i" => \$max_depth,
1382 "j|jobs:i" => \$jobs,
1383 "t|trust-all" => \$trust_all,
1385 if (! $result || @ARGV < 1) {
1386 die("Usage: mr [options] action [params ...]\n".
1387 "(Use mr help for man page.)\n");
1390 $ENV{MR_SWITCHES}="";
1391 foreach my $option (@saved) {
1392 last if $option eq $ARGV[0];
1393 $ENV{MR_SWITCHES}.="$option ";
1399 print STDERR "mr: interrupted\n";
1403 # This can happen if it's run in a directory that was removed
1404 # or other strangeness.
1405 if (! defined $directory) {
1406 die("mr: failed to determine working directory\n");
1408 # Make sure MR_CONFIG is an absolute path, but don't use abs_path since
1409 # the config file might be a symlink to elsewhere, and the directory it's
1410 # in is significant.
1411 if ($ENV{MR_CONFIG} !~ /^\//) {
1412 $ENV{MR_CONFIG}=getcwd()."/".$ENV{MR_CONFIG};
1414 # Try to set MR_PATH to the path to the program.
1416 use FindBin qw($Bin $Script);
1417 $ENV{MR_PATH}=$Bin."/".$Script;
1426 loadconfig($ENV{MR_CONFIG});
1427 #use Data::Dumper; print Dumper(\%config);
1429 my $action=expandaction(shift @ARGV);
1436 elsif (! @ok && @skipped) {
1444 # Finally, some useful actions that mr knows about by default.
1445 # These can be overridden in ~/.mrconfig.
1460 echo "mr (warning): $@" >&2
1466 if [ -z "$1" ] || [ -z "$2" ]; then
1467 error "mr: usage: hours_since action num"
1469 for dir in .git .svn .bzr CVS .hg _darcs; do
1470 if [ -e "$MR_REPO/$dir" ]; then
1471 flagfile="$MR_REPO/$dir/.mr_last$1"
1475 if [ -z "$flagfile" ]; then
1476 error "cannot determine flag filename"
1478 delta=`perl -wle 'print -f shift() ? int((-M _) * 24) : 9999' "$flagfile"`
1479 if [ "$delta" -lt "$2" ]; then
1487 svn_test = test -d "$MR_REPO"/.svn
1488 git_test = test -d "$MR_REPO"/.git
1489 bzr_test = test -d "$MR_REPO"/.bzr
1490 cvs_test = test -d "$MR_REPO"/CVS
1491 hg_test = test -d "$MR_REPO"/.hg
1492 darcs_test = test -d "$MR_REPO"/_darcs
1494 test -d "$MR_REPO"/refs/heads && test -d "$MR_REPO"/refs/tags &&
1495 test -d "$MR_REPO"/objects && test -f "$MR_REPO"/config &&
1496 test "`GIT_CONFIG="$MR_REPO"/config git config --get core.bare`" = true
1498 svn_update = svn update "$@"
1499 git_update = git pull "$@"
1500 bzr_update = bzr merge --pull "$@"
1501 cvs_update = cvs update "$@"
1502 hg_update = hg pull "$@" && hg update "$@"
1503 darcs_update = darcs pull -a "$@"
1505 svn_status = svn status "$@"
1506 git_status = git status "$@" || true
1507 bzr_status = bzr status "$@"
1508 cvs_status = cvs status "$@"
1509 hg_status = hg status "$@"
1510 darcs_status = darcs whatsnew -ls "$@" || true
1512 svn_commit = svn commit "$@"
1513 git_commit = git commit -a "$@" && git push --all
1514 bzr_commit = bzr commit "$@" && bzr push
1515 cvs_commit = cvs commit "$@"
1516 hg_commit = hg commit -m "$@" && hg push
1517 darcs_commit = darcs record -a -m "$@" && darcs push -a
1519 git_record = git commit -a "$@"
1520 bzr_record = bzr commit "$@"
1521 hg_record = hg commit -m "$@"
1522 darcs_record = darcs record -a -m "$@"
1525 git_push = git push "$@"
1526 bzr_push = bzr push "$@"
1528 hg_push = hg push "$@"
1529 darcs_push = darcs push -a "$@"
1531 svn_diff = svn diff "$@"
1532 git_diff = git diff "$@"
1533 bzr_diff = bzr diff "$@"
1534 cvs_diff = cvs diff "$@"
1535 hg_diff = hg diff "$@"
1536 darcs_diff = darcs diff -u "$@"
1538 svn_log = svn log "$@"
1539 git_log = git log "$@"
1540 bzr_log = bzr log "$@"
1541 cvs_log = cvs log "$@"
1542 hg_log = hg log "$@"
1543 darcs_log = darcs changes "$@"
1544 git_bare_log = git log "$@"
1547 url=`LC_ALL=C svn info . | grep -i '^URL:' | cut -d ' ' -f 2`
1548 if [ -z "$url" ]; then
1549 error "cannot determine svn url"
1551 echo "Registering svn url: $url in $MR_CONFIG"
1552 mr -c "$MR_CONFIG" config "`pwd`" checkout="svn co '$url' '$MR_REPO'"
1554 url="`LC_ALL=C git config --get remote.origin.url`" || true
1555 if [ -z "$url" ]; then
1556 error "cannot determine git url"
1558 echo "Registering git url: $url in $MR_CONFIG"
1559 mr -c "$MR_CONFIG" config "`pwd`" checkout="git clone '$url' '$MR_REPO'"
1561 url="`LC_ALL=C bzr info . | egrep -i 'checkout of branch|parent branch' | awk '{print $NF}'`"
1562 if [ -z "$url" ]; then
1563 error "cannot determine bzr url"
1565 echo "Registering bzr url: $url in $MR_CONFIG"
1566 mr -c "$MR_CONFIG" config "`pwd`" checkout="bzr clone '$url' '$MR_REPO'"
1568 repo=`cat CVS/Repository`
1570 if [ -z "$root" ]; then
1571 error "cannot determine cvs root"
1573 echo "Registering cvs repository $repo at root $root"
1574 mr -c "$MR_CONFIG" config "`pwd`" checkout="cvs -d '$root' co -d '$MR_REPO' '$repo'"
1576 url=`hg showconfig paths.default`
1577 echo "Registering mercurial repo url: $url in $MR_CONFIG"
1578 mr -c "$MR_CONFIG" config "`pwd`" checkout="hg clone '$url' '$MR_REPO'"
1580 url=`cat _darcs/prefs/defaultrepo`
1581 echo "Registering darcs repository $url in $MR_CONFIG"
1582 mr -c "$MR_CONFIG" config "`pwd`" checkout="darcs get '$url' '$MR_REPO'"
1584 url="`LC_ALL=C GIT_CONFIG=config git config --get remote.origin.url`" || true
1585 if [ -z "$url" ]; then
1586 error "cannot determine git url"
1588 echo "Registering git url: $url in $MR_CONFIG"
1589 mr -c "$MR_CONFIG" config "`pwd`" checkout="git clone --bare '$url' '$MR_REPO'"
1591 svn_trusted_checkout = svn co $url $repo
1592 svn_alt_trusted_checkout = svn checkout $url $repo
1593 git_trusted_checkout = git clone $url $repo
1594 bzr_trusted_checkout = bzr clone $url $repo
1596 hg_trusted_checkout = hg clone $url $repo
1597 darcs_trusted_checkout = darcs get $url $repo
1598 git_bare_trusted_checkout = git clone --bare $url $repo
1601 if [ ! -e "$MR_PATH" ]; then
1602 error "cannot find program path"
1604 tmp=$(mktemp -t mr.XXXXXXXXXX) || error "mktemp failed"
1605 trap "rm -f $tmp" exit
1606 pod2man -c mr "$MR_PATH" > "$tmp" || error "pod2man failed"
1607 man -l "$tmp" || error "man failed"
1613 if [ -s ~/.mrlog ]; then
1614 info "running offline commands"
1615 mv -f ~/.mrlog ~/.mrlog.old
1616 if ! sh -e ~/.mrlog.old; then
1617 error "offline command failed; left in ~/.mrlog.old"
1621 info "no offline commands to run"
1626 info "offline mode enabled"
1628 info "remembering command: 'mr $@'"
1629 command="mr -d '$(pwd)' $MR_SWITCHES"
1631 command="$command '$w'"
1633 if [ ! -e ~/.mrlog ] || ! grep -q -F "$command" ~/.mrlog; then
1634 echo "$command" >> ~/.mrlog
1637 ed = echo "A horse is a horse, of course, of course.."
1638 T = echo "I pity the fool."
1639 right = echo "Not found."
1641 # vim:sw=8:sts=0:ts=8:noet