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 my $dir=$topdir.$subdir;
236 next if $dir ne $directory && $dir !~ /^\Q$directory\E\//;
237 push @repos, [$dir, $topdir, $subdir];
241 # fallback to find a leaf repo
242 foreach my $topdir (sort keys %config) {
243 foreach my $subdir (sort keys %{$config{$topdir}}) {
244 my $dir=$topdir.$subdir;
246 $dir.="/" unless $dir=~/\/$/;
247 $d.="/" unless $d=~/\/$/;
248 if ($d=~/^\Q$dir\E/) {
249 push @repos, [$dir, $topdir, $subdir];
254 die "mr: found multiple leaf repos (should never happen)";
258 my (@failed, @successful, @skipped);
259 foreach my $repo (@repos) {
260 action($action, @$repo);
264 my ($action, $dir, $topdir, $subdir) = @_;
266 my $lib= exists $config{$topdir}{$subdir}{lib} ?
267 $config{$topdir}{$subdir}{lib} : "";
269 if ($action eq 'checkout') {
271 print "mr $action: $dir already exists, skipping checkout\n" if $verbose;
275 $dir=~s/^(.*)\/[^\/]+\/?$/$1/;
277 elsif ($action eq 'update') {
279 return action("checkout", $dir, $topdir, $subdir);
284 if (! $nochdir && ! chdir($dir)) {
285 print STDERR "mr $action: failed to chdir to $dir: $!\n";
289 if (exists $config{$topdir}{$subdir}{skip}) {
290 my $ret=system($lib.$config{$topdir}{$subdir}{skip});
291 if ($ret >> 8 == 0) {
292 print "mr $action: $dir skipped per config file\n" if $verbose;
298 if (! exists $config{$topdir}{$subdir}{$action}) {
299 print STDERR "mr $action: no defined $action command for $topdir$subdir, skipping\n";
303 print "mr $action: $dir\n";
304 my $command="set -e; ".$lib.
305 "my_action(){ $config{$topdir}{$subdir}{$action} ; }; my_action ".
306 join(" ", map { s/\//\/\//g; s/"/\"/g; '"'.$_.'"' } @ARGV);
307 my $ret=system($command);
309 print STDERR "mr $action: failed to run: $command\n" if $verbose;
310 push @failed, $topdir.$subdir;
311 if ($ret >> 8 != 0) {
312 print STDERR "mr $action: command failed\n";
315 print STDERR "mr $action: command died ($ret)\n";
319 push @successful, $dir;
331 return "$count ".($count > 1 ? $plural : $singular);
335 if (! @successful && ! @failed && ! @skipped) {
336 die "mr $action: no repositories found to work on\n";
338 print "mr $action: finished (".join("; ",
339 showstat($#successful+1, "successful", "successful"),
340 showstat($#failed+1, "failed", "failed"),
341 showstat($#skipped+1, "skipped", "skipped"),
346 elsif (! @successful && @skipped) {
359 if (ref $f eq 'GLOB') {
364 # $f might be a symlink
365 my $absf=abs_path($f);
366 if ($loaded{$absf}) {
371 print "mr: loading config $f\n" if $verbose;
372 open($in, "<", $f) || die "mr: open $f: $!\n";
373 ($dir)=$f=~/^(.*\/)[^\/]+$/;
374 if (! defined $dir) {
377 $dir=abs_path($dir)."/";
379 # copy in defaults from first parent
381 while ($parent=~s/^(.*)\/[^\/]+\/?$/$1/) {
382 if (exists $config{$parent} &&
383 exists $config{$parent}{default}) {
384 $config{$dir}{default}={ %{$config{$parent}{default}} };
393 next if /^\s*\#/ || /^\s*$/;
394 if (/^\s*\[([^\]]*)\]\s*$/) {
397 elsif (/^\s*(\w+)\s*=\s*(.*)/) {
402 while ($value=~/(.*)\\$/) {
407 if (! defined $section) {
408 die "$f line $.: parameter ($parameter) not in section\n";
410 if ($section ne 'alias' &&
411 ! exists $config{$dir}{$section} &&
412 exists $config{$dir}{default}) {
414 $config{$dir}{$section}={ %{$config{$dir}{default}} };
416 if ($section eq 'alias') {
417 $alias{$parameter}=$value;
419 elsif ($parameter eq 'lib') {
420 $config{$dir}{$section}{lib}.=$value." ; ";
423 $config{$dir}{$section}{$parameter}=$value;
424 $knownactions{$parameter}=1;
425 if ($parameter eq 'chain' &&
426 length $dir && $section ne "default" &&
427 -e $dir.$section."/.mrconfig" &&
428 system($value) >> 8 == 0) {
429 push @toload, $dir.$section."/.mrconfig";
434 die "$f line $.: parse error\n";
444 # Finally, some useful actions that mr knows about by default.
445 # These can be overridden in ~/.mrconfig.
457 if [ -d "$MR_REPO"/.svn ]; then \
459 elif [ -d "$MR_REPO"/.git ]; then \
460 git pull origin master "$@"; \
462 error "unknown repo type"; \
465 if [ -d "$MR_REPO"/.svn ]; then \
467 elif [ -d "$MR_REPO"/.git ]; then \
468 git status "$@" || true; \
470 error "unknown repo type"; \
473 if [ -d "$MR_REPO"/.svn ]; then \
475 elif [ -d "$MR_REPO"/.git ]; then \
476 git commit -a "$@" && git push --all; \
478 error "unknown repo type"; \
481 if [ -d "$MR_REPO"/.svn ]; then \
483 elif [ -d "$MR_REPO"/.git ]; then \
486 error "unknown repo type"; \
490 if [ ! -e "$MR_PATH" ]; then \
491 error "cannot find program path";\
493 (pod2man -c mr "$MR_PATH" | man -l -) || \
494 error "pod2man or man failed"