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);
567 # things that can happen when mr runs a command
576 my $config_overridden=0;
588 my $directory=getcwd();
589 my $terminal=-t STDOUT && eval{require IO::Pty::Easy;IO::Pty::Easy->import();1;} eq 1;
591 my $HOME_MR_CONFIG = "$ENV{HOME}/.mrconfig";
592 $ENV{MR_CONFIG}=find_mrconfig();
599 my (@ok, @failed, @skipped);
609 # Runs a shell command using a supplied function.
610 # The lib will be included in the shell command line, and any params
611 # will be available in the shell as $1, $2, etc.
614 my ($action, $topdir, $subdir, $command, $params, $runner) = @_;
616 # optimisation: avoid running the shell for true and false
617 if ($command =~ /^\s*true\s*$/) {
621 elsif ($command =~ /^\s*false\s*$/) {
626 my $quotedparams=join(" ", (map { shellquote($_) } @$params));
627 my $lib=exists $config{$topdir}{$subdir}{lib} ?
628 $config{$topdir}{$subdir}{lib}."\n" : "";
629 if ($verbose && (! defined $lastlib || $lastlib ne $lib)) {
630 print "mr library now: >>$lib<<\n";
633 my $shellcode="set -e;".$lib.
634 "my_sh(){ $command\n }; my_sh $quotedparams";
635 print "mr $action: running $action >>$command<<\n" if $verbose;
636 $runner->($shellcode);
643 if ($s =~ m/^perl:\s+(.*)/s) {
644 return $perl_cache{$1} if exists $perl_cache{$1};
645 my $sub=eval "sub {$1}";
646 if (! defined $sub) {
647 print STDERR "mr: bad perl code in $id: $@\n";
649 return $perl_cache{$1} = $sub;
656 my ($action, $dir, $topdir, $subdir) = @_;
658 if (exists $vcs{$dir}) {
664 foreach my $vcs_test (
666 length $a <=> length $b
669 } grep { /_test$/ } keys %{$config{$topdir}{$subdir}}) {
670 my ($vcs)=$vcs_test =~ /(.*)_test/;
671 my $p=perl($vcs_test, $config{$topdir}{$subdir}{$vcs_test});
676 $test="my_$vcs_test() {\n$config{$topdir}{$subdir}{$vcs_test}\n}\n".$test;
677 $test.="if my_$vcs_test; then echo $vcs; fi\n";
682 foreach my $vcs (keys %perltest) {
683 if ($perltest{$vcs}->()) {
688 push @vcs, split(/\n/,
689 runsh("vcs test", $topdir, $subdir, $test, [], sub {
695 print STDERR "mr $action: found multiple possible repository types (@vcs) for ".fulldir($topdir, $subdir)."\n";
699 return $vcs{$dir}=undef;
702 return $vcs{$dir}=$vcs[0];
707 my ($action, $dir, $topdir, $subdir, $is_checkout) = @_;
709 if (exists $config{$topdir}{$subdir}{$action}) {
710 return $config{$topdir}{$subdir}{$action};
717 my $vcs=vcs_test(@_);
720 exists $config{$topdir}{$subdir}{$vcs."_".$action}) {
721 return $config{$topdir}{$subdir}{$vcs."_".$action};
729 my ($topdir, $subdir) = @_;
730 return $subdir =~ /^\// ? $subdir : $topdir.$subdir;
733 sub terminal_friendly_spawn {
734 my $actionmsg = shift;
740 my $pty = IO::Pty::Easy->new;
742 while ($pty->is_active) {
743 my $data = $pty->read();
744 $output .= $data if defined $data;
748 $output = qx/$sh 2>&1/;
751 if ($quiet && $ret != 0) {
752 print "$actionmsg\n" if $actionmsg;
753 print STDERR $output;
754 } elsif (!$quiet && (!$minimal || $output)) {
755 print "$actionmsg\n" if $actionmsg;
758 return ($ret, $output ? 1 : 0);
762 my ($action, $dir, $topdir, $subdir, $force_checkout) = @_;
763 my $fulldir=fulldir($topdir, $subdir);
766 $ENV{MR_CONFIG}=$configfiles{$topdir};
767 my $is_checkout=($action eq 'checkout');
768 my $is_update=($action =~ /update/);
770 ($ENV{MR_REPO}=$dir) =~ s!/$!!;
771 $ENV{MR_ACTION}=$action;
773 foreach my $testname ("skip", "deleted") {
774 next if $force && $testname eq "skip";
776 my $testcommand=findcommand($testname, $dir, $topdir, $subdir, $is_checkout);
778 if (defined $testcommand) {
779 my $ret=runsh "$testname test", $topdir, $subdir,
780 $testcommand, [$action],
781 sub { system(shift()) };
783 if (($? & 127) == 2) {
784 print STDERR "mr $action: interrupted\n";
788 print STDERR "mr $action: $testname test received signal ".($? & 127)."\n";
792 if ($ret >> 8 == 0) {
793 if ($testname eq "deleted") {
795 print STDERR "mr error: $dir should be deleted yet still exists\n";
799 print "mr $action: skip $dir skipped\n" if $verbose;
807 if (! $force_checkout) {
809 print "mr $action: $dir already exists, skipping checkout\n" if $verbose;
813 $dir=~s/^(.*)\/[^\/]+\/?$/$1/;
818 return action("checkout", $dir, $topdir, $subdir);
822 my $command=findcommand($action, $dir, $topdir, $subdir, $is_checkout);
824 if ($is_checkout && ! -d $dir) {
825 print "mr $action: creating parent directory $dir\n" if $verbose;
826 system("mkdir", "-p", $dir);
829 if (! $no_chdir && ! chdir($dir)) {
830 print STDERR "mr $action: failed to chdir to $dir: $!\n";
833 elsif (! defined $command) {
834 my $vcs=vcs_test(@_);
835 if (! defined $vcs) {
836 print STDERR "mr $action: unknown repository type and no defined $action command for $fulldir\n";
840 print STDERR "mr $action: no defined action for $vcs repository $fulldir, skipping\n" unless $minimal;
847 $actionmsg="mr $action: $fulldir";
851 $s=~s/^\Q$fulldir\E\/?//;
852 $actionmsg="mr $action: $fulldir (in subdir $s)";
854 print "$actionmsg\n" unless $quiet || $minimal;
856 my $hookret=hook("pre_$action", $topdir, $subdir);
857 return $hookret if $hookret != OK;
859 my ($ret, $out)=runsh $action, $topdir, $subdir,
860 $command, \@ARGV, sub {
862 if (!$jobs || $jobs > 1 || $quiet || $minimal) {
863 return terminal_friendly_spawn($actionmsg, $sh, $quiet, $minimal);
870 if (($? & 127) == 2) {
871 print STDERR "mr $action: interrupted\n";
875 print STDERR "mr $action: received signal ".($? & 127)."\n";
878 print STDERR "mr $action: failed ($ret)\n" if $verbose;
879 if ($ret >> 8 != 0) {
880 print STDERR "mr $action: command failed\n";
881 if (-e "$ENV{HOME}/.mrlog" && $action ne 'remember') {
882 # recreate original command line to
883 # remember, and avoid recursing
885 @ARGV=('-n', $action, @orig);
886 action("remember", $dir, $topdir, $subdir);
891 print STDERR "mr $action: command died ($ret)\n";
896 if ($is_checkout && ! -d $dir) {
897 print STDERR "mr $action: $dir missing after checkout\n";;
901 my ($ret, $hook_out)=hook("post_$action", $topdir, $subdir);
902 return $ret if $ret != OK;
904 if ($is_checkout || $is_update) {
905 if ($is_checkout && ! $no_chdir) {
906 if (! chdir($checkout_dir)) {
907 print STDERR "mr $action: failed to chdir to $checkout_dir: $!\n";
911 my $ret=hook("fixups", $topdir, $subdir);
912 return $ret if $ret != OK;
915 return (OK, $out || $hook_out);
921 my ($hook, $topdir, $subdir) = @_;
923 my $command=$config{$topdir}{$subdir}{$hook};
924 return OK unless defined $command;
925 my ($ret,$out)=runsh $hook, $topdir, $subdir, $command, [], sub {
927 if (!$jobs || $jobs > 1 || $quiet || $minimal) {
928 return terminal_friendly_spawn(undef, $sh, $quiet, $minimal);
935 if (($? & 127) == 2) {
936 print STDERR "mr $hook: interrupted\n";
940 print STDERR "mr $hook: received signal ".($? & 127)."\n";
951 # run actions on multiple repos, in parallel
961 while (@fhs or @repos) {
962 while ((!$jobs || $running < $jobs) && @repos) {
964 my $repo = shift @repos;
965 pipe(my $outfh, CHILD_STDOUT);
966 pipe(my $errfh, CHILD_STDERR);
968 unless ($pid = fork) {
969 die "mr $action: cannot fork: $!" unless defined $pid;
970 open(STDOUT, ">&CHILD_STDOUT") || die "mr $action cannot reopen stdout: $!";
971 open(STDERR, ">&CHILD_STDERR") || die "mr $action cannot reopen stderr: $!";
976 exit +(action($action, @$repo))[0];
980 push @active, [$pid, $repo];
981 push @fhs, [$outfh, $errfh];
984 my ($rin, $rout) = ('','');
986 foreach my $fh (@fhs) {
987 next unless defined $fh;
988 vec($rin, fileno($fh->[0]), 1) = 1 if defined $fh->[0];
989 vec($rin, fileno($fh->[1]), 1) = 1 if defined $fh->[1];
991 $nfound = select($rout=$rin, undef, undef, 1);
992 foreach my $channel (0, 1) {
993 foreach my $i (0..$#fhs) {
994 next unless defined $fhs[$i];
995 my $fh = $fhs[$i][$channel];
996 next unless defined $fh;
997 if (vec($rout, fileno($fh), 1) == 1) {
999 if (sysread($fh, $r, 1024) == 0) {
1001 $fhs[$i][$channel] = undef;
1002 if (! defined $fhs[$i][0] &&
1003 ! defined $fhs[$i][1]) {
1004 waitpid($active[$i][0], 0);
1005 print STDOUT $out[$i][0];
1006 print STDERR $out[$i][1];
1007 record($active[$i][1], $? >> 8, $out[$i][0] || $out[$i][1]);
1008 splice(@fhs, $i, 1);
1009 splice(@active, $i, 1);
1010 splice(@out, $i, 1);
1014 $out[$i][$channel] .= $r;
1022 my $dir=shift()->[0];
1028 print "\n" unless $quiet || ($minimal && !$out);
1030 elsif ($ret == FAILED) {
1032 chdir($dir) unless $no_chdir;
1033 print STDERR "mr: Starting interactive shell. Exit shell to continue.\n";
1034 system((getpwuid($<))[8], "-i");
1039 elsif ($ret == SKIPPED) {
1040 push @skipped, $dir;
1042 elsif ($ret == ABORT) {
1046 die "unknown exit status $ret";
1052 if (! @ok && ! @failed && ! @skipped) {
1053 die "mr $action: no repositories found to work on\n";
1055 print "mr $action: finished (".join("; ",
1056 showstat($#ok+1, "ok", "ok"),
1057 showstat($#failed+1, "failed", "failed"),
1058 showstat($#skipped+1, "skipped", "skipped"),
1059 ).")\n" unless $quiet || $minimal;
1062 print "mr $action: (skipped: ".join(" ", @skipped).")\n" unless $quiet || $minimal;
1065 print STDERR "mr $action: (failed: ".join(" ", @failed).")\n";
1075 return "$count ".($count > 1 ? $plural : $singular);
1080 # an ordered list of repos
1083 foreach my $topdir (sort keys %config) {
1084 foreach my $subdir (sort keys %{$config{$topdir}}) {
1088 order => $config{$topdir}{$subdir}{order},
1093 $a->{order} <=> $b->{order}
1095 $a->{topdir} cmp $b->{topdir}
1097 $a->{subdir} cmp $b->{subdir}
1103 my $topdir=$repo->{topdir};
1104 my $subdir=$repo->{subdir};
1105 my $ret=($subdir =~/^\//) ? $subdir : $topdir.$subdir;
1110 # Figure out which repos to act on. Returns a list of array refs
1113 # [ "$full_repo_path/", "$mr_config_path/", $section_header ]
1116 foreach my $repo (repolist()) {
1117 my $topdir=$repo->{topdir};
1118 my $subdir=$repo->{subdir};
1120 next if $subdir eq 'DEFAULT';
1121 my $dir=repodir($repo);
1123 $dir.="/" unless $dir=~/\/$/;
1124 $d.="/" unless $d=~/\/$/;
1125 next if $dir ne $d && $dir !~ /^\Q$d\E/;
1126 if (defined $max_depth) {
1127 my @a=split('/', $dir);
1128 my @b=split('/', $d);
1129 do { } while (@a && @b && shift(@a) eq shift(@b));
1130 next if @a > $max_depth || @b > $max_depth;
1132 push @repos, [$dir, $topdir, $subdir];
1135 # fallback to find a leaf repo
1136 foreach my $repo (reverse repolist()) {
1137 my $topdir=$repo->{topdir};
1138 my $subdir=$repo->{subdir};
1140 next if $subdir eq 'DEFAULT';
1141 my $dir=repodir($repo);
1143 $dir.="/" unless $dir=~/\/$/;
1144 $d.="/" unless $d=~/\/$/;
1145 if ($d=~/^\Q$dir\E/) {
1146 push @repos, [$dir, $topdir, $subdir];
1168 sub is_trusted_config {
1169 my $config=shift; # must be abs_pathed already
1171 # We always trust ~/.mrconfig.
1172 return 1 if $config eq abs_path($HOME_MR_CONFIG);
1174 return 1 if $trust_all;
1176 my $trustfile=$ENV{HOME}."/.mrtrust";
1179 $trusted{$HOME_MR_CONFIG}=1;
1180 if (open (TRUST, "<", $trustfile)) {
1183 s/^~\//$ENV{HOME}\//;
1185 $trusted{$d}=1 if defined $d;
1191 return $trusted{$config};
1195 sub is_trusted_repo {
1198 # Tightly limit what is allowed in a repo name.
1199 # No ../, no absolute paths, and no unusual filenames
1200 # that might try to escape to the shell.
1201 return $repo =~ /^[-_.+\/A-Za-z0-9]+$/ &&
1202 $repo !~ /\.\./ && $repo !~ /^\//;
1205 sub is_trusted_checkout {
1208 # To determine if the command is safe, compare it with the
1209 # *_trusted_checkout config settings. Those settings are
1210 # templates for allowed commands, so make sure that each word
1211 # of the command matches the corresponding word of the template.
1214 foreach my $word (split(' ', $command)) {
1216 if ($word=~/^'(.*)'$/) {
1219 elsif ($word=~/^"(.*)"$/) {
1226 foreach my $key (grep { /_trusted_checkout$/ }
1227 keys %{$config{''}{DEFAULT}}) {
1228 my @twords=split(' ', $config{''}{DEFAULT}{$key});
1229 next if @words > @twords;
1233 for (my $c=0; $c < @twords && $match; $c++) {
1234 if ($twords[$c] eq '$url') {
1235 # Match all the typical characters found in
1236 # urls, plus @ which svn can use. Note
1237 # that the "url" might also be a local
1240 defined $words[$c] &&
1241 $words[$c] =~ /^[-_.+:@\/A-Za-z0-9]+$/
1245 elsif ($twords[$c] eq '$repo') {
1246 # If a repo is not specified, assume it
1247 # will be the last path component of the
1248 # url, or something derived from it, and
1250 if (! defined $words[$c] && defined $url) {
1251 ($words[$c])=$url=~/\/([^\/]+)\/?$/;
1255 defined $words[$c] &&
1256 is_trusted_repo($words[$c])
1259 elsif (defined $words[$c] && $words[$c]=~/^($twords[$c])$/) {
1276 my $bootstrap_src=shift;
1282 if (ref $f eq 'GLOB') {
1288 my $absf=abs_path($f);
1289 if ($loaded{$absf}) {
1294 $trusted=is_trusted_config($absf);
1296 if (! defined $dir) {
1297 ($dir)=$f=~/^(.*\/)[^\/]+$/;
1298 if (! defined $dir) {
1303 $dir=abs_path($dir)."/";
1305 if (! exists $configfiles{$dir}) {
1306 $configfiles{$dir}=$f;
1309 # copy in defaults from first parent
1311 while ($parent=~s/^(.*\/)[^\/]+\/?$/$1/) {
1312 if ($parent eq '/') {
1315 if (exists $config{$parent} &&
1316 exists $config{$parent}{DEFAULT}) {
1317 $config{$dir}{DEFAULT}={ %{$config{$parent}{DEFAULT}} };
1326 print "mr: loading config $f\n" if $verbose;
1327 open($in, "<", $f) || die "mr: open $f: $!\n";
1330 close $in unless ref $f eq 'GLOB';
1334 # Keep track of the current line in the config file;
1335 # when a file is included track the current line from the include.
1340 my $nextline = sub {
1352 my $lineerror = sub {
1354 if (defined $included) {
1355 die "mr: $msg at $f line $lineno, included line: $line\n";
1358 die "mr: $msg at $f line $lineno\n";
1361 my $trusterror = sub {
1364 if (defined $bootstrap_src) {
1365 die "mr: $msg in untrusted $bootstrap_src line $lineno\n".
1366 "(To trust this url, --trust-all can be used; but please use caution;\n".
1367 "this can allow arbitrary code execution!)\n";
1370 die "mr: $msg in untrusted $f line $lineno\n".
1371 "(To trust this file, list it in ~/.mrtrust.)\n";
1378 next if /^\s*\#/ || /^\s*$/;
1380 if (! $trusted && /[[:cntrl:]]/) {
1381 $trusterror->("illegal control character");
1384 if (/^\[([^\]]*)\]\s*$/) {
1388 if (! is_trusted_repo($section) ||
1389 $section eq 'ALIAS' ||
1390 $section eq 'DEFAULT') {
1391 $trusterror->("illegal section \"[$section]\"");
1394 $section=expandenv($section) if $trusted;
1395 if ($section ne 'ALIAS' &&
1396 ! exists $config{$dir}{$section} &&
1397 exists $config{$dir}{DEFAULT}) {
1399 $config{$dir}{$section}={ %{$config{$dir}{DEFAULT}} };
1402 elsif (/^(\w+)\s*=\s*(.*)/) {
1407 while (@lines && $lines[0]=~/^\s(.+)/) {
1414 # Untrusted files can only contain a few
1415 # settings in specific known-safe formats.
1416 if ($parameter eq 'checkout') {
1417 if (! is_trusted_checkout($value)) {
1418 $trusterror->("illegal checkout command \"$value\"");
1421 elsif ($parameter eq 'order') {
1422 # not interpreted as a command, so
1425 elsif ($value eq 'true' || $value eq 'false') {
1426 # skip=true , deleted=true etc are
1430 $trusterror->("illegal setting \"$parameter=$value\"");
1434 if ($parameter eq "include") {
1435 print "mr: including output of \"$value\"\n" if $verbose;
1438 print STDERR "mr: include command exited nonzero ($?)\n";
1441 unshift @lines, @inc;
1445 if (! defined $section) {
1446 $lineerror->("parameter ($parameter) not in section");
1448 if ($section eq 'ALIAS') {
1449 $alias{$parameter}=$value;
1451 elsif ($parameter eq 'lib' or $parameter =~ s/_append$//) {
1452 $config{$dir}{$section}{$parameter}.="\n".$value."\n";
1455 $config{$dir}{$section}{$parameter}=$value;
1456 if ($parameter =~ /.*_(.*)/) {
1457 $knownactions{$1}=1;
1460 $knownactions{$parameter}=1;
1462 if ($parameter eq 'chain' &&
1463 length $dir && $section ne "DEFAULT") {
1464 my $chaindir="$section";
1465 if ($chaindir !~ m!^/!) {
1466 $chaindir=$dir.$chaindir;
1468 if (-e "$chaindir/.mrconfig") {
1469 my $ret=system($value);
1471 if (($? & 127) == 2) {
1472 print STDERR "mr: chain test interrupted\n";
1476 print STDERR "mr: chain test received signal ".($? & 127)."\n";
1480 push @toload, ["$chaindir/.mrconfig", $chaindir];
1487 $lineerror->("parse error");
1491 foreach my $c (@toload) {
1496 sub startingconfig {
1497 %alias=%config=%configfiles=%knownactions=%loaded=();
1498 my $datapos=tell(DATA);
1500 seek(DATA,$datapos,0); # rewind
1505 # the section to modify or add
1506 my $targetsection=shift;
1507 # fields to change in the section
1508 # To remove a field, set its value to "".
1509 my %changefields=@_;
1515 open(my $in, "<", $f) || die "mr: open $f: $!\n";
1520 my $formatfield=sub {
1522 my @value=split(/\n/, shift);
1524 return "$field = ".shift(@value)."\n".
1525 join("", map { "\t$_\n" } @value);
1529 while ($out[$#out] =~ /^\s*$/) {
1530 unshift @blanks, pop @out;
1532 foreach my $field (sort keys %changefields) {
1533 if (length $changefields{$field}) {
1534 push @out, "$field = $changefields{$field}\n";
1535 delete $changefields{$field};
1545 if (/^\s*\#/ || /^\s*$/) {
1548 elsif (/^\[([^\]]*)\]\s*$/) {
1549 if (defined $section &&
1550 $section eq $targetsection) {
1554 $section=expandenv($1);
1558 elsif (/^(\w+)\s*=\s(.*)/) {
1563 while (@lines && $lines[0]=~/^\s(.+)/) {
1569 if ($section eq $targetsection) {
1570 if (exists $changefields{$parameter}) {
1571 if (length $changefields{$parameter}) {
1572 $value=$changefields{$parameter};
1574 delete $changefields{$parameter};
1578 push @out, $formatfield->($parameter, $value);
1582 if (defined $section &&
1583 $section eq $targetsection) {
1586 elsif (%changefields) {
1587 push @out, "\n[$targetsection]\n";
1588 foreach my $field (sort keys %changefields) {
1589 if (length $changefields{$field}) {
1590 push @out, $formatfield->($field, $changefields{$field});
1595 open(my $out, ">", $f) || die "mr: write $f: $!\n";
1603 # actions that do not operate on all repos
1604 if ($action eq 'config') {
1607 elsif ($action eq 'register') {
1610 elsif ($action eq 'bootstrap') {
1613 elsif ($action eq 'remember' ||
1614 $action eq 'offline' ||
1615 $action eq 'online') {
1616 my @repos=selectrepos;
1617 action($action, @{$repos[0]}) if @repos;
1621 if (!$jobs || $jobs > 1) {
1622 mrs($action, selectrepos());
1625 foreach my $repo (selectrepos()) {
1626 record($repo, action($action, @$repo));
1635 SHOWMANFILE="man -f"
1644 if [ ! -e "$MR_PATH" ]; then
1645 error "cannot find program path"
1647 tmp=$(mktemp -t mr.XXXXXXXXXX) || error "mktemp failed"
1648 trap "rm -f $tmp" exit
1649 pod2man -c mr "$MR_PATH" > "$tmp" || error "pod2man failed"
1650 $SHOWMANFILE "$tmp" || error "man failed"
1652 exec($help) || die "exec: $!";
1657 die "mr config: not enough parameters\n";
1660 if ($section=~/^\//) {
1661 # try to convert to a path relative to the config file
1662 my ($dir)=$ENV{MR_CONFIG}=~/^(.*\/)[^\/]+$/;
1663 $dir=abs_path($dir);
1664 $dir.="/" unless $dir=~/\/$/;
1665 if ($section=~/^\Q$dir\E(.*)/) {
1671 if (/^([^=]+)=(.*)$/) {
1672 $changefields{$1}=$2;
1676 foreach my $topdir (sort keys %config) {
1677 if (exists $config{$topdir}{$section} &&
1678 exists $config{$topdir}{$section}{$_}) {
1679 print $config{$topdir}{$section}{$_}."\n";
1681 last if $section eq 'DEFAULT';
1685 die "mr config: $section $_ not set\n";
1689 modifyconfig($ENV{MR_CONFIG}, $section, %changefields) if %changefields;
1694 if ($config_overridden) {
1695 # Find the directory that the specified config file is
1697 ($directory)=abs_path($ENV{MR_CONFIG})=~/^(.*\/)[^\/]+$/;
1700 # Find the closest known mrconfig file to the current
1702 $directory.="/" unless $directory=~/\/$/;
1704 foreach my $topdir (reverse sort keys %config) {
1705 next unless length $topdir;
1706 if ($directory=~/^\Q$topdir\E/) {
1707 $ENV{MR_CONFIG}=$configfiles{$topdir};
1713 if (! $foundconfig) {
1714 $directory=""; # no config file, use builtin
1718 my $subdir=shift @ARGV;
1719 if (! chdir($subdir)) {
1720 print STDERR "mr register: failed to chdir to $subdir: $!\n";
1724 $ENV{MR_REPO}=getcwd();
1725 my $command=findcommand("register", $ENV{MR_REPO}, $directory, 'DEFAULT', 0);
1726 if (! defined $command) {
1727 die "mr register: unknown repository type\n";
1730 $ENV{MR_REPO}=~s/.*\/(.*)/$1/;
1731 $command="set -e; ".$config{$directory}{DEFAULT}{lib}."\n".
1732 "my_action(){ $command\n }; my_action ".
1733 join(" ", map { s/\\/\\\\/g; s/"/\"/g; '"'.$_.'"' } @ARGV);
1734 print "mr register: running >>$command<<\n" if $verbose;
1735 exec($command) || die "exec: $!";
1739 eval q{use File::Copy};
1742 my $src=shift @ARGV;
1743 my $dir=shift @ARGV || ".";
1745 if (! defined $src || ! length $src) {
1746 die "mr: bootstrap requires source\n";
1749 # Retrieve config file.
1750 eval q{use File::Temp};
1752 my $tmpconfig=File::Temp->new();
1753 if ($src =~ m!^[\w\d]+://!) {
1754 # Download the config file to a temporary location.
1756 if ($src =~ m!^ssh://(.*)!) {
1757 @downloader = ("scp", $1, $tmpconfig);
1760 @downloader = ("curl", "-A", "mr", "-L", "-s", $src, "-o", $tmpconfig);
1761 push(@downloader, "-k") if $insecure;
1763 my $status = system(@downloader);
1764 die "mr bootstrap: invalid SSL certificate for $src (consider -k)\n"
1765 if $downloader[0] eq 'curl' && $status >> 8 == 60;
1766 die "mr bootstrap: download of $src failed\n" if $status != 0;
1768 elsif ($src eq '-') {
1769 # Config file is read from stdin.
1770 copy(\*STDIN, $tmpconfig) || die "stdin: $!";
1773 # Config file is local.
1774 die "mr bootstrap: cannot read file '$src'"
1776 copy($src, $tmpconfig) || die "copy: $!";
1779 # Sanity check on destination directory.
1781 system("mkdir", "-p", $dir);
1783 chdir($dir) || die "chdir $dir: $!";
1785 # Special case to handle checkout of the "." repo, which
1786 # would normally be skipped.
1787 my $topdir=abs_path(".")."/";
1788 my @repo=($topdir, $topdir, ".");
1789 loadconfig($tmpconfig, $topdir, $src);
1790 record(\@repo, action("checkout", @repo, 1))
1791 if exists $config{$topdir}{"."}{"checkout"};
1793 if (-e ".mrconfig") {
1794 print STDERR "mr bootstrap: .mrconfig file already exists, not overwriting with $src\n";
1797 move($tmpconfig, ".mrconfig") || die "rename: $!";
1800 # Reload the config file (in case we got a different version)
1801 # and checkout everything else.
1803 loadconfig(".mrconfig");
1804 dispatch("checkout");
1805 @skipped=grep { abs_path($_) ne abs_path($topdir) } @skipped;
1806 showstats("bootstrap");
1810 # alias expansion and command stemming
1813 if (exists $alias{$action}) {
1814 $action=$alias{$action};
1816 if (! exists $knownactions{$action}) {
1817 my @matches = grep { /^\Q$action\E/ }
1818 keys %knownactions, keys %alias;
1819 if (@matches == 1) {
1820 $action=$matches[0];
1822 elsif (@matches == 0) {
1823 die "mr: unknown action \"$action\" (known actions: ".
1824 join(", ", sort keys %knownactions).")\n";
1827 die "mr: ambiguous action \"$action\" (matches: ".
1828 join(", ", @matches).")\n";
1836 while (length $dir) {
1837 if (-e "$dir/.mrconfig") {
1838 return "$dir/.mrconfig";
1840 $dir=~s/\/[^\/]*$//;
1842 return $HOME_MR_CONFIG;
1847 Getopt::Long::Configure("bundling", "no_permute");
1848 my $result=GetOptions(
1849 "d|directory=s" => sub { $directory=abs_path($_[1]) },
1850 "c|config=s" => sub { $ENV{MR_CONFIG}=$_[1]; $config_overridden=1 },
1851 "p|path" => sub { }, # now default, ignore
1852 "f|force" => \$force,
1853 "v|verbose" => \$verbose,
1854 "m|minimal" => \$minimal,
1855 "q|quiet" => \$quiet,
1856 "s|stats" => \$stats,
1857 "k|insecure" => \$insecure,
1858 "i|interactive" => \$interactive,
1859 "n|no-recurse:i" => \$max_depth,
1860 "j|jobs:i" => \$jobs,
1861 "t|trust-all" => \$trust_all,
1863 if (! $result || @ARGV < 1) {
1864 die("Usage: mr [options] action [params ...]\n".
1865 "(Use mr help for man page.)\n");
1868 $ENV{MR_SWITCHES}="";
1869 foreach my $option (@saved) {
1870 last if $option eq $ARGV[0];
1871 $ENV{MR_SWITCHES}.="$option ";
1877 print STDERR "mr: interrupted\n";
1881 # This can happen if it's run in a directory that was removed
1882 # or other strangeness.
1883 if (! defined $directory) {
1884 die("mr: failed to determine working directory\n");
1886 # Make sure MR_CONFIG is an absolute path, but don't use abs_path since
1887 # the config file might be a symlink to elsewhere, and the directory it's
1888 # in is significant.
1889 if ($ENV{MR_CONFIG} !~ /^\//) {
1890 $ENV{MR_CONFIG}=getcwd()."/".$ENV{MR_CONFIG};
1892 # Try to set MR_PATH to the path to the program.
1894 use FindBin qw($Bin $Script);
1895 $ENV{MR_PATH}=$Bin."/".$Script;
1911 help(@ARGV) if $ARGV[0] eq 'help';
1914 loadconfig($HOME_MR_CONFIG);
1915 loadconfig($ENV{MR_CONFIG});
1916 #use Data::Dumper; print Dumper(\%config);
1918 my $action=expandaction(shift @ARGV);
1925 # Finally, some useful actions that mr knows about by default.
1926 # These can be overridden in ~/.mrconfig.
1941 echo "mr (warning): $@" >&2
1947 if [ -z "$1" ] || [ -z "$2" ]; then
1948 error "mr: usage: hours_since action num"
1950 for dir in .git .svn .bzr CVS .hg _darcs _FOSSIL_; do
1951 if [ -e "$MR_REPO/$dir" ]; then
1952 flagfile="$MR_REPO/$dir/.mr_last$1"
1956 if [ -z "$flagfile" ]; then
1957 error "cannot determine flag filename"
1959 delta=`perl -wle 'print -f shift() ? int((-M _) * 24) : 9999' "$flagfile"`
1960 if [ "$delta" -lt "$2" ]; then
1968 LANG=C bzr info | egrep -q '^Checkout'
1971 if [ -d "$MR_REPO" ]; then
1978 svn_test = perl: -d "$ENV{MR_REPO}/.svn"
1979 git_test = perl: -e "$ENV{MR_REPO}/.git"
1980 bzr_test = perl: -d "$ENV{MR_REPO}/.bzr"
1981 cvs_test = perl: -d "$ENV{MR_REPO}/CVS"
1982 hg_test = perl: -d "$ENV{MR_REPO}/.hg"
1983 darcs_test = perl: -d "$ENV{MR_REPO}/_darcs"
1984 fossil_test = perl: -f "$ENV{MR_REPO}/_FOSSIL_"
1985 git_bare_test = perl:
1986 -d "$ENV{MR_REPO}/refs/heads" && -d "$ENV{MR_REPO}/refs/tags" &&
1987 -d "$ENV{MR_REPO}/objects" && -f "$ENV{MR_REPO}/config" &&
1988 `GIT_CONFIG="$ENV{MR_REPO}"/config git config --get core.bare` =~ /true/
1990 -d "$ENV{MR_REPO}/refs/heads" && -d "$ENV{MR_REPO}/refs/tags" &&
1991 -d "$ENV{MR_REPO}/objects" && -f "$ENV{MR_REPO}/config" &&
1992 `GIT_CONFIG="$ENV{MR_REPO}"/config git config --get vcsh.vcsh` =~ /true/
1993 veracity_test = perl: -d "$ENV{MR_REPO}/.sgdrawer"
1995 svn_update = svn update "$@"
1996 git_update = git pull "$@"
1998 if is_bzr_checkout; then
2001 bzr merge --pull "$@"
2003 cvs_update = cvs -q update "$@"
2004 hg_update = hg pull "$@"; hg update "$@"
2005 darcs_update = darcs pull -a "$@"
2006 fossil_update = fossil pull "$@"
2007 vcsh_update = vcsh run "$MR_REPO" git pull "$@"
2008 veracity_update = vv pull "$@" && vv update "$@"
2010 git_fetch = git fetch --all --prune --tags
2011 git_svn_fetch = git svn fetch
2012 darcs_fetch = darcs fetch
2015 svn_status = svn status "$@"
2016 git_status = git status -s "$@" || true; git --no-pager log --branches --not --remotes --simplify-by-decoration --decorate --oneline || true
2017 bzr_status = bzr status --short "$@"; bzr missing
2018 cvs_status = cvs -q status | grep -E '^(File:.*Status:|\?)' | grep -v 'Status: Up-to-date'
2019 hg_status = hg status "$@"; hg summary --quiet | grep -v 'parent: 0:'
2020 darcs_status = darcs whatsnew -ls "$@" || true
2021 fossil_status = fossil changes "$@"
2022 vcsh_status = vcsh run "$MR_REPO" git -c status.relativePaths=false status -s "$@" || true
2023 veracity_status = vv status "$@"
2025 svn_commit = svn commit "$@"
2026 git_commit = git commit -a "$@" && git push --all
2028 if is_bzr_checkout; then
2031 bzr commit "$@" && bzr push
2033 cvs_commit = cvs commit "$@"
2034 hg_commit = hg commit "$@" && hg push
2035 darcs_commit = darcs record -a "$@" && darcs push -a
2036 fossil_commit = fossil commit "$@"
2037 vcsh_commit = vcsh run "$MR_REPO" git commit -a "$@" && vcsh run "$MR_REPO" git push --all
2038 veracity_commit = vv commit "$@" && vv push
2040 git_record = git commit -a "$@"
2042 if is_bzr_checkout; then
2043 bzr commit --local "$@"
2047 hg_record = hg commit "$@"
2048 darcs_record = darcs record -a "$@"
2049 fossil_record = fossil commit "$@"
2050 vcsh_record = vcsh run "$MR_REPO" git commit -a "$@"
2051 veracity_record = vv commit "$@"
2054 git_push = git push "$@"
2055 bzr_push = bzr push "$@"
2057 hg_push = hg push "$@"
2058 darcs_push = darcs push -a "$@"
2059 fossil_push = fossil push "$@"
2060 vcsh_push = vcsh run "$MR_REPO" git push "$@"
2061 veracity_push = vv push "$@"
2063 svn_diff = svn diff "$@"
2064 git_diff = git diff "$@"
2065 bzr_diff = bzr diff "$@"
2066 cvs_diff = cvs -q diff "$@"
2067 hg_diff = hg diff "$@"
2068 darcs_diff = darcs diff -u "$@"
2069 fossil_diff = fossil diff "$@"
2070 vcsh_diff = vcsh run "$MR_REPO" git diff "$@"
2071 veracity_diff = vv diff "$@"
2073 svn_log = svn log "$@"
2074 git_log = git log "$@"
2075 bzr_log = bzr log "$@"
2076 cvs_log = cvs log "$@"
2077 hg_log = hg log "$@"
2078 darcs_log = darcs changes "$@"
2079 git_bare_log = git log "$@"
2080 fossil_log = fossil timeline "$@"
2081 vcsh_log = vcsh run "$MR_REPO" git log "$@"
2082 veracity_log = vv log "$@"
2084 hg_grep = hg grep "$@"
2085 cvs_grep = ack-grep "$@"
2086 svn_grep = ack-grep "$@"
2087 git_svn_grep = git grep "$@"
2088 git_grep = git grep "$@"
2089 bzr_grep = ack-grep "$@"
2094 url=`LC_ALL=C svn info . | grep -i '^URL:' | cut -d ' ' -f 2`
2095 if [ -z "$url" ]; then
2096 error "cannot determine svn url"
2098 echo "Registering svn url: $url in $MR_CONFIG"
2099 mr -c "$MR_CONFIG" config "`pwd`" checkout="svn co '$url' '$MR_REPO'"
2101 url="`LC_ALL=C git config --get remote.origin.url`" || true
2102 if [ -z "$url" ]; then
2103 error "cannot determine git url"
2105 echo "Registering git url: $url in $MR_CONFIG"
2106 mr -c "$MR_CONFIG" config "`pwd`" checkout="git clone '$url' '$MR_REPO'"
2108 url="`LC_ALL=C bzr info . | egrep -i 'checkout of branch|parent branch' | awk '{print $NF}' | head -n 1`"
2109 if [ -z "$url" ]; then
2110 error "cannot determine bzr url"
2112 echo "Registering bzr url: $url in $MR_CONFIG"
2113 mr -c "$MR_CONFIG" config "`pwd`" checkout="bzr branch '$url' '$MR_REPO'"
2115 repo=`cat CVS/Repository`
2117 if [ -z "$root" ]; then
2118 error "cannot determine cvs root"
2120 echo "Registering cvs repository $repo at root $root"
2121 mr -c "$MR_CONFIG" config "`pwd`" checkout="cvs -d '$root' co -d '$MR_REPO' '$repo'"
2123 url=`hg showconfig paths.default`
2124 echo "Registering mercurial repo url: $url in $MR_CONFIG"
2125 mr -c "$MR_CONFIG" config "`pwd`" checkout="hg clone '$url' '$MR_REPO'"
2127 url=`cat _darcs/prefs/defaultrepo`
2128 echo "Registering darcs repository $url in $MR_CONFIG"
2129 mr -c "$MR_CONFIG" config "`pwd`" checkout="darcs get '$url' '$MR_REPO'"
2131 url="`LC_ALL=C GIT_CONFIG=config git config --get remote.origin.url`" || true
2132 if [ -z "$url" ]; then
2133 error "cannot determine git url"
2135 echo "Registering git url: $url in $MR_CONFIG"
2136 mr -c "$MR_CONFIG" config "`pwd`" checkout="git clone --bare '$url' '$MR_REPO'"
2138 url="`LC_ALL=C vcsh run "$MR_REPO" git config --get remote.origin.url`" || true
2139 if [ -z "$url" ]; then
2140 error "cannot determine git url"
2142 echo "Registering git url: $url in $MR_CONFIG"
2143 mr -c "$MR_CONFIG" config "`pwd`" checkout="vcsh clone '$url' '$MR_REPO'"
2145 url=`fossil remote-url`
2146 repo=`fossil info | grep repository | sed -e 's/repository:*.//g' -e 's/ //g'`
2147 echo "Registering fossil repository $url in $MR_CONFIG"
2148 mr -c "$MR_CONFIG" config "`pwd`" checkout="mkdir -p '$MR_REPO' && cd '$MR_REPO' && fossil open '$repo'"
2150 url=`vv config | grep sync_targets | sed -e 's/sync_targets:*.//g' -e 's/ //g'`
2151 repo=`vv repo info | grep repository | sed -e 's/Current repository:*.//g' -e 's/ //g'`
2152 echo "Registering veracity repository $url in $MR_CONFIG"
2153 mr -c "$MR_CONFIG" config "`pwd`" checkout="mkdir -p '$MR_REPO' && cd '$MR_REPO' && vv checkout '$repo'"
2155 svn_trusted_checkout = svn co $url $repo
2156 svn_alt_trusted_checkout = svn checkout $url $repo
2157 git_trusted_checkout = git clone $url $repo
2158 bzr_trusted_checkout = bzr checkout|clone|branch|get $url $repo
2160 hg_trusted_checkout = hg clone $url $repo
2161 darcs_trusted_checkout = darcs get $url $repo
2162 git_bare_trusted_checkout = git clone --bare $url $repo
2163 vcsh_old_trusted_checkout = vcsh run "$MR_REPO" git clone $url $repo
2164 vcsh_trusted_checkout = vcsh clone $url $repo
2165 # fossil: messy to do
2166 veracity_trusted_checkout = vv clone $url $repo
2174 if [ -s ~/.mrlog ]; then
2175 info "running offline commands"
2176 mv -f ~/.mrlog ~/.mrlog.old
2177 if ! sh -e ~/.mrlog.old; then
2178 error "offline command failed; left in ~/.mrlog.old"
2182 info "no offline commands to run"
2187 info "offline mode enabled"
2189 info "remembering command: 'mr $@'"
2190 command="mr -d '$(pwd)' $MR_SWITCHES"
2192 command="$command '$w'"
2194 if [ ! -e ~/.mrlog ] || ! grep -q -F "$command" ~/.mrlog; then
2195 echo "$command" >> ~/.mrlog
2198 ed = echo "A horse is a horse, of course, of course.."
2199 T = echo "I pity the fool."
2200 right = echo "Not found."
2202 # vim:sw=8:sts=0:ts=8:noet
2204 # indent-tabs-mode: t
2205 # cperl-indent-level: 8