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);
227 foreach my $topdir (sort keys %config) {
228 foreach my $subdir (sort keys %{$config{$topdir}}) {
229 next if $subdir eq 'default';
231 my $dir=$topdir.$subdir;
233 if (defined $directory &&
234 $dir ne $directory &&
235 $dir !~ /^\Q$directory\E\//) {
239 action($action, $dir, $topdir, $subdir);
244 my ($action, $dir, $topdir, $subdir) = @_;
246 my $lib= exists $config{$topdir}{$subdir}{lib} ?
247 $config{$topdir}{$subdir}{lib} : "";
249 if ($action eq 'checkout') {
251 print "mr $action: $dir already exists, skipping checkout\n" if $verbose;
255 $dir=~s/^(.*)\/[^\/]+\/?$/$1/;
257 elsif ($action eq 'update') {
259 return action("checkout", $dir, $topdir, $subdir);
264 print STDERR "mr $action: failed to chdir to $dir: $!\n";
268 if (exists $config{$topdir}{$subdir}{skip}) {
269 my $ret=system($lib.$config{$topdir}{$subdir}{skip});
270 if ($ret >> 8 == 0) {
271 print "mr $action: $dir skipped per config file\n" if $verbose;
277 if (! exists $config{$topdir}{$subdir}{$action}) {
278 print STDERR "mr $action: no defined $action command for $topdir$subdir, skipping\n";
282 print "mr $action: in $dir\n";
283 my $command="set -e; ".$lib.
284 "my_action(){ $config{$topdir}{$subdir}{$action} ; }; my_action ".
285 join(" ", map { s/\//\/\//g; s/"/\"/g; '"'.$_.'"' } @ARGV);
286 my $ret=system($command);
288 print STDERR "mr $action: failed to run: $command\n" if $verbose;
289 push @failed, $topdir.$subdir;
290 if ($ret >> 8 != 0) {
291 print STDERR "mr $action: command failed\n";
294 print STDERR "mr $action: command died ($ret)\n";
298 push @successful, $dir;
310 return "$count ".($count > 1 ? $plural : $singular);
314 print "mr $action: finished (".join("; ",
315 showstat($#successful+1, "successful", "successful"),
316 showstat($#failed+1, "failed", "failed"),
317 showstat($#skipped+1, "skipped", "skipped"),
322 elsif (! @successful && @skipped) {
335 if (ref $f eq 'GLOB') {
340 # $f might be a symlink
341 my $absf=abs_path($f);
342 if ($loaded{$absf}) {
347 print "mr: loading config $f\n" if $verbose;
348 open($in, "<", $f) || die "mr: open $f: $!\n";
349 ($dir)=$f=~/^(.*\/)[^\/]+$/;
350 if (! defined $dir) {
353 $dir=abs_path($dir)."/";
355 # copy in defaults from first parent
357 while ($parent=~s/^(.*)\/[^\/]+\/?$/$1/) {
358 if (exists $config{$parent} &&
359 exists $config{$parent}{default}) {
360 $config{$dir}{default}={ %{$config{$parent}{default}} };
369 next if /^\s*\#/ || /^\s*$/;
370 if (/^\s*\[([^\]]*)\]\s*$/) {
373 elsif (/^\s*(\w+)\s*=\s*(.*)/) {
378 while ($value=~/(.*)\\$/) {
383 if (! defined $section) {
384 die "$f line $.: parameter ($parameter) not in section\n";
386 if ($section ne 'alias' &&
387 ! exists $config{$dir}{$section} &&
388 exists $config{$dir}{default}) {
390 $config{$dir}{$section}={ %{$config{$dir}{default}} };
392 if ($section eq 'alias') {
393 $alias{$parameter}=$value;
395 elsif ($parameter eq 'lib') {
396 $config{$dir}{$section}{lib}.=$value." ; ";
399 $config{$dir}{$section}{$parameter}=$value;
400 $knownactions{$parameter}=1;
401 if ($parameter eq 'chain' &&
402 length $dir && $section ne "default" &&
403 -e $dir.$section."/.mrconfig" &&
404 system($value) >> 8 == 0) {
405 push @toload, $dir.$section."/.mrconfig";
410 die "$f line $.: parse error\n";
420 # Finally, some useful actions that mr knows about by default.
421 # These can be overridden in ~/.mrconfig.
433 if [ -d .svn ]; then \
435 elif [ -d .git ]; then \
436 git pull origin master "$@"; \
438 error "unknown repo type"; \
441 if [ -d .svn ]; then \
443 elif [ -d .git ]; then \
444 git status "$@" || true; \
446 error "unknown repo type"; \
449 if [ -d .svn ]; then \
451 elif [ -d .git ]; then \
452 git commit -a "$@" && git push --all; \
454 error "unknown repo type"; \
457 if [ -d .svn ]; then \
459 elif [ -d .git ]; then \
462 error "unknown repo type"; \
465 if [ ! -e "$MR_PATH" ]; then \
466 error "cannot find program path";\
468 (pod2man -c mr "$MR_PATH" | man -l -) || \
469 error "pod2man or man failed"