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 ["setting=[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 "setting=value". Use "setting=" to
194 remove a setting. Use just "setting" to get the value of a that setting.
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
272 Force mr to execute even though potentially dangerous environment variables
285 Minimise output. If a command fails or there is any output then the usual
286 output will be shown.
292 Be quiet. This suppresses mr's usual output, as well as any output from
293 commands that are run (including stderr output). If a command fails,
294 the output will be shown.
300 Accept untrusted SSL certificates when bootstrapping.
306 Expand the statistics line displayed at the end to include information
307 about exactly which repositories failed and were skipped, if any.
313 Interactive mode. If a repository fails to be processed, a subshell will be
314 started which you can use to resolve or investigate the problem. Exit the
315 subshell to continue the mr run.
319 =item --no-recurse [number]
321 If no number if specified, just operate on the repository for the current
322 directory, do not recurse into deeper repositories.
324 If a number is specified, will recurse into repositories at most that many
325 subdirectories deep. For example, with -n 2 it would recurse into ./src/foo,
326 but not ./src/packages/bar.
330 =item --jobs [number]
332 Run the specified number of jobs in parallel, or an unlimited number of jobs
333 with no number specified. This can greatly speed up operations such as updates.
334 It is not recommended for interactive operations.
336 Note that running more than 10 jobs at a time is likely to run afoul of
337 ssh connection limits. Running between 3 and 5 jobs at a time will yield
338 a good speedup in updates without loading the machine too much.
344 Trust all mrconfig files even if they are not listed in F<~/.mrtrust>.
351 This obsolete flag is ignored.
355 =head1 MRCONFIG FILES
357 Here is an example F<.mrconfig> file:
360 checkout = svn checkout svn://svn.example.com/src/trunk src
364 checkout = git clone git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux-2.6.git &&
366 git checkout -b mybranch origin/master
368 The F<.mrconfig> file uses a variant of the INI file format. Lines
369 starting with "#" are comments. Values can be continued to the
370 following line by indenting the line with whitespace.
372 The C<DEFAULT> section allows setting default values for the sections that
375 The C<ALIAS> section allows adding aliases for actions. Each setting
376 is an alias, and its value is the action to use.
378 All other sections add repositories. The section header specifies the
379 directory where the repository is located. This is relative to the directory
380 that contains the mrconfig file, but you can also choose to use absolute
381 paths. (Note that you can use environment variables in section names; they
382 will be passed through the shell for expansion. For example,
383 C<[$HOSTNAME]>, or C<[${HOSTNAME}foo]>).
385 Within a section, each setting defines a shell command to run to handle a
386 given action. mr contains default handlers for "update", "status",
387 "commit", and other standard actions.
389 Normally you only need to specify what to do for "checkout". Here you
390 specify the command to run in order to create a checkout of the repository.
391 The command will be run in the parent directory, and must create the
392 repository's directory. So use C<git clone>, C<svn checkout>, C<bzr branch>
393 or C<bzr checkout> (for a bound branch), etc.
395 Note that these shell commands are run in a C<set -e> shell
396 environment, where any additional parameters you pass are available in
397 C<$@>. All commands other than "checkout" are run inside the repository,
398 though not necessarily at the top of it.
400 The C<MR_REPO> environment variable is set to the path to the top of the
401 repository. (For the "register" action, "MR_REPO" is instead set to the
402 basename of the directory that should be created when checking the
405 The C<MR_CONFIG> environment variable is set to the .mrconfig file
406 that defines the repo being acted on, or, if the repo is not yet in a config
407 file, the F<.mrconfig> file that should be modified to register the repo.
409 The C<MR_ACTION> environment variable is set to the command being run
410 (update, checkout, etc).
412 A few settings have special meanings:
418 If "skip" is set and its command returns true, then B<mr>
419 will skip acting on that repository. The command is passed the action
422 Here are two examples. The first skips the repo unless
423 mr is run by joey. The second uses the hours_since function
424 (included in mr's built-in library) to skip updating the repo unless it's
425 been at least 12 hours since the last update.
429 skip = test `whoami` != joey
433 skip = [ "$1" = update ] && ! hours_since "$1" 12
435 Another way to use skip is for a lazy checkout. This makes mr skip
436 operating on a repo unless it already exists. To enable the
437 repo, you have to explicitly check it out (using "mr --force -d foo checkout").
445 The "order" setting can be used to override the default ordering of
446 repositories. The default order value is 10. Use smaller values to make
447 repositories be processed earlier, and larger values to make repositories
450 Note that if a repository is located in a subdirectory of another
451 repository, ordering it to be processed earlier is not recommended.
455 If "chain" is set and its command returns true, then B<mr>
456 will try to load a F<.mrconfig> file from the root of the repository.
460 If "include" is set, its command is ran, and should output
461 additional mrconfig file content. The content is included as if it were
462 part of the including file.
464 Unlike everything else, "include" does not need to be placed within a section.
466 B<mr> ships several libraries that can be included to add support for
467 additional version control type things (unison, git-svn, git-fake-bare,
468 git-subtree). To include them all, you could use:
470 include = cat /usr/share/mr/*
472 See the individual files for details.
476 If "deleted" is set and its command returns true, then
477 B<mr> will treat the repository as deleted. It won't ever actually delete
478 the repository, but it will warn if it sees the repository's directory.
479 This is useful when one mrconfig file is shared among multiple machines,
480 to keep track of and remember to delete old repositories.
484 The "lib" setting can contain some shell code that will be run
485 before each command, this can be a useful way to define shell
486 functions for other commands to use.
488 Unlike most other settings, this can be specified multiple times, in
489 which case the chunks of shell code are accumulatively concatenated
494 If "fixups" is set, its command is run whenever a repository
495 is checked out, or updated. This provides an easy way to do things
496 like permissions fixups, or other tweaks to the repository content,
497 whenever the repository is changed.
501 If "jobs" is set, run the specified number of jobs in parallel.
502 This can greatly speed up operations such as updates.
504 Note that running more than 10 jobs at a time is likely to run afoul of
505 ssh connection limits. Running between 3 and 5 jobs at a time will yield
506 a good speedup in updates without loading the machine too much.
510 When looking for a command to run for a given action, mr first looks for
511 a setting with the same name as the action. If that is not found, it
512 looks for a setting named "VCS_action" (substituting in the name of the
513 version control system and the action).
515 Internally, mr has settings for "git_update", "svn_update", etc. To change
516 the action that is performed for a given version control system, you can
517 override these VCS specific actions. To add a new version control system,
518 you can just add VCS specific actions for it.
522 If "pre_action" is set, its command is run before mr performs the
523 specified action. Similarly, "post_action" commands are run after mr
524 successfully performs the specified action. For example, "pre_commit" is
525 run before committing; "post_update" is run after updating.
529 Any setting can be suffixed with C<_append>, to add an additional value
530 to the existing value of the setting. In this way, actions
531 can be constructed accumulatively.
535 The name of the version control system is itself determined by
536 running each defined "VCS_test" action, until one succeeds.
540 =head1 UNTRUSTED MRCONFIG FILES
542 Since mrconfig files can contain arbitrary shell commands, they can do
543 anything. This flexibility is good, but it also allows a malicious mrconfig
544 file to delete your whole home directory. Such a file might be contained
545 inside a repository that your main F<~/.mrconfig> checks out. To
546 avoid worries about evil commands in a mrconfig file, mr defaults to
547 reading all mrconfig files other than the main F<~/.mrconfig> in untrusted
548 mode. In untrusted mode, mrconfig files are limited to running only known
549 safe commands (like "git clone") in a carefully checked manner.
551 To configure mr to trust other mrconfig files, list them in F<~/.mrtrust>.
552 One mrconfig file should be listed per line. Either the full pathname
553 should be listed, or the pathname can start with F<~/> to specify a file
554 relative to your home directory.
556 =head1 OFFLINE LOG FILE
558 The F<~/.mrlog> file contains commands that mr has remembered to run later,
559 due to being offline. You can delete or edit this file to remove commands,
560 or even to add other commands for 'mr online' to run. If the file is
561 present, mr assumes it is in offline mode.
565 mr can be extended to support things such as unison and git-svn. Some
566 files providing such extensions are available in F</usr/share/mr/>. See
567 the documentation in the files for details about using them.
571 mr returns nonzero if a command failed in any of the repositories.
575 Copyright 2007-2011 Joey Hess <joey@kitenet.net>
577 Licensed under the GNU GPL version 2 or higher.
579 http://myrepos.branchable.com/
586 use Cwd
qw(getcwd abs_path);
588 # things that can happen when mr runs a command
597 my $config_overridden=0;
610 my $directory=getcwd
();
611 my $terminal=-t STDOUT
&& eval{require IO
::Pty
::Easy
;IO
::Pty
::Easy
->import();1;};
612 my $erase_line=$terminal && eval{require Term
::Cap
;my $t=Term
::Cap
->Tgetent();$t->Tputs('ce');};
614 my $HOME_MR_CONFIG = "$ENV{HOME}/.mrconfig";
615 $ENV{MR_CONFIG
}=find_mrconfig
();
622 my (@ok, @failed, @skipped);
632 # Runs a shell command using a supplied function.
633 # The lib will be included in the shell command line, and any params
634 # will be available in the shell as $1, $2, etc.
637 my ($action, $topdir, $subdir, $command, $params, $runner) = @_;
639 # optimisation: avoid running the shell for true and false
640 if ($command =~ /^\s*true\s*$/) {
644 elsif ($command =~ /^\s*false\s*$/) {
649 my $quotedparams=join(" ", (map { shellquote
($_) } @
$params));
650 my $lib=exists $config{$topdir}{$subdir}{lib
} ?
651 $config{$topdir}{$subdir}{lib
}."\n" : "";
652 if ($verbose && (! defined $lastlib || $lastlib ne $lib)) {
653 print "mr library now: >>$lib<<\n";
656 my $shellcode="set -e;".$lib.
657 "my_sh(){ $command\n }; my_sh $quotedparams";
658 print "mr $action: running $action >>$command<<\n" if $verbose;
659 $runner->($shellcode);
666 if ($s =~ m/^perl:\s+(.*)/s) {
667 return $perl_cache{$1} if exists $perl_cache{$1};
668 my $sub=eval "sub {$1}";
669 if (! defined $sub) {
670 print STDERR
"mr: bad perl code in $id: $@\n";
672 return $perl_cache{$1} = $sub;
679 my ($action, $dir, $topdir, $subdir) = @_;
681 if (exists $vcs{$dir}) {
687 foreach my $vcs_test (
689 length $a <=> length $b
692 } grep { /_test$/ } keys %{$config{$topdir}{$subdir}}) {
693 my ($vcs)=$vcs_test =~ /(.*)_test/;
694 my $p=perl
($vcs_test, $config{$topdir}{$subdir}{$vcs_test});
699 $test="my_$vcs_test() {\n$config{$topdir}{$subdir}{$vcs_test}\n}\n".$test;
700 $test.="if my_$vcs_test; then echo $vcs; fi\n";
705 foreach my $vcs (keys %perltest) {
706 if ($perltest{$vcs}->()) {
711 push @vcs, split(/\n/,
712 runsh
("vcs test", $topdir, $subdir, $test, [], sub {
718 print STDERR
"mr $action: found multiple possible repository types (@vcs) for ".fulldir
($topdir, $subdir)."\n";
722 return $vcs{$dir}=undef;
725 return $vcs{$dir}=$vcs[0];
730 my ($action, $dir, $topdir, $subdir, $is_checkout) = @_;
732 if (exists $config{$topdir}{$subdir}{$action}) {
733 return $config{$topdir}{$subdir}{$action};
740 my $vcs=vcs_test
(@_);
743 exists $config{$topdir}{$subdir}{$vcs."_".$action}) {
744 return $config{$topdir}{$subdir}{$vcs."_".$action};
752 my ($topdir, $subdir) = @_;
753 return $subdir =~ /^\// ?
$subdir : $topdir.$subdir;
756 sub terminal_friendly_spawn
{
757 my $actionmsg = shift;
762 my $continuous = !$quiet && $minimal && 1 == $jobs;
764 my $continuous_output = 0;
766 my $pty = IO
::Pty
::Easy
->new;
770 print "$actionmsg$erase_line\r" if $actionmsg;
771 while ($pty->is_active) {
772 my $data = $pty->getline();
773 if (defined $data && $data ne '') {
774 print "\n" if ($actionmsg && !$continuous_output);
775 $continuous_output = 1;
781 while ($pty->is_active) {
782 my $data = $pty->read();
783 $output .= $data if defined $data;
789 $output = qx/$sh 2>&1/;
792 if ($quiet && $ret != 0) {
793 print "$actionmsg\n" if $actionmsg;
794 print STDERR
$output;
796 elsif (!$quiet && (!$minimal || $output)) {
797 print "$actionmsg\n" if $actionmsg;
800 return ($ret, ($output || $continuous_output) ?
1 : 0);
804 my ($action, $dir, $topdir, $subdir, $force_checkout) = @_;
805 my $fulldir=fulldir
($topdir, $subdir);
808 $ENV{MR_CONFIG
}=$configfiles{$topdir};
809 my $is_checkout=($action eq 'checkout');
810 my $is_update=($action =~ /update/);
812 ($ENV{MR_REPO
}=$dir) =~ s!/$!!;
813 $ENV{MR_ACTION
}=$action;
815 foreach my $testname ("skip", "deleted") {
816 next if $force && $testname eq "skip";
818 my $testcommand=findcommand
($testname, $dir, $topdir, $subdir, $is_checkout);
820 if (defined $testcommand) {
821 my $ret=runsh
"$testname test", $topdir, $subdir,
822 $testcommand, [$action],
823 sub { system(shift()) };
825 if (($?
& 127) == 2) {
826 print STDERR
"mr $action: interrupted\n";
830 print STDERR
"mr $action: $testname test received signal ".($?
& 127)."\n";
834 if ($ret >> 8 == 0) {
835 if ($testname eq "deleted") {
837 print STDERR
"mr error: $dir should be deleted yet still exists\n";
841 print "mr $action: skip $dir skipped\n" if $verbose;
849 if (! $force_checkout) {
851 print "mr $action: $dir already exists, skipping checkout\n" if $verbose;
855 $dir=~s/^(.*)\/[^\/]+\
/?$/$1/;
860 return action
("checkout", $dir, $topdir, $subdir);
864 my $command=findcommand
($action, $dir, $topdir, $subdir, $is_checkout);
866 if ($is_checkout && ! -d
$dir) {
867 print "mr $action: creating parent directory $dir\n" if $verbose;
868 system("mkdir", "-p", $dir);
871 if (! $no_chdir && ! chdir($dir)) {
872 print STDERR
"mr $action: failed to chdir to $dir: $!\n";
875 elsif (! defined $command) {
876 my $vcs=vcs_test
(@_);
877 if (! defined $vcs) {
878 print STDERR
"mr $action: unknown repository type and no defined $action command for $fulldir\n";
882 print STDERR
"mr $action: no defined action for $vcs repository $fulldir, skipping\n" unless $minimal;
889 $actionmsg="mr $action: $fulldir";
893 $s=~s/^\Q$fulldir\E\/?//;
894 $actionmsg="mr $action: $fulldir (in subdir $s)";
896 print "$actionmsg\n" unless !$jobs || $jobs > 1 || $quiet || $minimal;
898 my ($hookret, $hook_out)=hook
("pre_$action", $topdir, $subdir);
899 return $hookret if $hookret != OK
;
901 my ($ret, $out)=runsh
$action, $topdir, $subdir,
902 $command, \
@ARGV, sub {
904 if (!$jobs || $jobs > 1 || $quiet || $minimal) {
905 return terminal_friendly_spawn
($actionmsg, $sh, $quiet, $minimal, $jobs);
912 if (($?
& 127) == 2) {
913 print STDERR
"mr $action: interrupted\n";
917 print STDERR
"mr $action: received signal ".($?
& 127)."\n";
920 print STDERR
"mr $action: failed ($ret)\n" if $verbose;
921 if ($ret >> 8 != 0) {
922 print STDERR
"mr $action: command failed\n";
923 if (-e
"$ENV{HOME}/.mrlog" && $action ne 'remember') {
924 # recreate original command line to
925 # remember, and avoid recursing
927 @ARGV=('-n', $action, @orig);
928 action
("remember", $dir, $topdir, $subdir);
933 print STDERR
"mr $action: command died ($ret)\n";
938 if ($is_checkout && ! -d
$dir) {
939 print STDERR
"mr $action: $dir missing after checkout\n";;
943 my ($ret, $hook_out)=hook
("post_$action", $topdir, $subdir);
944 return $ret if $ret != OK
;
946 if ($is_checkout || $is_update) {
947 if ($is_checkout && ! $no_chdir) {
948 if (! chdir($checkout_dir)) {
949 print STDERR
"mr $action: failed to chdir to $checkout_dir: $!\n";
953 my ($ret, $hook_out)=hook
("fixups", $topdir, $subdir);
954 return $ret if $ret != OK
;
957 return (OK
, $out || $hook_out);
963 my ($hook, $topdir, $subdir) = @_;
965 my $command=$config{$topdir}{$subdir}{$hook};
966 return OK
unless defined $command;
967 my ($ret,$out)=runsh
$hook, $topdir, $subdir, $command, [], sub {
969 if (!$jobs || $jobs > 1 || $quiet || $minimal) {
970 return terminal_friendly_spawn
(undef, $sh, $quiet, $minimal, $jobs);
977 if (($?
& 127) == 2) {
978 print STDERR
"mr $hook: interrupted\n";
982 print STDERR
"mr $hook: received signal ".($?
& 127)."\n";
993 # run actions on multiple repos, in parallel
1003 while (@fhs or @repos) {
1004 while ((!$jobs || $running < $jobs) && @repos) {
1006 my $repo = shift @repos;
1007 pipe(my $outfh, CHILD_STDOUT
);
1008 pipe(my $errfh, CHILD_STDERR
);
1010 unless ($pid = fork) {
1011 die "mr $action: cannot fork: $!" unless defined $pid;
1012 open(STDOUT
, ">&CHILD_STDOUT") || die "mr $action cannot reopen stdout: $!";
1013 open(STDERR
, ">&CHILD_STDERR") || die "mr $action cannot reopen stderr: $!";
1018 exit +(action
($action, @
$repo))[0];
1022 push @active, [$pid, $repo];
1023 push @fhs, [$outfh, $errfh];
1024 push @out, ['', ''];
1026 my ($rin, $rout) = ('','');
1028 foreach my $fh (@fhs) {
1029 next unless defined $fh;
1030 vec($rin, fileno($fh->[0]), 1) = 1 if defined $fh->[0];
1031 vec($rin, fileno($fh->[1]), 1) = 1 if defined $fh->[1];
1033 $nfound = select($rout=$rin, undef, undef, 1);
1034 foreach my $channel (0, 1) {
1035 foreach my $i (0..$#fhs) {
1036 next unless defined $fhs[$i];
1037 my $fh = $fhs[$i][$channel];
1038 next unless defined $fh;
1039 if (vec($rout, fileno($fh), 1) == 1) {
1041 if (sysread($fh, $r, 1024) == 0) {
1043 $fhs[$i][$channel] = undef;
1044 if (! defined $fhs[$i][0] &&
1045 ! defined $fhs[$i][1]) {
1046 waitpid($active[$i][0], 0);
1047 print STDOUT
$out[$i][0];
1048 print STDERR
$out[$i][1];
1049 record
($active[$i][1], $?
>> 8, $out[$i][0] || $out[$i][1]);
1050 splice(@fhs, $i, 1);
1051 splice(@active, $i, 1);
1052 splice(@out, $i, 1);
1056 $out[$i][$channel] .= $r;
1064 my $dir=shift()->[0];
1070 print "\n" unless $quiet || ($minimal && !$out);
1072 elsif ($ret == FAILED
) {
1074 chdir($dir) unless $no_chdir;
1075 print STDERR
"mr: Starting interactive shell. Exit shell to continue.\n";
1076 system((getpwuid($<))[8], "-i");
1081 elsif ($ret == SKIPPED
) {
1082 push @skipped, $dir;
1084 elsif ($ret == ABORT
) {
1088 die "unknown exit status $ret";
1094 if (! @ok && ! @failed && ! @skipped) {
1095 die "mr $action: no repositories found to work on\n";
1097 print "mr $action: finished (".join("; ",
1098 showstat
($#ok+1, "ok", "ok"),
1099 showstat
($#failed+1, "failed", "failed"),
1100 showstat
($#skipped+1, "skipped", "skipped"),
1101 ).")\n" unless $quiet || $minimal;
1104 print "mr $action: (skipped: ".join(" ", @skipped).")\n" unless $quiet || $minimal;
1107 print STDERR
"mr $action: (failed: ".join(" ", @failed).")\n";
1117 return "$count ".($count > 1 ?
$plural : $singular);
1122 # an ordered list of repos
1125 foreach my $topdir (sort keys %config) {
1126 foreach my $subdir (sort keys %{$config{$topdir}}) {
1130 order
=> $config{$topdir}{$subdir}{order
},
1135 $a->{order
} <=> $b->{order
}
1137 $a->{topdir
} cmp $b->{topdir
}
1139 $a->{subdir
} cmp $b->{subdir
}
1145 my $topdir=$repo->{topdir
};
1146 my $subdir=$repo->{subdir
};
1147 my $ret=($subdir =~/^\//) ?
$subdir : $topdir.$subdir;
1149 my $absret=safe_abs_path
($ret);
1150 return (defined $absret ?
$absret : $ret);
1153 # Figure out which repos to act on. Returns a list of array refs
1156 # [ "$full_repo_path/", "$mr_config_path/", $section_header ]
1159 foreach my $repo (repolist
()) {
1160 my $topdir=$repo->{topdir
};
1161 my $subdir=$repo->{subdir
};
1163 next if $subdir eq 'DEFAULT';
1164 my $dir=absrepodir
($repo);
1166 $dir.="/" unless $dir=~/\
/$/;
1167 $d.="/" unless $d=~/\
/$/;
1168 next if $dir ne $d && $dir !~ /^\Q$d\E/;
1169 if (defined $max_depth) {
1170 my @a=split('/', $dir);
1171 my @b=split('/', $d);
1172 do { } while (@a && @b && shift(@a) eq shift(@b));
1173 next if @a > $max_depth || @b > $max_depth;
1175 push @repos, [$dir, $topdir, $subdir];
1178 # fallback to find a leaf repo
1179 foreach my $repo (reverse repolist
()) {
1180 my $topdir=$repo->{topdir
};
1181 my $subdir=$repo->{subdir
};
1183 next if $subdir eq 'DEFAULT';
1184 my $dir=absrepodir
($repo);
1186 $dir.="/" unless $dir=~/\
/$/;
1187 $d.="/" unless $d=~/\
/$/;
1188 if ($d=~/^\Q$dir\E/) {
1189 push @repos, [$dir, $topdir, $subdir];
1211 sub is_trusted_config
{
1212 my $config=shift; # must be abs_pathed already
1214 # We always trust ~/.mrconfig.
1215 return 1 if $config eq safe_abs_path
($HOME_MR_CONFIG);
1217 return 1 if $trust_all;
1219 my $trustfile=$ENV{HOME
}."/.mrtrust";
1222 $trusted{$HOME_MR_CONFIG}=1;
1223 if (open (TRUST
, "<", $trustfile)) {
1226 s/^~\//$ENV{HOME
}\
//;
1227 my $d=safe_abs_path
($_);
1228 $trusted{$d}=1 if defined $d;
1234 return $trusted{$config};
1238 sub is_trusted_repo
{
1241 # Tightly limit what is allowed in a repo name.
1242 # No ../, no absolute paths, and no unusual filenames
1243 # that might try to escape to the shell.
1244 return $repo =~ /^[-_.+\/A
-Za
-z0
-9]+$/ &&
1245 $repo !~ /\.\./ && $repo !~ /^\//;
1248 sub is_trusted_checkout
{
1251 # To determine if the command is safe, compare it with the
1252 # *_trusted_checkout config settings. Those settings are
1253 # templates for allowed commands, so make sure that each word
1254 # of the command matches the corresponding word of the template.
1257 foreach my $word (split(' ', $command)) {
1259 if ($word=~/^'(.*)'$/) {
1262 elsif ($word=~/^"(.*)"$/) {
1269 foreach my $key (grep { /_trusted_checkout$/ }
1270 keys %{$config{''}{DEFAULT
}}) {
1271 my @twords=split(' ', $config{''}{DEFAULT
}{$key});
1272 next if @words > @twords;
1276 for (my $c=0; $c < @twords && $match; $c++) {
1277 if ($twords[$c] eq '$url') {
1278 # Match all the typical characters found in
1279 # urls, plus @ which svn can use. Note
1280 # that the "url" might also be a local
1283 defined $words[$c] &&
1284 $words[$c] =~ /^[-_.+:@\/A
-Za
-z0
-9]+$/
1288 elsif ($twords[$c] eq '$repo') {
1289 # If a repo is not specified, assume it
1290 # will be the last path component of the
1291 # url, or something derived from it, and
1293 if (! defined $words[$c] && defined $url) {
1294 ($words[$c])=$url=~/\/([^\
/]+)\/?
$/;
1298 defined $words[$c] &&
1299 is_trusted_repo
($words[$c])
1302 elsif (defined $words[$c] && $words[$c]=~/^($twords[$c])$/) {
1319 my $bootstrap_src=shift;
1325 if (ref $f eq 'GLOB') {
1331 my $absf=safe_abs_path
($f);
1332 if ($loaded{$absf}) {
1337 $trusted=is_trusted_config
($absf);
1339 if (! defined $dir) {
1340 ($dir)=$f=~/^(.*\/)[^\
/]+$/;
1341 if (! defined $dir) {
1346 $dir=safe_abs_path
($dir)."/";
1348 if (! exists $configfiles{$dir}) {
1349 $configfiles{$dir}=$f;
1352 # copy in defaults from first parent
1354 while ($parent=~s/^(.*\/)[^\/]+\
/?$/$1/) {
1355 if ($parent eq '/') {
1358 if (exists $config{$parent} &&
1359 exists $config{$parent}{DEFAULT
}) {
1360 $config{$dir}{DEFAULT
}={ %{$config{$parent}{DEFAULT
}} };
1369 print "mr: loading config $f\n" if $verbose;
1370 open($in, "<", $f) || die "mr: open $f: $!\n";
1373 close $in unless ref $f eq 'GLOB';
1377 # Keep track of the current line in the config file;
1378 # when a file is included track the current line from the include.
1383 my $nextline = sub {
1395 my $lineerror = sub {
1397 if (defined $included) {
1398 die "mr: $msg at $f line $lineno, included line: $line\n";
1401 die "mr: $msg at $f line $lineno\n";
1404 my $trusterror = sub {
1407 if (defined $bootstrap_src) {
1408 die "mr: $msg in untrusted $bootstrap_src line $lineno\n".
1409 "(To trust this url, --trust-all can be used; but please use caution;\n".
1410 "this can allow arbitrary code execution!)\n";
1413 die "mr: $msg in untrusted $f line $lineno\n".
1414 "(To trust this file, list it in ~/.mrtrust.)\n";
1421 next if /^\s*\#/ || /^\s*$/;
1423 if (! $trusted && /[[:cntrl:]]/) {
1424 $trusterror->("illegal control character");
1427 if (/^\[([^\]]*)\]\s*$/) {
1431 if (! is_trusted_repo
($section) ||
1432 $section eq 'ALIAS' ||
1433 $section eq 'DEFAULT') {
1434 $trusterror->("illegal section \"[$section]\"");
1437 $section=expandenv
($section) if $trusted;
1438 if ($section ne 'ALIAS' &&
1439 ! exists $config{$dir}{$section} &&
1440 exists $config{$dir}{DEFAULT
}) {
1442 $config{$dir}{$section}={ %{$config{$dir}{DEFAULT
}} };
1445 elsif (/^(\w+)\s*=\s*(.*)/) {
1450 while (@lines && $lines[0]=~/^\s(.+)/) {
1457 # Untrusted files can only contain a few
1458 # settings in specific known-safe formats.
1459 if ($parameter eq 'checkout') {
1460 if (! is_trusted_checkout
($value)) {
1461 $trusterror->("illegal checkout command \"$value\"");
1464 elsif ($parameter eq 'order') {
1465 # not interpreted as a command, so
1468 elsif ($value eq 'true' || $value eq 'false') {
1469 # skip=true , deleted=true etc are
1473 $trusterror->("illegal setting \"$parameter=$value\"");
1477 if ($parameter eq "include") {
1478 print "mr: including output of \"$value\"\n" if $verbose;
1481 print STDERR
"mr: include command exited nonzero ($?)\n";
1484 unshift @lines, @inc;
1488 if ($parameter eq 'jobs') {
1489 print "mr: setting --jobs to \"$value\"\n" if $verbose;
1490 unless ($value =~ /^\d+$/) {
1491 print "mr: error: --jobs must be numeric\n";
1498 if (! defined $section) {
1499 $lineerror->("parameter ($parameter) not in section");
1501 if ($section eq 'ALIAS') {
1502 $alias{$parameter}=$value;
1504 elsif ($parameter eq 'lib' or $parameter =~ s/_append$//) {
1505 $config{$dir}{$section}{$parameter}.="\n".$value."\n";
1508 $config{$dir}{$section}{$parameter}=$value;
1509 if ($parameter =~ /.*_(.*)/) {
1510 $knownactions{$1}=1;
1513 $knownactions{$parameter}=1;
1515 if ($parameter eq 'chain' &&
1516 length $dir && $section ne "DEFAULT") {
1517 my $chaindir="$section";
1518 if ($chaindir !~ m!^/!) {
1519 $chaindir=$dir.$chaindir;
1521 if (-e
"$chaindir/.mrconfig") {
1522 my $ret=system($value);
1524 if (($?
& 127) == 2) {
1525 print STDERR
"mr: chain test interrupted\n";
1529 print STDERR
"mr: chain test received signal ".($?
& 127)."\n";
1533 push @toload, ["$chaindir/.mrconfig", $chaindir];
1540 $lineerror->("parse error");
1544 foreach my $c (@toload) {
1549 sub startingconfig
{
1550 %alias=%config=%configfiles=%knownactions=%loaded=();
1551 my $datapos=tell(DATA
);
1553 seek(DATA
,$datapos,0); # rewind
1558 # the section to modify or add
1559 my $targetsection=shift;
1560 # fields to change in the section
1561 # To remove a field, set its value to "".
1562 my %changefields=@_;
1568 open(my $in, "<", $f) || die "mr: open $f: $!\n";
1573 my $formatfield=sub {
1575 my @value=split(/\n/, shift);
1577 return "$field = ".shift(@value)."\n".
1578 join("", map { "\t$_\n" } @value);
1582 while ($out[$#out] =~ /^\s*$/) {
1583 unshift @blanks, pop @out;
1585 foreach my $field (sort keys %changefields) {
1586 if (length $changefields{$field}) {
1587 push @out, "$field = $changefields{$field}\n";
1588 delete $changefields{$field};
1598 if (/^\s*\#/ || /^\s*$/) {
1601 elsif (/^\[([^\]]*)\]\s*$/) {
1602 if (defined $section &&
1603 $section eq $targetsection) {
1607 $section=expandenv
($1);
1611 elsif (/^(\w+)\s*=\s(.*)/) {
1616 while (@lines && $lines[0]=~/^\s(.+)/) {
1622 if ($section eq $targetsection) {
1623 if (exists $changefields{$parameter}) {
1624 if (length $changefields{$parameter}) {
1625 $value=$changefields{$parameter};
1627 delete $changefields{$parameter};
1631 push @out, $formatfield->($parameter, $value);
1635 if (defined $section &&
1636 $section eq $targetsection) {
1639 elsif (%changefields) {
1640 push @out, "\n[$targetsection]\n";
1641 foreach my $field (sort keys %changefields) {
1642 if (length $changefields{$field}) {
1643 push @out, $formatfield->($field, $changefields{$field});
1648 open(my $out, ">", $f) || die "mr: write $f: $!\n";
1656 # actions that do not operate on all repos
1657 if ($action eq 'config') {
1660 elsif ($action eq 'register') {
1663 elsif ($action eq 'bootstrap') {
1666 elsif ($action eq 'remember' ||
1667 $action eq 'offline' ||
1668 $action eq 'online') {
1669 my @repos=selectrepos
;
1670 action
($action, @
{$repos[0]}) if @repos;
1674 if (!$jobs || $jobs > 1) {
1675 mrs
($action, selectrepos
());
1678 foreach my $repo (selectrepos
()) {
1679 record
($repo, action
($action, @
$repo));
1685 if (! -e
"$ENV{MR_PATH}") {
1686 die "cannot find the program path";
1688 exec("perldoc", $ENV{MR_PATH
}) || die "exec perldoc: $!";
1693 die "mr config: not enough parameters\n";
1696 if ($section=~/^\//) {
1697 # try to convert to a path relative to the config file
1698 my ($dir)=$ENV{MR_CONFIG
}=~/^(.*\/)[^\
/]+$/;
1699 $dir=safe_abs_path
($dir);
1700 $dir.="/" unless $dir=~/\
/$/;
1701 if ($section=~/^\Q$dir\E(.*)/) {
1707 if (/^([^=]+)=(.*)$/) {
1708 $changefields{$1}=$2;
1712 foreach my $topdir (sort keys %config) {
1713 if (exists $config{$topdir}{$section} &&
1714 exists $config{$topdir}{$section}{$_}) {
1715 print $config{$topdir}{$section}{$_}."\n";
1717 last if $section eq 'DEFAULT';
1721 die "mr config: $section $_ not set\n";
1725 modifyconfig
($ENV{MR_CONFIG
}, $section, %changefields) if %changefields;
1730 if ($config_overridden) {
1731 # Find the directory that the specified config file is
1733 ($directory)=safe_abs_path
($ENV{MR_CONFIG
})=~/^(.*\/)[^\
/]+$/;
1736 # Find the closest known mrconfig file to the current
1738 $directory.="/" unless $directory=~/\
/$/;
1740 foreach my $topdir (reverse sort keys %config) {
1741 next unless length $topdir;
1742 if ($directory=~/^\Q$topdir\E/) {
1743 $ENV{MR_CONFIG
}=$configfiles{$topdir};
1749 if (! $foundconfig) {
1750 $directory=""; # no config file, use builtin
1754 my $subdir=shift @ARGV;
1755 if (! chdir($subdir)) {
1756 print STDERR
"mr register: failed to chdir to $subdir: $!\n";
1760 $ENV{MR_REPO
}=getcwd
();
1761 my $command=findcommand
("register", $ENV{MR_REPO
}, $directory, 'DEFAULT', 0);
1762 if (! defined $command) {
1763 die "mr register: unknown repository type\n";
1766 $ENV{MR_REPO
}=~s/.*\/(.*)/$1/;
1767 $command="set -e; ".$config{$directory}{DEFAULT
}{lib
}."\n".
1768 "my_action(){ $command\n }; my_action ".
1769 join(" ", map { s/\\/\\\\/g; s/"/\"/g; '"'.$_.'"' } @ARGV);
1770 print "mr register: running >>$command<<\n" if $verbose;
1771 exec($command) || die "exec: $!";
1775 eval q{use File::Copy};
1778 my $src=shift @ARGV;
1779 my $dir=shift @ARGV || ".";
1781 if (! defined $src || ! length $src) {
1782 die "mr: bootstrap requires source\n";
1785 # Retrieve config file.
1786 eval q{use File::Temp};
1788 my $tmpconfig=File
::Temp
->new();
1789 if ($src =~ m!^[\w\d]+://!) {
1790 # Download the config file to a temporary location.
1792 if ($src =~ m!^ssh://(.*)!) {
1793 @downloader = ("scp", $1, $tmpconfig);
1796 @downloader = ("curl", "-A", "mr", "-L", "-s", $src, "-o", $tmpconfig);
1797 push(@downloader, "-k") if $insecure;
1799 my $status = system(@downloader);
1800 die "mr bootstrap: invalid SSL certificate for $src (consider -k)\n"
1801 if $downloader[0] eq 'curl' && $status >> 8 == 60;
1802 die "mr bootstrap: download of $src failed\n" if $status != 0;
1804 elsif ($src eq '-') {
1805 # Config file is read from stdin.
1806 copy
(\
*STDIN
, $tmpconfig) || die "stdin: $!";
1807 seek $tmpconfig, 0, 0;
1810 # Config file is local.
1811 die "mr bootstrap: cannot read file '$src'"
1813 copy
($src, $tmpconfig) || die "copy: $!";
1814 seek $tmpconfig, 0, 0;
1817 # Sanity check on destination directory.
1819 system("mkdir", "-p", $dir);
1821 chdir($dir) || die "chdir $dir: $!";
1823 # Special case to handle checkout of the "." repo, which
1824 # would normally be skipped.
1825 my $topdir=safe_abs_path
(".")."/";
1826 my @repo=($topdir, $topdir, ".");
1827 loadconfig
($tmpconfig, $topdir, $src);
1828 record
(\
@repo, action
("checkout", @repo, 1))
1829 if exists $config{$topdir}{"."}{"checkout"};
1831 if (-e
".mrconfig") {
1832 print STDERR
"mr bootstrap: .mrconfig file already exists, not overwriting with $src\n";
1835 move
($tmpconfig, ".mrconfig") || die "rename: $!";
1838 # Reload the config file (in case we got a different version)
1839 # and checkout everything else.
1841 loadconfig
(".mrconfig");
1842 dispatch
("checkout");
1843 @skipped=grep { safe_abs_path
($_) ne safe_abs_path
($topdir) } @skipped;
1844 showstats
("bootstrap");
1848 # alias expansion and command stemming
1851 if (exists $alias{$action}) {
1852 $action=$alias{$action};
1854 if (! exists $knownactions{$action}) {
1855 my @matches = grep { /^\Q$action\E/ }
1856 keys %knownactions, keys %alias;
1857 if (@matches == 1) {
1858 $action=$matches[0];
1860 elsif (@matches == 0) {
1861 die "mr: unknown action \"$action\" (known actions: ".
1862 join(", ", sort keys %knownactions).")\n";
1865 die "mr: ambiguous action \"$action\" (matches: ".
1866 join(", ", @matches).")\n";
1874 while (length $dir) {
1875 if (-e
"$dir/.mrconfig") {
1876 return "$dir/.mrconfig";
1878 $dir=~s/\/[^\/]*$//;
1880 return $HOME_MR_CONFIG;
1885 Getopt
::Long
::Configure
("bundling", "no_permute");
1886 my $result=GetOptions
(
1887 "d|directory=s" => sub { $directory=safe_abs_path
($_[1]) },
1888 "c|config=s" => sub { $ENV{MR_CONFIG
}=$_[1]; $config_overridden=1 },
1889 "p|path" => sub { }, # now default, ignore
1890 "f|force" => \
$force,
1891 "force-env" => \
$force_env,
1892 "v|verbose" => \
$verbose,
1893 "m|minimal" => \
$minimal,
1894 "q|quiet" => \
$quiet,
1895 "s|stats" => \
$stats,
1896 "k|insecure" => \
$insecure,
1897 "i|interactive" => \
$interactive,
1898 "n|no-recurse:i" => \
$max_depth,
1899 "j|jobs:i" => \
$jobs,
1900 "t|trust-all" => \
$trust_all,
1902 if (! $result || @ARGV < 1) {
1903 die("Usage: mr [options] action [params ...]\n".
1904 "(Use mr help for man page.)\n");
1907 $ENV{MR_SWITCHES
}="";
1908 foreach my $option (@saved) {
1909 last if $option eq $ARGV[0];
1910 $ENV{MR_SWITCHES
}.="$option ";
1915 return if $force_env;
1916 my @env = qw(GIT_DIR GIT_INDEX_FILE GIT_OBJECT_DIRECTORY GIT_WORK_TREE VCSH_COMMAND VCSH_DIRECTORY VCSH_REPO_NAME);
1921 print STDERR
"mr: environment variable '$_' is set.\n";
1924 die ("mr: The variables above would lead to very interesting effects.
1925 Unfortunately, most of those effects result in data loss so we stop here.\n") if $error;
1930 print STDERR
"$erase_line" if defined $erase_line && $terminal && !$quiet && $minimal && 1 == $jobs;
1931 print STDERR
"mr: interrupted\n";
1935 # This can happen if it's run in a directory that was removed
1936 # or other strangeness.
1937 if (! defined $directory) {
1938 die("mr: failed to determine working directory\n");
1940 # Make sure MR_CONFIG is an absolute path, but don't use abs_path since
1941 # the config file might be a symlink to elsewhere, and the directory it's
1942 # in is significant.
1943 if ($ENV{MR_CONFIG
} !~ /^\//) {
1944 $ENV{MR_CONFIG
}=getcwd
()."/".$ENV{MR_CONFIG
};
1946 # Try to set MR_PATH to the path to the program.
1948 use FindBin
qw($Bin $Script);
1949 $ENV{MR_PATH}=$Bin."/".$Script;
1962 # abs_path crashes on windows and some other platforms when given a file
1963 # that does not exist.
1966 my $p=eval { abs_path($f) };
1979 help(@ARGV) if $ARGV[0] eq 'help';
1982 loadconfig($HOME_MR_CONFIG);
1983 loadconfig($ENV{MR_CONFIG});
1984 #use Data::Dumper; print Dumper(\%config);
1986 my $action=expandaction(shift @ARGV);
1993 # Finally, some useful actions that mr knows about by default.
1994 # These can be overridden in ~/.mrconfig.
2009 echo "mr (warning): $@" >&2
2015 if [ -z "$1" ] || [ -z "$2" ]; then
2016 error "mr: usage: hours_since action num"
2018 for dir in .git .svn .bzr CVS .hg _darcs _FOSSIL_ .fslckout; do
2019 if [ -e "$MR_REPO/$dir" ]; then
2020 flagfile="$MR_REPO/$dir/.mr_last$1"
2024 if [ -z "$flagfile" ]; then
2025 error "cannot determine flag filename"
2027 delta=`perl -wle 'print -f shift() ? int((-M _) * 24) : 9999' "$flagfile"`
2028 if [ "$delta" -lt "$2" ]; then
2036 LANG=C bzr info | egrep -q '^Checkout'
2039 if [ -d "$MR_REPO" ]; then
2046 svn_test = perl: -d "$ENV{MR_REPO}/.svn"
2047 git_test = perl: -e "$ENV{MR_REPO}/.git"
2048 bzr_test = perl: -d "$ENV{MR_REPO}/.bzr"
2049 cvs_test = perl: -d "$ENV{MR_REPO}/CVS"
2050 hg_test = perl: -d "$ENV{MR_REPO}/.hg"
2051 darcs_test = perl: -d "$ENV{MR_REPO}/_darcs"
2052 fossil_test = perl: -f "$ENV{MR_REPO}/_FOSSIL_" || -f "$ENV{MR_REPO}/.fslckout"
2053 git_bare_test = perl:
2054 -d "$ENV{MR_REPO}/refs/heads" && -d "$ENV{MR_REPO}/refs/tags" &&
2055 -d "$ENV{MR_REPO}/objects" && -f "$ENV{MR_REPO}/config" &&
2056 `GIT_CONFIG="$ENV{MR_REPO}"/config git config --get core.bare` =~ /true/
2058 -d "$ENV{MR_REPO}/refs/heads" && -d "$ENV{MR_REPO}/refs/tags" &&
2059 -d "$ENV{MR_REPO}/objects" && -f "$ENV{MR_REPO}/config" &&
2060 `GIT_CONFIG="$ENV{MR_REPO}"/config git config --get vcsh.vcsh` =~ /true/
2061 veracity_test = perl: -d "$ENV{MR_REPO}/.sgdrawer"
2063 svn_update = svn update "$@"
2064 git_update = git pull "$@"
2066 if is_bzr_checkout; then
2069 bzr merge --pull "$@"
2071 cvs_update = cvs -q update "$@"
2072 hg_update = hg pull "$@"; hg update "$@"
2073 darcs_update = darcs pull -a "$@"
2074 fossil_update = fossil pull "$@"
2075 vcsh_update = vcsh run "$MR_REPO" git pull "$@"
2076 veracity_update = vv pull "$@" && vv update "$@"
2078 git_fetch = git fetch --all --prune --tags "$@"
2079 git_svn_fetch = git svn fetch "$@"
2080 darcs_fetch = darcs fetch "$@"
2081 hg_fetch = hg pull "$@"
2084 if [ "x$1" = x-f ] ; then
2088 svn-clean --print "$@"
2091 if [ "x$1" = x-f ] ; then
2093 git clean -dx --force "$@"
2095 git clean -dx --dry-run "$@"
2098 if [ "x$1" = x-f ] ; then
2100 git clean -dx --force "$@"
2102 git clean -dx --dry-run "$@"
2105 if [ "x$1" = x-f ] ; then
2107 bzr clean-tree --verbose --force --ignored --unknown --detritus "$@"
2109 bzr clean-tree --verbose --dry-run --ignored --unknown --detritus "$@"
2112 if [ "x$1" = x-f ] ; then
2116 cvs-clean --dry-run "$@"
2119 if [ "x$1" = x-f ] ; then
2121 hg purge --print --all "$@"
2124 hg purge --print --all "$@"
2127 if [ "x$1" = x-f ] ; then
2129 fossil clean --dry-run --dotfiles --emptydirs "$@"
2131 fossil clean --force --dotfiles --emptydirs "$@"
2134 if [ "x$1" = x-f ] ; then
2136 vcsh run "$MR_REPO" git clean -dx "$@"
2138 vcsh run "$MR_REPO" git clean -dx --dry-run "$@"
2141 svn_status = svn status "$@"
2142 git_status = git status -s "$@" || true; git --no-pager log --branches --not --remotes --simplify-by-decoration --decorate --oneline || true; git --no-pager stash list
2143 bzr_status = bzr status --short "$@"; bzr missing
2144 cvs_status = cvs -q status | grep -E '^(File:.*Status:|\?)' | grep -v 'Status: Up-to-date' || true
2145 hg_status = hg status "$@"; hg summary --quiet | grep -v 'parent: 0:'
2146 darcs_status = darcs whatsnew -ls "$@" || true
2147 fossil_status = fossil changes "$@"
2148 vcsh_status = vcsh status "$MR_REPO" "$@" || true
2149 veracity_status = vv status "$@"
2151 svn_commit = svn commit "$@"
2152 git_commit = git commit -a "$@" && git push --all
2154 if is_bzr_checkout; then
2157 bzr commit "$@" && bzr push
2159 cvs_commit = cvs commit "$@"
2160 hg_commit = hg commit "$@" && hg push
2161 darcs_commit = darcs record -a "$@" && darcs push -a
2162 fossil_commit = fossil commit "$@"
2163 vcsh_commit = vcsh run "$MR_REPO" git commit -a "$@" && vcsh run "$MR_REPO" git push --all
2164 veracity_commit = vv commit "$@" && vv push
2166 git_record = git commit -a "$@"
2168 if is_bzr_checkout; then
2169 bzr commit --local "$@"
2173 hg_record = hg commit "$@"
2174 darcs_record = darcs record -a "$@"
2175 fossil_record = fossil commit "$@"
2176 vcsh_record = vcsh run "$MR_REPO" git commit -a "$@"
2177 veracity_record = vv commit "$@"
2180 git_push = git push "$@"
2181 bzr_push = bzr push "$@"
2183 hg_push = hg push "$@" || if [ "$?" -eq "255" ] ; then exit 1; else exit 0; fi
2184 darcs_push = darcs push -a "$@"
2185 fossil_push = fossil push "$@"
2186 vcsh_push = vcsh run "$MR_REPO" git push "$@"
2187 veracity_push = vv push "$@"
2189 svn_diff = svn diff "$@"
2190 git_diff = git diff "$@"
2191 bzr_diff = bzr diff "$@"
2192 cvs_diff = cvs -q diff "$@"
2193 hg_diff = hg diff "$@"
2194 darcs_diff = darcs diff -u "$@"
2195 fossil_diff = fossil diff "$@"
2196 vcsh_diff = vcsh run "$MR_REPO" git diff "$@"
2197 veracity_diff = vv diff "$@"
2199 svn_log = svn log "$@"
2200 git_log = git log "$@"
2201 bzr_log = bzr log "$@"
2202 cvs_log = cvs log "$@"
2203 hg_log = hg log "$@"
2204 darcs_log = darcs changes "$@"
2205 git_bare_log = git log "$@"
2206 fossil_log = fossil timeline "$@"
2207 vcsh_log = vcsh run "$MR_REPO" git log "$@"
2208 veracity_log = vv log "$@"
2210 hg_grep = hg grep "$@"
2211 cvs_grep = ack-grep "$@"
2212 svn_grep = ack-grep "$@"
2213 git_svn_grep = git grep "$@"
2214 git_grep = git grep "$@"
2215 bzr_grep = ack-grep "$@"
2216 darcs_grep = ack-grep "$@"
2221 url=`LC_ALL=C svn info . | grep -i '^URL:' | cut -d ' ' -f 2`
2222 if [ -z "$url" ]; then
2223 error "cannot determine svn url"
2225 echo "Registering svn url: $url in $MR_CONFIG"
2226 mr -c "$MR_CONFIG" config "`pwd`" checkout="svn co '$url' '$MR_REPO'"
2228 url="`LC_ALL=C git config --get remote.origin.url`" || true
2229 if [ -z "$url" ]; then
2230 error "cannot determine git url"
2232 echo "Registering git url: $url in $MR_CONFIG"
2233 mr -c "$MR_CONFIG" config "`pwd`" checkout="git clone '$url' '$MR_REPO'"
2235 url="`LC_ALL=C bzr info . | egrep -i 'checkout of branch|parent branch' | awk '{print $NF}' | head -n 1`"
2236 if [ -z "$url" ]; then
2237 error "cannot determine bzr url"
2239 echo "Registering bzr url: $url in $MR_CONFIG"
2240 mr -c "$MR_CONFIG" config "`pwd`" checkout="bzr branch '$url' '$MR_REPO'"
2242 repo=`cat CVS/Repository`
2244 if [ -z "$root" ]; then
2245 error "cannot determine cvs root"
2247 echo "Registering cvs repository $repo at root $root"
2248 mr -c "$MR_CONFIG" config "`pwd`" checkout="cvs -d '$root' co -d '$MR_REPO' '$repo'"
2250 url=`hg showconfig paths.default`
2251 echo "Registering mercurial repo url: $url in $MR_CONFIG"
2252 mr -c "$MR_CONFIG" config "`pwd`" checkout="hg clone '$url' '$MR_REPO'"
2254 url=`cat _darcs/prefs/defaultrepo`
2255 echo "Registering darcs repository $url in $MR_CONFIG"
2256 mr -c "$MR_CONFIG" config "`pwd`" checkout="darcs get '$url' '$MR_REPO'"
2258 url="`LC_ALL=C GIT_CONFIG=config git config --get remote.origin.url`" || true
2259 if [ -z "$url" ]; then
2260 error "cannot determine git url"
2262 echo "Registering git url: $url in $MR_CONFIG"
2263 mr -c "$MR_CONFIG" config "`pwd`" checkout="git clone --bare '$url' '$MR_REPO'"
2265 mr_repo_basename=`basename "$MR_REPO" .git`
2266 url="`LC_ALL=C vcsh run "$mr_repo_basename" git config --get remote.origin.url`" || true
2267 if [ -z "$url" ]; then
2268 error "cannot determine git url"
2270 echo "Registering git url: $url in $MR_CONFIG"
2271 mr -c "$MR_CONFIG" config "`pwd`" checkout="vcsh clone '$url' '$mr_repo_basename'"
2273 url=`fossil remote-url`
2274 repo=`fossil info | grep repository | sed -e 's/repository:*.//g' -e 's/ //g'`
2275 echo "Registering fossil repository $url in $MR_CONFIG"
2276 mr -c "$MR_CONFIG" config "`pwd`" checkout="mkdir -p '$MR_REPO' && cd '$MR_REPO' && fossil open '$repo'"
2278 url=`vv config | grep sync_targets | sed -e 's/sync_targets:*.//g' -e 's/ //g'`
2279 repo=`vv repo info | grep repository | sed -e 's/Current repository:*.//g' -e 's/ //g'`
2280 echo "Registering veracity repository $url in $MR_CONFIG"
2281 mr -c "$MR_CONFIG" config "`pwd`" checkout="mkdir -p '$MR_REPO' && cd '$MR_REPO' && vv checkout '$repo'"
2283 svn_trusted_checkout = svn co $url $repo
2284 svn_alt_trusted_checkout = svn checkout $url $repo
2285 git_trusted_checkout = git clone $url $repo
2286 bzr_trusted_checkout = bzr checkout|clone|branch|get $url $repo
2288 hg_trusted_checkout = hg clone $url $repo
2289 darcs_trusted_checkout = darcs get $url $repo
2290 git_bare_trusted_checkout = git clone --bare $url $repo
2291 vcsh_old_trusted_checkout = vcsh run "$MR_REPO" git clone $url $repo
2292 vcsh_trusted_checkout = vcsh clone $url $repo
2293 # fossil: messy to do
2294 veracity_trusted_checkout = vv clone $url $repo
2302 if [ -s ~/.mrlog ]; then
2303 info "running offline commands"
2304 mv -f ~/.mrlog ~/.mrlog.old
2305 if ! sh -e ~/.mrlog.old; then
2306 error "offline command failed; left in ~/.mrlog.old"
2310 info "no offline commands to run"
2315 info "offline mode enabled"
2317 info "remembering command: 'mr $@'"
2318 command="mr -d '$(pwd)' $MR_SWITCHES"
2320 command="$command '$w'"
2322 if [ ! -e ~/.mrlog ] || ! grep -q -F "$command" ~/.mrlog; then
2323 echo "$command" >> ~/.mrlog
2326 ed = echo "A horse is a horse, of course, of course.."
2327 T = echo "I pity the fool."
2328 right = echo "Not found."
2330 # vim:sw=8:sts=0:ts=8:noet
2332 # indent-tabs-mode: t
2333 # cperl-indent-level: 8