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 loadconfig($parent);
1321 if (exists $config{$parent} &&
1322 exists $config{$parent}{DEFAULT}) {
1323 $config{$dir}{DEFAULT}={ %{$config{$parent}{DEFAULT}} };
1333 print "mr: loading config $f\n" if $verbose;
1335 print "mr: loading config $f (from ".getcwd().")\n" if $verbose;
1338 open($in, "<", $f) || die "mr: open $f: $!\n";
1341 close $in unless ref $f eq 'GLOB';
1345 # Keep track of the current line in the config file;
1346 # when a file is included track the current line from the include.
1351 my $nextline = sub {
1363 my $lineerror = sub {
1365 if (defined $included) {
1366 die "mr: $msg at $f line $lineno, included line: $line\n";
1369 die "mr: $msg at $f line $lineno\n";
1372 my $trusterror = sub {
1375 if (defined $bootstrap_src) {
1376 die "mr: $msg in untrusted $bootstrap_src line $lineno\n".
1377 "(To trust this url, --trust-all can be used; but please use caution;\n".
1378 "this can allow arbitrary code execution!)\n";
1381 die "mr: $msg in untrusted $absf line $lineno\n".
1382 "(To trust this file, list it in ~/.mrtrust.)\n";
1389 next if /^\s*\#/ || /^\s*$/;
1391 if (! $trusted && /[[:cntrl:]]/) {
1392 $trusterror->("illegal control character");
1395 if (/^\[([^\]]*)\]\s*$/) {
1399 if (! is_trusted_repo($section) ||
1400 $section eq 'ALIAS' ||
1401 $section eq 'DEFAULT') {
1402 $trusterror->("illegal section \"[$section]\"");
1405 $section=expandenv($section) if $trusted;
1406 if ($section ne 'ALIAS' &&
1407 ! exists $config{$dir}{$section} &&
1408 exists $config{$dir}{DEFAULT}) {
1410 $config{$dir}{$section}={ %{$config{$dir}{DEFAULT}} };
1413 elsif (/^(\w+)\s*=\s*(.*)/) {
1418 while (@lines && $lines[0]=~/^\s(.+)/) {
1425 # Untrusted files can only contain a few
1426 # settings in specific known-safe formats.
1427 if ($parameter eq 'checkout') {
1428 if (! is_trusted_checkout($value)) {
1429 $trusterror->("illegal checkout command \"$value\"");
1432 elsif ($parameter eq 'order') {
1433 # not interpreted as a command, so
1436 elsif ($value eq 'true' || $value eq 'false') {
1437 # skip=true , deleted=true etc are
1441 $trusterror->("illegal setting \"$parameter=$value\"");
1445 if ($parameter eq "include") {
1446 print "mr: including output of \"$value\"\n" if $verbose;
1449 print STDERR "mr: include command exited nonzero ($?)\n";
1452 unshift @lines, @inc;
1456 if (! defined $section) {
1457 $lineerror->("parameter ($parameter) not in section");
1459 if ($section eq 'ALIAS') {
1460 $alias{$parameter}=$value;
1462 elsif ($parameter eq 'lib' or $parameter =~ s/_append$//) {
1463 $config{$dir}{$section}{$parameter}.="\n".$value."\n";
1466 $config{$dir}{$section}{$parameter}=$value;
1467 if ($parameter =~ /.*_(.*)/) {
1468 $knownactions{$1}=1;
1471 $knownactions{$parameter}=1;
1473 if ($parameter eq 'chain' &&
1474 length $dir && $section ne "DEFAULT") {
1475 my $chaindir="$section";
1476 if ($chaindir !~ m!^/!) {
1477 $chaindir=$dir.$chaindir;
1479 if (-e "$chaindir/.mrconfig") {
1480 my $ret=system($value);
1482 if (($? & 127) == 2) {
1483 print STDERR "mr: chain test interrupted\n";
1487 print STDERR "mr: chain test received signal ".($? & 127)."\n";
1491 push @toload, ["$chaindir/.mrconfig", $chaindir];
1498 $lineerror->("parse error");
1502 foreach my $c (@toload) {
1507 sub startingconfig {
1508 %alias=%config=%configfiles=%knownactions=%loaded=();
1509 my $datapos=tell(DATA);
1511 seek(DATA,$datapos,0); # rewind
1516 # the section to modify or add
1517 my $targetsection=shift;
1518 # fields to change in the section
1519 # To remove a field, set its value to "".
1520 my %changefields=@_;
1526 open(my $in, "<", $f) || die "mr: open $f: $!\n";
1531 my $formatfield=sub {
1533 my @value=split(/\n/, shift);
1535 return "$field = ".shift(@value)."\n".
1536 join("", map { "\t$_\n" } @value);
1540 while ($out[$#out] =~ /^\s*$/) {
1541 unshift @blanks, pop @out;
1543 foreach my $field (sort keys %changefields) {
1544 if (length $changefields{$field}) {
1545 push @out, "$field = $changefields{$field}\n";
1546 delete $changefields{$field};
1556 if (/^\s*\#/ || /^\s*$/) {
1559 elsif (/^\[([^\]]*)\]\s*$/) {
1560 if (defined $section &&
1561 $section eq $targetsection) {
1565 $section=expandenv($1);
1569 elsif (/^(\w+)\s*=\s(.*)/) {
1574 while (@lines && $lines[0]=~/^\s(.+)/) {
1580 if ($section eq $targetsection) {
1581 if (exists $changefields{$parameter}) {
1582 if (length $changefields{$parameter}) {
1583 $value=$changefields{$parameter};
1585 delete $changefields{$parameter};
1589 push @out, $formatfield->($parameter, $value);
1593 if (defined $section &&
1594 $section eq $targetsection) {
1597 elsif (%changefields) {
1598 push @out, "\n[$targetsection]\n";
1599 foreach my $field (sort keys %changefields) {
1600 if (length $changefields{$field}) {
1601 push @out, $formatfield->($field, $changefields{$field});
1606 open(my $out, ">", $f) || die "mr: write $f: $!\n";
1614 # actions that do not operate on all repos
1615 if ($action eq 'config') {
1618 elsif ($action eq 'register') {
1621 elsif ($action eq 'bootstrap') {
1624 elsif ($action eq 'remember' ||
1625 $action eq 'offline' ||
1626 $action eq 'online') {
1627 my @repos=selectrepos;
1628 action($action, @{$repos[0]}) if @repos;
1632 if (!$jobs || $jobs > 1) {
1633 mrs($action, selectrepos());
1636 foreach my $repo (selectrepos()) {
1637 record($repo, action($action, @$repo));
1646 SHOWMANFILE="man -f"
1655 if [ ! -e "$MR_PATH" ]; then
1656 error "cannot find program path"
1658 tmp=$(mktemp -t mr.XXXXXXXXXX) || error "mktemp failed"
1659 trap "rm -f $tmp" exit
1660 pod2man -c mr "$MR_PATH" > "$tmp" || error "pod2man failed"
1661 $SHOWMANFILE "$tmp" || error "man failed"
1663 exec($help) || die "exec: $!";
1668 die "mr config: not enough parameters\n";
1671 if ($section=~/^\//) {
1672 # try to convert to a path relative to the config file
1673 my ($dir)=$ENV{MR_CONFIG}=~/^(.*\/)[^\/]+$/;
1674 $dir=abs_path($dir);
1675 $dir.="/" unless $dir=~/\/$/;
1676 if ($section=~/^\Q$dir\E(.*)/) {
1682 if (/^([^=]+)=(.*)$/) {
1683 $changefields{$1}=$2;
1687 foreach my $topdir (sort keys %config) {
1688 if (exists $config{$topdir}{$section} &&
1689 exists $config{$topdir}{$section}{$_}) {
1690 print $config{$topdir}{$section}{$_}."\n";
1692 last if $section eq 'DEFAULT';
1696 die "mr config: $section $_ not set\n";
1700 modifyconfig($ENV{MR_CONFIG}, $section, %changefields) if %changefields;
1705 if ($config_overridden) {
1706 # Find the directory that the specified config file is
1708 ($directory)=abs_path($ENV{MR_CONFIG})=~/^(.*\/)[^\/]+$/;
1711 # Find the closest known mrconfig file to the current
1713 $directory.="/" unless $directory=~/\/$/;
1715 foreach my $topdir (reverse sort keys %config) {
1716 next unless length $topdir;
1717 if ($directory=~/^\Q$topdir\E/) {
1718 $ENV{MR_CONFIG}=$configfiles{$topdir};
1724 if (! $foundconfig) {
1725 $directory=""; # no config file, use builtin
1729 my $subdir=shift @ARGV;
1730 if (! chdir($subdir)) {
1731 print STDERR "mr register: failed to chdir to $subdir: $!\n";
1735 $ENV{MR_REPO}=getcwd();
1736 my $command=findcommand("register", $ENV{MR_REPO}, $directory, 'DEFAULT', 0);
1737 if (! defined $command) {
1738 die "mr register: unknown repository type\n";
1741 $ENV{MR_REPO}=~s/.*\/(.*)/$1/;
1742 $command="set -e; ".$config{$directory}{DEFAULT}{lib}."\n".
1743 "my_action(){ $command\n }; my_action ".
1744 join(" ", map { s/\\/\\\\/g; s/"/\"/g; '"'.$_.'"' } @ARGV);
1745 print "mr register: running >>$command<<\n" if $verbose;
1746 exec($command) || die "exec: $!";
1750 eval q{use File::Copy};
1753 my $src=shift @ARGV;
1754 my $dir=shift @ARGV || ".";
1756 if (! defined $src || ! length $src) {
1757 die "mr: bootstrap requires source\n";
1760 # Retrieve config file.
1761 eval q{use File::Temp};
1763 my $tmpconfig=File::Temp->new();
1764 if ($src =~ m!^[\w\d]+://!) {
1765 # Download the config file to a temporary location.
1767 if ($src =~ m!^ssh://(.*)!) {
1768 @downloader = ("scp", $1, $tmpconfig);
1771 @downloader = ("curl", "-A", "mr", "-L", "-s", $src, "-o", $tmpconfig);
1772 push(@downloader, "-k") if $insecure;
1774 my $status = system(@downloader);
1775 die "mr bootstrap: invalid SSL certificate for $src (consider -k)\n"
1776 if $downloader[0] eq 'curl' && $status >> 8 == 60;
1777 die "mr bootstrap: download of $src failed\n" if $status != 0;
1779 elsif ($src eq '-') {
1780 # Config file is read from stdin.
1781 copy(\*STDIN, $tmpconfig) || die "stdin: $!";
1784 # Config file is local.
1785 die "mr bootstrap: cannot read file '$src'"
1787 copy($src, $tmpconfig) || die "copy: $!";
1790 # Sanity check on destination directory.
1792 system("mkdir", "-p", $dir);
1794 chdir($dir) || die "chdir $dir: $!";
1796 # Special case to handle checkout of the "." repo, which
1797 # would normally be skipped.
1798 my $topdir=abs_path(".")."/";
1799 my @repo=($topdir, $topdir, ".");
1800 loadconfig($tmpconfig, $topdir, $src);
1801 record(\@repo, action("checkout", @repo, 1))
1802 if exists $config{$topdir}{"."}{"checkout"};
1804 if (-e ".mrconfig") {
1805 print STDERR "mr bootstrap: .mrconfig file already exists, not overwriting with $src\n";
1808 move($tmpconfig, ".mrconfig") || die "rename: $!";
1811 # Reload the config file (in case we got a different version)
1812 # and checkout everything else.
1814 loadconfig(".mrconfig");
1815 dispatch("checkout");
1816 @skipped=grep { abs_path($_) ne abs_path($topdir) } @skipped;
1817 showstats("bootstrap");
1821 # alias expansion and command stemming
1824 if (exists $alias{$action}) {
1825 $action=$alias{$action};
1827 if (! exists $knownactions{$action}) {
1828 my @matches = grep { /^\Q$action\E/ }
1829 keys %knownactions, keys %alias;
1830 if (@matches == 1) {
1831 $action=$matches[0];
1833 elsif (@matches == 0) {
1834 die "mr: unknown action \"$action\" (known actions: ".
1835 join(", ", sort keys %knownactions).")\n";
1838 die "mr: ambiguous action \"$action\" (matches: ".
1839 join(", ", @matches).")\n";
1847 while (length $dir) {
1848 if (-e "$dir/.mrconfig") {
1849 return "$dir/.mrconfig";
1851 $dir=~s/\/[^\/]*$//;
1853 return $HOME_MR_CONFIG;
1858 Getopt::Long::Configure("bundling", "no_permute");
1859 my $result=GetOptions(
1860 "d|directory=s" => sub { $directory=abs_path($_[1]) },
1861 "c|config=s" => sub { $ENV{MR_CONFIG}=$_[1]; $config_overridden=1 },
1862 "p|path" => sub { }, # now default, ignore
1863 "f|force" => \$force,
1864 "v|verbose" => \$verbose,
1865 "m|minimal" => \$minimal,
1866 "q|quiet" => \$quiet,
1867 "s|stats" => \$stats,
1868 "k|insecure" => \$insecure,
1869 "i|interactive" => \$interactive,
1870 "n|no-recurse:i" => \$max_depth,
1871 "j|jobs:i" => \$jobs,
1872 "t|trust-all" => \$trust_all,
1874 if (! $result || @ARGV < 1) {
1875 die("Usage: mr [options] action [params ...]\n".
1876 "(Use mr help for man page.)\n");
1879 $ENV{MR_SWITCHES}="";
1880 foreach my $option (@saved) {
1881 last if $option eq $ARGV[0];
1882 $ENV{MR_SWITCHES}.="$option ";
1888 print STDERR "mr: interrupted\n";
1892 # This can happen if it's run in a directory that was removed
1893 # or other strangeness.
1894 if (! defined $directory) {
1895 die("mr: failed to determine working directory\n");
1897 # Make sure MR_CONFIG is an absolute path, but don't use abs_path since
1898 # the config file might be a symlink to elsewhere, and the directory it's
1899 # in is significant.
1900 if ($ENV{MR_CONFIG} !~ /^\//) {
1901 $ENV{MR_CONFIG}=getcwd()."/".$ENV{MR_CONFIG};
1903 # Try to set MR_PATH to the path to the program.
1905 use FindBin qw($Bin $Script);
1906 $ENV{MR_PATH}=$Bin."/".$Script;
1922 help(@ARGV) if $ARGV[0] eq 'help';
1925 loadconfig($HOME_MR_CONFIG);
1926 loadconfig($ENV{MR_CONFIG});
1927 #use Data::Dumper; print Dumper(\%config);
1929 my $action=expandaction(shift @ARGV);
1936 # Finally, some useful actions that mr knows about by default.
1937 # These can be overridden in ~/.mrconfig.
1952 echo "mr (warning): $@" >&2
1958 if [ -z "$1" ] || [ -z "$2" ]; then
1959 error "mr: usage: hours_since action num"
1961 for dir in .git .svn .bzr CVS .hg _darcs _FOSSIL_; do
1962 if [ -e "$MR_REPO/$dir" ]; then
1963 flagfile="$MR_REPO/$dir/.mr_last$1"
1967 if [ -z "$flagfile" ]; then
1968 error "cannot determine flag filename"
1970 delta=`perl -wle 'print -f shift() ? int((-M _) * 24) : 9999' "$flagfile"`
1971 if [ "$delta" -lt "$2" ]; then
1979 LANG=C bzr info | egrep -q '^Checkout'
1982 if [ -d "$MR_REPO" ]; then
1989 svn_test = perl: -d "$ENV{MR_REPO}/.svn"
1990 git_test = perl: -e "$ENV{MR_REPO}/.git"
1991 bzr_test = perl: -d "$ENV{MR_REPO}/.bzr"
1992 cvs_test = perl: -d "$ENV{MR_REPO}/CVS"
1993 hg_test = perl: -d "$ENV{MR_REPO}/.hg"
1994 darcs_test = perl: -d "$ENV{MR_REPO}/_darcs"
1995 fossil_test = perl: -f "$ENV{MR_REPO}/_FOSSIL_"
1996 git_bare_test = perl:
1997 -d "$ENV{MR_REPO}/refs/heads" && -d "$ENV{MR_REPO}/refs/tags" &&
1998 -d "$ENV{MR_REPO}/objects" && -f "$ENV{MR_REPO}/config" &&
1999 `GIT_CONFIG="$ENV{MR_REPO}"/config git config --get core.bare` =~ /true/
2001 -d "$ENV{MR_REPO}/refs/heads" && -d "$ENV{MR_REPO}/refs/tags" &&
2002 -d "$ENV{MR_REPO}/objects" && -f "$ENV{MR_REPO}/config" &&
2003 `GIT_CONFIG="$ENV{MR_REPO}"/config git config --get vcsh.vcsh` =~ /true/
2004 veracity_test = perl: -d "$ENV{MR_REPO}/.sgdrawer"
2006 svn_update = svn update "$@"
2007 git_update = git pull "$@"
2009 if is_bzr_checkout; then
2012 bzr merge --pull "$@"
2014 cvs_update = cvs -q update "$@"
2015 hg_update = hg pull "$@"; hg update "$@"
2016 darcs_update = darcs pull -a "$@"
2017 fossil_update = fossil pull "$@"
2018 vcsh_update = vcsh run "$MR_REPO" git pull "$@"
2019 veracity_update = vv pull "$@" && vv update "$@"
2021 git_fetch = git fetch --all --prune --tags
2022 git_svn_fetch = git svn fetch
2023 darcs_fetch = darcs fetch
2026 svn_status = svn status "$@"
2027 git_status = git status -s "$@" || true; git --no-pager log --branches --not --remotes --simplify-by-decoration --decorate --oneline || true
2028 bzr_status = bzr status --short "$@"; bzr missing
2029 cvs_status = cvs -q status | grep -E '^(File:.*Status:|\?)' | grep -v 'Status: Up-to-date'
2030 hg_status = hg status "$@"; hg summary --quiet | grep -v 'parent: 0:'
2031 darcs_status = darcs whatsnew -ls "$@" || true
2032 fossil_status = fossil changes "$@"
2033 vcsh_status = vcsh run "$MR_REPO" git -c status.relativePaths=false status -s "$@" || true
2034 veracity_status = vv status "$@"
2036 svn_commit = svn commit "$@"
2037 git_commit = git commit -a "$@" && git push --all
2039 if is_bzr_checkout; then
2042 bzr commit "$@" && bzr push
2044 cvs_commit = cvs commit "$@"
2045 hg_commit = hg commit "$@" && hg push
2046 darcs_commit = darcs record -a "$@" && darcs push -a
2047 fossil_commit = fossil commit "$@"
2048 vcsh_commit = vcsh run "$MR_REPO" git commit -a "$@" && vcsh run "$MR_REPO" git push --all
2049 veracity_commit = vv commit "$@" && vv push
2051 git_record = git commit -a "$@"
2053 if is_bzr_checkout; then
2054 bzr commit --local "$@"
2058 hg_record = hg commit "$@"
2059 darcs_record = darcs record -a "$@"
2060 fossil_record = fossil commit "$@"
2061 vcsh_record = vcsh run "$MR_REPO" git commit -a "$@"
2062 veracity_record = vv commit "$@"
2065 git_push = git push "$@"
2066 bzr_push = bzr push "$@"
2068 hg_push = hg push "$@"
2069 darcs_push = darcs push -a "$@"
2070 fossil_push = fossil push "$@"
2071 vcsh_push = vcsh run "$MR_REPO" git push "$@"
2072 veracity_push = vv push "$@"
2074 svn_diff = svn diff "$@"
2075 git_diff = git diff "$@"
2076 bzr_diff = bzr diff "$@"
2077 cvs_diff = cvs -q diff "$@"
2078 hg_diff = hg diff "$@"
2079 darcs_diff = darcs diff -u "$@"
2080 fossil_diff = fossil diff "$@"
2081 vcsh_diff = vcsh run "$MR_REPO" git diff "$@"
2082 veracity_diff = vv diff "$@"
2084 svn_log = svn log "$@"
2085 git_log = git log "$@"
2086 bzr_log = bzr log "$@"
2087 cvs_log = cvs log "$@"
2088 hg_log = hg log "$@"
2089 darcs_log = darcs changes "$@"
2090 git_bare_log = git log "$@"
2091 fossil_log = fossil timeline "$@"
2092 vcsh_log = vcsh run "$MR_REPO" git log "$@"
2093 veracity_log = vv log "$@"
2095 hg_grep = hg grep "$@"
2096 cvs_grep = ack-grep "$@"
2097 svn_grep = ack-grep "$@"
2098 git_svn_grep = git grep "$@"
2099 git_grep = git grep "$@"
2100 bzr_grep = ack-grep "$@"
2101 darcs_grep = ack-grep "$@"
2106 url=`LC_ALL=C svn info . | grep -i '^URL:' | cut -d ' ' -f 2`
2107 if [ -z "$url" ]; then
2108 error "cannot determine svn url"
2110 echo "Registering svn url: $url in $MR_CONFIG"
2111 mr -c "$MR_CONFIG" config "`pwd`" checkout="svn co '$url' '$MR_REPO'"
2113 url="`LC_ALL=C git config --get remote.origin.url`" || true
2114 if [ -z "$url" ]; then
2115 error "cannot determine git url"
2117 echo "Registering git url: $url in $MR_CONFIG"
2118 mr -c "$MR_CONFIG" config "`pwd`" checkout="git clone '$url' '$MR_REPO'"
2120 url="`LC_ALL=C bzr info . | egrep -i 'checkout of branch|parent branch' | awk '{print $NF}' | head -n 1`"
2121 if [ -z "$url" ]; then
2122 error "cannot determine bzr url"
2124 echo "Registering bzr url: $url in $MR_CONFIG"
2125 mr -c "$MR_CONFIG" config "`pwd`" checkout="bzr branch '$url' '$MR_REPO'"
2127 repo=`cat CVS/Repository`
2129 if [ -z "$root" ]; then
2130 error "cannot determine cvs root"
2132 echo "Registering cvs repository $repo at root $root"
2133 mr -c "$MR_CONFIG" config "`pwd`" checkout="cvs -d '$root' co -d '$MR_REPO' '$repo'"
2135 url=`hg showconfig paths.default`
2136 echo "Registering mercurial repo url: $url in $MR_CONFIG"
2137 mr -c "$MR_CONFIG" config "`pwd`" checkout="hg clone '$url' '$MR_REPO'"
2139 url=`cat _darcs/prefs/defaultrepo`
2140 echo "Registering darcs repository $url in $MR_CONFIG"
2141 mr -c "$MR_CONFIG" config "`pwd`" checkout="darcs get '$url' '$MR_REPO'"
2143 url="`LC_ALL=C GIT_CONFIG=config git config --get remote.origin.url`" || true
2144 if [ -z "$url" ]; then
2145 error "cannot determine git url"
2147 echo "Registering git url: $url in $MR_CONFIG"
2148 mr -c "$MR_CONFIG" config "`pwd`" checkout="git clone --bare '$url' '$MR_REPO'"
2150 url="`LC_ALL=C vcsh run "$MR_REPO" git config --get remote.origin.url`" || true
2151 if [ -z "$url" ]; then
2152 error "cannot determine git url"
2154 echo "Registering git url: $url in $MR_CONFIG"
2155 mr -c "$MR_CONFIG" config "`pwd`" checkout="vcsh clone '$url' '$MR_REPO'"
2157 url=`fossil remote-url`
2158 repo=`fossil info | grep repository | sed -e 's/repository:*.//g' -e 's/ //g'`
2159 echo "Registering fossil repository $url in $MR_CONFIG"
2160 mr -c "$MR_CONFIG" config "`pwd`" checkout="mkdir -p '$MR_REPO' && cd '$MR_REPO' && fossil open '$repo'"
2162 url=`vv config | grep sync_targets | sed -e 's/sync_targets:*.//g' -e 's/ //g'`
2163 repo=`vv repo info | grep repository | sed -e 's/Current repository:*.//g' -e 's/ //g'`
2164 echo "Registering veracity repository $url in $MR_CONFIG"
2165 mr -c "$MR_CONFIG" config "`pwd`" checkout="mkdir -p '$MR_REPO' && cd '$MR_REPO' && vv checkout '$repo'"
2167 svn_trusted_checkout = svn co $url $repo
2168 svn_alt_trusted_checkout = svn checkout $url $repo
2169 git_trusted_checkout = git clone $url $repo
2170 bzr_trusted_checkout = bzr checkout|clone|branch|get $url $repo
2172 hg_trusted_checkout = hg clone $url $repo
2173 darcs_trusted_checkout = darcs get $url $repo
2174 git_bare_trusted_checkout = git clone --bare $url $repo
2175 vcsh_old_trusted_checkout = vcsh run "$MR_REPO" git clone $url $repo
2176 vcsh_trusted_checkout = vcsh clone $url $repo
2177 # fossil: messy to do
2178 veracity_trusted_checkout = vv clone $url $repo
2186 if [ -s ~/.mrlog ]; then
2187 info "running offline commands"
2188 mv -f ~/.mrlog ~/.mrlog.old
2189 if ! sh -e ~/.mrlog.old; then
2190 error "offline command failed; left in ~/.mrlog.old"
2194 info "no offline commands to run"
2199 info "offline mode enabled"
2201 info "remembering command: 'mr $@'"
2202 command="mr -d '$(pwd)' $MR_SWITCHES"
2204 command="$command '$w'"
2206 if [ ! -e ~/.mrlog ] || ! grep -q -F "$command" ~/.mrlog; then
2207 echo "$command" >> ~/.mrlog
2210 ed = echo "A horse is a horse, of course, of course.."
2211 T = echo "I pity the fool."
2212 right = echo "Not found."
2214 # vim:sw=8:sts=0:ts=8:noet
2216 # indent-tabs-mode: t
2217 # cperl-indent-level: 8