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"]
17 B<mr> [options] action [params ...]
21 B<mr> is a Multiple Repository management tool. It allows you to register a
22 set of repositories in a .mrconfig file, and then checkout, update, or
23 perform other actions on all of the repositories at once.
25 Any mix of revision control systems can be used with B<mr>, and you can
26 define arbitrary actions for commands like "update", "checkout", or "commit".
28 The predefined commands should be fairly familiar to users of any revision
33 =item checkout (or co)
35 Checks out all the registered repositories that are not already checked
40 Updates each registered repository from its configured remote repository.
42 If a repository isn't checked out yet, it will first check it out.
46 Displays a status report for each registered repository, showing what
47 uncommitted changes are present in the repository.
51 Commits changes to each registered repository. (By default, changes
52 are pushed to the remote repository too, when using distributed systems
55 The optional -m parameter allows specifying a commit message.
59 Actions can be abbreviated to any unambiguous subsctring, so
60 "mr st" is equivilant to "mr status".
68 Specifies the topmost directory that B<mr> should work in. The default is
69 the current working directory. B<mr> will operate on all registered
70 repositories at or under the directory.
74 Use the specified mrconfig file, instead of looking for on in your home
85 B<mr> is configured by .mrconfig files. It starts by reading the .mrconfig
86 file in your home directory. Each repository specified in a .mrconfig file
87 can also have its own .mrconfig file in its root directory that can
88 optionally be used as well. So you could have a ~/.mrconfig that registers a
89 repository ~/src, that itself contains a ~/src/.mrconfig file, that in turn
90 registers several additional repositories.
92 The .mrconfig file uses a variant of the INI file format. Lines starting with
93 "#" are comments. Lines ending with "\" are continued on to the next line.
94 Sections specify where each repository is located, relative to the
95 directory that contains the .mrconfig file.
97 Within a section, each parameter defines a shell command to run to handle a
98 given action. Note that these shell commands are run in a "set -e" shell
99 environment, where any additional parameters you pass are available in
100 "$@". B<mr> cds into the repository directory before running
101 a command, except for the "checkout" command, which is run in the parent
102 of the repository directory, since the repository isn't checked out yet.
104 There are three special parameters. If the "skip" parameter is set and
105 its command returns nonzero, then B<mr> will skip acting on that repository.
106 If the "chain" parameter is set and its command returns nonzero, then B<mr>
107 will try to load a .mrconfig file from the root of the repository. (You
108 should avoid chaining from repositories with untrusted committers.) The
109 "lib" parameter can specify some shell code that will be run before each
110 command, this can be a useful way to define shell functions other commands
113 The "default" section allows setting up default handlers for each action,
114 and is overridden by the contents of other sections. mr contains default
115 handlers for the "update", "status", and "commit" actions, so normally
116 you only need to specify what to do for "checkout".
118 The "alias" section allows adding aliases for commands. Each parameter
119 is an alias, and its value is the command to run.
124 checkout = svn co svn://svn.example.com/src/trunk src
129 checkout = git clone git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux-2.6.git
134 case "$(hostname)" in; \
143 Copyright 2007 Joey Hess <joey@kitenet.net>
145 Licensed under the GNU GPL version 2 or higher.
147 http://kitenet.net/~joey/code/mr/
154 use Cwd qw(getcwd abs_path);
156 my $directory=getcwd();
157 my $config="$ENV{HOME}/.mrconfig";
163 Getopt::Long::Configure("no_permute");
164 my $result=GetOptions(
165 "d=s" => sub { $directory=abs_path($_[1]) },
169 if (! $result || @ARGV < 1) {
170 die("Usage: mr [-d directory] action [params ...]\n");
176 #print Dumper(\%config);
178 # alias expansion and command stemming
179 my $action=shift @ARGV;
180 if (! exists $knownactions{$action}) {
181 if (exists $alias{$action}) {
182 $action=$alias{$action};
185 my @matches = grep { /^\Q$action\E/ }
186 keys %knownactions, keys %alias;
191 die "mr: ambiguous action \"$action\" (matches @matches)\n";
196 # handle being in a subdir of a repository
197 foreach my $topdir (sort keys %config) {
198 foreach my $subdir (sort keys %{$config{$topdir}}) {
199 if ($directory =~ /^\Q$topdir$subdir\E\//) {
200 $directory=$topdir.$subdir;
205 my (@failed, @successful, @skipped);
207 foreach my $topdir (sort keys %config) {
208 foreach my $subdir (sort keys %{$config{$topdir}}) {
209 next if $subdir eq 'default';
211 my $dir=$topdir.$subdir;
213 if (defined $directory &&
214 $dir ne $directory &&
215 $dir !~ /^\Q$directory\E\//) {
216 print "mr $action: $dir skipped per -d parameter ($directory)\n" if $verbose;
221 print "\n" unless $first;
224 action($action, $dir, $topdir, $subdir);
230 my ($action, $dir, $topdir, $subdir) = @_;
232 my $lib= exists $config{$topdir}{$subdir}{lib} ?
233 $config{$topdir}{$subdir}{lib} : "";
235 if ($action eq 'checkout') {
237 print "mr $action: $dir already exists, skipping checkout\n" if $verbose;
241 $dir=~s/^(.*)\/[^\/]+\/?$/$1/;
243 elsif ($action eq 'update') {
245 return action("checkout", $dir, $topdir, $subdir);
250 print STDERR "mr $action: failed to chdir to $dir: $!\n";
254 if (exists $config{$topdir}{$subdir}{skip}) {
255 my $ret=system($lib.$config{$topdir}{$subdir}{skip});
256 if ($ret >> 8 == 0) {
257 print "mr $action: $dir skipped per config file\n" if $verbose;
263 if (! exists $config{$topdir}{$subdir}{$action}) {
264 print STDERR "mr $action: no defined $action command for $topdir$subdir, skipping\n";
268 print "mr $action: in $dir\n";
269 my $command="set -e; ".$lib.
270 "my_action(){ $config{$topdir}{$subdir}{$action} ; }; my_action ".
271 join(" ", map { s/\//\/\//g; s/"/\"/g; '"'.$_.'"' } @ARGV);
272 my $ret=system($command);
274 print STDERR "mr $action: failed to run: $command\n" if $verbose;
275 push @failed, $topdir.$subdir;
276 if ($ret >> 8 != 0) {
277 print STDERR "mr $action: command failed\n";
280 print STDERR "mr $action: command died ($ret)\n";
284 push @successful, $dir;
294 return "$count ".($count > 1 ? $plural : $singular);
298 print "\nmr $action: finished (".join("; ",
299 showstat($#successful+1, "successful", "successful"),
300 showstat($#failed+1, "failed", "failed"),
301 showstat($#skipped+1, "skipped", "skipped"),
306 elsif (! @successful && @skipped) {
319 if (ref $f eq 'GLOB') {
324 # $f might be a symlink
325 my $absf=abs_path($f);
326 if ($loaded{$absf}) {
331 print "mr: loading config $f\n" if $verbose;
332 open($in, "<", $f) || die "mr: open $f: $!\n";
333 ($dir)=$f=~/^(.*\/)[^\/]+$/;
334 if (! defined $dir) {
337 $dir=abs_path($dir)."/";
339 # copy in defaults from first parent
341 while ($parent=~s/^(.*)\/[^\/]+\/?$/$1/) {
342 if (exists $config{$parent} &&
343 exists $config{$parent}{default}) {
344 $config{$dir}{default}={ %{$config{$parent}{default}} };
353 next if /^\s*\#/ || /^\s*$/;
354 if (/^\s*\[([^\]]*)\]\s*$/) {
357 elsif (/^\s*(\w+)\s*=\s*(.*)/) {
362 while ($value=~/(.*)\\$/) {
367 if (! defined $section) {
368 die "$f line $.: parameter ($parameter) not in section\n";
370 if ($section ne 'alias' &&
371 ! exists $config{$dir}{$section} &&
372 exists $config{$dir}{default}) {
374 $config{$dir}{$section}={ %{$config{$dir}{default}} };
376 if ($section eq 'alias') {
377 $alias{$parameter}=$value;
379 elsif ($parameter eq 'lib') {
380 $config{$dir}{$section}{lib}.=$value." ; ";
383 $config{$dir}{$section}{$parameter}=$value;
384 $knownactions{$parameter}=1;
385 if ($parameter eq 'chain' &&
386 length $dir && $section ne "default" &&
387 -e $dir.$section."/.mrconfig" &&
388 system($value) >> 8 == 0) {
389 push @toload, $dir.$section."/.mrconfig";
394 die "$f line $.: parse error\n";
404 # Finally, some useful actions that mr knows about by default.
405 # These can be overridden in ~/.mrconfig.
417 if [ -d .svn ]; then \
419 elif [ -d .git ]; then \
420 git pull origin master "$@"; \
422 error "unknown repo type"; \
425 if [ -d .svn ]; then \
427 elif [ -d .git ]; then \
428 git status "$@" || true; \
430 error "unknown repo type"; \
433 if [ -d .svn ]; then \
435 elif [ -d .git ]; then \
436 git commit -a "$@" && git push --all; \
438 error "unknown repo type"; \