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, it will act on
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 # handle being in a subdir of a repository
232 foreach my $topdir (sort keys %config) {
233 foreach my $subdir (reverse sort keys %{$config{$topdir}}) {
234 next if $subdir eq 'default';
235 my $d=$directory."/";
236 my $dir=$topdir.$subdir;
237 $dir.="/" unless $dir=~/\/$/;
238 if ($d =~ /^\Q$dir\E/) {
239 $directory=$topdir.$subdir;
246 my (@failed, @successful, @skipped);
247 foreach my $topdir (sort keys %config) {
248 foreach my $subdir (sort keys %{$config{$topdir}}) {
250 my $dir=$topdir.$subdir;
252 if (defined $directory &&
253 $dir ne $directory &&
254 $dir !~ /^\Q$directory\E\//) {
258 action($action, $dir, $topdir, $subdir);
263 my ($action, $dir, $topdir, $subdir) = @_;
265 my $lib= exists $config{$topdir}{$subdir}{lib} ?
266 $config{$topdir}{$subdir}{lib} : "";
268 if ($action eq 'checkout') {
270 print "mr $action: $dir already exists, skipping checkout\n" if $verbose;
274 $dir=~s/^(.*)\/[^\/]+\/?$/$1/;
276 elsif ($action eq 'update') {
278 return action("checkout", $dir, $topdir, $subdir);
283 if (! $nochdir && ! chdir($dir)) {
284 print STDERR "mr $action: failed to chdir to $dir: $!\n";
288 if (exists $config{$topdir}{$subdir}{skip}) {
289 my $ret=system($lib.$config{$topdir}{$subdir}{skip});
290 if ($ret >> 8 == 0) {
291 print "mr $action: $dir skipped per config file\n" if $verbose;
297 if (! exists $config{$topdir}{$subdir}{$action}) {
298 print STDERR "mr $action: no defined $action command for $topdir$subdir, skipping\n";
302 print "mr $action: $dir\n";
303 my $command="set -e; ".$lib.
304 "my_action(){ $config{$topdir}{$subdir}{$action} ; }; my_action ".
305 join(" ", map { s/\//\/\//g; s/"/\"/g; '"'.$_.'"' } @ARGV);
306 my $ret=system($command);
308 print STDERR "mr $action: failed to run: $command\n" if $verbose;
309 push @failed, $topdir.$subdir;
310 if ($ret >> 8 != 0) {
311 print STDERR "mr $action: command failed\n";
314 print STDERR "mr $action: command died ($ret)\n";
318 push @successful, $dir;
330 return "$count ".($count > 1 ? $plural : $singular);
334 if (! @successful && ! @failed && ! @skipped) {
335 die "mr $action: no repositories found to work on\n";
337 print "mr $action: finished (".join("; ",
338 showstat($#successful+1, "successful", "successful"),
339 showstat($#failed+1, "failed", "failed"),
340 showstat($#skipped+1, "skipped", "skipped"),
345 elsif (! @successful && @skipped) {
358 if (ref $f eq 'GLOB') {
363 # $f might be a symlink
364 my $absf=abs_path($f);
365 if ($loaded{$absf}) {
370 print "mr: loading config $f\n" if $verbose;
371 open($in, "<", $f) || die "mr: open $f: $!\n";
372 ($dir)=$f=~/^(.*\/)[^\/]+$/;
373 if (! defined $dir) {
376 $dir=abs_path($dir)."/";
378 # copy in defaults from first parent
380 while ($parent=~s/^(.*)\/[^\/]+\/?$/$1/) {
381 if (exists $config{$parent} &&
382 exists $config{$parent}{default}) {
383 $config{$dir}{default}={ %{$config{$parent}{default}} };
392 next if /^\s*\#/ || /^\s*$/;
393 if (/^\s*\[([^\]]*)\]\s*$/) {
396 elsif (/^\s*(\w+)\s*=\s*(.*)/) {
401 while ($value=~/(.*)\\$/) {
406 if (! defined $section) {
407 die "$f line $.: parameter ($parameter) not in section\n";
409 if ($section ne 'alias' &&
410 ! exists $config{$dir}{$section} &&
411 exists $config{$dir}{default}) {
413 $config{$dir}{$section}={ %{$config{$dir}{default}} };
415 if ($section eq 'alias') {
416 $alias{$parameter}=$value;
418 elsif ($parameter eq 'lib') {
419 $config{$dir}{$section}{lib}.=$value." ; ";
422 $config{$dir}{$section}{$parameter}=$value;
423 $knownactions{$parameter}=1;
424 if ($parameter eq 'chain' &&
425 length $dir && $section ne "default" &&
426 -e $dir.$section."/.mrconfig" &&
427 system($value) >> 8 == 0) {
428 push @toload, $dir.$section."/.mrconfig";
433 die "$f line $.: parse error\n";
443 # Finally, some useful actions that mr knows about by default.
444 # These can be overridden in ~/.mrconfig.
456 if [ -d "$MR_REPO"/.svn ]; then \
458 elif [ -d "$MR_REPO"/.git ]; then \
459 git pull origin master "$@"; \
461 error "unknown repo type"; \
464 if [ -d "$MR_REPO"/.svn ]; then \
466 elif [ -d "$MR_REPO"/.git ]; then \
467 git status "$@" || true; \
469 error "unknown repo type"; \
472 if [ -d "$MR_REPO"/.svn ]; then \
474 elif [ -d "$MR_REPO"/.git ]; then \
475 git commit -a "$@" && git push --all; \
477 error "unknown repo type"; \
480 if [ -d "$MR_REPO"/.svn ]; then \
482 elif [ -d "$MR_REPO"/.git ]; then \
485 error "unknown repo type"; \
489 if [ ! -e "$MR_PATH" ]; then \
490 error "cannot find program path";\
492 (pod2man -c mr "$MR_PATH" | man -l -) || \
493 error "pod2man or man failed"