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] clean [-f]
17 B<mr> [options] commit [-m "message"]
19 B<mr> [options] record [-m "message"]
29 B<mr> [options] grep pattern
31 B<mr> [options] run command [param ...]
33 B<mr> [options] bootstrap src [directory]
35 B<mr> [options] register [repository]
37 B<mr> [options] config section ["parameter=[value]" ...]
39 B<mr> [options] action [params ...]
41 B<mr> [options] [online|offline]
43 B<mr> [options] remember action [params ...]
47 B<mr> is a tool to manage all your version control repos. It can checkout,
48 update, or perform other actions on a set of repositories as if they were
49 one combined repository. It supports any combination of subversion, git,
50 cvs, mercurial, bzr, darcs, fossil and veracity repositories, and support
51 for other version control systems can easily be added.
53 B<mr> cds into and operates on all registered repositories at or below your
54 working directory. Or, if you are in a subdirectory of a repository that
55 contains no other registered repositories, it will stay in that directory,
56 and work on only that repository,
58 B<mr> is configured by .mrconfig files, which list the repositories. It
59 starts by reading the .mrconfig file in your home directory, and this can
60 in turn chain load .mrconfig files from repositories. It also automatically
61 looks for a .mrconfig file in the current directory, or in one of its
64 These predefined commands should be fairly familiar to users of any version
69 =item checkout (or co)
71 Checks out any repositories that are not already checked out.
75 Updates each repository from its configured remote repository.
77 If a repository isn't checked out yet, it will first check it out.
81 Displays a status report for each repository, showing what
82 uncommitted changes are present in the repository. For distributed version
83 control systems, also shows unpushed local branches.
87 Print ignored files, untracked files and other cruft in the working directory.
89 The optional -f parameter allows removing the files as well as printing them.
93 Commits changes to each repository. (By default, changes are pushed to the
94 remote repository too, when using distributed systems like git. If you
95 don't like this default, you can change it in your .mrconfig, or use record
98 The optional -m parameter allows specifying a commit message.
102 Records changes to the local repository, but does not push them to the
103 remote repository. Only supported for distributed version control systems.
105 The optional -m parameter allows specifying a commit message.
109 Fetches from each repository's remote repository, but does not
110 update the working copy. Only supported for some distributed version
115 Pushes committed local changes to the remote repository. A no-op for
116 centralized version control systems.
120 Show a diff of uncommitted changes.
128 Searches for a pattern in each repository using the grep subcommand. Uses
129 ack-grep on VCS that do not have their own.
131 =item run command [param ...]
133 Runs the specified command in each repository.
137 These commands are also available:
141 =item bootstrap src [directory]
143 Causes mr to retrieve the source C<src> and use it as a .mrconfig file to
144 checkout the repositories listed in it, into the specified directory.
146 B<mr> understands several types of sources:
152 C<src> may be an URL understood by B<curl>.
156 To use B<scp> to download, the C<src> may have the form
157 C<ssh://[user@]host:file>.
161 You can retrieve the config file by other means and pass its B<path> as C<src>.
165 If source C<src> consists in a single dash C<->, config file is read from
170 The directory will be created if it does not exist. If no directory is
171 specified, the current directory will be used.
173 As a special case, if source C<src> includes a repository named ".", that
174 is checked out into the top of the specified directory.
178 List the repositories that mr will act on.
182 Register an existing repository in a mrconfig file. By default, the
183 repository in the current directory is registered, or you can specify a
184 directory to register.
186 The mrconfig file that is modified is chosen by either the -c option, or by
187 looking for the closest known one at or in a parent of the current directory.
191 Adds, modifies, removes, or prints a value from a mrconfig file. The next
192 parameter is the name of the section the value is in. To add or modify
193 values, use one or more instances of "parameter=value". Use "parameter=" to
194 remove a parameter. Use just "parameter" to get the value of a parameter.
196 For example, to add (or edit) a repository in src/foo:
198 mr config src/foo checkout="svn co svn://example.com/foo/trunk foo"
200 To show the command that mr uses to update the repository in src/foo:
202 mr config src/foo update
204 To see the built-in library of shell functions contained in mr:
206 mr config DEFAULT lib
208 The mrconfig file that is used is chosen by either the -c option, or by
209 looking for the closest known one at or in a parent of the current directory.
213 Advises mr that it is in offline mode. Any commands that fail in
214 offline mode will be remembered, and retried when mr is told it's online.
218 Advices mr that it is in online mode again. Commands that failed while in
219 offline mode will be re-run.
223 Remember a command, to be run later when mr re-enters online mode. This
224 implicitly puts mr into offline mode. The command can be any regular mr
225 command. This is useful when you know that a command will fail due to being
226 offline, and so don't want to run it right now at all, but just remember
227 to run it when you go back online.
235 Actions can be abbreviated to any unambiguous substring, so
236 "mr st" is equivalent to "mr status", and "mr up" is equivalent to "mr
239 Additional parameters can be passed to most commands, and are passed on
240 unchanged to the underlying version control system. This is mostly useful
241 if the repositories mr will act on all use the same version control
250 =item --directory directory
252 Specifies the topmost directory that B<mr> should work in. The default is
253 the current working directory.
257 =item --config mrconfig
259 Use the specified mrconfig file. The default is to use both F<~/.mrconfig>
260 as well as look for a F<.mrconfig> file in the current directory, or in one
261 of its parent directories.
267 Force mr to act on repositories that would normally be skipped due to their
280 Minimise output. If a command fails or there is any output then the usual
281 output will be shown.
287 Be quiet. This suppresses mr's usual output, as well as any output from
288 commands that are run (including stderr output). If a command fails,
289 the output will be shown.
295 Accept untrusted SSL certificates when bootstrapping.
301 Expand the statistics line displayed at the end to include information
302 about exactly which repositories failed and were skipped, if any.
308 Interactive mode. If a repository fails to be processed, a subshell will be
309 started which you can use to resolve or investigate the problem. Exit the
310 subshell to continue the mr run.
314 =item --no-recurse [number]
316 If no number if specified, just operate on the repository for the current
317 directory, do not recurse into deeper repositories.
319 If a number is specified, will recurse into repositories at most that many
320 subdirectories deep. For example, with -n 2 it would recurse into ./src/foo,
321 but not ./src/packages/bar.
325 =item --jobs [number]
327 Run the specified number of jobs in parallel, or an unlimited number of jobs
328 with no number specified. This can greatly speed up operations such as updates.
329 It is not recommended for interactive operations.
331 Note that running more than 10 jobs at a time is likely to run afoul of
332 ssh connection limits. Running between 3 and 5 jobs at a time will yield
333 a good speedup in updates without loading the machine too much.
339 Trust all mrconfig files even if they are not listed in F<~/.mrtrust>.
346 This obsolete flag is ignored.
350 =head1 MRCONFIG FILES
352 Here is an example F<.mrconfig> file:
355 checkout = svn checkout svn://svn.example.com/src/trunk src
359 checkout = git clone git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux-2.6.git &&
361 git checkout -b mybranch origin/master
363 The F<.mrconfig> file uses a variant of the INI file format. Lines
364 starting with "#" are comments. Values can be continued to the
365 following line by indenting the line with whitespace.
367 The C<DEFAULT> section allows setting default values for the sections that
370 The C<ALIAS> section allows adding aliases for actions. Each parameter
371 is an alias, and its value is the action to use.
373 All other sections add repositories. The section header specifies the
374 directory where the repository is located. This is relative to the directory
375 that contains the mrconfig file, but you can also choose to use absolute
376 paths. (Note that you can use environment variables in section names; they
377 will be passed through the shell for expansion. For example,
378 C<[$HOSTNAME]>, or C<[${HOSTNAME}foo]>).
380 Within a section, each parameter defines a shell command to run to handle a
381 given action. mr contains default handlers for "update", "status",
382 "commit", and other standard actions.
384 Normally you only need to specify what to do for "checkout". Here you
385 specify the command to run in order to create a checkout of the repository.
386 The command will be run in the parent directory, and must create the
387 repository's directory. So use C<git clone>, C<svn checkout>, C<bzr branch>
388 or C<bzr checkout> (for a bound branch), etc.
390 Note that these shell commands are run in a C<set -e> shell
391 environment, where any additional parameters you pass are available in
392 C<$@>. All commands other than "checkout" are run inside the repository,
393 though not necessarily at the top of it.
395 The C<MR_REPO> environment variable is set to the path to the top of the
396 repository. (For the "register" action, "MR_REPO" is instead set to the
397 basename of the directory that should be created when checking the
400 The C<MR_CONFIG> environment variable is set to the .mrconfig file
401 that defines the repo being acted on, or, if the repo is not yet in a config
402 file, the F<.mrconfig> file that should be modified to register the repo.
404 The C<MR_ACTION> environment variable is set to the command being run
405 (update, checkout, etc).
407 A few parameters have special meanings:
413 If the "skip" parameter is set and its command returns true, then B<mr>
414 will skip acting on that repository. The command is passed the action
417 Here are two examples. The first skips the repo unless
418 mr is run by joey. The second uses the hours_since function
419 (included in mr's built-in library) to skip updating the repo unless it's
420 been at least 12 hours since the last update.
424 skip = test `whoami` != joey
428 skip = [ "$1" = update ] && ! hours_since "$1" 12
430 Another way to use skip is for a lazy checkout. This makes mr skip
431 operating on a repo unless it already exists. To enable the
432 repo, you have to explicitly check it out (using "mr --force -d foo checkout").
440 The "order" parameter can be used to override the default ordering of
441 repositories. The default order value is 10. Use smaller values to make
442 repositories be processed earlier, and larger values to make repositories
445 Note that if a repository is located in a subdirectory of another
446 repository, ordering it to be processed earlier is not recommended.
450 If the "chain" parameter is set and its command returns true, then B<mr>
451 will try to load a F<.mrconfig> file from the root of the repository.
455 If the "include" parameter is set, its command is ran, and should output
456 additional mrconfig file content. The content is included as if it were
457 part of the including file.
459 Unlike all other parameters, this parameter does not need to be placed
462 B<mr> ships several libraries that can be included to add support for
463 additional version control type things (unison, git-svn, git-fake-bare,
464 git-subtree). To include them all, you could use:
466 include = cat /usr/share/mr/*
468 See the individual files for details.
472 If the "deleted" parameter is set and its command returns true, then
473 B<mr> will treat the repository as deleted. It won't ever actually delete
474 the repository, but it will warn if it sees the repository's directory.
475 This is useful when one mrconfig file is shared among multiple machines,
476 to keep track of and remember to delete old repositories.
480 The "lib" parameter can specify some shell code that will be run
481 before each command, this can be a useful way to define shell
482 functions for other commands to use.
484 Unlike most other parameters, this can be specified multiple times, in
485 which case the chunks of shell code are accumulatively concatenated
490 If the "fixups" parameter is set, its command is run whenever a repository
491 is checked out, or updated. This provides an easy way to do things
492 like permissions fixups, or other tweaks to the repository content,
493 whenever the repository is changed.
497 When looking for a command to run for a given action, mr first looks for
498 a parameter with the same name as the action. If that is not found, it
499 looks for a parameter named "VCS_action" (substituting in the name of the
500 version control system and the action).
502 Internally, mr has settings for "git_update", "svn_update", etc. To change
503 the action that is performed for a given version control system, you can
504 override these VCS specific actions. To add a new version control system,
505 you can just add VCS specific actions for it.
509 If a "pre_action" parameter is set, its command is run before mr performs the
510 specified action. Similarly, "post_action" parameters are run after mr
511 successfully performs the specified action. For example, "pre_commit" is
512 run before committing; "post_update" is run after updating.
516 Any parameter can be suffixed with C<_append>, to add an additional value
517 to the existing value of the parameter. In this way, actions
518 can be constructed accumulatively.
522 The name of the version control system is itself determined by
523 running each defined "VCS_test" action, until one succeeds.
527 =head1 UNTRUSTED MRCONFIG FILES
529 Since mrconfig files can contain arbitrary shell commands, they can do
530 anything. This flexibility is good, but it also allows a malicious mrconfig
531 file to delete your whole home directory. Such a file might be contained
532 inside a repository that your main F<~/.mrconfig> checks out. To
533 avoid worries about evil commands in a mrconfig file, mr defaults to
534 reading all mrconfig files other than the main F<~/.mrconfig> in untrusted
535 mode. In untrusted mode, mrconfig files are limited to running only known
536 safe commands (like "git clone") in a carefully checked manner.
538 To configure mr to trust other mrconfig files, list them in F<~/.mrtrust>.
539 One mrconfig file should be listed per line. Either the full pathname
540 should be listed, or the pathname can start with F<~/> to specify a file
541 relative to your home directory.
543 =head1 OFFLINE LOG FILE
545 The F<~/.mrlog> file contains commands that mr has remembered to run later,
546 due to being offline. You can delete or edit this file to remove commands,
547 or even to add other commands for 'mr online' to run. If the file is
548 present, mr assumes it is in offline mode.
552 mr can be extended to support things such as unison and git-svn. Some
553 files providing such extensions are available in F</usr/share/mr/>. See
554 the documentation in the files for details about using them.
558 mr returns nonzero if a command failed in any of the repositories.
562 Copyright 2007-2011 Joey Hess <joey@kitenet.net>
564 Licensed under the GNU GPL version 2 or higher.
566 http://myrepos.branchable.com/
573 use Cwd qw(getcwd abs_path);
575 # things that can happen when mr runs a command
584 my $config_overridden=0;
596 my $directory=getcwd();
597 my $terminal=-t STDOUT && eval{require IO::Pty::Easy;IO::Pty::Easy->import();1;};
599 my $HOME_MR_CONFIG = "$ENV{HOME}/.mrconfig";
600 $ENV{MR_CONFIG}=find_mrconfig();
607 my (@ok, @failed, @skipped);
617 # Runs a shell command using a supplied function.
618 # The lib will be included in the shell command line, and any params
619 # will be available in the shell as $1, $2, etc.
622 my ($action, $topdir, $subdir, $command, $params, $runner) = @_;
624 # optimisation: avoid running the shell for true and false
625 if ($command =~ /^\s*true\s*$/) {
629 elsif ($command =~ /^\s*false\s*$/) {
634 my $quotedparams=join(" ", (map { shellquote($_) } @$params));
635 my $lib=exists $config{$topdir}{$subdir}{lib} ?
636 $config{$topdir}{$subdir}{lib}."\n" : "";
637 if ($verbose && (! defined $lastlib || $lastlib ne $lib)) {
638 print "mr library now: >>$lib<<\n";
641 my $shellcode="set -e;".$lib.
642 "my_sh(){ $command\n }; my_sh $quotedparams";
643 print "mr $action: running $action >>$command<<\n" if $verbose;
644 $runner->($shellcode);
651 if ($s =~ m/^perl:\s+(.*)/s) {
652 return $perl_cache{$1} if exists $perl_cache{$1};
653 my $sub=eval "sub {$1}";
654 if (! defined $sub) {
655 print STDERR "mr: bad perl code in $id: $@\n";
657 return $perl_cache{$1} = $sub;
664 my ($action, $dir, $topdir, $subdir) = @_;
666 if (exists $vcs{$dir}) {
672 foreach my $vcs_test (
674 length $a <=> length $b
677 } grep { /_test$/ } keys %{$config{$topdir}{$subdir}}) {
678 my ($vcs)=$vcs_test =~ /(.*)_test/;
679 my $p=perl($vcs_test, $config{$topdir}{$subdir}{$vcs_test});
684 $test="my_$vcs_test() {\n$config{$topdir}{$subdir}{$vcs_test}\n}\n".$test;
685 $test.="if my_$vcs_test; then echo $vcs; fi\n";
690 foreach my $vcs (keys %perltest) {
691 if ($perltest{$vcs}->()) {
696 push @vcs, split(/\n/,
697 runsh("vcs test", $topdir, $subdir, $test, [], sub {
703 print STDERR "mr $action: found multiple possible repository types (@vcs) for ".fulldir($topdir, $subdir)."\n";
707 return $vcs{$dir}=undef;
710 return $vcs{$dir}=$vcs[0];
715 my ($action, $dir, $topdir, $subdir, $is_checkout) = @_;
717 if (exists $config{$topdir}{$subdir}{$action}) {
718 return $config{$topdir}{$subdir}{$action};
725 my $vcs=vcs_test(@_);
728 exists $config{$topdir}{$subdir}{$vcs."_".$action}) {
729 return $config{$topdir}{$subdir}{$vcs."_".$action};
737 my ($topdir, $subdir) = @_;
738 return $subdir =~ /^\// ? $subdir : $topdir.$subdir;
741 sub terminal_friendly_spawn {
742 my $actionmsg = shift;
748 my $pty = IO::Pty::Easy->new;
750 while ($pty->is_active) {
751 my $data = $pty->read();
752 $output .= $data if defined $data;
756 $output = qx/$sh 2>&1/;
759 if ($quiet && $ret != 0) {
760 print "$actionmsg\n" if $actionmsg;
761 print STDERR $output;
762 } elsif (!$quiet && (!$minimal || $output)) {
763 print "$actionmsg\n" if $actionmsg;
766 return ($ret, $output ? 1 : 0);
770 my ($action, $dir, $topdir, $subdir, $force_checkout) = @_;
771 my $fulldir=fulldir($topdir, $subdir);
774 $ENV{MR_CONFIG}=$configfiles{$topdir};
775 my $is_checkout=($action eq 'checkout');
776 my $is_update=($action =~ /update/);
778 ($ENV{MR_REPO}=$dir) =~ s!/$!!;
779 $ENV{MR_ACTION}=$action;
781 foreach my $testname ("skip", "deleted") {
782 next if $force && $testname eq "skip";
784 my $testcommand=findcommand($testname, $dir, $topdir, $subdir, $is_checkout);
786 if (defined $testcommand) {
787 my $ret=runsh "$testname test", $topdir, $subdir,
788 $testcommand, [$action],
789 sub { system(shift()) };
791 if (($? & 127) == 2) {
792 print STDERR "mr $action: interrupted\n";
796 print STDERR "mr $action: $testname test received signal ".($? & 127)."\n";
800 if ($ret >> 8 == 0) {
801 if ($testname eq "deleted") {
803 print STDERR "mr error: $dir should be deleted yet still exists\n";
807 print "mr $action: skip $dir skipped\n" if $verbose;
815 if (! $force_checkout) {
817 print "mr $action: $dir already exists, skipping checkout\n" if $verbose;
821 $dir=~s/^(.*)\/[^\/]+\/?$/$1/;
826 return action("checkout", $dir, $topdir, $subdir);
830 my $command=findcommand($action, $dir, $topdir, $subdir, $is_checkout);
832 if ($is_checkout && ! -d $dir) {
833 print "mr $action: creating parent directory $dir\n" if $verbose;
834 system("mkdir", "-p", $dir);
837 if (! $no_chdir && ! chdir($dir)) {
838 print STDERR "mr $action: failed to chdir to $dir: $!\n";
841 elsif (! defined $command) {
842 my $vcs=vcs_test(@_);
843 if (! defined $vcs) {
844 print STDERR "mr $action: unknown repository type and no defined $action command for $fulldir\n";
848 print STDERR "mr $action: no defined action for $vcs repository $fulldir, skipping\n" unless $minimal;
855 $actionmsg="mr $action: $fulldir";
859 $s=~s/^\Q$fulldir\E\/?//;
860 $actionmsg="mr $action: $fulldir (in subdir $s)";
862 print "$actionmsg\n" unless $quiet || $minimal;
864 my ($hookret, $hook_out)=hook("pre_$action", $topdir, $subdir);
865 return $hookret if $hookret != OK;
867 my ($ret, $out)=runsh $action, $topdir, $subdir,
868 $command, \@ARGV, sub {
870 if (!$jobs || $jobs > 1 || $quiet || $minimal) {
871 return terminal_friendly_spawn($actionmsg, $sh, $quiet, $minimal);
878 if (($? & 127) == 2) {
879 print STDERR "mr $action: interrupted\n";
883 print STDERR "mr $action: received signal ".($? & 127)."\n";
886 print STDERR "mr $action: failed ($ret)\n" if $verbose;
887 if ($ret >> 8 != 0) {
888 print STDERR "mr $action: command failed\n";
889 if (-e "$ENV{HOME}/.mrlog" && $action ne 'remember') {
890 # recreate original command line to
891 # remember, and avoid recursing
893 @ARGV=('-n', $action, @orig);
894 action("remember", $dir, $topdir, $subdir);
899 print STDERR "mr $action: command died ($ret)\n";
904 if ($is_checkout && ! -d $dir) {
905 print STDERR "mr $action: $dir missing after checkout\n";;
909 my ($ret, $hook_out)=hook("post_$action", $topdir, $subdir);
910 return $ret if $ret != OK;
912 if ($is_checkout || $is_update) {
913 if ($is_checkout && ! $no_chdir) {
914 if (! chdir($checkout_dir)) {
915 print STDERR "mr $action: failed to chdir to $checkout_dir: $!\n";
919 my ($ret, $hook_out)=hook("fixups", $topdir, $subdir);
920 return $ret if $ret != OK;
923 return (OK, $out || $hook_out);
929 my ($hook, $topdir, $subdir) = @_;
931 my $command=$config{$topdir}{$subdir}{$hook};
932 return OK unless defined $command;
933 my ($ret,$out)=runsh $hook, $topdir, $subdir, $command, [], sub {
935 if (!$jobs || $jobs > 1 || $quiet || $minimal) {
936 return terminal_friendly_spawn(undef, $sh, $quiet, $minimal);
943 if (($? & 127) == 2) {
944 print STDERR "mr $hook: interrupted\n";
948 print STDERR "mr $hook: received signal ".($? & 127)."\n";
959 # run actions on multiple repos, in parallel
969 while (@fhs or @repos) {
970 while ((!$jobs || $running < $jobs) && @repos) {
972 my $repo = shift @repos;
973 pipe(my $outfh, CHILD_STDOUT);
974 pipe(my $errfh, CHILD_STDERR);
976 unless ($pid = fork) {
977 die "mr $action: cannot fork: $!" unless defined $pid;
978 open(STDOUT, ">&CHILD_STDOUT") || die "mr $action cannot reopen stdout: $!";
979 open(STDERR, ">&CHILD_STDERR") || die "mr $action cannot reopen stderr: $!";
984 exit +(action($action, @$repo))[0];
988 push @active, [$pid, $repo];
989 push @fhs, [$outfh, $errfh];
992 my ($rin, $rout) = ('','');
994 foreach my $fh (@fhs) {
995 next unless defined $fh;
996 vec($rin, fileno($fh->[0]), 1) = 1 if defined $fh->[0];
997 vec($rin, fileno($fh->[1]), 1) = 1 if defined $fh->[1];
999 $nfound = select($rout=$rin, undef, undef, 1);
1000 foreach my $channel (0, 1) {
1001 foreach my $i (0..$#fhs) {
1002 next unless defined $fhs[$i];
1003 my $fh = $fhs[$i][$channel];
1004 next unless defined $fh;
1005 if (vec($rout, fileno($fh), 1) == 1) {
1007 if (sysread($fh, $r, 1024) == 0) {
1009 $fhs[$i][$channel] = undef;
1010 if (! defined $fhs[$i][0] &&
1011 ! defined $fhs[$i][1]) {
1012 waitpid($active[$i][0], 0);
1013 print STDOUT $out[$i][0];
1014 print STDERR $out[$i][1];
1015 record($active[$i][1], $? >> 8, $out[$i][0] || $out[$i][1]);
1016 splice(@fhs, $i, 1);
1017 splice(@active, $i, 1);
1018 splice(@out, $i, 1);
1022 $out[$i][$channel] .= $r;
1030 my $dir=shift()->[0];
1036 print "\n" unless $quiet || ($minimal && !$out);
1038 elsif ($ret == FAILED) {
1040 chdir($dir) unless $no_chdir;
1041 print STDERR "mr: Starting interactive shell. Exit shell to continue.\n";
1042 system((getpwuid($<))[8], "-i");
1047 elsif ($ret == SKIPPED) {
1048 push @skipped, $dir;
1050 elsif ($ret == ABORT) {
1054 die "unknown exit status $ret";
1060 if (! @ok && ! @failed && ! @skipped) {
1061 die "mr $action: no repositories found to work on\n";
1063 print "mr $action: finished (".join("; ",
1064 showstat($#ok+1, "ok", "ok"),
1065 showstat($#failed+1, "failed", "failed"),
1066 showstat($#skipped+1, "skipped", "skipped"),
1067 ).")\n" unless $quiet || $minimal;
1070 print "mr $action: (skipped: ".join(" ", @skipped).")\n" unless $quiet || $minimal;
1073 print STDERR "mr $action: (failed: ".join(" ", @failed).")\n";
1083 return "$count ".($count > 1 ? $plural : $singular);
1088 # an ordered list of repos
1091 foreach my $topdir (sort keys %config) {
1092 foreach my $subdir (sort keys %{$config{$topdir}}) {
1096 order => $config{$topdir}{$subdir}{order},
1101 $a->{order} <=> $b->{order}
1103 $a->{topdir} cmp $b->{topdir}
1105 $a->{subdir} cmp $b->{subdir}
1111 my $topdir=$repo->{topdir};
1112 my $subdir=$repo->{subdir};
1113 my $ret=($subdir =~/^\//) ? $subdir : $topdir.$subdir;
1118 # Figure out which repos to act on. Returns a list of array refs
1121 # [ "$full_repo_path/", "$mr_config_path/", $section_header ]
1124 foreach my $repo (repolist()) {
1125 my $topdir=$repo->{topdir};
1126 my $subdir=$repo->{subdir};
1128 next if $subdir eq 'DEFAULT';
1129 my $dir=repodir($repo);
1131 $dir.="/" unless $dir=~/\/$/;
1132 $d.="/" unless $d=~/\/$/;
1133 next if $dir ne $d && $dir !~ /^\Q$d\E/;
1134 if (defined $max_depth) {
1135 my @a=split('/', $dir);
1136 my @b=split('/', $d);
1137 do { } while (@a && @b && shift(@a) eq shift(@b));
1138 next if @a > $max_depth || @b > $max_depth;
1140 push @repos, [$dir, $topdir, $subdir];
1143 # fallback to find a leaf repo
1144 foreach my $repo (reverse repolist()) {
1145 my $topdir=$repo->{topdir};
1146 my $subdir=$repo->{subdir};
1148 next if $subdir eq 'DEFAULT';
1149 my $dir=repodir($repo);
1151 $dir.="/" unless $dir=~/\/$/;
1152 $d.="/" unless $d=~/\/$/;
1153 if ($d=~/^\Q$dir\E/) {
1154 push @repos, [$dir, $topdir, $subdir];
1176 sub is_trusted_config {
1177 my $config=shift; # must be abs_pathed already
1179 # We always trust ~/.mrconfig.
1180 return 1 if $config eq abs_path($HOME_MR_CONFIG);
1182 return 1 if $trust_all;
1184 my $trustfile=$ENV{HOME}."/.mrtrust";
1187 $trusted{$HOME_MR_CONFIG}=1;
1188 if (open (TRUST, "<", $trustfile)) {
1191 s/^~\//$ENV{HOME}\//;
1193 $trusted{$d}=1 if defined $d;
1199 return $trusted{$config};
1203 sub is_trusted_repo {
1206 # Tightly limit what is allowed in a repo name.
1207 # No ../, no absolute paths, and no unusual filenames
1208 # that might try to escape to the shell.
1209 return $repo =~ /^[-_.+\/A-Za-z0-9]+$/ &&
1210 $repo !~ /\.\./ && $repo !~ /^\//;
1213 sub is_trusted_checkout {
1216 # To determine if the command is safe, compare it with the
1217 # *_trusted_checkout config settings. Those settings are
1218 # templates for allowed commands, so make sure that each word
1219 # of the command matches the corresponding word of the template.
1222 foreach my $word (split(' ', $command)) {
1224 if ($word=~/^'(.*)'$/) {
1227 elsif ($word=~/^"(.*)"$/) {
1234 foreach my $key (grep { /_trusted_checkout$/ }
1235 keys %{$config{''}{DEFAULT}}) {
1236 my @twords=split(' ', $config{''}{DEFAULT}{$key});
1237 next if @words > @twords;
1241 for (my $c=0; $c < @twords && $match; $c++) {
1242 if ($twords[$c] eq '$url') {
1243 # Match all the typical characters found in
1244 # urls, plus @ which svn can use. Note
1245 # that the "url" might also be a local
1248 defined $words[$c] &&
1249 $words[$c] =~ /^[-_.+:@\/A-Za-z0-9]+$/
1253 elsif ($twords[$c] eq '$repo') {
1254 # If a repo is not specified, assume it
1255 # will be the last path component of the
1256 # url, or something derived from it, and
1258 if (! defined $words[$c] && defined $url) {
1259 ($words[$c])=$url=~/\/([^\/]+)\/?$/;
1263 defined $words[$c] &&
1264 is_trusted_repo($words[$c])
1267 elsif (defined $words[$c] && $words[$c]=~/^($twords[$c])$/) {
1284 my $bootstrap_src=shift;
1290 if (ref $f eq 'GLOB') {
1296 my $absf=abs_path($f);
1297 if ($loaded{$absf}) {
1302 $trusted=is_trusted_config($absf);
1304 if (! defined $dir) {
1305 ($dir)=$f=~/^(.*\/)[^\/]+$/;
1306 if (! defined $dir) {
1311 $dir=abs_path($dir)."/";
1313 if (! exists $configfiles{$dir}) {
1314 $configfiles{$dir}=$f;
1317 # copy in defaults from first parent
1319 while ($parent=~s/^(.*\/)[^\/]+\/?$/$1/) {
1320 if ($parent eq '/') {
1323 if (exists $config{$parent} &&
1324 exists $config{$parent}{DEFAULT}) {
1325 $config{$dir}{DEFAULT}={ %{$config{$parent}{DEFAULT}} };
1334 print "mr: loading config $f\n" if $verbose;
1335 open($in, "<", $f) || die "mr: open $f: $!\n";
1338 close $in unless ref $f eq 'GLOB';
1342 # Keep track of the current line in the config file;
1343 # when a file is included track the current line from the include.
1348 my $nextline = sub {
1360 my $lineerror = sub {
1362 if (defined $included) {
1363 die "mr: $msg at $f line $lineno, included line: $line\n";
1366 die "mr: $msg at $f line $lineno\n";
1369 my $trusterror = sub {
1372 if (defined $bootstrap_src) {
1373 die "mr: $msg in untrusted $bootstrap_src line $lineno\n".
1374 "(To trust this url, --trust-all can be used; but please use caution;\n".
1375 "this can allow arbitrary code execution!)\n";
1378 die "mr: $msg in untrusted $f line $lineno\n".
1379 "(To trust this file, list it in ~/.mrtrust.)\n";
1386 next if /^\s*\#/ || /^\s*$/;
1388 if (! $trusted && /[[:cntrl:]]/) {
1389 $trusterror->("illegal control character");
1392 if (/^\[([^\]]*)\]\s*$/) {
1396 if (! is_trusted_repo($section) ||
1397 $section eq 'ALIAS' ||
1398 $section eq 'DEFAULT') {
1399 $trusterror->("illegal section \"[$section]\"");
1402 $section=expandenv($section) if $trusted;
1403 if ($section ne 'ALIAS' &&
1404 ! exists $config{$dir}{$section} &&
1405 exists $config{$dir}{DEFAULT}) {
1407 $config{$dir}{$section}={ %{$config{$dir}{DEFAULT}} };
1410 elsif (/^(\w+)\s*=\s*(.*)/) {
1415 while (@lines && $lines[0]=~/^\s(.+)/) {
1422 # Untrusted files can only contain a few
1423 # settings in specific known-safe formats.
1424 if ($parameter eq 'checkout') {
1425 if (! is_trusted_checkout($value)) {
1426 $trusterror->("illegal checkout command \"$value\"");
1429 elsif ($parameter eq 'order') {
1430 # not interpreted as a command, so
1433 elsif ($value eq 'true' || $value eq 'false') {
1434 # skip=true , deleted=true etc are
1438 $trusterror->("illegal setting \"$parameter=$value\"");
1442 if ($parameter eq "include") {
1443 print "mr: including output of \"$value\"\n" if $verbose;
1446 print STDERR "mr: include command exited nonzero ($?)\n";
1449 unshift @lines, @inc;
1453 if (! defined $section) {
1454 $lineerror->("parameter ($parameter) not in section");
1456 if ($section eq 'ALIAS') {
1457 $alias{$parameter}=$value;
1459 elsif ($parameter eq 'lib' or $parameter =~ s/_append$//) {
1460 $config{$dir}{$section}{$parameter}.="\n".$value."\n";
1463 $config{$dir}{$section}{$parameter}=$value;
1464 if ($parameter =~ /.*_(.*)/) {
1465 $knownactions{$1}=1;
1468 $knownactions{$parameter}=1;
1470 if ($parameter eq 'chain' &&
1471 length $dir && $section ne "DEFAULT") {
1472 my $chaindir="$section";
1473 if ($chaindir !~ m!^/!) {
1474 $chaindir=$dir.$chaindir;
1476 if (-e "$chaindir/.mrconfig") {
1477 my $ret=system($value);
1479 if (($? & 127) == 2) {
1480 print STDERR "mr: chain test interrupted\n";
1484 print STDERR "mr: chain test received signal ".($? & 127)."\n";
1488 push @toload, ["$chaindir/.mrconfig", $chaindir];
1495 $lineerror->("parse error");
1499 foreach my $c (@toload) {
1504 sub startingconfig {
1505 %alias=%config=%configfiles=%knownactions=%loaded=();
1506 my $datapos=tell(DATA);
1508 seek(DATA,$datapos,0); # rewind
1513 # the section to modify or add
1514 my $targetsection=shift;
1515 # fields to change in the section
1516 # To remove a field, set its value to "".
1517 my %changefields=@_;
1523 open(my $in, "<", $f) || die "mr: open $f: $!\n";
1528 my $formatfield=sub {
1530 my @value=split(/\n/, shift);
1532 return "$field = ".shift(@value)."\n".
1533 join("", map { "\t$_\n" } @value);
1537 while ($out[$#out] =~ /^\s*$/) {
1538 unshift @blanks, pop @out;
1540 foreach my $field (sort keys %changefields) {
1541 if (length $changefields{$field}) {
1542 push @out, "$field = $changefields{$field}\n";
1543 delete $changefields{$field};
1553 if (/^\s*\#/ || /^\s*$/) {
1556 elsif (/^\[([^\]]*)\]\s*$/) {
1557 if (defined $section &&
1558 $section eq $targetsection) {
1562 $section=expandenv($1);
1566 elsif (/^(\w+)\s*=\s(.*)/) {
1571 while (@lines && $lines[0]=~/^\s(.+)/) {
1577 if ($section eq $targetsection) {
1578 if (exists $changefields{$parameter}) {
1579 if (length $changefields{$parameter}) {
1580 $value=$changefields{$parameter};
1582 delete $changefields{$parameter};
1586 push @out, $formatfield->($parameter, $value);
1590 if (defined $section &&
1591 $section eq $targetsection) {
1594 elsif (%changefields) {
1595 push @out, "\n[$targetsection]\n";
1596 foreach my $field (sort keys %changefields) {
1597 if (length $changefields{$field}) {
1598 push @out, $formatfield->($field, $changefields{$field});
1603 open(my $out, ">", $f) || die "mr: write $f: $!\n";
1611 # actions that do not operate on all repos
1612 if ($action eq 'config') {
1615 elsif ($action eq 'register') {
1618 elsif ($action eq 'bootstrap') {
1621 elsif ($action eq 'remember' ||
1622 $action eq 'offline' ||
1623 $action eq 'online') {
1624 my @repos=selectrepos;
1625 action($action, @{$repos[0]}) if @repos;
1629 if (!$jobs || $jobs > 1) {
1630 mrs($action, selectrepos());
1633 foreach my $repo (selectrepos()) {
1634 record($repo, action($action, @$repo));
1643 SHOWMANFILE="man -f"
1652 if [ ! -e "$MR_PATH" ]; then
1653 error "cannot find program path"
1655 tmp=$(mktemp -t mr.XXXXXXXXXX) || error "mktemp failed"
1656 trap "rm -f $tmp" exit
1657 pod2man -c mr "$MR_PATH" > "$tmp" || error "pod2man failed"
1658 $SHOWMANFILE "$tmp" || error "man failed"
1660 exec($help) || die "exec: $!";
1665 die "mr config: not enough parameters\n";
1668 if ($section=~/^\//) {
1669 # try to convert to a path relative to the config file
1670 my ($dir)=$ENV{MR_CONFIG}=~/^(.*\/)[^\/]+$/;
1671 $dir=abs_path($dir);
1672 $dir.="/" unless $dir=~/\/$/;
1673 if ($section=~/^\Q$dir\E(.*)/) {
1679 if (/^([^=]+)=(.*)$/) {
1680 $changefields{$1}=$2;
1684 foreach my $topdir (sort keys %config) {
1685 if (exists $config{$topdir}{$section} &&
1686 exists $config{$topdir}{$section}{$_}) {
1687 print $config{$topdir}{$section}{$_}."\n";
1689 last if $section eq 'DEFAULT';
1693 die "mr config: $section $_ not set\n";
1697 modifyconfig($ENV{MR_CONFIG}, $section, %changefields) if %changefields;
1702 if ($config_overridden) {
1703 # Find the directory that the specified config file is
1705 ($directory)=abs_path($ENV{MR_CONFIG})=~/^(.*\/)[^\/]+$/;
1708 # Find the closest known mrconfig file to the current
1710 $directory.="/" unless $directory=~/\/$/;
1712 foreach my $topdir (reverse sort keys %config) {
1713 next unless length $topdir;
1714 if ($directory=~/^\Q$topdir\E/) {
1715 $ENV{MR_CONFIG}=$configfiles{$topdir};
1721 if (! $foundconfig) {
1722 $directory=""; # no config file, use builtin
1726 my $subdir=shift @ARGV;
1727 if (! chdir($subdir)) {
1728 print STDERR "mr register: failed to chdir to $subdir: $!\n";
1732 $ENV{MR_REPO}=getcwd();
1733 my $command=findcommand("register", $ENV{MR_REPO}, $directory, 'DEFAULT', 0);
1734 if (! defined $command) {
1735 die "mr register: unknown repository type\n";
1738 $ENV{MR_REPO}=~s/.*\/(.*)/$1/;
1739 $command="set -e; ".$config{$directory}{DEFAULT}{lib}."\n".
1740 "my_action(){ $command\n }; my_action ".
1741 join(" ", map { s/\\/\\\\/g; s/"/\"/g; '"'.$_.'"' } @ARGV);
1742 print "mr register: running >>$command<<\n" if $verbose;
1743 exec($command) || die "exec: $!";
1747 eval q{use File::Copy};
1750 my $src=shift @ARGV;
1751 my $dir=shift @ARGV || ".";
1753 if (! defined $src || ! length $src) {
1754 die "mr: bootstrap requires source\n";
1757 # Retrieve config file.
1758 eval q{use File::Temp};
1760 my $tmpconfig=File::Temp->new();
1761 if ($src =~ m!^[\w\d]+://!) {
1762 # Download the config file to a temporary location.
1764 if ($src =~ m!^ssh://(.*)!) {
1765 @downloader = ("scp", $1, $tmpconfig);
1768 @downloader = ("curl", "-A", "mr", "-L", "-s", $src, "-o", $tmpconfig);
1769 push(@downloader, "-k") if $insecure;
1771 my $status = system(@downloader);
1772 die "mr bootstrap: invalid SSL certificate for $src (consider -k)\n"
1773 if $downloader[0] eq 'curl' && $status >> 8 == 60;
1774 die "mr bootstrap: download of $src failed\n" if $status != 0;
1776 elsif ($src eq '-') {
1777 # Config file is read from stdin.
1778 copy(\*STDIN, $tmpconfig) || die "stdin: $!";
1781 # Config file is local.
1782 die "mr bootstrap: cannot read file '$src'"
1784 copy($src, $tmpconfig) || die "copy: $!";
1787 # Sanity check on destination directory.
1789 system("mkdir", "-p", $dir);
1791 chdir($dir) || die "chdir $dir: $!";
1793 # Special case to handle checkout of the "." repo, which
1794 # would normally be skipped.
1795 my $topdir=abs_path(".")."/";
1796 my @repo=($topdir, $topdir, ".");
1797 loadconfig($tmpconfig, $topdir, $src);
1798 record(\@repo, action("checkout", @repo, 1))
1799 if exists $config{$topdir}{"."}{"checkout"};
1801 if (-e ".mrconfig") {
1802 print STDERR "mr bootstrap: .mrconfig file already exists, not overwriting with $src\n";
1805 move($tmpconfig, ".mrconfig") || die "rename: $!";
1808 # Reload the config file (in case we got a different version)
1809 # and checkout everything else.
1811 loadconfig(".mrconfig");
1812 dispatch("checkout");
1813 @skipped=grep { abs_path($_) ne abs_path($topdir) } @skipped;
1814 showstats("bootstrap");
1818 # alias expansion and command stemming
1821 if (exists $alias{$action}) {
1822 $action=$alias{$action};
1824 if (! exists $knownactions{$action}) {
1825 my @matches = grep { /^\Q$action\E/ }
1826 keys %knownactions, keys %alias;
1827 if (@matches == 1) {
1828 $action=$matches[0];
1830 elsif (@matches == 0) {
1831 die "mr: unknown action \"$action\" (known actions: ".
1832 join(", ", sort keys %knownactions).")\n";
1835 die "mr: ambiguous action \"$action\" (matches: ".
1836 join(", ", @matches).")\n";
1844 while (length $dir) {
1845 if (-e "$dir/.mrconfig") {
1846 return "$dir/.mrconfig";
1848 $dir=~s/\/[^\/]*$//;
1850 return $HOME_MR_CONFIG;
1855 Getopt::Long::Configure("bundling", "no_permute");
1856 my $result=GetOptions(
1857 "d|directory=s" => sub { $directory=abs_path($_[1]) },
1858 "c|config=s" => sub { $ENV{MR_CONFIG}=$_[1]; $config_overridden=1 },
1859 "p|path" => sub { }, # now default, ignore
1860 "f|force" => \$force,
1861 "v|verbose" => \$verbose,
1862 "m|minimal" => \$minimal,
1863 "q|quiet" => \$quiet,
1864 "s|stats" => \$stats,
1865 "k|insecure" => \$insecure,
1866 "i|interactive" => \$interactive,
1867 "n|no-recurse:i" => \$max_depth,
1868 "j|jobs:i" => \$jobs,
1869 "t|trust-all" => \$trust_all,
1871 if (! $result || @ARGV < 1) {
1872 die("Usage: mr [options] action [params ...]\n".
1873 "(Use mr help for man page.)\n");
1876 $ENV{MR_SWITCHES}="";
1877 foreach my $option (@saved) {
1878 last if $option eq $ARGV[0];
1879 $ENV{MR_SWITCHES}.="$option ";
1885 print STDERR "mr: interrupted\n";
1889 # This can happen if it's run in a directory that was removed
1890 # or other strangeness.
1891 if (! defined $directory) {
1892 die("mr: failed to determine working directory\n");
1894 # Make sure MR_CONFIG is an absolute path, but don't use abs_path since
1895 # the config file might be a symlink to elsewhere, and the directory it's
1896 # in is significant.
1897 if ($ENV{MR_CONFIG} !~ /^\//) {
1898 $ENV{MR_CONFIG}=getcwd()."/".$ENV{MR_CONFIG};
1900 # Try to set MR_PATH to the path to the program.
1902 use FindBin qw($Bin $Script);
1903 $ENV{MR_PATH}=$Bin."/".$Script;
1919 help(@ARGV) if $ARGV[0] eq 'help';
1922 loadconfig($HOME_MR_CONFIG);
1923 loadconfig($ENV{MR_CONFIG});
1924 #use Data::Dumper; print Dumper(\%config);
1926 my $action=expandaction(shift @ARGV);
1933 # Finally, some useful actions that mr knows about by default.
1934 # These can be overridden in ~/.mrconfig.
1949 echo "mr (warning): $@" >&2
1955 if [ -z "$1" ] || [ -z "$2" ]; then
1956 error "mr: usage: hours_since action num"
1958 for dir in .git .svn .bzr CVS .hg _darcs _FOSSIL_; do
1959 if [ -e "$MR_REPO/$dir" ]; then
1960 flagfile="$MR_REPO/$dir/.mr_last$1"
1964 if [ -z "$flagfile" ]; then
1965 error "cannot determine flag filename"
1967 delta=`perl -wle 'print -f shift() ? int((-M _) * 24) : 9999' "$flagfile"`
1968 if [ "$delta" -lt "$2" ]; then
1976 LANG=C bzr info | egrep -q '^Checkout'
1979 if [ -d "$MR_REPO" ]; then
1986 svn_test = perl: -d "$ENV{MR_REPO}/.svn"
1987 git_test = perl: -e "$ENV{MR_REPO}/.git"
1988 bzr_test = perl: -d "$ENV{MR_REPO}/.bzr"
1989 cvs_test = perl: -d "$ENV{MR_REPO}/CVS"
1990 hg_test = perl: -d "$ENV{MR_REPO}/.hg"
1991 darcs_test = perl: -d "$ENV{MR_REPO}/_darcs"
1992 fossil_test = perl: -f "$ENV{MR_REPO}/_FOSSIL_"
1993 git_bare_test = perl:
1994 -d "$ENV{MR_REPO}/refs/heads" && -d "$ENV{MR_REPO}/refs/tags" &&
1995 -d "$ENV{MR_REPO}/objects" && -f "$ENV{MR_REPO}/config" &&
1996 `GIT_CONFIG="$ENV{MR_REPO}"/config git config --get core.bare` =~ /true/
1998 -d "$ENV{MR_REPO}/refs/heads" && -d "$ENV{MR_REPO}/refs/tags" &&
1999 -d "$ENV{MR_REPO}/objects" && -f "$ENV{MR_REPO}/config" &&
2000 `GIT_CONFIG="$ENV{MR_REPO}"/config git config --get vcsh.vcsh` =~ /true/
2001 veracity_test = perl: -d "$ENV{MR_REPO}/.sgdrawer"
2003 svn_update = svn update "$@"
2004 git_update = git pull "$@"
2006 if is_bzr_checkout; then
2009 bzr merge --pull "$@"
2011 cvs_update = cvs -q update "$@"
2012 hg_update = hg pull "$@"; hg update "$@"
2013 darcs_update = darcs pull -a "$@"
2014 fossil_update = fossil pull "$@"
2015 vcsh_update = vcsh run "$MR_REPO" git pull "$@"
2016 veracity_update = vv pull "$@" && vv update "$@"
2018 git_fetch = git fetch --all --prune --tags
2019 git_svn_fetch = git svn fetch
2020 darcs_fetch = darcs fetch
2024 if [ "x$1" = x-f ] ; then
2028 svn-clean --print "$@"
2031 if [ "x$1" = x-f ] ; then
2033 git clean -dx --force "$@"
2035 git clean -dx --dry-run "$@"
2038 if [ "x$1" = x-f ] ; then
2040 git clean -dx --force "$@"
2042 git clean -dx --dry-run "$@"
2045 if [ "x$1" = x-f ] ; then
2047 bzr clean-tree --verbose --force --ignored --unknown --detritus "$@"
2049 bzr clean-tree --verbose --dry-run --ignored --unknown --detritus "$@"
2052 if [ "x$1" = x-f ] ; then
2056 cvs-clean --dry-run "$@"
2059 if [ "x$1" = x-f ] ; then
2061 hg purge --print --all "$@"
2064 hg purge --print --all "$@"
2067 if [ "x$1" = x-f ] ; then
2069 fossil clean --dry-run --dotfiles --emptydirs "$@"
2071 fossil clean --force --dotfiles --emptydirs "$@"
2074 if [ "x$1" = x-f ] ; then
2076 vcsh run "$MR_REPO" git clean -dx "$@"
2078 vcsh run "$MR_REPO" git clean -dx --dry-run "$@"
2081 svn_status = svn status "$@"
2082 git_status = git status -s "$@" || true; git --no-pager log --branches --not --remotes --simplify-by-decoration --decorate --oneline || true
2083 bzr_status = bzr status --short "$@"; bzr missing
2084 cvs_status = cvs -q status | grep -E '^(File:.*Status:|\?)' | grep -v 'Status: Up-to-date'
2085 hg_status = hg status "$@"; hg summary --quiet | grep -v 'parent: 0:'
2086 darcs_status = darcs whatsnew -ls "$@" || true
2087 fossil_status = fossil changes "$@"
2088 vcsh_status = vcsh run "$MR_REPO" git -c status.relativePaths=false status -s "$@" || true
2089 veracity_status = vv status "$@"
2091 svn_commit = svn commit "$@"
2092 git_commit = git commit -a "$@" && git push --all
2094 if is_bzr_checkout; then
2097 bzr commit "$@" && bzr push
2099 cvs_commit = cvs commit "$@"
2100 hg_commit = hg commit "$@" && hg push
2101 darcs_commit = darcs record -a "$@" && darcs push -a
2102 fossil_commit = fossil commit "$@"
2103 vcsh_commit = vcsh run "$MR_REPO" git commit -a "$@" && vcsh run "$MR_REPO" git push --all
2104 veracity_commit = vv commit "$@" && vv push
2106 git_record = git commit -a "$@"
2108 if is_bzr_checkout; then
2109 bzr commit --local "$@"
2113 hg_record = hg commit "$@"
2114 darcs_record = darcs record -a "$@"
2115 fossil_record = fossil commit "$@"
2116 vcsh_record = vcsh run "$MR_REPO" git commit -a "$@"
2117 veracity_record = vv commit "$@"
2120 git_push = git push "$@"
2121 bzr_push = bzr push "$@"
2123 hg_push = hg push "$@"
2124 darcs_push = darcs push -a "$@"
2125 fossil_push = fossil push "$@"
2126 vcsh_push = vcsh run "$MR_REPO" git push "$@"
2127 veracity_push = vv push "$@"
2129 svn_diff = svn diff "$@"
2130 git_diff = git diff "$@"
2131 bzr_diff = bzr diff "$@"
2132 cvs_diff = cvs -q diff "$@"
2133 hg_diff = hg diff "$@"
2134 darcs_diff = darcs diff -u "$@"
2135 fossil_diff = fossil diff "$@"
2136 vcsh_diff = vcsh run "$MR_REPO" git diff "$@"
2137 veracity_diff = vv diff "$@"
2139 svn_log = svn log "$@"
2140 git_log = git log "$@"
2141 bzr_log = bzr log "$@"
2142 cvs_log = cvs log "$@"
2143 hg_log = hg log "$@"
2144 darcs_log = darcs changes "$@"
2145 git_bare_log = git log "$@"
2146 fossil_log = fossil timeline "$@"
2147 vcsh_log = vcsh run "$MR_REPO" git log "$@"
2148 veracity_log = vv log "$@"
2150 hg_grep = hg grep "$@"
2151 cvs_grep = ack-grep "$@"
2152 svn_grep = ack-grep "$@"
2153 git_svn_grep = git grep "$@"
2154 git_grep = git grep "$@"
2155 bzr_grep = ack-grep "$@"
2156 darcs_grep = ack-grep "$@"
2161 url=`LC_ALL=C svn info . | grep -i '^URL:' | cut -d ' ' -f 2`
2162 if [ -z "$url" ]; then
2163 error "cannot determine svn url"
2165 echo "Registering svn url: $url in $MR_CONFIG"
2166 mr -c "$MR_CONFIG" config "`pwd`" checkout="svn co '$url' '$MR_REPO'"
2168 url="`LC_ALL=C git config --get remote.origin.url`" || true
2169 if [ -z "$url" ]; then
2170 error "cannot determine git url"
2172 echo "Registering git url: $url in $MR_CONFIG"
2173 mr -c "$MR_CONFIG" config "`pwd`" checkout="git clone '$url' '$MR_REPO'"
2175 url="`LC_ALL=C bzr info . | egrep -i 'checkout of branch|parent branch' | awk '{print $NF}' | head -n 1`"
2176 if [ -z "$url" ]; then
2177 error "cannot determine bzr url"
2179 echo "Registering bzr url: $url in $MR_CONFIG"
2180 mr -c "$MR_CONFIG" config "`pwd`" checkout="bzr branch '$url' '$MR_REPO'"
2182 repo=`cat CVS/Repository`
2184 if [ -z "$root" ]; then
2185 error "cannot determine cvs root"
2187 echo "Registering cvs repository $repo at root $root"
2188 mr -c "$MR_CONFIG" config "`pwd`" checkout="cvs -d '$root' co -d '$MR_REPO' '$repo'"
2190 url=`hg showconfig paths.default`
2191 echo "Registering mercurial repo url: $url in $MR_CONFIG"
2192 mr -c "$MR_CONFIG" config "`pwd`" checkout="hg clone '$url' '$MR_REPO'"
2194 url=`cat _darcs/prefs/defaultrepo`
2195 echo "Registering darcs repository $url in $MR_CONFIG"
2196 mr -c "$MR_CONFIG" config "`pwd`" checkout="darcs get '$url' '$MR_REPO'"
2198 url="`LC_ALL=C GIT_CONFIG=config git config --get remote.origin.url`" || true
2199 if [ -z "$url" ]; then
2200 error "cannot determine git url"
2202 echo "Registering git url: $url in $MR_CONFIG"
2203 mr -c "$MR_CONFIG" config "`pwd`" checkout="git clone --bare '$url' '$MR_REPO'"
2205 url="`LC_ALL=C vcsh run "$MR_REPO" git config --get remote.origin.url`" || true
2206 if [ -z "$url" ]; then
2207 error "cannot determine git url"
2209 echo "Registering git url: $url in $MR_CONFIG"
2210 mr -c "$MR_CONFIG" config "`pwd`" checkout="vcsh clone '$url' '$MR_REPO'"
2212 url=`fossil remote-url`
2213 repo=`fossil info | grep repository | sed -e 's/repository:*.//g' -e 's/ //g'`
2214 echo "Registering fossil repository $url in $MR_CONFIG"
2215 mr -c "$MR_CONFIG" config "`pwd`" checkout="mkdir -p '$MR_REPO' && cd '$MR_REPO' && fossil open '$repo'"
2217 url=`vv config | grep sync_targets | sed -e 's/sync_targets:*.//g' -e 's/ //g'`
2218 repo=`vv repo info | grep repository | sed -e 's/Current repository:*.//g' -e 's/ //g'`
2219 echo "Registering veracity repository $url in $MR_CONFIG"
2220 mr -c "$MR_CONFIG" config "`pwd`" checkout="mkdir -p '$MR_REPO' && cd '$MR_REPO' && vv checkout '$repo'"
2222 svn_trusted_checkout = svn co $url $repo
2223 svn_alt_trusted_checkout = svn checkout $url $repo
2224 git_trusted_checkout = git clone $url $repo
2225 bzr_trusted_checkout = bzr checkout|clone|branch|get $url $repo
2227 hg_trusted_checkout = hg clone $url $repo
2228 darcs_trusted_checkout = darcs get $url $repo
2229 git_bare_trusted_checkout = git clone --bare $url $repo
2230 vcsh_old_trusted_checkout = vcsh run "$MR_REPO" git clone $url $repo
2231 vcsh_trusted_checkout = vcsh clone $url $repo
2232 # fossil: messy to do
2233 veracity_trusted_checkout = vv clone $url $repo
2241 if [ -s ~/.mrlog ]; then
2242 info "running offline commands"
2243 mv -f ~/.mrlog ~/.mrlog.old
2244 if ! sh -e ~/.mrlog.old; then
2245 error "offline command failed; left in ~/.mrlog.old"
2249 info "no offline commands to run"
2254 info "offline mode enabled"
2256 info "remembering command: 'mr $@'"
2257 command="mr -d '$(pwd)' $MR_SWITCHES"
2259 command="$command '$w'"
2261 if [ ! -e ~/.mrlog ] || ! grep -q -F "$command" ~/.mrlog; then
2262 echo "$command" >> ~/.mrlog
2265 ed = echo "A horse is a horse, of course, of course.."
2266 T = echo "I pity the fool."
2267 right = echo "Not found."
2269 # vim:sw=8:sts=0:ts=8:noet
2271 # indent-tabs-mode: t
2272 # cperl-indent-level: 8