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"]
21 B<mr> [options] action [params ...]
25 B<mr> is a Multiple Repository management tool. It allows you to register a
26 set of repositories in a .mrconfig file, and then checkout, update, or
27 perform other actions on the repositories as if they were one big
30 Any mix of revision control systems can be used with B<mr>, and you can
31 define arbitrary actions for commands like "update", "checkout", or "commit".
33 B<mr> cds into and operates on all registered repsitories at or below your
34 working directory. Or, if you are in a subdirectory of a repository that
35 contains no other registered repositories, it will stay in that directory,
36 and work on only that repository,
38 The predefined commands should be fairly familiar to users of any revision
43 =item checkout (or co)
45 Checks out any repositories that are not already checked out.
49 Updates each repository from its configured remote repository.
51 If a repository isn't checked out yet, it will first check it out.
55 Displays a status report for each repository, showing what
56 uncommitted changes are present in the repository.
60 Commits changes to each repository. (By default, changes are pushed to the
61 remote repository too, when using distributed systems like git.)
63 The optional -m parameter allows specifying a commit message.
67 Show a diff of uncommitted changes.
75 List the repositories that mr will act on.
83 Actions can be abbreviated to any unambiguous subsctring, so
84 "mr st" is equivilant to "mr status", and "mr up" is equivilant to "mr
87 Additional parameters can be passed to other commands than "commit", they
88 will be passed on unchanged to the underlying revision control system.
89 This is mostly useful if the repositories mr will act on all use the same
90 revision control system.
98 Specifies the topmost directory that B<mr> should work in. The default is
99 the current working directory.
103 Use the specified mrconfig file, instead of looking for one in your home
114 B<mr> is configured by .mrconfig files. It starts by reading the .mrconfig
115 file in your home directory. Each repository specified in a .mrconfig file
116 can also have its own .mrconfig file in its root directory that can
117 optionally be used as well. So you could have a ~/.mrconfig that registers a
118 repository ~/src, that itself contains a ~/src/.mrconfig file, that in turn
119 registers several additional repositories.
121 The .mrconfig file uses a variant of the INI file format. Lines starting with
122 "#" are comments. Lines ending with "\" are continued on to the next line.
124 The "default" section allows setting default values for the sections that
127 The "alias" section allows adding aliases for actions. Each parameter
128 is an alias, and its value is the action to use.
130 All other sections specify where each repository is located, relative to the
131 directory that contains the .mrconfig file.
133 Within a section, each parameter defines a shell command to run to handle a
134 given action. mr contains default handlers for the "update", "status", and
135 "commit" actions, so normally you only need to specify what to do for
138 Note that these shell commands are run in a "set -e" shell
139 environment, where any additional parameters you pass are available in
140 "$@". The "checkout" command is run in the parent of the repository
141 directory, since the repository isn't checked out yet. All other commands
142 are run inside the repository, though not necessarily at the top of it.
143 The "MR_REPO" environment variable is set to the path to the top of the
146 There are three special parameters. If the "skip" parameter is set and
147 its command returns nonzero, then B<mr> will skip acting on that repository.
148 If the "chain" parameter is set and its command returns nonzero, then B<mr>
149 will try to load a .mrconfig file from the root of the repository. (You
150 should avoid chaining from repositories with untrusted committers.) The
151 "lib" parameter can specify some shell code that will be run before each
152 command, this can be a useful way to define shell functions for other commands
158 checkout = svn co svn://svn.example.com/src/trunk src
163 checkout = git clone git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux-2.6.git
168 case "$(hostname)" in; \
177 Copyright 2007 Joey Hess <joey@kitenet.net>
179 Licensed under the GNU GPL version 2 or higher.
181 http://kitenet.net/~joey/code/mr/
188 use Cwd qw(getcwd abs_path);
190 my $directory=getcwd();
191 my $config="$ENV{HOME}/.mrconfig";
197 Getopt::Long::Configure("no_permute");
198 my $result=GetOptions(
199 "d=s" => sub { $directory=abs_path($_[1]) },
203 if (! $result || @ARGV < 1) {
204 die("Usage: mr [-d directory] action [params ...]\n".
205 "(Use mr help for man page.)\n");
212 #print Dumper(\%config);
215 use FindBin qw($Bin $Script);
216 $ENV{MR_PATH}=$Bin."/".$Script;
219 # alias expansion and command stemming
220 my $action=shift @ARGV;
221 if (! exists $knownactions{$action}) {
222 if (exists $alias{$action}) {
223 $action=$alias{$action};
226 my @matches = grep { /^\Q$action\E/ }
227 keys %knownactions, keys %alias;
231 elsif (@matches == 0) {
232 die "mr: unknown action \"$action\" (known actions: ".
233 join(", ", sort keys %knownactions).")\n";
236 die "mr: ambiguous action \"$action\" (matches: ".
237 join(", ", @matches).")\n";
242 if ($action eq 'help') {
243 exec($config{''}{default}{help});
246 # work out what repos to act on
249 foreach my $topdir (sort keys %config) {
250 foreach my $subdir (sort keys %{$config{$topdir}}) {
251 next if $subdir eq 'default';
252 my $dir=$topdir.$subdir;
253 next if $dir ne $directory && $dir !~ /^\Q$directory\E\//;
254 push @repos, [$dir, $topdir, $subdir];
258 # fallback to find a leaf repo
259 LEAF: foreach my $topdir (reverse sort keys %config) {
260 foreach my $subdir (reverse sort keys %{$config{$topdir}}) {
261 next if $subdir eq 'default';
262 my $dir=$topdir.$subdir;
264 $dir.="/" unless $dir=~/\/$/;
265 $d.="/" unless $d=~/\/$/;
266 if ($d=~/^\Q$dir\E/) {
267 push @repos, [$dir, $topdir, $subdir];
275 my (@failed, @successful, @skipped);
276 foreach my $repo (@repos) {
277 action($action, @$repo);
281 my ($action, $dir, $topdir, $subdir) = @_;
283 my $lib= exists $config{$topdir}{$subdir}{lib} ?
284 $config{$topdir}{$subdir}{lib} : "";
286 if ($action eq 'checkout') {
288 print "mr $action: $dir already exists, skipping checkout\n" if $verbose;
292 $dir=~s/^(.*)\/[^\/]+\/?$/$1/;
294 elsif ($action eq 'update') {
296 return action("checkout", $dir, $topdir, $subdir);
302 if (exists $config{$topdir}{$subdir}{skip}) {
303 my $test="set -e;".$lib.$config{$topdir}{$subdir}{skip};
304 print "mr $action: running skip test $test\n" if $verbose;
305 my $ret=system($test);
306 if ($ret >> 8 == 0) {
307 print "mr $action: $dir skipped per config file\n" if $verbose;
313 if (! $nochdir && ! chdir($dir)) {
314 print STDERR "mr $action: failed to chdir to $dir: $!\n";
317 elsif (! exists $config{$topdir}{$subdir}{$action}) {
318 print STDERR "mr $action: no defined $action command for $topdir$subdir, skipping\n";
323 print "mr $action: $dir\n";
326 print "mr $action: $dir (in subdir $directory)\n";
328 my $command="set -e; ".$lib.
329 "my_action(){ $config{$topdir}{$subdir}{$action} ; }; my_action ".
330 join(" ", map { s/\//\/\//g; s/"/\"/g; '"'.$_.'"' } @ARGV);
331 print STDERR "mr $action: running $command\n" if $verbose;
332 my $ret=system($command);
334 print STDERR "mr $action: failed ($ret)\n" if $verbose;
335 push @failed, $topdir.$subdir;
336 if ($ret >> 8 != 0) {
337 print STDERR "mr $action: command failed\n";
340 print STDERR "mr $action: command died ($ret)\n";
344 push @successful, $dir;
356 return "$count ".($count > 1 ? $plural : $singular);
360 if (! @successful && ! @failed && ! @skipped) {
361 die "mr $action: no repositories found to work on\n";
363 print "mr $action: finished (".join("; ",
364 showstat($#successful+1, "successful", "successful"),
365 showstat($#failed+1, "failed", "failed"),
366 showstat($#skipped+1, "skipped", "skipped"),
371 elsif (! @successful && @skipped) {
384 if (ref $f eq 'GLOB') {
389 # $f might be a symlink
390 my $absf=abs_path($f);
391 if ($loaded{$absf}) {
396 print "mr: loading config $f\n" if $verbose;
397 open($in, "<", $f) || die "mr: open $f: $!\n";
398 ($dir)=$f=~/^(.*\/)[^\/]+$/;
399 if (! defined $dir) {
402 $dir=abs_path($dir)."/";
404 # copy in defaults from first parent
406 while ($parent=~s/^(.*)\/[^\/]+\/?$/$1/) {
407 if (exists $config{$parent} &&
408 exists $config{$parent}{default}) {
409 $config{$dir}{default}={ %{$config{$parent}{default}} };
418 next if /^\s*\#/ || /^\s*$/;
419 if (/^\s*\[([^\]]*)\]\s*$/) {
422 elsif (/^\s*(\w+)\s*=\s*(.*)/) {
427 while ($value=~/(.*)\\$/) {
432 if (! defined $section) {
433 die "$f line $.: parameter ($parameter) not in section\n";
435 if ($section ne 'alias' &&
436 ! exists $config{$dir}{$section} &&
437 exists $config{$dir}{default}) {
439 $config{$dir}{$section}={ %{$config{$dir}{default}} };
441 if ($section eq 'alias') {
442 $alias{$parameter}=$value;
444 elsif ($parameter eq 'lib') {
445 $config{$dir}{$section}{lib}.=$value." ; ";
448 $config{$dir}{$section}{$parameter}=$value;
449 $knownactions{$parameter}=1;
450 if ($parameter eq 'chain' &&
451 length $dir && $section ne "default" &&
452 -e $dir.$section."/.mrconfig" &&
453 system($value) >> 8 == 0) {
454 push @toload, $dir.$section."/.mrconfig";
459 die "$f line $.: parse error\n";
469 # Finally, some useful actions that mr knows about by default.
470 # These can be overridden in ~/.mrconfig.
483 if [ -d "$MR_REPO"/.svn ]; then \
485 elif [ -d "$MR_REPO"/.git ]; then \
486 git pull origin master "$@"; \
487 elif [ -d "$MR_REPO"/CVS ]; then \
490 error "unknown repo type"; \
493 if [ -d "$MR_REPO"/.svn ]; then \
495 elif [ -d "$MR_REPO"/.git ]; then \
496 git status "$@" || true; \
497 elif [ -d "$MR_REPO"/CVS ]; then \
500 error "unknown repo type"; \
503 if [ -d "$MR_REPO"/.svn ]; then \
505 elif [ -d "$MR_REPO"/.git ]; then \
506 git commit -a "$@" && git push --all; \
507 elif [ -d "$MR_REPO"/CVS ]; then \
510 error "unknown repo type"; \
513 if [ -d "$MR_REPO"/.svn ]; then \
515 elif [ -d "$MR_REPO"/.git ]; then \
517 elif [ -d "$MR_REPO"/CVS ]; then \
520 error "unknown repo type"; \
523 if [ -d "$MR_REPO"/.svn ]; then \
525 elif [ -d "$MR_REPO"/.git ]; then \
527 elif [ -d "$MR_REPO"/CVS ]; then \
530 error "unknown repo type"; \
534 if [ ! -e "$MR_PATH" ]; then \
535 error "cannot find program path";\
537 (pod2man -c mr "$MR_PATH" | man -l -) || \
538 error "pod2man or man failed"