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 any repositories that are not already checked out.
41 Updates each repository from its configured remote repository.
43 If a repository isn't checked out yet, it will first check it out.
47 Displays a status report for each repository, showing what
48 uncommitted changes are present in the repository.
52 Commits changes to each repository. (By default, changes are pushed to the
53 remote repository too, when using distributed systems like git.)
55 The optional -m parameter allows specifying a commit message.
59 Show a diff of uncommitted changes.
63 List the repositories that mr will act on.
71 Actions can be abbreviated to any unambiguous subsctring, so
72 "mr st" is equivilant to "mr status".
74 B<mr> operates on all registered repsitories at or below your working
75 directory. Or, if you are in a subdirectory of a repository, it will act on
78 Additional parameters can be passed to other commands than "commit", they
79 will be passed on unchanged to the underlying revision control system.
80 This is mostly useful if the repositories mr will act on all use the same
81 revision control system.
89 Specifies the topmost directory that B<mr> should work in. The default is
90 the current working directory. B<mr> will operate on all registered
91 repositories at or under the 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 "$@". B<mr> cds into the repository directory before running
122 a command, except for the "checkout" command, which is run in the parent
123 of the repository directory, since the repository isn't checked out yet.
125 There are three special parameters. If the "skip" parameter is set and
126 its command returns nonzero, then B<mr> will skip acting on that repository.
127 If the "chain" parameter is set and its command returns nonzero, then B<mr>
128 will try to load a .mrconfig file from the root of the repository. (You
129 should avoid chaining from repositories with untrusted committers.) The
130 "lib" parameter can specify some shell code that will be run before each
131 command, this can be a useful way to define shell functions other commands
134 The "default" section allows setting up default handlers for each action,
135 and is overridden by the contents of other sections. mr contains default
136 handlers for the "update", "status", and "commit" actions, so normally
137 you only need to specify what to do for "checkout".
139 The "alias" section allows adding aliases for commands. Each parameter
140 is an alias, and its value is the command to run.
145 checkout = svn co svn://svn.example.com/src/trunk src
150 checkout = git clone git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux-2.6.git
155 case "$(hostname)" in; \
164 Copyright 2007 Joey Hess <joey@kitenet.net>
166 Licensed under the GNU GPL version 2 or higher.
168 http://kitenet.net/~joey/code/mr/
175 use Cwd qw(getcwd abs_path);
177 my $directory=getcwd();
178 my $config="$ENV{HOME}/.mrconfig";
184 Getopt::Long::Configure("no_permute");
185 my $result=GetOptions(
186 "d=s" => sub { $directory=abs_path($_[1]) },
190 if (! $result || @ARGV < 1) {
191 die("Usage: mr [-d directory] action [params ...]\n".
192 "(Use mr help for man page.)\n");
199 #print Dumper(\%config);
202 use FindBin qw($Bin $Script);
203 $ENV{MR_PATH}=$Bin."/".$Script;
206 # alias expansion and command stemming
207 my $action=shift @ARGV;
208 if (! exists $knownactions{$action}) {
209 if (exists $alias{$action}) {
210 $action=$alias{$action};
213 my @matches = grep { /^\Q$action\E/ }
214 keys %knownactions, keys %alias;
219 die "mr: ambiguous action \"$action\" (matches @matches)\n";
224 if ($action eq 'help') {
225 exec($config{''}{default}{help});
228 # handle being in a subdir of a repository
229 foreach my $topdir (sort keys %config) {
230 foreach my $subdir (sort keys %{$config{$topdir}}) {
231 if ($directory =~ /^\Q$topdir$subdir\E\//) {
232 $directory=$topdir.$subdir;
237 my (@failed, @successful, @skipped);
238 foreach my $topdir (sort keys %config) {
239 foreach my $subdir (sort keys %{$config{$topdir}}) {
240 next if $subdir eq 'default';
242 my $dir=$topdir.$subdir;
244 if (defined $directory &&
245 $dir ne $directory &&
246 $dir !~ /^\Q$directory\E\//) {
250 action($action, $dir, $topdir, $subdir);
255 my ($action, $dir, $topdir, $subdir) = @_;
257 my $lib= exists $config{$topdir}{$subdir}{lib} ?
258 $config{$topdir}{$subdir}{lib} : "";
260 if ($action eq 'checkout') {
262 print "mr $action: $dir already exists, skipping checkout\n" if $verbose;
266 $dir=~s/^(.*)\/[^\/]+\/?$/$1/;
268 elsif ($action eq 'update') {
270 return action("checkout", $dir, $topdir, $subdir);
275 print STDERR "mr $action: failed to chdir to $dir: $!\n";
279 if (exists $config{$topdir}{$subdir}{skip}) {
280 my $ret=system($lib.$config{$topdir}{$subdir}{skip});
281 if ($ret >> 8 == 0) {
282 print "mr $action: $dir skipped per config file\n" if $verbose;
288 if (! exists $config{$topdir}{$subdir}{$action}) {
289 print STDERR "mr $action: no defined $action command for $topdir$subdir, skipping\n";
293 print "mr $action: $dir\n";
294 my $command="set -e; ".$lib.
295 "my_action(){ $config{$topdir}{$subdir}{$action} ; }; my_action ".
296 join(" ", map { s/\//\/\//g; s/"/\"/g; '"'.$_.'"' } @ARGV);
297 my $ret=system($command);
299 print STDERR "mr $action: failed to run: $command\n" if $verbose;
300 push @failed, $topdir.$subdir;
301 if ($ret >> 8 != 0) {
302 print STDERR "mr $action: command failed\n";
305 print STDERR "mr $action: command died ($ret)\n";
309 push @successful, $dir;
321 return "$count ".($count > 1 ? $plural : $singular);
325 print "mr $action: finished (".join("; ",
326 showstat($#successful+1, "successful", "successful"),
327 showstat($#failed+1, "failed", "failed"),
328 showstat($#skipped+1, "skipped", "skipped"),
333 elsif (! @successful && @skipped) {
346 if (ref $f eq 'GLOB') {
351 # $f might be a symlink
352 my $absf=abs_path($f);
353 if ($loaded{$absf}) {
358 print "mr: loading config $f\n" if $verbose;
359 open($in, "<", $f) || die "mr: open $f: $!\n";
360 ($dir)=$f=~/^(.*\/)[^\/]+$/;
361 if (! defined $dir) {
364 $dir=abs_path($dir)."/";
366 # copy in defaults from first parent
368 while ($parent=~s/^(.*)\/[^\/]+\/?$/$1/) {
369 if (exists $config{$parent} &&
370 exists $config{$parent}{default}) {
371 $config{$dir}{default}={ %{$config{$parent}{default}} };
380 next if /^\s*\#/ || /^\s*$/;
381 if (/^\s*\[([^\]]*)\]\s*$/) {
384 elsif (/^\s*(\w+)\s*=\s*(.*)/) {
389 while ($value=~/(.*)\\$/) {
394 if (! defined $section) {
395 die "$f line $.: parameter ($parameter) not in section\n";
397 if ($section ne 'alias' &&
398 ! exists $config{$dir}{$section} &&
399 exists $config{$dir}{default}) {
401 $config{$dir}{$section}={ %{$config{$dir}{default}} };
403 if ($section eq 'alias') {
404 $alias{$parameter}=$value;
406 elsif ($parameter eq 'lib') {
407 $config{$dir}{$section}{lib}.=$value." ; ";
410 $config{$dir}{$section}{$parameter}=$value;
411 $knownactions{$parameter}=1;
412 if ($parameter eq 'chain' &&
413 length $dir && $section ne "default" &&
414 -e $dir.$section."/.mrconfig" &&
415 system($value) >> 8 == 0) {
416 push @toload, $dir.$section."/.mrconfig";
421 die "$f line $.: parse error\n";
431 # Finally, some useful actions that mr knows about by default.
432 # These can be overridden in ~/.mrconfig.
444 if [ -d .svn ]; then \
446 elif [ -d .git ]; then \
447 git pull origin master "$@"; \
449 error "unknown repo type"; \
452 if [ -d .svn ]; then \
454 elif [ -d .git ]; then \
455 git status "$@" || true; \
457 error "unknown repo type"; \
460 if [ -d .svn ]; then \
462 elif [ -d .git ]; then \
463 git commit -a "$@" && git push --all; \
465 error "unknown repo type"; \
468 if [ -d .svn ]; then \
470 elif [ -d .git ]; then \
473 error "unknown repo type"; \
477 if [ ! -e "$MR_PATH" ]; then \
478 error "cannot find program path";\
480 (pod2man -c mr "$MR_PATH" | man -l -) || \
481 error "pod2man or man failed"