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"]
21 B<mr> [options] action [params ...]
25 B<mr> is a Multiple Repository management tool. It allows you to register a
26 set of repositories in a .mrconfig file, and then checkout, update, or
27 perform other actions on the repositories as if they were one big
30 Any mix of revision control systems can be used with B<mr>, and you can
31 define arbitrary actions for commands like "update", "checkout", or "commit".
33 B<mr> cds into and operates on all registered repsitories at or below your
34 working directory. Or, if you are in a subdirectory of a repository that
35 contains no other registered repositories, it will stay in that directory,
36 and work on only that repository,
38 The predefined commands should be fairly familiar to users of any revision
43 =item checkout (or co)
45 Checks out any repositories that are not already checked out.
49 Updates each repository from its configured remote repository.
51 If a repository isn't checked out yet, it will first check it out.
55 Displays a status report for each repository, showing what
56 uncommitted changes are present in the repository.
60 Commits changes to each repository. (By default, changes are pushed to the
61 remote repository too, when using distributed systems like git.)
63 The optional -m parameter allows specifying a commit message.
67 Show a diff of uncommitted changes.
75 List the repositories that mr will act on.
83 Actions can be abbreviated to any unambiguous subsctring, so
84 "mr st" is equivilant to "mr status", and "mr up" is equivilant to "mr
87 Additional parameters can be passed to other commands than "commit", they
88 will be passed on unchanged to the underlying revision control system.
89 This is mostly useful if the repositories mr will act on all use the same
90 revision control system.
98 Specifies the topmost directory that B<mr> should work in. The default is
99 the current working directory.
103 Use the specified mrconfig file, instead of looking for one in your home
114 B<mr> is configured by .mrconfig files. It starts by reading the .mrconfig
115 file in your home directory. Each repository specified in a .mrconfig file
116 can also have its own .mrconfig file in its root directory that can
117 optionally be used as well. So you could have a ~/.mrconfig that registers a
118 repository ~/src, that itself contains a ~/src/.mrconfig file, that in turn
119 registers several additional repositories.
121 The .mrconfig file uses a variant of the INI file format. Lines starting with
122 "#" are comments. Lines ending with "\" are continued on to the next line.
123 Sections specify where each repository is located, relative to the
124 directory that contains the .mrconfig file.
126 Within a section, each parameter defines a shell command to run to handle a
127 given action. Note that these shell commands are run in a "set -e" shell
128 environment, where any additional parameters you pass are available in
129 "$@". The "checkout" command is run in the parent of the repository
130 directory, since the repository isn't checked out yet. All other commands
131 are run inside the repository, though not necessarily at the top of it.
132 The "MR_REPO" environment variable is set to the path to the top of the
135 There are three special parameters. If the "skip" parameter is set and
136 its command returns nonzero, then B<mr> will skip acting on that repository.
137 If the "chain" parameter is set and its command returns nonzero, then B<mr>
138 will try to load a .mrconfig file from the root of the repository. (You
139 should avoid chaining from repositories with untrusted committers.) The
140 "lib" parameter can specify some shell code that will be run before each
141 command, this can be a useful way to define shell functions for other commands
144 The "default" section allows setting up default handlers for each action,
145 and is overridden by the contents of other sections. mr contains default
146 handlers for the "update", "status", and "commit" actions, so normally
147 you only need to specify what to do for "checkout".
149 The "alias" section allows adding aliases for actions. Each parameter
150 is an alias, and its value is the action to use.
155 checkout = svn co svn://svn.example.com/src/trunk src
160 checkout = git clone git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux-2.6.git
165 case "$(hostname)" in; \
174 Copyright 2007 Joey Hess <joey@kitenet.net>
176 Licensed under the GNU GPL version 2 or higher.
178 http://kitenet.net/~joey/code/mr/
185 use Cwd qw(getcwd abs_path);
187 my $directory=getcwd();
188 my $config="$ENV{HOME}/.mrconfig";
194 Getopt::Long::Configure("no_permute");
195 my $result=GetOptions(
196 "d=s" => sub { $directory=abs_path($_[1]) },
200 if (! $result || @ARGV < 1) {
201 die("Usage: mr [-d directory] action [params ...]\n".
202 "(Use mr help for man page.)\n");
209 #print Dumper(\%config);
212 use FindBin qw($Bin $Script);
213 $ENV{MR_PATH}=$Bin."/".$Script;
216 # alias expansion and command stemming
217 my $action=shift @ARGV;
218 if (! exists $knownactions{$action}) {
219 if (exists $alias{$action}) {
220 $action=$alias{$action};
223 my @matches = grep { /^\Q$action\E/ }
224 keys %knownactions, keys %alias;
228 elsif (@matches == 0) {
229 die "mr: unknown action \"$action\" (known actions: ".
230 join(", ", sort keys %knownactions).")\n";
233 die "mr: ambiguous action \"$action\" (matches: ".
234 join(", ", @matches).")\n";
239 if ($action eq 'help') {
240 exec($config{''}{default}{help});
243 # work out what repos to act on
246 foreach my $topdir (sort keys %config) {
247 foreach my $subdir (sort keys %{$config{$topdir}}) {
248 next if $subdir eq 'default';
249 my $dir=$topdir.$subdir;
250 next if $dir ne $directory && $dir !~ /^\Q$directory\E\//;
251 push @repos, [$dir, $topdir, $subdir];
255 # fallback to find a leaf repo
256 LEAF: foreach my $topdir (reverse sort keys %config) {
257 foreach my $subdir (reverse sort keys %{$config{$topdir}}) {
258 next if $subdir eq 'default';
259 my $dir=$topdir.$subdir;
261 $dir.="/" unless $dir=~/\/$/;
262 $d.="/" unless $d=~/\/$/;
263 if ($d=~/^\Q$dir\E/) {
264 push @repos, [$dir, $topdir, $subdir];
272 my (@failed, @successful, @skipped);
273 foreach my $repo (@repos) {
274 action($action, @$repo);
278 my ($action, $dir, $topdir, $subdir) = @_;
280 my $lib= exists $config{$topdir}{$subdir}{lib} ?
281 $config{$topdir}{$subdir}{lib} : "";
283 if ($action eq 'checkout') {
285 print "mr $action: $dir already exists, skipping checkout\n" if $verbose;
289 $dir=~s/^(.*)\/[^\/]+\/?$/$1/;
291 elsif ($action eq 'update') {
293 return action("checkout", $dir, $topdir, $subdir);
298 if (! $nochdir && ! chdir($dir)) {
299 print STDERR "mr $action: failed to chdir to $dir: $!\n";
303 if (exists $config{$topdir}{$subdir}{skip}) {
304 my $ret=system($lib.$config{$topdir}{$subdir}{skip});
305 if ($ret >> 8 == 0) {
306 print "mr $action: $dir skipped per config file\n" if $verbose;
312 if (! exists $config{$topdir}{$subdir}{$action}) {
313 print STDERR "mr $action: no defined $action command for $topdir$subdir, skipping\n";
318 print "mr $action: $dir\n";
321 print "mr $action: $dir (in subdir $directory)\n";
323 my $command="set -e; ".$lib.
324 "my_action(){ $config{$topdir}{$subdir}{$action} ; }; my_action ".
325 join(" ", map { s/\//\/\//g; s/"/\"/g; '"'.$_.'"' } @ARGV);
326 print STDERR "mr $action: running $command\n" if $verbose;
327 my $ret=system($command);
329 print STDERR "mr $action: failed ($ret)\n" if $verbose;
330 push @failed, $topdir.$subdir;
331 if ($ret >> 8 != 0) {
332 print STDERR "mr $action: command failed\n";
335 print STDERR "mr $action: command died ($ret)\n";
339 push @successful, $dir;
351 return "$count ".($count > 1 ? $plural : $singular);
355 if (! @successful && ! @failed && ! @skipped) {
356 die "mr $action: no repositories found to work on\n";
358 print "mr $action: finished (".join("; ",
359 showstat($#successful+1, "successful", "successful"),
360 showstat($#failed+1, "failed", "failed"),
361 showstat($#skipped+1, "skipped", "skipped"),
366 elsif (! @successful && @skipped) {
379 if (ref $f eq 'GLOB') {
384 # $f might be a symlink
385 my $absf=abs_path($f);
386 if ($loaded{$absf}) {
391 print "mr: loading config $f\n" if $verbose;
392 open($in, "<", $f) || die "mr: open $f: $!\n";
393 ($dir)=$f=~/^(.*\/)[^\/]+$/;
394 if (! defined $dir) {
397 $dir=abs_path($dir)."/";
399 # copy in defaults from first parent
401 while ($parent=~s/^(.*)\/[^\/]+\/?$/$1/) {
402 if (exists $config{$parent} &&
403 exists $config{$parent}{default}) {
404 $config{$dir}{default}={ %{$config{$parent}{default}} };
413 next if /^\s*\#/ || /^\s*$/;
414 if (/^\s*\[([^\]]*)\]\s*$/) {
417 elsif (/^\s*(\w+)\s*=\s*(.*)/) {
422 while ($value=~/(.*)\\$/) {
427 if (! defined $section) {
428 die "$f line $.: parameter ($parameter) not in section\n";
430 if ($section ne 'alias' &&
431 ! exists $config{$dir}{$section} &&
432 exists $config{$dir}{default}) {
434 $config{$dir}{$section}={ %{$config{$dir}{default}} };
436 if ($section eq 'alias') {
437 $alias{$parameter}=$value;
439 elsif ($parameter eq 'lib') {
440 $config{$dir}{$section}{lib}.=$value." ; ";
443 $config{$dir}{$section}{$parameter}=$value;
444 $knownactions{$parameter}=1;
445 if ($parameter eq 'chain' &&
446 length $dir && $section ne "default" &&
447 -e $dir.$section."/.mrconfig" &&
448 system($value) >> 8 == 0) {
449 push @toload, $dir.$section."/.mrconfig";
454 die "$f line $.: parse error\n";
464 # Finally, some useful actions that mr knows about by default.
465 # These can be overridden in ~/.mrconfig.
478 if [ -d "$MR_REPO"/.svn ]; then \
480 elif [ -d "$MR_REPO"/.git ]; then \
481 git pull origin master "$@"; \
482 elif [ -d "$MR_REPO"/CVS ]; then \
485 error "unknown repo type"; \
488 if [ -d "$MR_REPO"/.svn ]; then \
490 elif [ -d "$MR_REPO"/.git ]; then \
491 git status "$@" || true; \
492 elif [ -d "$MR_REPO"/CVS ]; then \
495 error "unknown repo type"; \
498 if [ -d "$MR_REPO"/.svn ]; then \
500 elif [ -d "$MR_REPO"/.git ]; then \
501 git commit -a "$@" && git push --all; \
502 elif [ -d "$MR_REPO"/CVS ]; then \
505 error "unknown repo type"; \
508 if [ -d "$MR_REPO"/.svn ]; then \
510 elif [ -d "$MR_REPO"/.git ]; then \
512 elif [ -d "$MR_REPO"/CVS ]; then \
515 error "unknown repo type"; \
518 if [ -d "$MR_REPO"/.svn ]; then \
520 elif [ -d "$MR_REPO"/.git ]; then \
522 elif [ -d "$MR_REPO"/CVS ]; then \
525 error "unknown repo type"; \
529 if [ ! -e "$MR_PATH" ]; then \
530 error "cannot find program path";\
532 (pod2man -c mr "$MR_PATH" | man -l -) || \
533 error "pod2man or man failed"