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 The predefined commands should be fairly familiar to users of any revision
36 =item checkout (or co)
38 Checks out any repositories that are not already checked out.
42 Updates each repository from its configured remote repository.
44 If a repository isn't checked out yet, it will first check it out.
48 Displays a status report for each repository, showing what
49 uncommitted changes are present in the repository.
53 Commits changes to each repository. (By default, changes are pushed to the
54 remote repository too, when using distributed systems like git.)
56 The optional -m parameter allows specifying a commit message.
60 Show a diff of uncommitted changes.
64 List the repositories that mr will act on.
72 Actions can be abbreviated to any unambiguous subsctring, so
73 "mr st" is equivilant to "mr status".
75 B<mr> operates on all registered repsitories at or below your working
76 directory. Or, if you are in a subdirectory of a repository that contains
77 no other registered repositories, it will act on only that repository.
79 Additional parameters can be passed to other commands than "commit", they
80 will be passed on unchanged to the underlying revision control system.
81 This is mostly useful if the repositories mr will act on all use the same
82 revision control system.
90 Specifies the topmost directory that B<mr> should work in. The default is
91 the current working directory.
95 Use the specified mrconfig file, instead of looking for on in your home
106 B<mr> is configured by .mrconfig files. It starts by reading the .mrconfig
107 file in your home directory. Each repository specified in a .mrconfig file
108 can also have its own .mrconfig file in its root directory that can
109 optionally be used as well. So you could have a ~/.mrconfig that registers a
110 repository ~/src, that itself contains a ~/src/.mrconfig file, that in turn
111 registers several additional repositories.
113 The .mrconfig file uses a variant of the INI file format. Lines starting with
114 "#" are comments. Lines ending with "\" are continued on to the next line.
115 Sections specify where each repository is located, relative to the
116 directory that contains the .mrconfig file.
118 Within a section, each parameter defines a shell command to run to handle a
119 given action. Note that these shell commands are run in a "set -e" shell
120 environment, where any additional parameters you pass are available in
121 "$@". The "checkout" command is run in the parent of the repository
122 directory, since the repository isn't checked out yet. All other commands
123 are run inside the repository, though not necessarily at the top of it.
124 The "MR_REPO" environment variable is set to the path to the top of the
127 There are three special parameters. If the "skip" parameter is set and
128 its command returns nonzero, then B<mr> will skip acting on that repository.
129 If the "chain" parameter is set and its command returns nonzero, then B<mr>
130 will try to load a .mrconfig file from the root of the repository. (You
131 should avoid chaining from repositories with untrusted committers.) The
132 "lib" parameter can specify some shell code that will be run before each
133 command, this can be a useful way to define shell functions other commands
136 The "default" section allows setting up default handlers for each action,
137 and is overridden by the contents of other sections. mr contains default
138 handlers for the "update", "status", and "commit" actions, so normally
139 you only need to specify what to do for "checkout".
141 The "alias" section allows adding aliases for commands. Each parameter
142 is an alias, and its value is the command to run.
147 checkout = svn co svn://svn.example.com/src/trunk src
152 checkout = git clone git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux-2.6.git
157 case "$(hostname)" in; \
166 Copyright 2007 Joey Hess <joey@kitenet.net>
168 Licensed under the GNU GPL version 2 or higher.
170 http://kitenet.net/~joey/code/mr/
177 use Cwd qw(getcwd abs_path);
179 my $directory=getcwd();
180 my $config="$ENV{HOME}/.mrconfig";
186 Getopt::Long::Configure("no_permute");
187 my $result=GetOptions(
188 "d=s" => sub { $directory=abs_path($_[1]) },
192 if (! $result || @ARGV < 1) {
193 die("Usage: mr [-d directory] action [params ...]\n".
194 "(Use mr help for man page.)\n");
201 #print Dumper(\%config);
204 use FindBin qw($Bin $Script);
205 $ENV{MR_PATH}=$Bin."/".$Script;
208 # alias expansion and command stemming
209 my $action=shift @ARGV;
210 if (! exists $knownactions{$action}) {
211 if (exists $alias{$action}) {
212 $action=$alias{$action};
215 my @matches = grep { /^\Q$action\E/ }
216 keys %knownactions, keys %alias;
221 die "mr: ambiguous action \"$action\" (matches @matches)\n";
226 if ($action eq 'help') {
227 exec($config{''}{default}{help});
230 # work out what repos to act on
233 foreach my $topdir (sort keys %config) {
234 foreach my $subdir (sort keys %{$config{$topdir}}) {
235 next if $subdir eq 'default';
236 my $dir=$topdir.$subdir;
237 next if $dir ne $directory && $dir !~ /^\Q$directory\E\//;
238 push @repos, [$dir, $topdir, $subdir];
242 # fallback to find a leaf repo
243 LEAF: foreach my $topdir (reverse sort keys %config) {
244 foreach my $subdir (reverse sort keys %{$config{$topdir}}) {
245 next if $subdir eq 'default';
246 my $dir=$topdir.$subdir;
248 $dir.="/" unless $dir=~/\/$/;
249 $d.="/" unless $d=~/\/$/;
250 if ($d=~/^\Q$dir\E/) {
251 push @repos, [$dir, $topdir, $subdir];
259 my (@failed, @successful, @skipped);
260 foreach my $repo (@repos) {
261 action($action, @$repo);
265 my ($action, $dir, $topdir, $subdir) = @_;
267 my $lib= exists $config{$topdir}{$subdir}{lib} ?
268 $config{$topdir}{$subdir}{lib} : "";
270 if ($action eq 'checkout') {
272 print "mr $action: $dir already exists, skipping checkout\n" if $verbose;
276 $dir=~s/^(.*)\/[^\/]+\/?$/$1/;
278 elsif ($action eq 'update') {
280 return action("checkout", $dir, $topdir, $subdir);
285 if (! $nochdir && ! chdir($dir)) {
286 print STDERR "mr $action: failed to chdir to $dir: $!\n";
290 if (exists $config{$topdir}{$subdir}{skip}) {
291 my $ret=system($lib.$config{$topdir}{$subdir}{skip});
292 if ($ret >> 8 == 0) {
293 print "mr $action: $dir skipped per config file\n" if $verbose;
299 if (! exists $config{$topdir}{$subdir}{$action}) {
300 print STDERR "mr $action: no defined $action command for $topdir$subdir, skipping\n";
304 print "mr $action: $dir\n";
305 my $command="set -e; ".$lib.
306 "my_action(){ $config{$topdir}{$subdir}{$action} ; }; my_action ".
307 join(" ", map { s/\//\/\//g; s/"/\"/g; '"'.$_.'"' } @ARGV);
308 my $ret=system($command);
310 print STDERR "mr $action: failed to run: $command\n" if $verbose;
311 push @failed, $topdir.$subdir;
312 if ($ret >> 8 != 0) {
313 print STDERR "mr $action: command failed\n";
316 print STDERR "mr $action: command died ($ret)\n";
320 push @successful, $dir;
332 return "$count ".($count > 1 ? $plural : $singular);
336 if (! @successful && ! @failed && ! @skipped) {
337 die "mr $action: no repositories found to work on\n";
339 print "mr $action: finished (".join("; ",
340 showstat($#successful+1, "successful", "successful"),
341 showstat($#failed+1, "failed", "failed"),
342 showstat($#skipped+1, "skipped", "skipped"),
347 elsif (! @successful && @skipped) {
360 if (ref $f eq 'GLOB') {
365 # $f might be a symlink
366 my $absf=abs_path($f);
367 if ($loaded{$absf}) {
372 print "mr: loading config $f\n" if $verbose;
373 open($in, "<", $f) || die "mr: open $f: $!\n";
374 ($dir)=$f=~/^(.*\/)[^\/]+$/;
375 if (! defined $dir) {
378 $dir=abs_path($dir)."/";
380 # copy in defaults from first parent
382 while ($parent=~s/^(.*)\/[^\/]+\/?$/$1/) {
383 if (exists $config{$parent} &&
384 exists $config{$parent}{default}) {
385 $config{$dir}{default}={ %{$config{$parent}{default}} };
394 next if /^\s*\#/ || /^\s*$/;
395 if (/^\s*\[([^\]]*)\]\s*$/) {
398 elsif (/^\s*(\w+)\s*=\s*(.*)/) {
403 while ($value=~/(.*)\\$/) {
408 if (! defined $section) {
409 die "$f line $.: parameter ($parameter) not in section\n";
411 if ($section ne 'alias' &&
412 ! exists $config{$dir}{$section} &&
413 exists $config{$dir}{default}) {
415 $config{$dir}{$section}={ %{$config{$dir}{default}} };
417 if ($section eq 'alias') {
418 $alias{$parameter}=$value;
420 elsif ($parameter eq 'lib') {
421 $config{$dir}{$section}{lib}.=$value." ; ";
424 $config{$dir}{$section}{$parameter}=$value;
425 $knownactions{$parameter}=1;
426 if ($parameter eq 'chain' &&
427 length $dir && $section ne "default" &&
428 -e $dir.$section."/.mrconfig" &&
429 system($value) >> 8 == 0) {
430 push @toload, $dir.$section."/.mrconfig";
435 die "$f line $.: parse error\n";
445 # Finally, some useful actions that mr knows about by default.
446 # These can be overridden in ~/.mrconfig.
458 if [ -d "$MR_REPO"/.svn ]; then \
460 elif [ -d "$MR_REPO"/.git ]; then \
461 git pull origin master "$@"; \
463 error "unknown repo type"; \
466 if [ -d "$MR_REPO"/.svn ]; then \
468 elif [ -d "$MR_REPO"/.git ]; then \
469 git status "$@" || true; \
471 error "unknown repo type"; \
474 if [ -d "$MR_REPO"/.svn ]; then \
476 elif [ -d "$MR_REPO"/.git ]; then \
477 git commit -a "$@" && git push --all; \
479 error "unknown repo type"; \
482 if [ -d "$MR_REPO"/.svn ]; then \
484 elif [ -d "$MR_REPO"/.git ]; then \
487 error "unknown repo type"; \
491 if [ ! -e "$MR_PATH" ]; then \
492 error "cannot find program path";\
494 (pod2man -c mr "$MR_PATH" | man -l -) || \
495 error "pod2man or man failed"