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 all of the repositories at once.
27 Any mix of revision control systems can be used with B<mr>, and you can
28 define arbitrary actions for commands like "update", "checkout", or "commit".
30 The predefined commands should be fairly familiar to users of any revision
35 =item checkout (or co)
37 Checks out all the registered repositories that are not already checked
42 Updates each registered 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 registered repository, showing what
49 uncommitted changes are present in the repository.
53 Commits changes to each registered repository. (By default, changes
54 are pushed to the remote repository too, when using distributed systems
57 The optional -m parameter allows specifying a commit message.
61 Show a diff of uncommitted changes.
69 Actions can be abbreviated to any unambiguous subsctring, so
70 "mr st" is equivilant to "mr status".
78 Specifies the topmost directory that B<mr> should work in. The default is
79 the current working directory. B<mr> will operate on all registered
80 repositories at or under the directory.
84 Use the specified mrconfig file, instead of looking for on in your home
95 B<mr> is configured by .mrconfig files. It starts by reading the .mrconfig
96 file in your home directory. Each repository specified in a .mrconfig file
97 can also have its own .mrconfig file in its root directory that can
98 optionally be used as well. So you could have a ~/.mrconfig that registers a
99 repository ~/src, that itself contains a ~/src/.mrconfig file, that in turn
100 registers several additional repositories.
102 The .mrconfig file uses a variant of the INI file format. Lines starting with
103 "#" are comments. Lines ending with "\" are continued on to the next line.
104 Sections specify where each repository is located, relative to the
105 directory that contains the .mrconfig file.
107 Within a section, each parameter defines a shell command to run to handle a
108 given action. Note that these shell commands are run in a "set -e" shell
109 environment, where any additional parameters you pass are available in
110 "$@". B<mr> cds into the repository directory before running
111 a command, except for the "checkout" command, which is run in the parent
112 of the repository directory, since the repository isn't checked out yet.
114 There are three special parameters. If the "skip" parameter is set and
115 its command returns nonzero, then B<mr> will skip acting on that repository.
116 If the "chain" parameter is set and its command returns nonzero, then B<mr>
117 will try to load a .mrconfig file from the root of the repository. (You
118 should avoid chaining from repositories with untrusted committers.) The
119 "lib" parameter can specify some shell code that will be run before each
120 command, this can be a useful way to define shell functions other commands
123 The "default" section allows setting up default handlers for each action,
124 and is overridden by the contents of other sections. mr contains default
125 handlers for the "update", "status", and "commit" actions, so normally
126 you only need to specify what to do for "checkout".
128 The "alias" section allows adding aliases for commands. Each parameter
129 is an alias, and its value is the command to run.
134 checkout = svn co svn://svn.example.com/src/trunk src
139 checkout = git clone git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux-2.6.git
144 case "$(hostname)" in; \
153 Copyright 2007 Joey Hess <joey@kitenet.net>
155 Licensed under the GNU GPL version 2 or higher.
157 http://kitenet.net/~joey/code/mr/
164 use Cwd qw(getcwd abs_path);
166 my $directory=getcwd();
167 my $config="$ENV{HOME}/.mrconfig";
173 Getopt::Long::Configure("no_permute");
174 my $result=GetOptions(
175 "d=s" => sub { $directory=abs_path($_[1]) },
179 if (! $result || @ARGV < 1) {
180 die("Usage: mr [-d directory] action [params ...]\n".
181 "(Use mr help for man page.)\n");
188 #print Dumper(\%config);
191 use FindBin qw($Bin $Script);
192 $ENV{MR_PATH}=$Bin."/".$Script;
195 # alias expansion and command stemming
196 my $action=shift @ARGV;
197 if (! exists $knownactions{$action}) {
198 if (exists $alias{$action}) {
199 $action=$alias{$action};
202 my @matches = grep { /^\Q$action\E/ }
203 keys %knownactions, keys %alias;
208 die "mr: ambiguous action \"$action\" (matches @matches)\n";
213 if ($action eq 'help') {
214 exec($config{''}{default}{help});
217 # handle being in a subdir of a repository
218 foreach my $topdir (sort keys %config) {
219 foreach my $subdir (sort keys %{$config{$topdir}}) {
220 if ($directory =~ /^\Q$topdir$subdir\E\//) {
221 $directory=$topdir.$subdir;
226 my (@failed, @successful, @skipped);
228 foreach my $topdir (sort keys %config) {
229 foreach my $subdir (sort keys %{$config{$topdir}}) {
230 next if $subdir eq 'default';
232 my $dir=$topdir.$subdir;
234 if (defined $directory &&
235 $dir ne $directory &&
236 $dir !~ /^\Q$directory\E\//) {
240 action($action, $dir, $topdir, $subdir);
245 my ($action, $dir, $topdir, $subdir) = @_;
247 my $lib= exists $config{$topdir}{$subdir}{lib} ?
248 $config{$topdir}{$subdir}{lib} : "";
250 if ($action eq 'checkout') {
252 print "mr $action: $dir already exists, skipping checkout\n" if $verbose;
256 $dir=~s/^(.*)\/[^\/]+\/?$/$1/;
258 elsif ($action eq 'update') {
260 return action("checkout", $dir, $topdir, $subdir);
265 print STDERR "mr $action: failed to chdir to $dir: $!\n";
269 if (exists $config{$topdir}{$subdir}{skip}) {
270 my $ret=system($lib.$config{$topdir}{$subdir}{skip});
271 if ($ret >> 8 == 0) {
272 print "mr $action: $dir skipped per config file\n" if $verbose;
278 if (! exists $config{$topdir}{$subdir}{$action}) {
279 print STDERR "mr $action: no defined $action command for $topdir$subdir, skipping\n";
283 print "mr $action: in $dir\n";
284 my $command="set -e; ".$lib.
285 "my_action(){ $config{$topdir}{$subdir}{$action} ; }; my_action ".
286 join(" ", map { s/\//\/\//g; s/"/\"/g; '"'.$_.'"' } @ARGV);
287 my $ret=system($command);
289 print STDERR "mr $action: failed to run: $command\n" if $verbose;
290 push @failed, $topdir.$subdir;
291 if ($ret >> 8 != 0) {
292 print STDERR "mr $action: command failed\n";
295 print STDERR "mr $action: command died ($ret)\n";
299 push @successful, $dir;
302 print "\n" unless $first;
312 return "$count ".($count > 1 ? $plural : $singular);
316 print "\n" unless $first;
317 print "mr $action: finished (".join("; ",
318 showstat($#successful+1, "successful", "successful"),
319 showstat($#failed+1, "failed", "failed"),
320 showstat($#skipped+1, "skipped", "skipped"),
325 elsif (! @successful && @skipped) {
338 if (ref $f eq 'GLOB') {
343 # $f might be a symlink
344 my $absf=abs_path($f);
345 if ($loaded{$absf}) {
350 print "mr: loading config $f\n" if $verbose;
351 open($in, "<", $f) || die "mr: open $f: $!\n";
352 ($dir)=$f=~/^(.*\/)[^\/]+$/;
353 if (! defined $dir) {
356 $dir=abs_path($dir)."/";
358 # copy in defaults from first parent
360 while ($parent=~s/^(.*)\/[^\/]+\/?$/$1/) {
361 if (exists $config{$parent} &&
362 exists $config{$parent}{default}) {
363 $config{$dir}{default}={ %{$config{$parent}{default}} };
372 next if /^\s*\#/ || /^\s*$/;
373 if (/^\s*\[([^\]]*)\]\s*$/) {
376 elsif (/^\s*(\w+)\s*=\s*(.*)/) {
381 while ($value=~/(.*)\\$/) {
386 if (! defined $section) {
387 die "$f line $.: parameter ($parameter) not in section\n";
389 if ($section ne 'alias' &&
390 ! exists $config{$dir}{$section} &&
391 exists $config{$dir}{default}) {
393 $config{$dir}{$section}={ %{$config{$dir}{default}} };
395 if ($section eq 'alias') {
396 $alias{$parameter}=$value;
398 elsif ($parameter eq 'lib') {
399 $config{$dir}{$section}{lib}.=$value." ; ";
402 $config{$dir}{$section}{$parameter}=$value;
403 $knownactions{$parameter}=1;
404 if ($parameter eq 'chain' &&
405 length $dir && $section ne "default" &&
406 -e $dir.$section."/.mrconfig" &&
407 system($value) >> 8 == 0) {
408 push @toload, $dir.$section."/.mrconfig";
413 die "$f line $.: parse error\n";
423 # Finally, some useful actions that mr knows about by default.
424 # These can be overridden in ~/.mrconfig.
436 if [ -d .svn ]; then \
438 elif [ -d .git ]; then \
439 git pull origin master "$@"; \
441 error "unknown repo type"; \
444 if [ -d .svn ]; then \
446 elif [ -d .git ]; then \
447 git status "$@" || true; \
449 error "unknown repo type"; \
452 if [ -d .svn ]; then \
454 elif [ -d .git ]; then \
455 git commit -a "$@" && git push --all; \
457 error "unknown repo type"; \
460 if [ -d .svn ]; then \
462 elif [ -d .git ]; then \
465 error "unknown repo type"; \
468 if [ ! -e "$MR_PATH" ]; then \
469 error "cannot find program path";\
471 (pod2man -c mr "$MR_PATH" | man -l -) || \
472 error "pod2man or man failed"