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 tool to manage all your version control repos
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"]
27 B<mr> [options] grep pattern
29 B<mr> [options] run command [param ...]
31 B<mr> [options] bootstrap src [directory]
33 B<mr> [options] register [repository]
35 B<mr> [options] config section ["parameter=[value]" ...]
37 B<mr> [options] action [params ...]
39 B<mr> [options] [online|offline]
41 B<mr> [options] remember action [params ...]
45 B<mr> is a tool to manage all your version control repos. It can checkout,
46 update, or perform other actions on a set of repositories as if they were
47 one combined repository. It supports any combination of subversion, git,
48 cvs, mercurial, bzr, darcs, fossil and veracity repositories, and support
49 for other version control systems can easily be added.
51 B<mr> cds into and operates on all registered repositories at or below your
52 working directory. Or, if you are in a subdirectory of a repository that
53 contains no other registered repositories, it will stay in that directory,
54 and work on only that repository,
56 B<mr> is configured by .mrconfig files, which list the repositories. It
57 starts by reading the .mrconfig file in your home directory, and this can
58 in turn chain load .mrconfig files from repositories. It also automatically
59 looks for a .mrconfig file in the current directory, or in one of its
62 These predefined commands should be fairly familiar to users of any version
67 =item checkout (or co)
69 Checks out any repositories that are not already checked out.
73 Updates each repository from its configured remote repository.
75 If a repository isn't checked out yet, it will first check it out.
79 Displays a status report for each repository, showing what
80 uncommitted changes are present in the repository. For distributed version
81 control systems, also shows unpushed local branches.
85 Commits changes to each repository. (By default, changes are pushed to the
86 remote repository too, when using distributed systems like git. If you
87 don't like this default, you can change it in your .mrconfig, or use record
90 The optional -m parameter allows specifying a commit message.
94 Records changes to the local repository, but does not push them to the
95 remote repository. Only supported for distributed version control systems.
97 The optional -m parameter allows specifying a commit message.
101 Fetches from each repository's remote repository, but does not
102 update the working copy. Only supported for some distributed version
107 Pushes committed local changes to the remote repository. A no-op for
108 centralized version control systems.
112 Show a diff of uncommitted changes.
120 Searches for a pattern in each repository using the grep subcommand. Uses
121 ack-grep on VCS that do not have their own.
123 =item run command [param ...]
125 Runs the specified command in each repository.
129 These commands are also available:
133 =item bootstrap src [directory]
135 Causes mr to retrieve the source C<src> and use it as a .mrconfig file to
136 checkout the repositories listed in it, into the specified directory.
138 B<mr> understands several types of sources:
144 C<src> may be an URL understood by B<curl>.
148 To use B<scp> to download, the C<src> may have the form
149 C<ssh://[user@]host:file>.
153 You can retrieve the config file by other means and pass its B<path> as C<src>.
157 If source C<src> consists in a single dash C<->, config file is read from
162 The directory will be created if it does not exist. If no directory is
163 specified, the current directory will be used.
165 As a special case, if source C<src> includes a repository named ".", that
166 is checked out into the top of the specified directory.
170 List the repositories that mr will act on.
174 Register an existing repository in a mrconfig file. By default, the
175 repository in the current directory is registered, or you can specify a
176 directory to register.
178 The mrconfig file that is modified is chosen by either the -c option, or by
179 looking for the closest known one at or in a parent of the current directory.
183 Adds, modifies, removes, or prints a value from a mrconfig file. The next
184 parameter is the name of the section the value is in. To add or modify
185 values, use one or more instances of "parameter=value". Use "parameter=" to
186 remove a parameter. Use just "parameter" to get the value of a parameter.
188 For example, to add (or edit) a repository in src/foo:
190 mr config src/foo checkout="svn co svn://example.com/foo/trunk foo"
192 To show the command that mr uses to update the repository in src/foo:
194 mr config src/foo update
196 To see the built-in library of shell functions contained in mr:
198 mr config DEFAULT lib
200 The mrconfig file that is used is chosen by either the -c option, or by
201 looking for the closest known one at or in a parent of the current directory.
205 Advises mr that it is in offline mode. Any commands that fail in
206 offline mode will be remembered, and retried when mr is told it's online.
210 Advices mr that it is in online mode again. Commands that failed while in
211 offline mode will be re-run.
215 Remember a command, to be run later when mr re-enters online mode. This
216 implicitly puts mr into offline mode. The command can be any regular mr
217 command. This is useful when you know that a command will fail due to being
218 offline, and so don't want to run it right now at all, but just remember
219 to run it when you go back online.
227 Actions can be abbreviated to any unambiguous substring, so
228 "mr st" is equivalent to "mr status", and "mr up" is equivalent to "mr
231 Additional parameters can be passed to most commands, and are passed on
232 unchanged to the underlying version control system. This is mostly useful
233 if the repositories mr will act on all use the same version control
242 =item --directory directory
244 Specifies the topmost directory that B<mr> should work in. The default is
245 the current working directory.
249 =item --config mrconfig
251 Use the specified mrconfig file. The default is to use both F<~/.mrconfig>
252 as well as look for a F<.mrconfig> file in the current directory, or in one
253 of its parent directories.
259 Force mr to act on repositories that would normally be skipped due to their
272 Minimise output. If a command fails or there is any output then the usual
273 output will be shown.
279 Be quiet. This suppresses mr's usual output, as well as any output from
280 commands that are run (including stderr output). If a command fails,
281 the output will be shown.
287 Accept untrusted SSL certificates when bootstrapping.
293 Expand the statistics line displayed at the end to include information
294 about exactly which repositories failed and were skipped, if any.
300 Interactive mode. If a repository fails to be processed, a subshell will be
301 started which you can use to resolve or investigate the problem. Exit the
302 subshell to continue the mr run.
306 =item --no-recurse [number]
308 If no number if specified, just operate on the repository for the current
309 directory, do not recurse into deeper repositories.
311 If a number is specified, will recurse into repositories at most that many
312 subdirectories deep. For example, with -n 2 it would recurse into ./src/foo,
313 but not ./src/packages/bar.
317 =item --jobs [number]
319 Run the specified number of jobs in parallel, or an unlimited number of jobs
320 with no number specified. This can greatly speed up operations such as updates.
321 It is not recommended for interactive operations.
323 Note that running more than 10 jobs at a time is likely to run afoul of
324 ssh connection limits. Running between 3 and 5 jobs at a time will yield
325 a good speedup in updates without loading the machine too much.
331 Trust all mrconfig files even if they are not listed in F<~/.mrtrust>.
338 This obsolete flag is ignored.
342 =head1 MRCONFIG FILES
344 Here is an example F<.mrconfig> file:
347 checkout = svn checkout svn://svn.example.com/src/trunk src
351 checkout = git clone git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux-2.6.git &&
353 git checkout -b mybranch origin/master
355 The F<.mrconfig> file uses a variant of the INI file format. Lines
356 starting with "#" are comments. Values can be continued to the
357 following line by indenting the line with whitespace.
359 The C<DEFAULT> section allows setting default values for the sections that
362 The C<ALIAS> section allows adding aliases for actions. Each parameter
363 is an alias, and its value is the action to use.
365 All other sections add repositories. The section header specifies the
366 directory where the repository is located. This is relative to the directory
367 that contains the mrconfig file, but you can also choose to use absolute
368 paths. (Note that you can use environment variables in section names; they
369 will be passed through the shell for expansion. For example,
370 C<[$HOSTNAME]>, or C<[${HOSTNAME}foo]>).
372 Within a section, each parameter defines a shell command to run to handle a
373 given action. mr contains default handlers for "update", "status",
374 "commit", and other standard actions.
376 Normally you only need to specify what to do for "checkout". Here you
377 specify the command to run in order to create a checkout of the repository.
378 The command will be run in the parent directory, and must create the
379 repository's directory. So use C<git clone>, C<svn checkout>, C<bzr branch>
380 or C<bzr checkout> (for a bound branch), etc.
382 Note that these shell commands are run in a C<set -e> shell
383 environment, where any additional parameters you pass are available in
384 C<$@>. All commands other than "checkout" are run inside the repository,
385 though not necessarily at the top of it.
387 The C<MR_REPO> environment variable is set to the path to the top of the
388 repository. (For the "register" action, "MR_REPO" is instead set to the
389 basename of the directory that should be created when checking the
392 The C<MR_CONFIG> environment variable is set to the .mrconfig file
393 that defines the repo being acted on, or, if the repo is not yet in a config
394 file, the F<.mrconfig> file that should be modified to register the repo.
396 The C<MR_ACTION> environment variable is set to the command being run
397 (update, checkout, etc).
399 A few parameters have special meanings:
405 If the "skip" parameter is set and its command returns true, then B<mr>
406 will skip acting on that repository. The command is passed the action
409 Here are two examples. The first skips the repo unless
410 mr is run by joey. The second uses the hours_since function
411 (included in mr's built-in library) to skip updating the repo unless it's
412 been at least 12 hours since the last update.
416 skip = test `whoami` != joey
420 skip = [ "$1" = update ] && ! hours_since "$1" 12
422 Another way to use skip is for a lazy checkout. This makes mr skip
423 operating on a repo unless it already exists. To enable the
424 repo, you have to explicitly check it out (using "mr --force -d foo checkout").
432 The "order" parameter can be used to override the default ordering of
433 repositories. The default order value is 10. Use smaller values to make
434 repositories be processed earlier, and larger values to make repositories
437 Note that if a repository is located in a subdirectory of another
438 repository, ordering it to be processed earlier is not recommended.
442 If the "chain" parameter is set and its command returns true, then B<mr>
443 will try to load a F<.mrconfig> file from the root of the repository.
447 If the "include" parameter is set, its command is ran, and should output
448 additional mrconfig file content. The content is included as if it were
449 part of the including file.
451 Unlike all other parameters, this parameter does not need to be placed
454 B<mr> ships several libraries that can be included to add support for
455 additional version control type things (unison, git-svn, git-fake-bare,
456 git-subtree). To include them all, you could use:
458 include = cat /usr/share/mr/*
460 See the individual files for details.
464 If the "deleted" parameter is set and its command returns true, then
465 B<mr> will treat the repository as deleted. It won't ever actually delete
466 the repository, but it will warn if it sees the repository's directory.
467 This is useful when one mrconfig file is shared among multiple machines,
468 to keep track of and remember to delete old repositories.
472 The "lib" parameter can specify some shell code that will be run
473 before each command, this can be a useful way to define shell
474 functions for other commands to use.
476 Unlike most other parameters, this can be specified multiple times, in
477 which case the chunks of shell code are accumulatively concatenated
482 If the "fixups" parameter is set, its command is run whenever a repository
483 is checked out, or updated. This provides an easy way to do things
484 like permissions fixups, or other tweaks to the repository content,
485 whenever the repository is changed.
489 When looking for a command to run for a given action, mr first looks for
490 a parameter with the same name as the action. If that is not found, it
491 looks for a parameter named "VCS_action" (substituting in the name of the
492 version control system and the action).
494 Internally, mr has settings for "git_update", "svn_update", etc. To change
495 the action that is performed for a given version control system, you can
496 override these VCS specific actions. To add a new version control system,
497 you can just add VCS specific actions for it.
501 If a "pre_action" parameter is set, its command is run before mr performs the
502 specified action. Similarly, "post_action" parameters are run after mr
503 successfully performs the specified action. For example, "pre_commit" is
504 run before committing; "post_update" is run after updating.
508 Any parameter can be suffixed with C<_append>, to add an additional value
509 to the existing value of the parameter. In this way, actions
510 can be constructed accumulatively.
514 The name of the version control system is itself determined by
515 running each defined "VCS_test" action, until one succeeds.
519 =head1 UNTRUSTED MRCONFIG FILES
521 Since mrconfig files can contain arbitrary shell commands, they can do
522 anything. This flexibility is good, but it also allows a malicious mrconfig
523 file to delete your whole home directory. Such a file might be contained
524 inside a repository that your main F<~/.mrconfig> checks out. To
525 avoid worries about evil commands in a mrconfig file, mr defaults to
526 reading all mrconfig files other than the main F<~/.mrconfig> in untrusted
527 mode. In untrusted mode, mrconfig files are limited to running only known
528 safe commands (like "git clone") in a carefully checked manner.
530 To configure mr to trust other mrconfig files, list them in F<~/.mrtrust>.
531 One mrconfig file should be listed per line. Either the full pathname
532 should be listed, or the pathname can start with F<~/> to specify a file
533 relative to your home directory.
535 =head1 OFFLINE LOG FILE
537 The F<~/.mrlog> file contains commands that mr has remembered to run later,
538 due to being offline. You can delete or edit this file to remove commands,
539 or even to add other commands for 'mr online' to run. If the file is
540 present, mr assumes it is in offline mode.
544 mr can be extended to support things such as unison and git-svn. Some
545 files providing such extensions are available in F</usr/share/mr/>. See
546 the documentation in the files for details about using them.
550 mr returns nonzero if a command failed in any of the repositories.
554 Copyright 2007-2011 Joey Hess <joey@kitenet.net>
556 Licensed under the GNU GPL version 2 or higher.
558 http://myrepos.branchable.com/
565 use Cwd qw(getcwd abs_path);
568 # things that can happen when mr runs a command
577 my $config_overridden=0;
589 my $directory=getcwd();
590 my $terminal=-t STDOUT && eval{require IO::Pty::Easy;IO::Pty::Easy->import();1;};
592 my $HOME_MR_CONFIG = "$ENV{HOME}/.mrconfig";
593 $ENV{MR_CONFIG}=find_mrconfig();
600 my (@ok, @failed, @skipped);
610 # Runs a shell command using a supplied function.
611 # The lib will be included in the shell command line, and any params
612 # will be available in the shell as $1, $2, etc.
615 my ($action, $topdir, $subdir, $command, $params, $runner) = @_;
617 # optimisation: avoid running the shell for true and false
618 if ($command =~ /^\s*true\s*$/) {
622 elsif ($command =~ /^\s*false\s*$/) {
627 my $quotedparams=join(" ", (map { shellquote($_) } @$params));
628 my $lib=exists $config{$topdir}{$subdir}{lib} ?
629 $config{$topdir}{$subdir}{lib}."\n" : "";
630 if ($verbose && (! defined $lastlib || $lastlib ne $lib)) {
631 print "mr library now: >>$lib<<\n";
634 my $shellcode="set -e;".$lib.
635 "my_sh(){ $command\n }; my_sh $quotedparams";
636 print "mr $action: running $action >>$command<<\n" if $verbose;
637 $runner->($shellcode);
644 if ($s =~ m/^perl:\s+(.*)/s) {
645 return $perl_cache{$1} if exists $perl_cache{$1};
646 my $sub=eval "sub {$1}";
647 if (! defined $sub) {
648 print STDERR "mr: bad perl code in $id: $@\n";
650 return $perl_cache{$1} = $sub;
657 my ($action, $dir, $topdir, $subdir) = @_;
659 if (exists $vcs{$dir}) {
665 foreach my $vcs_test (
667 length $a <=> length $b
670 } grep { /_test$/ } keys %{$config{$topdir}{$subdir}}) {
671 my ($vcs)=$vcs_test =~ /(.*)_test/;
672 my $p=perl($vcs_test, $config{$topdir}{$subdir}{$vcs_test});
677 $test="my_$vcs_test() {\n$config{$topdir}{$subdir}{$vcs_test}\n}\n".$test;
678 $test.="if my_$vcs_test; then echo $vcs; fi\n";
683 foreach my $vcs (keys %perltest) {
684 if ($perltest{$vcs}->()) {
689 push @vcs, split(/\n/,
690 runsh("vcs test", $topdir, $subdir, $test, [], sub {
696 print STDERR "mr $action: found multiple possible repository types (@vcs) for ".fulldir($topdir, $subdir)."\n";
700 return $vcs{$dir}=undef;
703 return $vcs{$dir}=$vcs[0];
708 my ($action, $dir, $topdir, $subdir, $is_checkout) = @_;
710 if (exists $config{$topdir}{$subdir}{$action}) {
711 return $config{$topdir}{$subdir}{$action};
718 my $vcs=vcs_test(@_);
721 exists $config{$topdir}{$subdir}{$vcs."_".$action}) {
722 return $config{$topdir}{$subdir}{$vcs."_".$action};
730 my ($topdir, $subdir) = @_;
731 return $subdir =~ /^\// ? $subdir : $topdir.$subdir;
734 sub terminal_friendly_spawn {
735 my $actionmsg = shift;
741 my $pty = IO::Pty::Easy->new;
743 while ($pty->is_active) {
744 my $data = $pty->read();
745 $output .= $data if defined $data;
749 $output = qx/$sh 2>&1/;
752 if ($quiet && $ret != 0) {
753 print "$actionmsg\n" if $actionmsg;
754 print STDERR $output;
755 } elsif (!$quiet && (!$minimal || $output)) {
756 print "$actionmsg\n" if $actionmsg;
759 return ($ret, $output ? 1 : 0);
763 my ($action, $dir, $topdir, $subdir, $force_checkout) = @_;
764 my $fulldir=fulldir($topdir, $subdir);
767 $ENV{MR_CONFIG}=$configfiles{$topdir};
768 my $is_checkout=($action eq 'checkout');
769 my $is_update=($action =~ /update/);
771 ($ENV{MR_REPO}=$dir) =~ s!/$!!;
772 $ENV{MR_ACTION}=$action;
774 foreach my $testname ("skip", "deleted") {
775 next if $force && $testname eq "skip";
777 my $testcommand=findcommand($testname, $dir, $topdir, $subdir, $is_checkout);
779 if (defined $testcommand) {
780 my $ret=runsh "$testname test", $topdir, $subdir,
781 $testcommand, [$action],
782 sub { system(shift()) };
784 if (($? & 127) == 2) {
785 print STDERR "mr $action: interrupted\n";
789 print STDERR "mr $action: $testname test received signal ".($? & 127)."\n";
793 if ($ret >> 8 == 0) {
794 if ($testname eq "deleted") {
796 print STDERR "mr error: $dir should be deleted yet still exists\n";
800 print "mr $action: skip $dir skipped\n" if $verbose;
808 if (! $force_checkout) {
810 print "mr $action: $dir already exists, skipping checkout\n" if $verbose;
814 $dir=~s/^(.*)\/[^\/]+\/?$/$1/;
819 return action("checkout", $dir, $topdir, $subdir);
823 my $command=findcommand($action, $dir, $topdir, $subdir, $is_checkout);
825 if ($is_checkout && ! -d $dir) {
826 print "mr $action: creating parent directory $dir\n" if $verbose;
827 system("mkdir", "-p", $dir);
830 if (! $no_chdir && ! chdir($dir)) {
831 print STDERR "mr $action: failed to chdir to $dir: $!\n";
834 elsif (! defined $command) {
835 my $vcs=vcs_test(@_);
836 if (! defined $vcs) {
837 print STDERR "mr $action: unknown repository type and no defined $action command for $fulldir\n";
841 print STDERR "mr $action: no defined action for $vcs repository $fulldir, skipping\n" unless $minimal;
848 $actionmsg="mr $action: $fulldir";
852 $s=~s/^\Q$fulldir\E\/?//;
853 $actionmsg="mr $action: $fulldir (in subdir $s)";
855 print "$actionmsg\n" unless $quiet || $minimal;
857 my ($hookret, $hook_out)=hook("pre_$action", $topdir, $subdir);
858 return $hookret if $hookret != OK;
860 my ($ret, $out)=runsh $action, $topdir, $subdir,
861 $command, \@ARGV, sub {
863 if (!$jobs || $jobs > 1 || $quiet || $minimal) {
864 return terminal_friendly_spawn($actionmsg, $sh, $quiet, $minimal);
871 if (($? & 127) == 2) {
872 print STDERR "mr $action: interrupted\n";
876 print STDERR "mr $action: received signal ".($? & 127)."\n";
879 print STDERR "mr $action: failed ($ret)\n" if $verbose;
880 if ($ret >> 8 != 0) {
881 print STDERR "mr $action: command failed\n";
882 if (-e "$ENV{HOME}/.mrlog" && $action ne 'remember') {
883 # recreate original command line to
884 # remember, and avoid recursing
886 @ARGV=('-n', $action, @orig);
887 action("remember", $dir, $topdir, $subdir);
892 print STDERR "mr $action: command died ($ret)\n";
897 if ($is_checkout && ! -d $dir) {
898 print STDERR "mr $action: $dir missing after checkout\n";;
902 my ($ret, $hook_out)=hook("post_$action", $topdir, $subdir);
903 return $ret if $ret != OK;
905 if ($is_checkout || $is_update) {
906 if ($is_checkout && ! $no_chdir) {
907 if (! chdir($checkout_dir)) {
908 print STDERR "mr $action: failed to chdir to $checkout_dir: $!\n";
912 my ($ret, $hook_out)=hook("fixups", $topdir, $subdir);
913 return $ret if $ret != OK;
916 return (OK, $out || $hook_out);
922 my ($hook, $topdir, $subdir) = @_;
924 my $command=$config{$topdir}{$subdir}{$hook};
925 return OK unless defined $command;
926 my ($ret,$out)=runsh $hook, $topdir, $subdir, $command, [], sub {
928 if (!$jobs || $jobs > 1 || $quiet || $minimal) {
929 return terminal_friendly_spawn(undef, $sh, $quiet, $minimal);
936 if (($? & 127) == 2) {
937 print STDERR "mr $hook: interrupted\n";
941 print STDERR "mr $hook: received signal ".($? & 127)."\n";
952 # run actions on multiple repos, in parallel
962 while (@fhs or @repos) {
963 while ((!$jobs || $running < $jobs) && @repos) {
965 my $repo = shift @repos;
966 pipe(my $outfh, CHILD_STDOUT);
967 pipe(my $errfh, CHILD_STDERR);
969 unless ($pid = fork) {
970 die "mr $action: cannot fork: $!" unless defined $pid;
971 open(STDOUT, ">&CHILD_STDOUT") || die "mr $action cannot reopen stdout: $!";
972 open(STDERR, ">&CHILD_STDERR") || die "mr $action cannot reopen stderr: $!";
977 exit +(action($action, @$repo))[0];
981 push @active, [$pid, $repo];
982 push @fhs, [$outfh, $errfh];
985 my ($rin, $rout) = ('','');
987 foreach my $fh (@fhs) {
988 next unless defined $fh;
989 vec($rin, fileno($fh->[0]), 1) = 1 if defined $fh->[0];
990 vec($rin, fileno($fh->[1]), 1) = 1 if defined $fh->[1];
992 $nfound = select($rout=$rin, undef, undef, 1);
993 foreach my $channel (0, 1) {
994 foreach my $i (0..$#fhs) {
995 next unless defined $fhs[$i];
996 my $fh = $fhs[$i][$channel];
997 next unless defined $fh;
998 if (vec($rout, fileno($fh), 1) == 1) {
1000 if (sysread($fh, $r, 1024) == 0) {
1002 $fhs[$i][$channel] = undef;
1003 if (! defined $fhs[$i][0] &&
1004 ! defined $fhs[$i][1]) {
1005 waitpid($active[$i][0], 0);
1006 print STDOUT $out[$i][0];
1007 print STDERR $out[$i][1];
1008 record($active[$i][1], $? >> 8, $out[$i][0] || $out[$i][1]);
1009 splice(@fhs, $i, 1);
1010 splice(@active, $i, 1);
1011 splice(@out, $i, 1);
1015 $out[$i][$channel] .= $r;
1023 my $dir=shift()->[0];
1029 print "\n" unless $quiet || ($minimal && !$out);
1031 elsif ($ret == FAILED) {
1033 chdir($dir) unless $no_chdir;
1034 print STDERR "mr: Starting interactive shell. Exit shell to continue.\n";
1035 system((getpwuid($<))[8], "-i");
1040 elsif ($ret == SKIPPED) {
1041 push @skipped, $dir;
1043 elsif ($ret == ABORT) {
1047 die "unknown exit status $ret";
1053 if (! @ok && ! @failed && ! @skipped) {
1054 die "mr $action: no repositories found to work on\n";
1056 print "mr $action: finished (".join("; ",
1057 showstat($#ok+1, "ok", "ok"),
1058 showstat($#failed+1, "failed", "failed"),
1059 showstat($#skipped+1, "skipped", "skipped"),
1060 ).")\n" unless $quiet || $minimal;
1063 print "mr $action: (skipped: ".join(" ", @skipped).")\n" unless $quiet || $minimal;
1066 print STDERR "mr $action: (failed: ".join(" ", @failed).")\n";
1076 return "$count ".($count > 1 ? $plural : $singular);
1081 # an ordered list of repos
1084 foreach my $topdir (sort keys %config) {
1085 foreach my $subdir (sort keys %{$config{$topdir}}) {
1089 order => $config{$topdir}{$subdir}{order},
1094 $a->{order} <=> $b->{order}
1096 $a->{topdir} cmp $b->{topdir}
1098 $a->{subdir} cmp $b->{subdir}
1104 my $topdir=$repo->{topdir};
1105 my $subdir=$repo->{subdir};
1106 my $ret=($subdir =~/^\//) ? $subdir : $topdir.$subdir;
1111 # Figure out which repos to act on. Returns a list of array refs
1114 # [ "$full_repo_path/", "$mr_config_path/", $section_header ]
1117 foreach my $repo (repolist()) {
1118 my $topdir=$repo->{topdir};
1119 my $subdir=$repo->{subdir};
1121 next if $subdir eq 'DEFAULT';
1122 my $dir=repodir($repo);
1124 $dir.="/" unless $dir=~/\/$/;
1125 $d.="/" unless $d=~/\/$/;
1126 next if $dir ne $d && $dir !~ /^\Q$d\E/;
1127 if (defined $max_depth) {
1128 my @a=split('/', $dir);
1129 my @b=split('/', $d);
1130 do { } while (@a && @b && shift(@a) eq shift(@b));
1131 next if @a > $max_depth || @b > $max_depth;
1133 push @repos, [$dir, $topdir, $subdir];
1136 # fallback to find a leaf repo
1137 foreach my $repo (reverse repolist()) {
1138 my $topdir=$repo->{topdir};
1139 my $subdir=$repo->{subdir};
1141 next if $subdir eq 'DEFAULT';
1142 my $dir=repodir($repo);
1144 $dir.="/" unless $dir=~/\/$/;
1145 $d.="/" unless $d=~/\/$/;
1146 if ($d=~/^\Q$dir\E/) {
1147 push @repos, [$dir, $topdir, $subdir];
1169 sub is_trusted_config {
1170 my $config=shift; # must be abs_pathed already
1172 # We always trust ~/.mrconfig.
1173 return 1 if $config eq abs_path($HOME_MR_CONFIG);
1175 return 1 if $trust_all;
1177 my $trustfile=$ENV{HOME}."/.mrtrust";
1180 $trusted{$HOME_MR_CONFIG}=1;
1181 if (open (TRUST, "<", $trustfile)) {
1184 s/^~\//$ENV{HOME}\//;
1186 $trusted{$d}=1 if defined $d;
1192 return $trusted{$config};
1196 sub is_trusted_repo {
1199 # Tightly limit what is allowed in a repo name.
1200 # No ../, no absolute paths, and no unusual filenames
1201 # that might try to escape to the shell.
1202 return $repo =~ /^[-_.+\/A-Za-z0-9]+$/ &&
1203 $repo !~ /\.\./ && $repo !~ /^\//;
1206 sub is_trusted_checkout {
1209 # To determine if the command is safe, compare it with the
1210 # *_trusted_checkout config settings. Those settings are
1211 # templates for allowed commands, so make sure that each word
1212 # of the command matches the corresponding word of the template.
1215 foreach my $word (split(' ', $command)) {
1217 if ($word=~/^'(.*)'$/) {
1220 elsif ($word=~/^"(.*)"$/) {
1227 foreach my $key (grep { /_trusted_checkout$/ }
1228 keys %{$config{''}{DEFAULT}}) {
1229 my @twords=split(' ', $config{''}{DEFAULT}{$key});
1230 next if @words > @twords;
1234 for (my $c=0; $c < @twords && $match; $c++) {
1235 if ($twords[$c] eq '$url') {
1236 # Match all the typical characters found in
1237 # urls, plus @ which svn can use. Note
1238 # that the "url" might also be a local
1241 defined $words[$c] &&
1242 $words[$c] =~ /^[-_.+:@\/A-Za-z0-9]+$/
1246 elsif ($twords[$c] eq '$repo') {
1247 # If a repo is not specified, assume it
1248 # will be the last path component of the
1249 # url, or something derived from it, and
1251 if (! defined $words[$c] && defined $url) {
1252 ($words[$c])=$url=~/\/([^\/]+)\/?$/;
1256 defined $words[$c] &&
1257 is_trusted_repo($words[$c])
1260 elsif (defined $words[$c] && $words[$c]=~/^($twords[$c])$/) {
1277 my $bootstrap_src=shift;
1282 my $absf=abs_path($f);
1284 if (ref $f eq 'GLOB') {
1290 if ($loaded{$absf}) {
1295 $trusted=is_trusted_config($absf);
1297 if (! defined $dir) {
1298 ($dir)=$f=~/^(.*\/)[^\/]+$/;
1299 if (! defined $dir) {
1304 $dir=abs_path($dir)."/";
1310 if (! exists $configfiles{$dir}) {
1311 $configfiles{$dir}=$f;
1314 # copy in defaults from first parent
1316 while ($parent=~s/^(.*\/)[^\/]+\/?$/$1/) {
1317 if ($parent eq '/') {
1320 if (exists $config{$parent} &&
1321 exists $config{$parent}{DEFAULT}) {
1322 $config{$dir}{DEFAULT}={ %{$config{$parent}{DEFAULT}} };
1332 print "mr: loading config $f\n" if $verbose;
1334 print "mr: loading config $f (from ".getcwd().")\n" if $verbose;
1337 open($in, "<", $f) || die "mr: open $f: $!\n";
1340 close $in unless ref $f eq 'GLOB';
1344 # Keep track of the current line in the config file;
1345 # when a file is included track the current line from the include.
1350 my $nextline = sub {
1362 my $lineerror = sub {
1364 if (defined $included) {
1365 die "mr: $msg at $f line $lineno, included line: $line\n";
1368 die "mr: $msg at $f line $lineno\n";
1371 my $trusterror = sub {
1374 if (defined $bootstrap_src) {
1375 die "mr: $msg in untrusted $bootstrap_src line $lineno\n".
1376 "(To trust this url, --trust-all can be used; but please use caution;\n".
1377 "this can allow arbitrary code execution!)\n";
1380 die "mr: $msg in untrusted $absf line $lineno\n".
1381 "(To trust this file, list it in ~/.mrtrust.)\n";
1388 next if /^\s*\#/ || /^\s*$/;
1390 if (! $trusted && /[[:cntrl:]]/) {
1391 $trusterror->("illegal control character");
1394 if (/^\[([^\]]*)\]\s*$/) {
1398 if (! is_trusted_repo($section) ||
1399 $section eq 'ALIAS' ||
1400 $section eq 'DEFAULT') {
1401 $trusterror->("illegal section \"[$section]\"");
1404 $section=expandenv($section) if $trusted;
1405 if ($section ne 'ALIAS' &&
1406 ! exists $config{$dir}{$section} &&
1407 exists $config{$dir}{DEFAULT}) {
1409 $config{$dir}{$section}={ %{$config{$dir}{DEFAULT}} };
1412 elsif (/^(\w+)\s*=\s*(.*)/) {
1417 while (@lines && $lines[0]=~/^\s(.+)/) {
1424 # Untrusted files can only contain a few
1425 # settings in specific known-safe formats.
1426 if ($parameter eq 'checkout') {
1427 if (! is_trusted_checkout($value)) {
1428 $trusterror->("illegal checkout command \"$value\"");
1431 elsif ($parameter eq 'order') {
1432 # not interpreted as a command, so
1435 elsif ($value eq 'true' || $value eq 'false') {
1436 # skip=true , deleted=true etc are
1440 $trusterror->("illegal setting \"$parameter=$value\"");
1444 if ($parameter eq "include") {
1445 print "mr: including output of \"$value\"\n" if $verbose;
1448 print STDERR "mr: include command exited nonzero ($?)\n";
1451 unshift @lines, @inc;
1455 if (! defined $section) {
1456 $lineerror->("parameter ($parameter) not in section");
1458 if ($section eq 'ALIAS') {
1459 $alias{$parameter}=$value;
1461 elsif ($parameter eq 'lib' or $parameter =~ s/_append$//) {
1462 $config{$dir}{$section}{$parameter}.="\n".$value."\n";
1465 $config{$dir}{$section}{$parameter}=$value;
1466 if ($parameter =~ /.*_(.*)/) {
1467 $knownactions{$1}=1;
1470 $knownactions{$parameter}=1;
1472 if ($parameter eq 'chain' &&
1473 length $dir && $section ne "DEFAULT") {
1474 my $chaindir="$section";
1475 if ($chaindir !~ m!^/!) {
1476 $chaindir=$dir.$chaindir;
1478 if (-e "$chaindir/.mrconfig") {
1479 my $ret=system($value);
1481 if (($? & 127) == 2) {
1482 print STDERR "mr: chain test interrupted\n";
1486 print STDERR "mr: chain test received signal ".($? & 127)."\n";
1490 push @toload, ["$chaindir/.mrconfig", $chaindir];
1497 $lineerror->("parse error");
1501 foreach my $c (@toload) {
1506 sub startingconfig {
1507 %alias=%config=%configfiles=%knownactions=%loaded=();
1508 my $datapos=tell(DATA);
1510 seek(DATA,$datapos,0); # rewind
1515 # the section to modify or add
1516 my $targetsection=shift;
1517 # fields to change in the section
1518 # To remove a field, set its value to "".
1519 my %changefields=@_;
1525 open(my $in, "<", $f) || die "mr: open $f: $!\n";
1530 my $formatfield=sub {
1532 my @value=split(/\n/, shift);
1534 return "$field = ".shift(@value)."\n".
1535 join("", map { "\t$_\n" } @value);
1539 while ($out[$#out] =~ /^\s*$/) {
1540 unshift @blanks, pop @out;
1542 foreach my $field (sort keys %changefields) {
1543 if (length $changefields{$field}) {
1544 push @out, "$field = $changefields{$field}\n";
1545 delete $changefields{$field};
1555 if (/^\s*\#/ || /^\s*$/) {
1558 elsif (/^\[([^\]]*)\]\s*$/) {
1559 if (defined $section &&
1560 $section eq $targetsection) {
1564 $section=expandenv($1);
1568 elsif (/^(\w+)\s*=\s(.*)/) {
1573 while (@lines && $lines[0]=~/^\s(.+)/) {
1579 if ($section eq $targetsection) {
1580 if (exists $changefields{$parameter}) {
1581 if (length $changefields{$parameter}) {
1582 $value=$changefields{$parameter};
1584 delete $changefields{$parameter};
1588 push @out, $formatfield->($parameter, $value);
1592 if (defined $section &&
1593 $section eq $targetsection) {
1596 elsif (%changefields) {
1597 push @out, "\n[$targetsection]\n";
1598 foreach my $field (sort keys %changefields) {
1599 if (length $changefields{$field}) {
1600 push @out, $formatfield->($field, $changefields{$field});
1605 open(my $out, ">", $f) || die "mr: write $f: $!\n";
1613 # actions that do not operate on all repos
1614 if ($action eq 'config') {
1617 elsif ($action eq 'register') {
1620 elsif ($action eq 'bootstrap') {
1623 elsif ($action eq 'remember' ||
1624 $action eq 'offline' ||
1625 $action eq 'online') {
1626 my @repos=selectrepos;
1627 action($action, @{$repos[0]}) if @repos;
1631 if (!$jobs || $jobs > 1) {
1632 mrs($action, selectrepos());
1635 foreach my $repo (selectrepos()) {
1636 record($repo, action($action, @$repo));
1645 SHOWMANFILE="man -f"
1654 if [ ! -e "$MR_PATH" ]; then
1655 error "cannot find program path"
1657 tmp=$(mktemp -t mr.XXXXXXXXXX) || error "mktemp failed"
1658 trap "rm -f $tmp" exit
1659 pod2man -c mr "$MR_PATH" > "$tmp" || error "pod2man failed"
1660 $SHOWMANFILE "$tmp" || error "man failed"
1662 exec($help) || die "exec: $!";
1667 die "mr config: not enough parameters\n";
1670 if ($section=~/^\//) {
1671 # try to convert to a path relative to the config file
1672 my ($dir)=$ENV{MR_CONFIG}=~/^(.*\/)[^\/]+$/;
1673 $dir=abs_path($dir);
1674 $dir.="/" unless $dir=~/\/$/;
1675 if ($section=~/^\Q$dir\E(.*)/) {
1681 if (/^([^=]+)=(.*)$/) {
1682 $changefields{$1}=$2;
1686 foreach my $topdir (sort keys %config) {
1687 if (exists $config{$topdir}{$section} &&
1688 exists $config{$topdir}{$section}{$_}) {
1689 print $config{$topdir}{$section}{$_}."\n";
1691 last if $section eq 'DEFAULT';
1695 die "mr config: $section $_ not set\n";
1699 modifyconfig($ENV{MR_CONFIG}, $section, %changefields) if %changefields;
1704 if ($config_overridden) {
1705 # Find the directory that the specified config file is
1707 ($directory)=abs_path($ENV{MR_CONFIG})=~/^(.*\/)[^\/]+$/;
1710 # Find the closest known mrconfig file to the current
1712 $directory.="/" unless $directory=~/\/$/;
1714 foreach my $topdir (reverse sort keys %config) {
1715 next unless length $topdir;
1716 if ($directory=~/^\Q$topdir\E/) {
1717 $ENV{MR_CONFIG}=$configfiles{$topdir};
1723 if (! $foundconfig) {
1724 $directory=""; # no config file, use builtin
1728 my $subdir=shift @ARGV;
1729 if (! chdir($subdir)) {
1730 print STDERR "mr register: failed to chdir to $subdir: $!\n";
1734 $ENV{MR_REPO}=getcwd();
1735 my $command=findcommand("register", $ENV{MR_REPO}, $directory, 'DEFAULT', 0);
1736 if (! defined $command) {
1737 die "mr register: unknown repository type\n";
1740 $ENV{MR_REPO}=~s/.*\/(.*)/$1/;
1741 $command="set -e; ".$config{$directory}{DEFAULT}{lib}."\n".
1742 "my_action(){ $command\n }; my_action ".
1743 join(" ", map { s/\\/\\\\/g; s/"/\"/g; '"'.$_.'"' } @ARGV);
1744 print "mr register: running >>$command<<\n" if $verbose;
1745 exec($command) || die "exec: $!";
1749 eval q{use File::Copy};
1752 my $src=shift @ARGV;
1753 my $dir=shift @ARGV || ".";
1755 if (! defined $src || ! length $src) {
1756 die "mr: bootstrap requires source\n";
1759 # Retrieve config file.
1760 eval q{use File::Temp};
1762 my $tmpconfig=File::Temp->new();
1763 if ($src =~ m!^[\w\d]+://!) {
1764 # Download the config file to a temporary location.
1766 if ($src =~ m!^ssh://(.*)!) {
1767 @downloader = ("scp", $1, $tmpconfig);
1770 @downloader = ("curl", "-A", "mr", "-L", "-s", $src, "-o", $tmpconfig);
1771 push(@downloader, "-k") if $insecure;
1773 my $status = system(@downloader);
1774 die "mr bootstrap: invalid SSL certificate for $src (consider -k)\n"
1775 if $downloader[0] eq 'curl' && $status >> 8 == 60;
1776 die "mr bootstrap: download of $src failed\n" if $status != 0;
1778 elsif ($src eq '-') {
1779 # Config file is read from stdin.
1780 copy(\*STDIN, $tmpconfig) || die "stdin: $!";
1783 # Config file is local.
1784 die "mr bootstrap: cannot read file '$src'"
1786 copy($src, $tmpconfig) || die "copy: $!";
1789 # Sanity check on destination directory.
1791 system("mkdir", "-p", $dir);
1793 chdir($dir) || die "chdir $dir: $!";
1795 # Special case to handle checkout of the "." repo, which
1796 # would normally be skipped.
1797 my $topdir=abs_path(".")."/";
1798 my @repo=($topdir, $topdir, ".");
1799 loadconfig($tmpconfig, $topdir, $src);
1800 record(\@repo, action("checkout", @repo, 1))
1801 if exists $config{$topdir}{"."}{"checkout"};
1803 if (-e ".mrconfig") {
1804 print STDERR "mr bootstrap: .mrconfig file already exists, not overwriting with $src\n";
1807 move($tmpconfig, ".mrconfig") || die "rename: $!";
1810 # Reload the config file (in case we got a different version)
1811 # and checkout everything else.
1813 loadconfig(".mrconfig");
1814 dispatch("checkout");
1815 @skipped=grep { abs_path($_) ne abs_path($topdir) } @skipped;
1816 showstats("bootstrap");
1820 # alias expansion and command stemming
1823 if (exists $alias{$action}) {
1824 $action=$alias{$action};
1826 if (! exists $knownactions{$action}) {
1827 my @matches = grep { /^\Q$action\E/ }
1828 keys %knownactions, keys %alias;
1829 if (@matches == 1) {
1830 $action=$matches[0];
1832 elsif (@matches == 0) {
1833 die "mr: unknown action \"$action\" (known actions: ".
1834 join(", ", sort keys %knownactions).")\n";
1837 die "mr: ambiguous action \"$action\" (matches: ".
1838 join(", ", @matches).")\n";
1846 while (length $dir) {
1847 if (-e "$dir/.mrconfig") {
1848 return "$dir/.mrconfig";
1850 $dir=~s/\/[^\/]*$//;
1852 return $HOME_MR_CONFIG;
1857 Getopt::Long::Configure("bundling", "no_permute");
1858 my $result=GetOptions(
1859 "d|directory=s" => sub { $directory=abs_path($_[1]) },
1860 "c|config=s" => sub { $ENV{MR_CONFIG}=$_[1]; $config_overridden=1 },
1861 "p|path" => sub { }, # now default, ignore
1862 "f|force" => \$force,
1863 "v|verbose" => \$verbose,
1864 "m|minimal" => \$minimal,
1865 "q|quiet" => \$quiet,
1866 "s|stats" => \$stats,
1867 "k|insecure" => \$insecure,
1868 "i|interactive" => \$interactive,
1869 "n|no-recurse:i" => \$max_depth,
1870 "j|jobs:i" => \$jobs,
1871 "t|trust-all" => \$trust_all,
1873 if (! $result || @ARGV < 1) {
1874 die("Usage: mr [options] action [params ...]\n".
1875 "(Use mr help for man page.)\n");
1878 $ENV{MR_SWITCHES}="";
1879 foreach my $option (@saved) {
1880 last if $option eq $ARGV[0];
1881 $ENV{MR_SWITCHES}.="$option ";
1887 print STDERR "mr: interrupted\n";
1891 # This can happen if it's run in a directory that was removed
1892 # or other strangeness.
1893 if (! defined $directory) {
1894 die("mr: failed to determine working directory\n");
1896 # Make sure MR_CONFIG is an absolute path, but don't use abs_path since
1897 # the config file might be a symlink to elsewhere, and the directory it's
1898 # in is significant.
1899 if ($ENV{MR_CONFIG} !~ /^\//) {
1900 $ENV{MR_CONFIG}=getcwd()."/".$ENV{MR_CONFIG};
1902 # Try to set MR_PATH to the path to the program.
1904 use FindBin qw($Bin $Script);
1905 $ENV{MR_PATH}=$Bin."/".$Script;
1921 help(@ARGV) if $ARGV[0] eq 'help';
1924 loadconfig($HOME_MR_CONFIG);
1925 loadconfig($ENV{MR_CONFIG});
1926 #use Data::Dumper; print Dumper(\%config);
1928 my $action=expandaction(shift @ARGV);
1935 # Finally, some useful actions that mr knows about by default.
1936 # These can be overridden in ~/.mrconfig.
1951 echo "mr (warning): $@" >&2
1957 if [ -z "$1" ] || [ -z "$2" ]; then
1958 error "mr: usage: hours_since action num"
1960 for dir in .git .svn .bzr CVS .hg _darcs _FOSSIL_; do
1961 if [ -e "$MR_REPO/$dir" ]; then
1962 flagfile="$MR_REPO/$dir/.mr_last$1"
1966 if [ -z "$flagfile" ]; then
1967 error "cannot determine flag filename"
1969 delta=`perl -wle 'print -f shift() ? int((-M _) * 24) : 9999' "$flagfile"`
1970 if [ "$delta" -lt "$2" ]; then
1978 LANG=C bzr info | egrep -q '^Checkout'
1981 if [ -d "$MR_REPO" ]; then
1988 svn_test = perl: -d "$ENV{MR_REPO}/.svn"
1989 git_test = perl: -e "$ENV{MR_REPO}/.git"
1990 bzr_test = perl: -d "$ENV{MR_REPO}/.bzr"
1991 cvs_test = perl: -d "$ENV{MR_REPO}/CVS"
1992 hg_test = perl: -d "$ENV{MR_REPO}/.hg"
1993 darcs_test = perl: -d "$ENV{MR_REPO}/_darcs"
1994 fossil_test = perl: -f "$ENV{MR_REPO}/_FOSSIL_"
1995 git_bare_test = perl:
1996 -d "$ENV{MR_REPO}/refs/heads" && -d "$ENV{MR_REPO}/refs/tags" &&
1997 -d "$ENV{MR_REPO}/objects" && -f "$ENV{MR_REPO}/config" &&
1998 `GIT_CONFIG="$ENV{MR_REPO}"/config git config --get core.bare` =~ /true/
2000 -d "$ENV{MR_REPO}/refs/heads" && -d "$ENV{MR_REPO}/refs/tags" &&
2001 -d "$ENV{MR_REPO}/objects" && -f "$ENV{MR_REPO}/config" &&
2002 `GIT_CONFIG="$ENV{MR_REPO}"/config git config --get vcsh.vcsh` =~ /true/
2003 veracity_test = perl: -d "$ENV{MR_REPO}/.sgdrawer"
2005 svn_update = svn update "$@"
2006 git_update = git pull "$@"
2008 if is_bzr_checkout; then
2011 bzr merge --pull "$@"
2013 cvs_update = cvs -q update "$@"
2014 hg_update = hg pull "$@"; hg update "$@"
2015 darcs_update = darcs pull -a "$@"
2016 fossil_update = fossil pull "$@"
2017 vcsh_update = vcsh run "$MR_REPO" git pull "$@"
2018 veracity_update = vv pull "$@" && vv update "$@"
2020 git_fetch = git fetch --all --prune --tags
2021 git_svn_fetch = git svn fetch
2022 darcs_fetch = darcs fetch
2025 svn_status = svn status "$@"
2026 git_status = git status -s "$@" || true; git --no-pager log --branches --not --remotes --simplify-by-decoration --decorate --oneline || true
2027 bzr_status = bzr status --short "$@"; bzr missing
2028 cvs_status = cvs -q status | grep -E '^(File:.*Status:|\?)' | grep -v 'Status: Up-to-date'
2029 hg_status = hg status "$@"; hg summary --quiet | grep -v 'parent: 0:'
2030 darcs_status = darcs whatsnew -ls "$@" || true
2031 fossil_status = fossil changes "$@"
2032 vcsh_status = vcsh run "$MR_REPO" git -c status.relativePaths=false status -s "$@" || true
2033 veracity_status = vv status "$@"
2035 svn_commit = svn commit "$@"
2036 git_commit = git commit -a "$@" && git push --all
2038 if is_bzr_checkout; then
2041 bzr commit "$@" && bzr push
2043 cvs_commit = cvs commit "$@"
2044 hg_commit = hg commit "$@" && hg push
2045 darcs_commit = darcs record -a "$@" && darcs push -a
2046 fossil_commit = fossil commit "$@"
2047 vcsh_commit = vcsh run "$MR_REPO" git commit -a "$@" && vcsh run "$MR_REPO" git push --all
2048 veracity_commit = vv commit "$@" && vv push
2050 git_record = git commit -a "$@"
2052 if is_bzr_checkout; then
2053 bzr commit --local "$@"
2057 hg_record = hg commit "$@"
2058 darcs_record = darcs record -a "$@"
2059 fossil_record = fossil commit "$@"
2060 vcsh_record = vcsh run "$MR_REPO" git commit -a "$@"
2061 veracity_record = vv commit "$@"
2064 git_push = git push "$@"
2065 bzr_push = bzr push "$@"
2067 hg_push = hg push "$@"
2068 darcs_push = darcs push -a "$@"
2069 fossil_push = fossil push "$@"
2070 vcsh_push = vcsh run "$MR_REPO" git push "$@"
2071 veracity_push = vv push "$@"
2073 svn_diff = svn diff "$@"
2074 git_diff = git diff "$@"
2075 bzr_diff = bzr diff "$@"
2076 cvs_diff = cvs -q diff "$@"
2077 hg_diff = hg diff "$@"
2078 darcs_diff = darcs diff -u "$@"
2079 fossil_diff = fossil diff "$@"
2080 vcsh_diff = vcsh run "$MR_REPO" git diff "$@"
2081 veracity_diff = vv diff "$@"
2083 svn_log = svn log "$@"
2084 git_log = git log "$@"
2085 bzr_log = bzr log "$@"
2086 cvs_log = cvs log "$@"
2087 hg_log = hg log "$@"
2088 darcs_log = darcs changes "$@"
2089 git_bare_log = git log "$@"
2090 fossil_log = fossil timeline "$@"
2091 vcsh_log = vcsh run "$MR_REPO" git log "$@"
2092 veracity_log = vv log "$@"
2094 hg_grep = hg grep "$@"
2095 cvs_grep = ack-grep "$@"
2096 svn_grep = ack-grep "$@"
2097 git_svn_grep = git grep "$@"
2098 git_grep = git grep "$@"
2099 bzr_grep = ack-grep "$@"
2100 darcs_grep = ack-grep "$@"
2105 url=`LC_ALL=C svn info . | grep -i '^URL:' | cut -d ' ' -f 2`
2106 if [ -z "$url" ]; then
2107 error "cannot determine svn url"
2109 echo "Registering svn url: $url in $MR_CONFIG"
2110 mr -c "$MR_CONFIG" config "`pwd`" checkout="svn co '$url' '$MR_REPO'"
2112 url="`LC_ALL=C git config --get remote.origin.url`" || true
2113 if [ -z "$url" ]; then
2114 error "cannot determine git url"
2116 echo "Registering git url: $url in $MR_CONFIG"
2117 mr -c "$MR_CONFIG" config "`pwd`" checkout="git clone '$url' '$MR_REPO'"
2119 url="`LC_ALL=C bzr info . | egrep -i 'checkout of branch|parent branch' | awk '{print $NF}' | head -n 1`"
2120 if [ -z "$url" ]; then
2121 error "cannot determine bzr url"
2123 echo "Registering bzr url: $url in $MR_CONFIG"
2124 mr -c "$MR_CONFIG" config "`pwd`" checkout="bzr branch '$url' '$MR_REPO'"
2126 repo=`cat CVS/Repository`
2128 if [ -z "$root" ]; then
2129 error "cannot determine cvs root"
2131 echo "Registering cvs repository $repo at root $root"
2132 mr -c "$MR_CONFIG" config "`pwd`" checkout="cvs -d '$root' co -d '$MR_REPO' '$repo'"
2134 url=`hg showconfig paths.default`
2135 echo "Registering mercurial repo url: $url in $MR_CONFIG"
2136 mr -c "$MR_CONFIG" config "`pwd`" checkout="hg clone '$url' '$MR_REPO'"
2138 url=`cat _darcs/prefs/defaultrepo`
2139 echo "Registering darcs repository $url in $MR_CONFIG"
2140 mr -c "$MR_CONFIG" config "`pwd`" checkout="darcs get '$url' '$MR_REPO'"
2142 url="`LC_ALL=C GIT_CONFIG=config git config --get remote.origin.url`" || true
2143 if [ -z "$url" ]; then
2144 error "cannot determine git url"
2146 echo "Registering git url: $url in $MR_CONFIG"
2147 mr -c "$MR_CONFIG" config "`pwd`" checkout="git clone --bare '$url' '$MR_REPO'"
2149 url="`LC_ALL=C vcsh run "$MR_REPO" git config --get remote.origin.url`" || true
2150 if [ -z "$url" ]; then
2151 error "cannot determine git url"
2153 echo "Registering git url: $url in $MR_CONFIG"
2154 mr -c "$MR_CONFIG" config "`pwd`" checkout="vcsh clone '$url' '$MR_REPO'"
2156 url=`fossil remote-url`
2157 repo=`fossil info | grep repository | sed -e 's/repository:*.//g' -e 's/ //g'`
2158 echo "Registering fossil repository $url in $MR_CONFIG"
2159 mr -c "$MR_CONFIG" config "`pwd`" checkout="mkdir -p '$MR_REPO' && cd '$MR_REPO' && fossil open '$repo'"
2161 url=`vv config | grep sync_targets | sed -e 's/sync_targets:*.//g' -e 's/ //g'`
2162 repo=`vv repo info | grep repository | sed -e 's/Current repository:*.//g' -e 's/ //g'`
2163 echo "Registering veracity repository $url in $MR_CONFIG"
2164 mr -c "$MR_CONFIG" config "`pwd`" checkout="mkdir -p '$MR_REPO' && cd '$MR_REPO' && vv checkout '$repo'"
2166 svn_trusted_checkout = svn co $url $repo
2167 svn_alt_trusted_checkout = svn checkout $url $repo
2168 git_trusted_checkout = git clone $url $repo
2169 bzr_trusted_checkout = bzr checkout|clone|branch|get $url $repo
2171 hg_trusted_checkout = hg clone $url $repo
2172 darcs_trusted_checkout = darcs get $url $repo
2173 git_bare_trusted_checkout = git clone --bare $url $repo
2174 vcsh_old_trusted_checkout = vcsh run "$MR_REPO" git clone $url $repo
2175 vcsh_trusted_checkout = vcsh clone $url $repo
2176 # fossil: messy to do
2177 veracity_trusted_checkout = vv clone $url $repo
2185 if [ -s ~/.mrlog ]; then
2186 info "running offline commands"
2187 mv -f ~/.mrlog ~/.mrlog.old
2188 if ! sh -e ~/.mrlog.old; then
2189 error "offline command failed; left in ~/.mrlog.old"
2193 info "no offline commands to run"
2198 info "offline mode enabled"
2200 info "remembering command: 'mr $@'"
2201 command="mr -d '$(pwd)' $MR_SWITCHES"
2203 command="$command '$w'"
2205 if [ ! -e ~/.mrlog ] || ! grep -q -F "$command" ~/.mrlog; then
2206 echo "$command" >> ~/.mrlog
2209 ed = echo "A horse is a horse, of course, of course.."
2210 T = echo "I pity the fool."
2211 right = echo "Not found."
2213 # vim:sw=8:sts=0:ts=8:noet
2215 # indent-tabs-mode: t
2216 # cperl-indent-level: 8