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 Multiple Repository management tool
9 B<mr> [options] checkout
11 B<mr> [options] update
13 B<mr> [options] status
15 B<mr> [options] commit [-m "message"]
17 B<mr> [options] record [-m "message"]
27 B<mr> [options] run command [param ...]
29 B<mr> [options] bootstrap url [directory]
31 B<mr> [options] register [repository]
33 B<mr> [options] config section ["parameter=[value]" ...]
35 B<mr> [options] action [params ...]
37 B<mr> [options] [online|offline]
39 B<mr> [options] remember action [params ...]
43 B<mr> is a Multiple Repository management tool. It can checkout, update, or
44 perform other actions on a set of repositories as if they were one combined
45 repository. It supports any combination of subversion, git, cvs, mercurial,
46 bzr, darcs, fossil and veracity repositories, and support for other version
47 control systems can easily be added.
49 B<mr> cds into and operates on all registered repositories at or below your
50 working directory. Or, if you are in a subdirectory of a repository that
51 contains no other registered repositories, it will stay in that directory,
52 and work on only that repository,
54 B<mr> is configured by .mrconfig files, which list the repositories. It
55 starts by reading the .mrconfig file in your home directory, and this can
56 in turn chain load .mrconfig files from repositories. It also automatically
57 looks for a .mrconfig file in the current directory, or in one of its
60 These predefined commands should be fairly familiar to users of any version
65 =item checkout (or co)
67 Checks out any repositories that are not already checked out.
71 Updates each repository from its configured remote repository.
73 If a repository isn't checked out yet, it will first check it out.
77 Displays a status report for each repository, showing what
78 uncommitted changes are present in the repository. For distributed version
79 control systems, also shows unpushed local branches.
83 Commits changes to each repository. (By default, changes are pushed to the
84 remote repository too, when using distributed systems like git. If you
85 don't like this default, you can change it in your .mrconfig, or use record
88 The optional -m parameter allows specifying a commit message.
92 Records changes to the local repository, but does not push them to the
93 remote repository. Only supported for distributed version control systems.
95 The optional -m parameter allows specifying a commit message.
99 Fetches from each repository's remote repository, but does not
100 update the working copy. Only supported for some distributed version
105 Pushes committed local changes to the remote repository. A no-op for
106 centralized version control systems.
110 Show a diff of uncommitted changes.
116 =item run command [param ...]
118 Runs the specified command in each repository.
122 These commands are also available:
126 =item bootstrap url [directory]
128 Causes mr to download the url, and use it as a .mrconfig file to checkout
129 the repositories listed in it, into the specified directory.
131 To use scp to download, the url may have the form ssh://[user@]host:file
133 The directory will be created if it does not exist. If no directory is
134 specified, the current directory will be used.
136 If the .mrconfig file includes a repository named ".", that
137 is checked out into the top of the specified directory.
141 List the repositories that mr will act on.
145 Register an existing repository in a mrconfig file. By default, the
146 repository in the current directory is registered, or you can specify a
147 directory to register.
149 The mrconfig file that is modified is chosen by either the -c option, or by
150 looking for the closest known one at or in a parent of the current directory.
154 Adds, modifies, removes, or prints a value from a mrconfig file. The next
155 parameter is the name of the section the value is in. To add or modify
156 values, use one or more instances of "parameter=value". Use "parameter=" to
157 remove a parameter. Use just "parameter" to get the value of a parameter.
159 For example, to add (or edit) a repository in src/foo:
161 mr config src/foo checkout="svn co svn://example.com/foo/trunk foo"
163 To show the command that mr uses to update the repository in src/foo:
165 mr config src/foo update
167 To see the built-in library of shell functions contained in mr:
169 mr config DEFAULT lib
171 The mrconfig file that is used is chosen by either the -c option, or by
172 looking for the closest known one at or in a parent of the current directory.
176 Advises mr that it is in offline mode. Any commands that fail in
177 offline mode will be remembered, and retried when mr is told it's online.
181 Advices mr that it is in online mode again. Commands that failed while in
182 offline mode will be re-run.
186 Remember a command, to be run later when mr re-enters online mode. This
187 implicitly puts mr into offline mode. The command can be any regular mr
188 command. This is useful when you know that a command will fail due to being
189 offline, and so don't want to run it right now at all, but just remember
190 to run it when you go back online.
198 Actions can be abbreviated to any unambiguous substring, so
199 "mr st" is equivalent to "mr status", and "mr up" is equivalent to "mr
202 Additional parameters can be passed to most commands, and are passed on
203 unchanged to the underlying version control system. This is mostly useful
204 if the repositories mr will act on all use the same version control
213 =item --directory directory
215 Specifies the topmost directory that B<mr> should work in. The default is
216 the current working directory.
220 =item --config mrconfig
222 Use the specified mrconfig file. The default is to use both F<~/.mrconfig>
223 as well as look for a F<.mrconfig> file in the current directory, or in one
224 of its parent directories.
230 Force mr to act on repositories that would normally be skipped due to their
243 Be quiet. This suppresses mr's usual output, as well as any output from
244 commands that are run (including stderr output). If a command fails,
245 the output will be shown.
251 Accept untrusted SSL certificates when bootstrapping.
257 Expand the statistics line displayed at the end to include information
258 about exactly which repositories failed and were skipped, if any.
264 Interactive mode. If a repository fails to be processed, a subshell will be
265 started which you can use to resolve or investigate the problem. Exit the
266 subshell to continue the mr run.
270 =item --no-recurse [number]
272 If no number if specified, just operate on the repository for the current
273 directory, do not recurse into deeper repositories.
275 If a number is specified, will recurse into repositories at most that many
276 subdirectories deep. For example, with -n 2 it would recurse into ./src/foo,
277 but not ./src/packages/bar.
281 =item --jobs [number]
283 Run the specified number of jobs in parallel, or an unlimited number of jobs
284 with no number specified. This can greatly speed up operations such as updates.
285 It is not recommended for interactive operations.
287 Note that running more than 10 jobs at a time is likely to run afoul of
288 ssh connection limits. Running between 3 and 5 jobs at a time will yield
289 a good speedup in updates without loading the machine too much.
295 Trust all mrconfig files even if they are not listed in F<~/.mrtrust>.
302 This obsolete flag is ignored.
306 =head1 MRCONFIG FILES
308 Here is an example F<.mrconfig> file:
311 checkout = svn checkout svn://svn.example.com/src/trunk src
315 checkout = git clone git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux-2.6.git &&
317 git checkout -b mybranch origin/master
319 The F<.mrconfig> file uses a variant of the INI file format. Lines
320 starting with "#" are comments. Values can be continued to the
321 following line by indenting the line with whitespace.
323 The C<DEFAULT> section allows setting default values for the sections that
326 The C<ALIAS> section allows adding aliases for actions. Each parameter
327 is an alias, and its value is the action to use.
329 All other sections add repositories. The section header specifies the
330 directory where the repository is located. This is relative to the directory
331 that contains the mrconfig file, but you can also choose to use absolute
332 paths. (Note that you can use environment variables in section names; they
333 will be passed through the shell for expansion. For example,
334 C<[$HOSTNAME]>, or C<[${HOSTNAME}foo]>).
336 Within a section, each parameter defines a shell command to run to handle a
337 given action. mr contains default handlers for "update", "status",
338 "commit", and other standard actions.
340 Normally you only need to specify what to do for "checkout". Here you
341 specify the command to run in order to create a checkout of the repository.
342 The command will be run in the parent directory, and must create the
343 repository's directory. So use C<git clone>, C<svn checkout>, C<bzr branch>
344 or C<bzr checkout> (for a bound branch), etc.
346 Note that these shell commands are run in a C<set -e> shell
347 environment, where any additional parameters you pass are available in
348 C<$@>. All commands other than "checkout" are run inside the repository,
349 though not necessarily at the top of it.
351 The C<MR_REPO> environment variable is set to the path to the top of the
352 repository. (For the "register" action, "MR_REPO" is instead set to the
353 basename of the directory that should be created when checking the
356 The C<MR_CONFIG> environment variable is set to the .mrconfig file
357 that defines the repo being acted on, or, if the repo is not yet in a config
358 file, the F<.mrconfig> file that should be modified to register the repo.
360 The C<MR_ACTION> environment variable is set to the command being run
361 (update, checkout, etc).
363 A few parameters have special meanings:
369 If the "skip" parameter is set and its command returns true, then B<mr>
370 will skip acting on that repository. The command is passed the action
373 Here are two examples. The first skips the repo unless
374 mr is run by joey. The second uses the hours_since function
375 (included in mr's built-in library) to skip updating the repo unless it's
376 been at least 12 hours since the last update.
380 skip = test `whoami` != joey
384 skip = [ "$1" = update ] && ! hours_since "$1" 12
386 Another way to use skip is for a lazy checkout. This makes mr skip
387 operating on a repo unless it already exists. To enable the
388 repo, you have to explicitly check it out (using "mr --force -d foo checkout").
396 The "order" parameter can be used to override the default ordering of
397 repositories. The default order value is 10. Use smaller values to make
398 repositories be processed earlier, and larger values to make repositories
401 Note that if a repository is located in a subdirectory of another
402 repository, ordering it to be processed earlier is not recommended.
406 If the "chain" parameter is set and its command returns true, then B<mr>
407 will try to load a F<.mrconfig> file from the root of the repository.
411 If the "include" parameter is set, its command is ran, and should output
412 additional mrconfig file content. The content is included as if it were
413 part of the including file.
415 Unlike all other parameters, this parameter does not need to be placed
418 B<mr> ships several libraries that can be included to add support for
419 additional version control type things (unison, git-svn, git-fake-bare,
420 git-subtree). To include them all, you could use:
422 include = cat /usr/share/mr/*
424 See the individual files for details.
428 If the "deleted" parameter is set and its command returns true, then
429 B<mr> will treat the repository as deleted. It won't ever actually delete
430 the repository, but it will warn if it sees the repository's directory.
431 This is useful when one mrconfig file is shared among multiple machines,
432 to keep track of and remember to delete old repositories.
436 The "lib" parameter can specify some shell code that will be run
437 before each command, this can be a useful way to define shell
438 functions for other commands to use.
440 Unlike most other parameters, this can be specified multiple times, in
441 which case the chunks of shell code are accumulatively concatenated
446 If the "fixups" parameter is set, its command is run whenever a repository
447 is checked out, or updated. This provides an easy way to do things
448 like permissions fixups, or other tweaks to the repository content,
449 whenever the repository is changed.
453 When looking for a command to run for a given action, mr first looks for
454 a parameter with the same name as the action. If that is not found, it
455 looks for a parameter named "VCS_action" (substituting in the name of the
456 version control system and the action).
458 Internally, mr has settings for "git_update", "svn_update", etc. To change
459 the action that is performed for a given version control system, you can
460 override these VCS specific actions. To add a new version control system,
461 you can just add VCS specific actions for it.
465 If a "pre_action" parameter is set, its command is run before mr performs the
466 specified action. Similarly, "post_action" parameters are run after mr
467 successfully performs the specified action. For example, "pre_commit" is
468 run before committing; "post_update" is run after updating.
472 Any parameter can be suffixed with C<_append>, to add an additional value
473 to the existing value of the parameter. In this way, actions
474 can be constructed accumulatively.
478 The name of the version control system is itself determined by
479 running each defined "VCS_test" action, until one succeeds.
483 =head1 UNTRUSTED MRCONFIG FILES
485 Since mrconfig files can contain arbitrary shell commands, they can do
486 anything. This flexibility is good, but it also allows a malicious mrconfig
487 file to delete your whole home directory. Such a file might be contained
488 inside a repository that your main F<~/.mrconfig> checks out. To
489 avoid worries about evil commands in a mrconfig file, mr defaults to
490 reading all mrconfig files other than the main F<~/.mrconfig> in untrusted
491 mode. In untrusted mode, mrconfig files are limited to running only known
492 safe commands (like "git clone") in a carefully checked manner.
494 To configure mr to trust other mrconfig files, list them in F<~/.mrtrust>.
495 One mrconfig file should be listed per line. Either the full pathname
496 should be listed, or the pathname can start with F<~/> to specify a file
497 relative to your home directory.
499 =head1 OFFLINE LOG FILE
501 The F<~/.mrlog> file contains commands that mr has remembered to run later,
502 due to being offline. You can delete or edit this file to remove commands,
503 or even to add other commands for 'mr online' to run. If the file is
504 present, mr assumes it is in offline mode.
508 mr can be extended to support things such as unison and git-svn. Some
509 files providing such extensions are available in F</usr/share/mr/>. See
510 the documentation in the files for details about using them.
514 mr returns nonzero if a command failed in any of the repositories.
518 Copyright 2007-2011 Joey Hess <joey@kitenet.net>
520 Licensed under the GNU GPL version 2 or higher.
522 http://kitenet.net/~joey/code/mr/
529 use Cwd qw(getcwd abs_path);
531 # things that can happen when mr runs a command
540 my $config_overridden=0;
551 my $directory=getcwd();
553 my $HOME_MR_CONFIG = "$ENV{HOME}/.mrconfig";
554 $ENV{MR_CONFIG}=find_mrconfig();
561 my (@ok, @failed, @skipped);
571 # Runs a shell command using a supplied function.
572 # The lib will be included in the shell command line, and any params
573 # will be available in the shell as $1, $2, etc.
576 my ($action, $topdir, $subdir, $command, $params, $runner) = @_;
578 # optimisation: avoid running the shell for true and false
579 if ($command =~ /^\s*true\s*$/) {
583 elsif ($command =~ /^\s*false\s*$/) {
588 my $quotedparams=join(" ", (map { shellquote($_) } @$params));
589 my $lib=exists $config{$topdir}{$subdir}{lib} ?
590 $config{$topdir}{$subdir}{lib}."\n" : "";
591 if ($verbose && (! defined $lastlib || $lastlib ne $lib)) {
592 print "mr library now: >>$lib<<\n";
595 my $shellcode="set -e;".$lib.
596 "my_sh(){ $command\n }; my_sh $quotedparams";
597 print "mr $action: running $action >>$command<<\n" if $verbose;
598 $runner->($shellcode);
605 if ($s =~ m/^perl:\s+(.*)/s) {
606 return $perl_cache{$1} if exists $perl_cache{$1};
607 my $sub=eval "sub {$1}";
608 if (! defined $sub) {
609 print STDERR "mr: bad perl code in $id: $@\n";
611 return $perl_cache{$1} = $sub;
618 my ($action, $dir, $topdir, $subdir) = @_;
620 if (exists $vcs{$dir}) {
626 foreach my $vcs_test (
628 length $a <=> length $b
631 } grep { /_test$/ } keys %{$config{$topdir}{$subdir}}) {
632 my ($vcs)=$vcs_test =~ /(.*)_test/;
633 my $p=perl($vcs_test, $config{$topdir}{$subdir}{$vcs_test});
638 $test="my_$vcs_test() {\n$config{$topdir}{$subdir}{$vcs_test}\n}\n".$test;
639 $test.="if my_$vcs_test; then echo $vcs; fi\n";
644 foreach my $vcs (keys %perltest) {
645 if ($perltest{$vcs}->()) {
650 push @vcs, split(/\n/,
651 runsh("vcs test", $topdir, $subdir, $test, [], sub {
657 print STDERR "mr $action: found multiple possible repository types (@vcs) for ".fulldir($topdir, $subdir)."\n";
661 return $vcs{$dir}=undef;
664 return $vcs{$dir}=$vcs[0];
669 my ($action, $dir, $topdir, $subdir, $is_checkout) = @_;
671 if (exists $config{$topdir}{$subdir}{$action}) {
672 return $config{$topdir}{$subdir}{$action};
679 my $vcs=vcs_test(@_);
682 exists $config{$topdir}{$subdir}{$vcs."_".$action}) {
683 return $config{$topdir}{$subdir}{$vcs."_".$action};
691 my ($topdir, $subdir) = @_;
692 return $subdir =~ /^\// ? $subdir : $topdir.$subdir;
696 my ($action, $dir, $topdir, $subdir, $force_checkout) = @_;
697 my $fulldir=fulldir($topdir, $subdir);
700 $ENV{MR_CONFIG}=$configfiles{$topdir};
701 my $is_checkout=($action eq 'checkout');
702 my $is_update=($action =~ /update/);
704 ($ENV{MR_REPO}=$dir) =~ s!/$!!;
705 $ENV{MR_ACTION}=$action;
707 foreach my $testname ("skip", "deleted") {
708 next if $force && $testname eq "skip";
710 my $testcommand=findcommand($testname, $dir, $topdir, $subdir, $is_checkout);
712 if (defined $testcommand) {
713 my $ret=runsh "$testname test", $topdir, $subdir,
714 $testcommand, [$action],
715 sub { system(shift()) };
717 if (($? & 127) == 2) {
718 print STDERR "mr $action: interrupted\n";
722 print STDERR "mr $action: $testname test received signal ".($? & 127)."\n";
726 if ($ret >> 8 == 0) {
727 if ($testname eq "deleted") {
729 print STDERR "mr error: $dir should be deleted yet still exists\n";
733 print "mr $action: skip $dir skipped\n" if $verbose;
741 if (! $force_checkout) {
743 print "mr $action: $dir already exists, skipping checkout\n" if $verbose;
747 $dir=~s/^(.*)\/[^\/]+\/?$/$1/;
752 return action("checkout", $dir, $topdir, $subdir);
756 my $command=findcommand($action, $dir, $topdir, $subdir, $is_checkout);
758 if ($is_checkout && ! -d $dir) {
759 print "mr $action: creating parent directory $dir\n" if $verbose;
760 system("mkdir", "-p", $dir);
763 if (! $no_chdir && ! chdir($dir)) {
764 print STDERR "mr $action: failed to chdir to $dir: $!\n";
767 elsif (! defined $command) {
768 my $vcs=vcs_test(@_);
769 if (! defined $vcs) {
770 print STDERR "mr $action: unknown repository type and no defined $action command for $fulldir\n";
774 print STDERR "mr $action: no defined action for $vcs repository $fulldir, skipping\n";
781 $actionmsg="mr $action: $fulldir";
785 $s=~s/^\Q$fulldir\E\/?//;
786 $actionmsg="mr $action: $fulldir (in subdir $s)";
788 print "$actionmsg\n" unless $quiet;
790 my $hookret=hook("pre_$action", $topdir, $subdir);
791 return $hookret if $hookret != OK;
793 my $ret=runsh $action, $topdir, $subdir,
794 $command, \@ARGV, sub {
797 my $output = qx/$sh 2>&1/;
800 print "$actionmsg\n";
801 print STDERR $output;
810 if (($? & 127) == 2) {
811 print STDERR "mr $action: interrupted\n";
815 print STDERR "mr $action: received signal ".($? & 127)."\n";
818 print STDERR "mr $action: failed ($ret)\n" if $verbose;
819 if ($ret >> 8 != 0) {
820 print STDERR "mr $action: command failed\n";
821 if (-e "$ENV{HOME}/.mrlog" && $action ne 'remember') {
822 # recreate original command line to
823 # remember, and avoid recursing
825 @ARGV=('-n', $action, @orig);
826 action("remember", $dir, $topdir, $subdir);
831 print STDERR "mr $action: command died ($ret)\n";
836 if ($is_checkout && ! -d $dir) {
837 print STDERR "mr $action: $dir missing after checkout\n";;
841 my $ret=hook("post_$action", $topdir, $subdir);
842 return $ret if $ret != OK;
844 if ($is_checkout || $is_update) {
845 if ($is_checkout && ! $no_chdir) {
846 if (! chdir($checkout_dir)) {
847 print STDERR "mr $action: failed to chdir to $checkout_dir: $!\n";
851 my $ret=hook("fixups", $topdir, $subdir);
852 return $ret if $ret != OK;
861 my ($hook, $topdir, $subdir) = @_;
863 my $command=$config{$topdir}{$subdir}{$hook};
864 return OK unless defined $command;
865 my $ret=runsh $hook, $topdir, $subdir, $command, [], sub {
868 my $output = qx/$sh 2>&1/;
871 print STDERR $output;
880 if (($? & 127) == 2) {
881 print STDERR "mr $hook: interrupted\n";
885 print STDERR "mr $hook: received signal ".($? & 127)."\n";
896 # run actions on multiple repos, in parallel
906 while (@fhs or @repos) {
907 while ((!$jobs || $running < $jobs) && @repos) {
909 my $repo = shift @repos;
910 pipe(my $outfh, CHILD_STDOUT);
911 pipe(my $errfh, CHILD_STDERR);
913 unless ($pid = fork) {
914 die "mr $action: cannot fork: $!" unless defined $pid;
915 open(STDOUT, ">&CHILD_STDOUT") || die "mr $action cannot reopen stdout: $!";
916 open(STDERR, ">&CHILD_STDERR") || die "mr $action cannot reopen stderr: $!";
921 exit action($action, @$repo);
925 push @active, [$pid, $repo];
926 push @fhs, [$outfh, $errfh];
929 my ($rin, $rout) = ('','');
931 foreach my $fh (@fhs) {
932 next unless defined $fh;
933 vec($rin, fileno($fh->[0]), 1) = 1 if defined $fh->[0];
934 vec($rin, fileno($fh->[1]), 1) = 1 if defined $fh->[1];
936 $nfound = select($rout=$rin, undef, undef, 1);
937 foreach my $channel (0, 1) {
938 foreach my $i (0..$#fhs) {
939 next unless defined $fhs[$i];
940 my $fh = $fhs[$i][$channel];
941 next unless defined $fh;
942 if (vec($rout, fileno($fh), 1) == 1) {
944 if (sysread($fh, $r, 1024) == 0) {
946 $fhs[$i][$channel] = undef;
947 if (! defined $fhs[$i][0] &&
948 ! defined $fhs[$i][1]) {
949 waitpid($active[$i][0], 0);
950 print STDOUT $out[$i][0];
951 print STDERR $out[$i][1];
952 record($active[$i][1], $? >> 8);
954 splice(@active, $i, 1);
959 $out[$i][$channel] .= $r;
967 my $dir=shift()->[0];
972 print "\n" unless $quiet;
974 elsif ($ret == FAILED) {
976 chdir($dir) unless $no_chdir;
977 print STDERR "mr: Starting interactive shell. Exit shell to continue.\n";
978 system((getpwuid($<))[8], "-i");
981 print "\n" unless $quiet;
983 elsif ($ret == SKIPPED) {
986 elsif ($ret == ABORT) {
990 die "unknown exit status $ret";
996 if (! @ok && ! @failed && ! @skipped) {
997 die "mr $action: no repositories found to work on\n";
999 print "mr $action: finished (".join("; ",
1000 showstat($#ok+1, "ok", "ok"),
1001 showstat($#failed+1, "failed", "failed"),
1002 showstat($#skipped+1, "skipped", "skipped"),
1003 ).")\n" unless $quiet;
1006 print "mr $action: (skipped: ".join(" ", @skipped).")\n" unless $quiet;
1009 print STDERR "mr $action: (failed: ".join(" ", @failed).")\n";
1019 return "$count ".($count > 1 ? $plural : $singular);
1024 # an ordered list of repos
1027 foreach my $topdir (sort keys %config) {
1028 foreach my $subdir (sort keys %{$config{$topdir}}) {
1032 order => $config{$topdir}{$subdir}{order},
1037 $a->{order} <=> $b->{order}
1039 $a->{topdir} cmp $b->{topdir}
1041 $a->{subdir} cmp $b->{subdir}
1047 my $topdir=$repo->{topdir};
1048 my $subdir=$repo->{subdir};
1049 my $ret=($subdir =~/^\//) ? $subdir : $topdir.$subdir;
1054 # Figure out which repos to act on. Returns a list of array refs
1057 # [ "$full_repo_path/", "$mr_config_path/", $section_header ]
1060 foreach my $repo (repolist()) {
1061 my $topdir=$repo->{topdir};
1062 my $subdir=$repo->{subdir};
1064 next if $subdir eq 'DEFAULT';
1065 my $dir=repodir($repo);
1067 $dir.="/" unless $dir=~/\/$/;
1068 $d.="/" unless $d=~/\/$/;
1069 next if $dir ne $d && $dir !~ /^\Q$d\E/;
1070 if (defined $max_depth) {
1071 my @a=split('/', $dir);
1072 my @b=split('/', $d);
1073 do { } while (@a && @b && shift(@a) eq shift(@b));
1074 next if @a > $max_depth || @b > $max_depth;
1076 push @repos, [$dir, $topdir, $subdir];
1079 # fallback to find a leaf repo
1080 foreach my $repo (reverse repolist()) {
1081 my $topdir=$repo->{topdir};
1082 my $subdir=$repo->{subdir};
1084 next if $subdir eq 'DEFAULT';
1085 my $dir=repodir($repo);
1087 $dir.="/" unless $dir=~/\/$/;
1088 $d.="/" unless $d=~/\/$/;
1089 if ($d=~/^\Q$dir\E/) {
1090 push @repos, [$dir, $topdir, $subdir];
1112 sub is_trusted_config {
1113 my $config=shift; # must be abs_pathed already
1115 # We always trust ~/.mrconfig.
1116 return 1 if $config eq abs_path($HOME_MR_CONFIG);
1118 return 1 if $trust_all;
1120 my $trustfile=$ENV{HOME}."/.mrtrust";
1123 $trusted{$HOME_MR_CONFIG}=1;
1124 if (open (TRUST, "<", $trustfile)) {
1127 s/^~\//$ENV{HOME}\//;
1128 $trusted{abs_path($_)}=1;
1134 return $trusted{$config};
1138 sub is_trusted_repo {
1141 # Tightly limit what is allowed in a repo name.
1142 # No ../, no absolute paths, and no unusual filenames
1143 # that might try to escape to the shell.
1144 return $repo =~ /^[-_.+\/A-Za-z0-9]+$/ &&
1145 $repo !~ /\.\./ && $repo !~ /^\//;
1148 sub is_trusted_checkout {
1151 # To determine if the command is safe, compare it with the
1152 # *_trusted_checkout config settings. Those settings are
1153 # templates for allowed commands, so make sure that each word
1154 # of the command matches the corresponding word of the template.
1157 foreach my $word (split(' ', $command)) {
1159 if ($word=~/^'(.*)'$/) {
1162 elsif ($word=~/^"(.*)"$/) {
1169 foreach my $key (grep { /_trusted_checkout$/ }
1170 keys %{$config{''}{DEFAULT}}) {
1171 my @twords=split(' ', $config{''}{DEFAULT}{$key});
1172 next if @words > @twords;
1176 for (my $c=0; $c < @twords && $match; $c++) {
1177 if ($twords[$c] eq '$url') {
1178 # Match all the typical characters found in
1179 # urls, plus @ which svn can use. Note
1180 # that the "url" might also be a local
1183 defined $words[$c] &&
1184 $words[$c] =~ /^[-_.+:@\/A-Za-z0-9]+$/
1188 elsif ($twords[$c] eq '$repo') {
1189 # If a repo is not specified, assume it
1190 # will be the last path component of the
1191 # url, or something derived from it, and
1193 if (! defined $words[$c] && defined $url) {
1194 ($words[$c])=$url=~/\/([^\/]+)\/?$/;
1198 defined $words[$c] &&
1199 is_trusted_repo($words[$c])
1202 elsif (defined $words[$c] && $words[$c]=~/^($twords[$c])$/) {
1219 my $bootstrap_url=shift;
1225 if (ref $f eq 'GLOB') {
1231 my $absf=abs_path($f);
1232 if ($loaded{$absf}) {
1237 $trusted=is_trusted_config($absf);
1239 if (! defined $dir) {
1240 ($dir)=$f=~/^(.*\/)[^\/]+$/;
1241 if (! defined $dir) {
1246 $dir=abs_path($dir)."/";
1248 if (! exists $configfiles{$dir}) {
1249 $configfiles{$dir}=$f;
1252 # copy in defaults from first parent
1254 while ($parent=~s/^(.*\/)[^\/]+\/?$/$1/) {
1255 if ($parent eq '/') {
1258 if (exists $config{$parent} &&
1259 exists $config{$parent}{DEFAULT}) {
1260 $config{$dir}{DEFAULT}={ %{$config{$parent}{DEFAULT}} };
1269 print "mr: loading config $f\n" if $verbose;
1270 open($in, "<", $f) || die "mr: open $f: $!\n";
1273 close $in unless ref $f eq 'GLOB';
1277 # Keep track of the current line in the config file;
1278 # when a file is included track the current line from the include.
1283 my $nextline = sub {
1295 my $lineerror = sub {
1297 if (defined $included) {
1298 die "mr: $msg at $f line $lineno, included line: $line\n";
1301 die "mr: $msg at $f line $lineno\n";
1304 my $trusterror = sub {
1307 if (defined $bootstrap_url) {
1308 die "mr: $msg in untrusted $bootstrap_url line $lineno\n".
1309 "(To trust this url, --trust-all can be used; but please use caution;\n".
1310 "this can allow arbitrary code execution!)\n";
1313 die "mr: $msg in untrusted $f line $lineno\n".
1314 "(To trust this file, list it in ~/.mrtrust.)\n";
1321 if (! $trusted && /[[:cntrl:]]/) {
1322 $trusterror->("illegal control character");
1325 next if /^\s*\#/ || /^\s*$/;
1326 if (/^\[([^\]]*)\]\s*$/) {
1330 if (! is_trusted_repo($section) ||
1331 $section eq 'ALIAS' ||
1332 $section eq 'DEFAULT') {
1333 $trusterror->("illegal section \"[$section]\"");
1336 $section=expandenv($section) if $trusted;
1337 if ($section ne 'ALIAS' &&
1338 ! exists $config{$dir}{$section} &&
1339 exists $config{$dir}{DEFAULT}) {
1341 $config{$dir}{$section}={ %{$config{$dir}{DEFAULT}} };
1344 elsif (/^(\w+)\s*=\s*(.*)/) {
1349 while (@lines && $lines[0]=~/^\s(.+)/) {
1356 # Untrusted files can only contain a few
1357 # settings in specific known-safe formats.
1358 if ($parameter eq 'checkout') {
1359 if (! is_trusted_checkout($value)) {
1360 $trusterror->("illegal checkout command \"$value\"");
1363 elsif ($parameter eq 'order') {
1364 # not interpreted as a command, so
1367 elsif ($value eq 'true' || $value eq 'false') {
1368 # skip=true , deleted=true etc are
1372 $trusterror->("illegal setting \"$parameter=$value\"");
1376 if ($parameter eq "include") {
1377 print "mr: including output of \"$value\"\n" if $verbose;
1380 print STDERR "mr: include command exited nonzero ($?)\n";
1383 unshift @lines, @inc;
1387 if (! defined $section) {
1388 $lineerror->("parameter ($parameter) not in section");
1390 if ($section eq 'ALIAS') {
1391 $alias{$parameter}=$value;
1393 elsif ($parameter eq 'lib' or $parameter =~ s/_append$//) {
1394 $config{$dir}{$section}{$parameter}.="\n".$value."\n";
1397 $config{$dir}{$section}{$parameter}=$value;
1398 if ($parameter =~ /.*_(.*)/) {
1399 $knownactions{$1}=1;
1402 $knownactions{$parameter}=1;
1404 if ($parameter eq 'chain' &&
1405 length $dir && $section ne "DEFAULT") {
1406 my $chaindir="$section";
1407 if ($chaindir !~ m!^/!) {
1408 $chaindir=$dir.$chaindir;
1410 if (-e "$chaindir/.mrconfig") {
1411 my $ret=system($value);
1413 if (($? & 127) == 2) {
1414 print STDERR "mr: chain test interrupted\n";
1418 print STDERR "mr: chain test received signal ".($? & 127)."\n";
1422 push @toload, ["$chaindir/.mrconfig", $chaindir];
1429 $lineerror->("parse error");
1433 foreach my $c (@toload) {
1438 sub startingconfig {
1439 %alias=%config=%configfiles=%knownactions=%loaded=();
1440 my $datapos=tell(DATA);
1442 seek(DATA,$datapos,0); # rewind
1447 # the section to modify or add
1448 my $targetsection=shift;
1449 # fields to change in the section
1450 # To remove a field, set its value to "".
1451 my %changefields=@_;
1457 open(my $in, "<", $f) || die "mr: open $f: $!\n";
1462 my $formatfield=sub {
1464 my @value=split(/\n/, shift);
1466 return "$field = ".shift(@value)."\n".
1467 join("", map { "\t$_\n" } @value);
1471 while ($out[$#out] =~ /^\s*$/) {
1472 unshift @blanks, pop @out;
1474 foreach my $field (sort keys %changefields) {
1475 if (length $changefields{$field}) {
1476 push @out, "$field = $changefields{$field}\n";
1477 delete $changefields{$field};
1487 if (/^\s*\#/ || /^\s*$/) {
1490 elsif (/^\[([^\]]*)\]\s*$/) {
1491 if (defined $section &&
1492 $section eq $targetsection) {
1496 $section=expandenv($1);
1500 elsif (/^(\w+)\s*=\s(.*)/) {
1505 while (@lines && $lines[0]=~/^\s(.+)/) {
1511 if ($section eq $targetsection) {
1512 if (exists $changefields{$parameter}) {
1513 if (length $changefields{$parameter}) {
1514 $value=$changefields{$parameter};
1516 delete $changefields{$parameter};
1520 push @out, $formatfield->($parameter, $value);
1524 if (defined $section &&
1525 $section eq $targetsection) {
1528 elsif (%changefields) {
1529 push @out, "\n[$targetsection]\n";
1530 foreach my $field (sort keys %changefields) {
1531 if (length $changefields{$field}) {
1532 push @out, $formatfield->($field, $changefields{$field});
1537 open(my $out, ">", $f) || die "mr: write $f: $!\n";
1545 # actions that do not operate on all repos
1546 if ($action eq 'help') {
1549 elsif ($action eq 'config') {
1552 elsif ($action eq 'register') {
1555 elsif ($action eq 'bootstrap') {
1558 elsif ($action eq 'remember' ||
1559 $action eq 'offline' ||
1560 $action eq 'online') {
1561 my @repos=selectrepos;
1562 action($action, @{$repos[0]}) if @repos;
1566 if (!$jobs || $jobs > 1) {
1567 mrs($action, selectrepos());
1570 foreach my $repo (selectrepos()) {
1571 record($repo, action($action, @$repo));
1577 exec($config{''}{DEFAULT}{help}) || die "exec: $!";
1582 die "mr config: not enough parameters\n";
1585 if ($section=~/^\//) {
1586 # try to convert to a path relative to the config file
1587 my ($dir)=$ENV{MR_CONFIG}=~/^(.*\/)[^\/]+$/;
1588 $dir=abs_path($dir);
1589 $dir.="/" unless $dir=~/\/$/;
1590 if ($section=~/^\Q$dir\E(.*)/) {
1596 if (/^([^=]+)=(.*)$/) {
1597 $changefields{$1}=$2;
1601 foreach my $topdir (sort keys %config) {
1602 if (exists $config{$topdir}{$section} &&
1603 exists $config{$topdir}{$section}{$_}) {
1604 print $config{$topdir}{$section}{$_}."\n";
1606 last if $section eq 'DEFAULT';
1610 die "mr config: $section $_ not set\n";
1614 modifyconfig($ENV{MR_CONFIG}, $section, %changefields) if %changefields;
1619 if ($config_overridden) {
1620 # Find the directory that the specified config file is
1622 ($directory)=abs_path($ENV{MR_CONFIG})=~/^(.*\/)[^\/]+$/;
1625 # Find the closest known mrconfig file to the current
1627 $directory.="/" unless $directory=~/\/$/;
1629 foreach my $topdir (reverse sort keys %config) {
1630 next unless length $topdir;
1631 if ($directory=~/^\Q$topdir\E/) {
1632 $ENV{MR_CONFIG}=$configfiles{$topdir};
1638 if (! $foundconfig) {
1639 $directory=""; # no config file, use builtin
1643 my $subdir=shift @ARGV;
1644 if (! chdir($subdir)) {
1645 print STDERR "mr register: failed to chdir to $subdir: $!\n";
1649 $ENV{MR_REPO}=getcwd();
1650 my $command=findcommand("register", $ENV{MR_REPO}, $directory, 'DEFAULT', 0);
1651 if (! defined $command) {
1652 die "mr register: unknown repository type\n";
1655 $ENV{MR_REPO}=~s/.*\/(.*)/$1/;
1656 $command="set -e; ".$config{$directory}{DEFAULT}{lib}."\n".
1657 "my_action(){ $command\n }; my_action ".
1658 join(" ", map { s/\\/\\\\/g; s/"/\"/g; '"'.$_.'"' } @ARGV);
1659 print "mr register: running >>$command<<\n" if $verbose;
1660 exec($command) || die "exec: $!";
1664 my $url=shift @ARGV;
1665 my $dir=shift @ARGV || ".";
1667 if (! defined $url || ! length $url) {
1668 die "mr: bootstrap requires url\n";
1671 # Download the config file to a temporary location.
1672 eval q{use File::Temp};
1674 my $tmpconfig=File::Temp->new();
1676 if ($url =~ m!^ssh://(.*)!) {
1677 @downloader = ("scp", $1, $tmpconfig);
1680 @downloader = ("curl", "-A", "mr", "-L", "-s", $url, "-o", $tmpconfig);
1681 push(@downloader, "-k") if $insecure;
1683 my $status = system(@downloader);
1684 die "mr bootstrap: invalid SSL certificate for $url (consider -k)\n"
1685 if $downloader[0] eq 'curl' && $status >> 8 == 60;
1686 die "mr bootstrap: download of $url failed\n" if $status != 0;
1689 system("mkdir", "-p", $dir);
1691 chdir($dir) || die "chdir $dir: $!";
1693 # Special case to handle checkout of the "." repo, which
1694 # would normally be skipped.
1695 my $topdir=abs_path(".")."/";
1696 my @repo=($topdir, $topdir, ".");
1697 loadconfig($tmpconfig, $topdir, $url);
1698 record(\@repo, action("checkout", @repo, 1))
1699 if exists $config{$topdir}{"."}{"checkout"};
1701 if (-e ".mrconfig") {
1702 print STDERR "mr bootstrap: .mrconfig file already exists, not overwriting with $url\n";
1705 eval q{use File::Copy};
1707 move($tmpconfig, ".mrconfig") || die "rename: $!";
1710 # Reload the config file (in case we got a different version)
1711 # and checkout everything else.
1713 loadconfig(".mrconfig");
1714 dispatch("checkout");
1715 @skipped=grep { abs_path($_) ne abs_path($topdir) } @skipped;
1716 showstats("bootstrap");
1720 # alias expansion and command stemming
1723 if (exists $alias{$action}) {
1724 $action=$alias{$action};
1726 if (! exists $knownactions{$action}) {
1727 my @matches = grep { /^\Q$action\E/ }
1728 keys %knownactions, keys %alias;
1729 if (@matches == 1) {
1730 $action=$matches[0];
1732 elsif (@matches == 0) {
1733 die "mr: unknown action \"$action\" (known actions: ".
1734 join(", ", sort keys %knownactions).")\n";
1737 die "mr: ambiguous action \"$action\" (matches: ".
1738 join(", ", @matches).")\n";
1746 while (length $dir) {
1747 if (-e "$dir/.mrconfig") {
1748 return "$dir/.mrconfig";
1750 $dir=~s/\/[^\/]*$//;
1752 return $HOME_MR_CONFIG;
1757 Getopt::Long::Configure("bundling", "no_permute");
1758 my $result=GetOptions(
1759 "d|directory=s" => sub { $directory=abs_path($_[1]) },
1760 "c|config=s" => sub { $ENV{MR_CONFIG}=$_[1]; $config_overridden=1 },
1761 "p|path" => sub { }, # now default, ignore
1762 "f|force" => \$force,
1763 "v|verbose" => \$verbose,
1764 "q|quiet" => \$quiet,
1765 "s|stats" => \$stats,
1766 "k|insecure" => \$insecure,
1767 "i|interactive" => \$interactive,
1768 "n|no-recurse:i" => \$max_depth,
1769 "j|jobs:i" => \$jobs,
1770 "t|trust-all" => \$trust_all,
1772 if (! $result || @ARGV < 1) {
1773 die("Usage: mr [options] action [params ...]\n".
1774 "(Use mr help for man page.)\n");
1777 $ENV{MR_SWITCHES}="";
1778 foreach my $option (@saved) {
1779 last if $option eq $ARGV[0];
1780 $ENV{MR_SWITCHES}.="$option ";
1786 print STDERR "mr: interrupted\n";
1790 # This can happen if it's run in a directory that was removed
1791 # or other strangeness.
1792 if (! defined $directory) {
1793 die("mr: failed to determine working directory\n");
1795 # Make sure MR_CONFIG is an absolute path, but don't use abs_path since
1796 # the config file might be a symlink to elsewhere, and the directory it's
1797 # in is significant.
1798 if ($ENV{MR_CONFIG} !~ /^\//) {
1799 $ENV{MR_CONFIG}=getcwd()."/".$ENV{MR_CONFIG};
1801 # Try to set MR_PATH to the path to the program.
1803 use FindBin qw($Bin $Script);
1804 $ENV{MR_PATH}=$Bin."/".$Script;
1822 loadconfig($HOME_MR_CONFIG);
1823 loadconfig($ENV{MR_CONFIG});
1824 #use Data::Dumper; print Dumper(\%config);
1826 my $action=expandaction(shift @ARGV);
1833 # Finally, some useful actions that mr knows about by default.
1834 # These can be overridden in ~/.mrconfig.
1849 echo "mr (warning): $@" >&2
1855 if [ -z "$1" ] || [ -z "$2" ]; then
1856 error "mr: usage: hours_since action num"
1858 for dir in .git .svn .bzr CVS .hg _darcs _FOSSIL_; do
1859 if [ -e "$MR_REPO/$dir" ]; then
1860 flagfile="$MR_REPO/$dir/.mr_last$1"
1864 if [ -z "$flagfile" ]; then
1865 error "cannot determine flag filename"
1867 delta=`perl -wle 'print -f shift() ? int((-M _) * 24) : 9999' "$flagfile"`
1868 if [ "$delta" -lt "$2" ]; then
1876 LANG=C bzr info | egrep -q '^Checkout'
1879 if [ -d "$MR_REPO" ]; then
1886 svn_test = perl: -d "$ENV{MR_REPO}/.svn"
1887 git_test = perl: -e "$ENV{MR_REPO}/.git"
1888 bzr_test = perl: -d "$ENV{MR_REPO}/.bzr"
1889 cvs_test = perl: -d "$ENV{MR_REPO}/CVS"
1890 hg_test = perl: -d "$ENV{MR_REPO}/.hg"
1891 darcs_test = perl: -d "$ENV{MR_REPO}/_darcs"
1892 fossil_test = perl: -f "$ENV{MR_REPO}/_FOSSIL_"
1893 git_bare_test = perl:
1894 -d "$ENV{MR_REPO}/refs/heads" && -d "$ENV{MR_REPO}/refs/tags" &&
1895 -d "$ENV{MR_REPO}/objects" && -f "$ENV{MR_REPO}/config" &&
1896 `GIT_CONFIG="$ENV{MR_REPO}"/config git config --get core.bare` =~ /true/
1898 -d "$ENV{MR_REPO}/refs/heads" && -d "$ENV{MR_REPO}/refs/tags" &&
1899 -d "$ENV{MR_REPO}/objects" && -f "$ENV{MR_REPO}/config" &&
1900 `GIT_CONFIG="$ENV{MR_REPO}"/config git config --get vcsh.vcsh` =~ /true/
1901 veracity_test = perl: -d "$ENV{MR_REPO}/.sgdrawer"
1903 svn_update = svn update "$@"
1904 git_update = git pull "$@"
1906 if is_bzr_checkout; then
1909 bzr merge --pull "$@"
1911 cvs_update = cvs -q update "$@"
1912 hg_update = hg pull "$@"; hg update "$@"
1913 darcs_update = darcs pull -a "$@"
1914 fossil_update = fossil pull "$@"
1915 vcsh_update = vcsh run "$MR_REPO" git pull "$@"
1916 veracity_update = vv pull "$@" && vv update "$@"
1918 git_fetch = git fetch --all --prune --tags
1919 git_svn_fetch = git svn fetch
1920 darcs_fetch = darcs fetch
1923 svn_status = svn status "$@"
1924 git_status = git status -s "$@" || true; git --no-pager log --branches --not --remotes --simplify-by-decoration --decorate --oneline || true
1925 bzr_status = bzr status --short "$@"; bzr missing
1926 cvs_status = cvs status "$@"
1927 hg_status = hg status "$@"; hg summary --quiet | grep -v 'parent: 0:'
1928 darcs_status = darcs whatsnew -ls "$@" || true
1929 fossil_status = fossil changes "$@"
1930 vcsh_status = vcsh run "$MR_REPO" git -c status.relativePaths=false status -s "$@" || true
1931 veracity_status = vv status "$@"
1933 svn_commit = svn commit "$@"
1934 git_commit = git commit -a "$@" && git push --all
1936 if is_bzr_checkout; then
1939 bzr commit "$@" && bzr push
1941 cvs_commit = cvs commit "$@"
1942 hg_commit = hg commit -m "$@" && hg push
1943 darcs_commit = darcs record -a -m "$@" && darcs push -a
1944 fossil_commit = fossil commit "$@"
1945 vcsh_commit = vcsh run "$MR_REPO" git commit -a "$@" && vcsh run "$MR_REPO" git push --all
1946 veracity_commit = vv commit -m "@" && vv push
1948 git_record = git commit -a "$@"
1950 if is_bzr_checkout; then
1951 bzr commit --local "$@"
1955 hg_record = hg commit -m "$@"
1956 darcs_record = darcs record -a -m "$@"
1957 fossil_record = fossil commit "$@"
1958 vcsh_record = vcsh run "$MR_REPO" git commit -a "$@"
1959 veracity_record = vv commit -m "@"
1962 git_push = git push "$@"
1963 bzr_push = bzr push "$@"
1965 hg_push = hg push "$@"
1966 darcs_push = darcs push -a "$@"
1967 fossil_push = fossil push "$@"
1968 vcsh_push = vcsh run "$MR_REPO" git push "$@"
1969 veracity_push = vv push "$@"
1971 svn_diff = svn diff "$@"
1972 git_diff = git diff "$@"
1973 bzr_diff = bzr diff "$@"
1974 cvs_diff = cvs -q diff "$@"
1975 hg_diff = hg diff "$@"
1976 darcs_diff = darcs diff -u "$@"
1977 fossil_diff = fossil diff "$@"
1978 vcsh_diff = vcsh run "$MR_REPO" git diff "$@"
1979 veracity_diff = vv diff "$@"
1981 svn_log = svn log "$@"
1982 git_log = git log "$@"
1983 bzr_log = bzr log "$@"
1984 cvs_log = cvs log "$@"
1985 hg_log = hg log "$@"
1986 darcs_log = darcs changes "$@"
1987 git_bare_log = git log "$@"
1988 fossil_log = fossil timeline "$@"
1989 vcsh_log = vcsh run "$MR_REPO" git log "$@"
1990 veracity_log = vv log "$@"
1992 hg_grep = hg grep "$@"
1993 cvs_grep = ack-grep "$@"
1994 svn_grep = ack-grep "$@"
1995 git_svn_grep = git grep "$@"
1996 git_grep = git grep "$@"
1997 bzr_grep = ack-grep "$@"
2002 url=`LC_ALL=C svn info . | grep -i '^URL:' | cut -d ' ' -f 2`
2003 if [ -z "$url" ]; then
2004 error "cannot determine svn url"
2006 echo "Registering svn url: $url in $MR_CONFIG"
2007 mr -c "$MR_CONFIG" config "`pwd`" checkout="svn co '$url' '$MR_REPO'"
2009 url="`LC_ALL=C git config --get remote.origin.url`" || true
2010 if [ -z "$url" ]; then
2011 error "cannot determine git url"
2013 echo "Registering git url: $url in $MR_CONFIG"
2014 mr -c "$MR_CONFIG" config "`pwd`" checkout="git clone '$url' '$MR_REPO'"
2016 url="`LC_ALL=C bzr info . | egrep -i 'checkout of branch|parent branch' | awk '{print $NF}' | head -n 1`"
2017 if [ -z "$url" ]; then
2018 error "cannot determine bzr url"
2020 echo "Registering bzr url: $url in $MR_CONFIG"
2021 mr -c "$MR_CONFIG" config "`pwd`" checkout="bzr branch '$url' '$MR_REPO'"
2023 repo=`cat CVS/Repository`
2025 if [ -z "$root" ]; then
2026 error "cannot determine cvs root"
2028 echo "Registering cvs repository $repo at root $root"
2029 mr -c "$MR_CONFIG" config "`pwd`" checkout="cvs -d '$root' co -d '$MR_REPO' '$repo'"
2031 url=`hg showconfig paths.default`
2032 echo "Registering mercurial repo url: $url in $MR_CONFIG"
2033 mr -c "$MR_CONFIG" config "`pwd`" checkout="hg clone '$url' '$MR_REPO'"
2035 url=`cat _darcs/prefs/defaultrepo`
2036 echo "Registering darcs repository $url in $MR_CONFIG"
2037 mr -c "$MR_CONFIG" config "`pwd`" checkout="darcs get '$url' '$MR_REPO'"
2039 url="`LC_ALL=C GIT_CONFIG=config git config --get remote.origin.url`" || true
2040 if [ -z "$url" ]; then
2041 error "cannot determine git url"
2043 echo "Registering git url: $url in $MR_CONFIG"
2044 mr -c "$MR_CONFIG" config "`pwd`" checkout="git clone --bare '$url' '$MR_REPO'"
2046 url="`LC_ALL=C vcsh run "$MR_REPO" git config --get remote.origin.url`" || true
2047 if [ -z "$url" ]; then
2048 error "cannot determine git url"
2050 echo "Registering git url: $url in $MR_CONFIG"
2051 mr -c "$MR_CONFIG" config "`pwd`" checkout="vcsh clone '$url' '$MR_REPO'"
2053 url=`fossil remote-url`
2054 repo=`fossil info | grep repository | sed -e 's/repository:*.//g' -e 's/ //g'`
2055 echo "Registering fossil repository $url in $MR_CONFIG"
2056 mr -c "$MR_CONFIG" config "`pwd`" checkout="mkdir -p '$MR_REPO' && cd '$MR_REPO' && fossil open '$repo'"
2058 url=`vv config | grep sync_targets | sed -e 's/sync_targets:*.//g' -e 's/ //g'`
2059 repo=`vv repo info | grep repository | sed -e 's/Current repository:*.//g' -e 's/ //g'`
2060 echo "Registering veracity repository $url in $MR_CONFIG"
2061 mr -c "$MR_CONFIG" config "`pwd`" checkout="mkdir -p '$MR_REPO' && cd '$MR_REPO' && vv checkout '$repo'"
2063 svn_trusted_checkout = svn co $url $repo
2064 svn_alt_trusted_checkout = svn checkout $url $repo
2065 git_trusted_checkout = git clone $url $repo
2066 bzr_trusted_checkout = bzr checkout|clone|branch|get $url $repo
2068 hg_trusted_checkout = hg clone $url $repo
2069 darcs_trusted_checkout = darcs get $url $repo
2070 git_bare_trusted_checkout = git clone --bare $url $repo
2071 vcsh_trusted_checkout = vcsh run "$MR_REPO" git clone $url $repo
2072 # fossil: messy to do
2073 veracity_trusted_checkout = vv clone $url $repo
2079 SHOWMANFILE="man -f"
2085 SHOWMANFILE="man -l"
2088 if [ ! -e "$MR_PATH" ]; then
2089 error "cannot find program path"
2091 tmp=$(mktemp -t mr.XXXXXXXXXX) || error "mktemp failed"
2092 trap "rm -f $tmp" exit
2093 pod2man -c mr "$MR_PATH" > "$tmp" || error "pod2man failed"
2094 $SHOWMANFILE "$tmp" || error "man failed"
2100 if [ -s ~/.mrlog ]; then
2101 info "running offline commands"
2102 mv -f ~/.mrlog ~/.mrlog.old
2103 if ! sh -e ~/.mrlog.old; then
2104 error "offline command failed; left in ~/.mrlog.old"
2108 info "no offline commands to run"
2113 info "offline mode enabled"
2115 info "remembering command: 'mr $@'"
2116 command="mr -d '$(pwd)' $MR_SWITCHES"
2118 command="$command '$w'"
2120 if [ ! -e ~/.mrlog ] || ! grep -q -F "$command" ~/.mrlog; then
2121 echo "$command" >> ~/.mrlog
2124 ed = echo "A horse is a horse, of course, of course.."
2125 T = echo "I pity the fool."
2126 right = echo "Not found."
2128 # vim:sw=8:sts=0:ts=8:noet
2130 # indent-tabs-mode: t
2131 # cperl-indent-level: 8