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, and this can in turn chain load .mrconfig files
118 Here is an example .mrconfig file:
121 checkout = svn co svn://svn.example.com/src/trunk src
125 checkout = git clone git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux-2.6.git
127 The .mrconfig file uses a variant of the INI file format. Lines starting with
128 "#" are comments. Lines ending with "\" are continued on to the next line.
130 The "DEFAULT" section allows setting default values for the sections that
133 The "ALIAS" section allows adding aliases for actions. Each parameter
134 is an alias, and its value is the action to use.
136 All other sections specify where each repository is located, relative to the
137 directory that contains the .mrconfig file.
139 Within a section, each parameter defines a shell command to run to handle a
140 given action. mr contains default handlers for the "update", "status", and
141 "commit" actions, so normally you only need to specify what to do for
144 Note that these shell commands are run in a "set -e" shell
145 environment, where any additional parameters you pass are available in
146 "$@". The "checkout" command is run in the parent of the repository
147 directory, since the repository isn't checked out yet. All other commands
148 are run inside the repository, though not necessarily at the top of it.
149 The "MR_REPO" environment variable is set to the path to the top of the
152 A few parameters have special meanings:
158 If the "skip" parameter is set and its command returns nonzero, then B<mr>
159 will skip acting on that repository.
163 If the "chain" parameter is set and its command returns nonzero, then B<mr>
164 will try to load a .mrconfig file from the root of the repository. (You
165 should avoid chaining from repositories with untrusted committers.)
169 The "lib" parameter can specify some shell code that will be run before each
170 command, this can be a useful way to define shell functions for other commands
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|directory=s" => sub { $directory=abs_path($_[1]) },
200 "c|config=s" => \$config,
201 "verbose" => \$verbose,
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 $alias{$action}) {
222 $action=$alias{$action};
224 if (! exists $knownactions{$action}) {
225 my @matches = grep { /^\Q$action\E/ }
226 keys %knownactions, keys %alias;
230 elsif (@matches == 0) {
231 die "mr: unknown action \"$action\" (known actions: ".
232 join(", ", sort keys %knownactions).")\n";
235 die "mr: ambiguous action \"$action\" (matches: ".
236 join(", ", @matches).")\n";
240 if ($action eq 'help') {
241 exec($config{''}{DEFAULT}{help});
244 # work out what repos to act on
247 foreach my $topdir (sort keys %config) {
248 foreach my $subdir (sort keys %{$config{$topdir}}) {
249 next if $subdir eq 'DEFAULT';
250 my $dir=$topdir.$subdir;
251 next if $dir ne $directory && $dir !~ /^\Q$directory\E\//;
252 push @repos, [$dir, $topdir, $subdir];
256 # fallback to find a leaf repo
257 LEAF: foreach my $topdir (reverse sort keys %config) {
258 foreach my $subdir (reverse sort keys %{$config{$topdir}}) {
259 next if $subdir eq 'DEFAULT';
260 my $dir=$topdir.$subdir;
262 $dir.="/" unless $dir=~/\/$/;
263 $d.="/" unless $d=~/\/$/;
264 if ($d=~/^\Q$dir\E/) {
265 push @repos, [$dir, $topdir, $subdir];
273 my (@failed, @successful, @skipped);
274 foreach my $repo (@repos) {
275 action($action, @$repo);
279 my ($action, $dir, $topdir, $subdir) = @_;
281 my $lib= exists $config{$topdir}{$subdir}{lib} ?
282 $config{$topdir}{$subdir}{lib} : "";
284 if ($action eq 'checkout') {
286 print "mr $action: $dir already exists, skipping checkout\n" if $verbose;
290 $dir=~s/^(.*)\/[^\/]+\/?$/$1/;
292 elsif ($action eq 'update') {
294 return action("checkout", $dir, $topdir, $subdir);
300 if (exists $config{$topdir}{$subdir}{skip}) {
301 my $test="set -e;".$lib.$config{$topdir}{$subdir}{skip};
302 print "mr $action: running skip test $test\n" if $verbose;
303 my $ret=system($test);
304 if ($ret >> 8 == 0) {
305 print "mr $action: $dir skipped per config file\n" if $verbose;
311 if (! $nochdir && ! chdir($dir)) {
312 print STDERR "mr $action: failed to chdir to $dir: $!\n";
315 elsif (! exists $config{$topdir}{$subdir}{$action}) {
316 print STDERR "mr $action: no defined $action command for $topdir$subdir, skipping\n";
321 print "mr $action: $dir\n";
324 print "mr $action: $dir (in subdir $directory)\n";
326 my $command="set -e; ".$lib.
327 "my_action(){ $config{$topdir}{$subdir}{$action} ; }; my_action ".
328 join(" ", map { s/\//\/\//g; s/"/\"/g; '"'.$_.'"' } @ARGV);
329 print STDERR "mr $action: running $command\n" if $verbose;
330 my $ret=system($command);
332 print STDERR "mr $action: failed ($ret)\n" if $verbose;
333 push @failed, $topdir.$subdir;
334 if ($ret >> 8 != 0) {
335 print STDERR "mr $action: command failed\n";
338 print STDERR "mr $action: command died ($ret)\n";
342 push @successful, $dir;
354 return "$count ".($count > 1 ? $plural : $singular);
358 if (! @successful && ! @failed && ! @skipped) {
359 die "mr $action: no repositories found to work on\n";
361 print "mr $action: finished (".join("; ",
362 showstat($#successful+1, "successful", "successful"),
363 showstat($#failed+1, "failed", "failed"),
364 showstat($#skipped+1, "skipped", "skipped"),
369 elsif (! @successful && @skipped) {
382 if (ref $f eq 'GLOB') {
387 # $f might be a symlink
388 my $absf=abs_path($f);
389 if ($loaded{$absf}) {
394 print "mr: loading config $f\n" if $verbose;
395 open($in, "<", $f) || die "mr: open $f: $!\n";
396 ($dir)=$f=~/^(.*\/)[^\/]+$/;
397 if (! defined $dir) {
400 $dir=abs_path($dir)."/";
402 # copy in defaults from first parent
404 while ($parent=~s/^(.*)\/[^\/]+\/?$/$1/) {
405 if (exists $config{$parent} &&
406 exists $config{$parent}{DEFAULT}) {
407 $config{$dir}{DEFAULT}={ %{$config{$parent}{DEFAULT}} };
416 next if /^\s*\#/ || /^\s*$/;
417 if (/^\s*\[([^\]]*)\]\s*$/) {
420 elsif (/^\s*(\w+)\s*=\s*(.*)/) {
425 while ($value=~/(.*)\\$/) {
430 if (! defined $section) {
431 die "$f line $.: parameter ($parameter) not in section\n";
433 if ($section ne 'ALIAS' &&
434 ! exists $config{$dir}{$section} &&
435 exists $config{$dir}{DEFAULT}) {
437 $config{$dir}{$section}={ %{$config{$dir}{DEFAULT}} };
439 if ($section eq 'ALIAS') {
440 $alias{$parameter}=$value;
442 elsif ($parameter eq 'lib') {
443 $config{$dir}{$section}{lib}.=$value." ; ";
446 $config{$dir}{$section}{$parameter}=$value;
447 $knownactions{$parameter}=1;
448 if ($parameter eq 'chain' &&
449 length $dir && $section ne "DEFAULT" &&
450 -e $dir.$section."/.mrconfig" &&
451 system($value) >> 8 == 0) {
452 push @toload, $dir.$section."/.mrconfig";
457 die "$f line $.: parse error\n";
467 # Finally, some useful actions that mr knows about by default.
468 # 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"/.bzr ]; then \
489 elif [ -d "$MR_REPO"/CVS ]; then \
492 error "unknown repo type"; \
495 if [ -d "$MR_REPO"/.svn ]; then \
497 elif [ -d "$MR_REPO"/.git ]; then \
498 git status "$@" || true; \
499 elif [ -d "$MR_REPO"/.bzr ]; then \
501 elif [ -d "$MR_REPO"/CVS ]; then \
504 error "unknown repo type"; \
507 if [ -d "$MR_REPO"/.svn ]; then \
509 elif [ -d "$MR_REPO"/.git ]; then \
510 git commit -a "$@" && git push --all; \
511 elif [ -d "$MR_REPO"/.bzr ]; then \
512 bzr commit "$@" && bzr push; \
513 elif [ -d "$MR_REPO"/CVS ]; then \
516 error "unknown repo type"; \
519 if [ -d "$MR_REPO"/.svn ]; then \
521 elif [ -d "$MR_REPO"/.git ]; then \
523 elif [ -d "$MR_REPO"/.bzr ]; then \
525 elif [ -d "$MR_REPO"/CVS ]; then \
528 error "unknown repo type"; \
531 if [ -d "$MR_REPO"/.svn ]; then \
533 elif [ -d "$MR_REPO"/.git ]; then \
535 elif [ -d "$MR_REPO"/.bzr ]; then \
537 elif [ -d "$MR_REPO"/CVS ]; then \
540 error "unknown repo type"; \
545 if [ ! -e "$MR_PATH" ]; then \
546 error "cannot find program path";\
548 (pod2man -c mr "$MR_PATH" | man -l -) || \
549 error "pod2man or man failed"
551 ed = echo "A horse is a horse, of course, of course.."
552 T = echo "I pity the fool."