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"]
19 B<mr> [options] action [params ...]
23 B<mr> is a Multiple Repository management tool. It allows you to register a
24 set of repositories in a .mrconfig file, and then checkout, update, or
25 perform other actions on the repositories as if they were one big
28 Any mix of revision control systems can be used with B<mr>, and you can
29 define arbitrary actions for commands like "update", "checkout", or "commit".
31 B<mr> cds into and operates on all registered repsitories at or below your
32 working directory. Or, if you are in a subdirectory of a repository that
33 contains no other registered repositories, it will stay in that directory,
34 and work on only that repository,
36 The predefined commands should be fairly familiar to users of any revision
41 =item checkout (or co)
43 Checks out any repositories that are not already checked out.
47 Updates each repository from its configured remote repository.
49 If a repository isn't checked out yet, it will first check it out.
53 Displays a status report for each repository, showing what
54 uncommitted changes are present in the repository.
58 Commits changes to each repository. (By default, changes are pushed to the
59 remote repository too, when using distributed systems like git.)
61 The optional -m parameter allows specifying a commit message.
65 Show a diff of uncommitted changes.
69 List the repositories that mr will act on.
77 Actions can be abbreviated to any unambiguous subsctring, so
78 "mr st" is equivilant to "mr status", and "mr up" is equivilant to "mr
81 Additional parameters can be passed to other commands than "commit", they
82 will be passed on unchanged to the underlying revision control system.
83 This is mostly useful if the repositories mr will act on all use the same
84 revision control system.
92 Specifies the topmost directory that B<mr> should work in. The default is
93 the current working directory.
97 Use the specified mrconfig file, instead of looking for on in your home
108 B<mr> is configured by .mrconfig files. It starts by reading the .mrconfig
109 file in your home directory. Each repository specified in a .mrconfig file
110 can also have its own .mrconfig file in its root directory that can
111 optionally be used as well. So you could have a ~/.mrconfig that registers a
112 repository ~/src, that itself contains a ~/src/.mrconfig file, that in turn
113 registers several additional repositories.
115 The .mrconfig file uses a variant of the INI file format. Lines starting with
116 "#" are comments. Lines ending with "\" are continued on to the next line.
117 Sections specify where each repository is located, relative to the
118 directory that contains the .mrconfig file.
120 Within a section, each parameter defines a shell command to run to handle a
121 given action. Note that these shell commands are run in a "set -e" shell
122 environment, where any additional parameters you pass are available in
123 "$@". The "checkout" command is run in the parent of the repository
124 directory, since the repository isn't checked out yet. All other commands
125 are run inside the repository, though not necessarily at the top of it.
126 The "MR_REPO" environment variable is set to the path to the top of the
129 There are three special parameters. If the "skip" parameter is set and
130 its command returns nonzero, then B<mr> will skip acting on that repository.
131 If the "chain" parameter is set and its command returns nonzero, then B<mr>
132 will try to load a .mrconfig file from the root of the repository. (You
133 should avoid chaining from repositories with untrusted committers.) The
134 "lib" parameter can specify some shell code that will be run before each
135 command, this can be a useful way to define shell functions other commands
138 The "default" section allows setting up default handlers for each action,
139 and is overridden by the contents of other sections. mr contains default
140 handlers for the "update", "status", and "commit" actions, so normally
141 you only need to specify what to do for "checkout".
143 The "alias" section allows adding aliases for commands. Each parameter
144 is an alias, and its value is the command to run.
149 checkout = svn co svn://svn.example.com/src/trunk src
154 checkout = git clone git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux-2.6.git
159 case "$(hostname)" in; \
168 Copyright 2007 Joey Hess <joey@kitenet.net>
170 Licensed under the GNU GPL version 2 or higher.
172 http://kitenet.net/~joey/code/mr/
179 use Cwd qw(getcwd abs_path);
181 my $directory=getcwd();
182 my $config="$ENV{HOME}/.mrconfig";
188 Getopt::Long::Configure("no_permute");
189 my $result=GetOptions(
190 "d=s" => sub { $directory=abs_path($_[1]) },
194 if (! $result || @ARGV < 1) {
195 die("Usage: mr [-d directory] action [params ...]\n".
196 "(Use mr help for man page.)\n");
203 #print Dumper(\%config);
206 use FindBin qw($Bin $Script);
207 $ENV{MR_PATH}=$Bin."/".$Script;
210 # alias expansion and command stemming
211 my $action=shift @ARGV;
212 if (! exists $knownactions{$action}) {
213 if (exists $alias{$action}) {
214 $action=$alias{$action};
217 my @matches = grep { /^\Q$action\E/ }
218 keys %knownactions, keys %alias;
223 die "mr: ambiguous action \"$action\" (matches @matches)\n";
228 if ($action eq 'help') {
229 exec($config{''}{default}{help});
232 # work out what repos to act on
235 foreach my $topdir (sort keys %config) {
236 foreach my $subdir (sort keys %{$config{$topdir}}) {
237 next if $subdir eq 'default';
238 my $dir=$topdir.$subdir;
239 next if $dir ne $directory && $dir !~ /^\Q$directory\E\//;
240 push @repos, [$dir, $topdir, $subdir];
244 # fallback to find a leaf repo
245 LEAF: foreach my $topdir (reverse sort keys %config) {
246 foreach my $subdir (reverse sort keys %{$config{$topdir}}) {
247 next if $subdir eq 'default';
248 my $dir=$topdir.$subdir;
250 $dir.="/" unless $dir=~/\/$/;
251 $d.="/" unless $d=~/\/$/;
252 if ($d=~/^\Q$dir\E/) {
253 push @repos, [$dir, $topdir, $subdir];
261 my (@failed, @successful, @skipped);
262 foreach my $repo (@repos) {
263 action($action, @$repo);
267 my ($action, $dir, $topdir, $subdir) = @_;
269 my $lib= exists $config{$topdir}{$subdir}{lib} ?
270 $config{$topdir}{$subdir}{lib} : "";
272 if ($action eq 'checkout') {
274 print "mr $action: $dir already exists, skipping checkout\n" if $verbose;
278 $dir=~s/^(.*)\/[^\/]+\/?$/$1/;
280 elsif ($action eq 'update') {
282 return action("checkout", $dir, $topdir, $subdir);
287 if (! $nochdir && ! chdir($dir)) {
288 print STDERR "mr $action: failed to chdir to $dir: $!\n";
292 if (exists $config{$topdir}{$subdir}{skip}) {
293 my $ret=system($lib.$config{$topdir}{$subdir}{skip});
294 if ($ret >> 8 == 0) {
295 print "mr $action: $dir skipped per config file\n" if $verbose;
301 if (! exists $config{$topdir}{$subdir}{$action}) {
302 print STDERR "mr $action: no defined $action command for $topdir$subdir, skipping\n";
306 print "mr $action: $dir\n";
307 my $command="set -e; ".$lib.
308 "my_action(){ $config{$topdir}{$subdir}{$action} ; }; my_action ".
309 join(" ", map { s/\//\/\//g; s/"/\"/g; '"'.$_.'"' } @ARGV);
310 my $ret=system($command);
312 print STDERR "mr $action: failed to run: $command\n" if $verbose;
313 push @failed, $topdir.$subdir;
314 if ($ret >> 8 != 0) {
315 print STDERR "mr $action: command failed\n";
318 print STDERR "mr $action: command died ($ret)\n";
322 push @successful, $dir;
334 return "$count ".($count > 1 ? $plural : $singular);
338 if (! @successful && ! @failed && ! @skipped) {
339 die "mr $action: no repositories found to work on\n";
341 print "mr $action: finished (".join("; ",
342 showstat($#successful+1, "successful", "successful"),
343 showstat($#failed+1, "failed", "failed"),
344 showstat($#skipped+1, "skipped", "skipped"),
349 elsif (! @successful && @skipped) {
362 if (ref $f eq 'GLOB') {
367 # $f might be a symlink
368 my $absf=abs_path($f);
369 if ($loaded{$absf}) {
374 print "mr: loading config $f\n" if $verbose;
375 open($in, "<", $f) || die "mr: open $f: $!\n";
376 ($dir)=$f=~/^(.*\/)[^\/]+$/;
377 if (! defined $dir) {
380 $dir=abs_path($dir)."/";
382 # copy in defaults from first parent
384 while ($parent=~s/^(.*)\/[^\/]+\/?$/$1/) {
385 if (exists $config{$parent} &&
386 exists $config{$parent}{default}) {
387 $config{$dir}{default}={ %{$config{$parent}{default}} };
396 next if /^\s*\#/ || /^\s*$/;
397 if (/^\s*\[([^\]]*)\]\s*$/) {
400 elsif (/^\s*(\w+)\s*=\s*(.*)/) {
405 while ($value=~/(.*)\\$/) {
410 if (! defined $section) {
411 die "$f line $.: parameter ($parameter) not in section\n";
413 if ($section ne 'alias' &&
414 ! exists $config{$dir}{$section} &&
415 exists $config{$dir}{default}) {
417 $config{$dir}{$section}={ %{$config{$dir}{default}} };
419 if ($section eq 'alias') {
420 $alias{$parameter}=$value;
422 elsif ($parameter eq 'lib') {
423 $config{$dir}{$section}{lib}.=$value." ; ";
426 $config{$dir}{$section}{$parameter}=$value;
427 $knownactions{$parameter}=1;
428 if ($parameter eq 'chain' &&
429 length $dir && $section ne "default" &&
430 -e $dir.$section."/.mrconfig" &&
431 system($value) >> 8 == 0) {
432 push @toload, $dir.$section."/.mrconfig";
437 die "$f line $.: parse error\n";
447 # Finally, some useful actions that mr knows about by default.
448 # These can be overridden in ~/.mrconfig.
460 if [ -d "$MR_REPO"/.svn ]; then \
462 elif [ -d "$MR_REPO"/.git ]; then \
463 git pull origin master "$@"; \
465 error "unknown repo type"; \
468 if [ -d "$MR_REPO"/.svn ]; then \
470 elif [ -d "$MR_REPO"/.git ]; then \
471 git status "$@" || true; \
473 error "unknown repo type"; \
476 if [ -d "$MR_REPO"/.svn ]; then \
478 elif [ -d "$MR_REPO"/.git ]; then \
479 git commit -a "$@" && git push --all; \
481 error "unknown repo type"; \
484 if [ -d "$MR_REPO"/.svn ]; then \
486 elif [ -d "$MR_REPO"/.git ]; then \
489 error "unknown repo type"; \
493 if [ ! -e "$MR_PATH" ]; then \
494 error "cannot find program path";\
496 (pod2man -c mr "$MR_PATH" | man -l -) || \
497 error "pod2man or man failed"