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"]
17 B<mr> [options] action [params ...]
21 B<mr> is a Multiple Repository management tool. It allows you to register a
22 set of repositories in a .mrconfig file, and then checkout, update, or
23 perform other actions on all of the repositories at once.
25 Any mix of revision control systems can be used with B<mr>, and you can
26 define arbitrary actions for commands like "update", "checkout", or "commit".
28 The predefined commands should be fairly familiar to users of any revision
35 Checks out all the registered repositories that are not already checked
40 Updates each registered repository from its configured remote repository.
42 If a repository isn't checked out yet, it will first check it out.
46 Displays a status report for each registered repository, showing what
47 uncommitted changes are present in the repository.
51 Commits changes to each registered repository. (By default, changes
52 are pushed to the remote repository too, when using distributed systems
55 The optional -m parameter allows specifying a commit message.
59 Actions can be abbreviated to any unambiguous subsctring, so
60 "mr st" is equivilant to "mr status".
68 Specifies the topmost directory that B<mr> should work in. The default is
69 the current working directory. B<mr> will operate on all registered
70 repositories at or under the directory.
74 Use the specified mrconfig file, instead of looking for on in your home
85 B<mr> is configured by .mrconfig files. It searches for .mrconfig files in
86 your home directory, and in the root directory of each repository specified
87 in a .mrconfig file. So you could have a ~/.mrconfig that registers a
88 repository ~/src, that itself contains a ~/src/.mrconfig file, that in turn
89 registers several additional repositories.
91 The .mrconfig file uses a variant of the INI file format. Lines starting with
92 "#" are comments. Lines ending with "\" are continued on to the next line.
93 Sections specify where each repository is located, relative to the
94 directory that contains the .mrconfig file.
96 Within a section, each parameter defines a shell command to run to handle a
97 given action. Note that these shell commands are run in a "set -e" shell
98 environment, where any additional parameters you pass are available in
99 "$@". B<mr> cds into the repository directory before running
100 a command, except for the "checkout" command, which is run in the parent
101 of the repository directory, since the repository isn't checked out yet.
103 There are two special parameters. If the "skip" parameter is set and
104 its command returns nonzero, then B<mr> will skip acting on that repository.
106 The "default" section allows setting up default handlers for each action,
107 and is overridden by the contents of other sections. mr contains default
108 handlers for the "update", "status", and "commit" actions, so normally
109 you only need to specify what to do for "checkout".
114 checkout = svn co svn://svn.example.com/src/trunk src
117 # only check this out on kodama
118 skip = test $(hostname) != kodama
119 checkout = git clone git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux-2.6.git
123 Copyright 2007 Joey Hess <joey@kitenet.net>
125 Licensed under the GNU GPL version 2 or higher.
127 http://kitenet.net/~joey/code/mr/
134 use Cwd qw(getcwd abs_path);
136 my $directory=getcwd();
137 my $config="$ENV{HOME}/.mrconfig";
142 Getopt::Long::Configure("no_permute");
143 my $result=GetOptions(
144 "d=s" => sub { $directory=abs_path($_[1]) },
148 if (! $result || @ARGV < 1) {
149 die("Usage: mr [-d directory] action [params ...]\n");
155 #print Dumper(\%config);
157 my $action=shift @ARGV;
158 if (! $knownactions{$action}) {
159 my @matches = grep { /^\Q$action\E/ } keys %knownactions;
164 die "mr: ambiguous action \"$action\" (matches @matches)\n";
168 my (@failed, @successful, @skipped);
170 foreach my $topdir (sort keys %config) {
171 foreach my $subdir (sort keys %{$config{$topdir}}) {
172 next if $subdir eq 'default';
174 my $dir=$topdir.$subdir;
176 if (defined $directory &&
177 $dir ne $directory &&
178 $dir !~ /^\Q$directory\E\//) {
179 print "mr $action: $dir skipped per -d parameter ($directory)\n" if $verbose;
184 print "\n" unless $first;
187 if (exists $config{$topdir}{$subdir}{skip}) {
188 my $ret=system($config{$topdir}{$subdir}{skip});
189 if ($ret >> 8 == 0) {
190 print "mr $action: $dir skipped per config file\n" if $verbose;
196 action($action, $dir, $topdir, $subdir);
202 my ($action, $dir, $topdir, $subdir) = @_;
204 if ($action eq 'checkout') {
206 print "mr $action: $dir already exists, skipping checkout\n";
210 $dir=~s/^(.*)\/[^\/]+\/?$/$1/;
212 if ($action eq 'update') {
214 return action("checkout", $dir, $topdir, $subdir);
219 print STDERR "mr $action: failed to chdir to $dir: $!\n";
222 elsif (! exists $config{$topdir}{$subdir}{$action}) {
223 print STDERR "mr $action: no defined $action command for $topdir$subdir, skipping\n";
227 print "mr $action: in $dir\n";
228 my $command="set -e; my_action(){ $config{$topdir}{$subdir}{$action} ; }; my_action @ARGV";
229 my $ret=system($command);
231 print STDERR "mr $action: failed to run: $command\n" if $verbose;
232 push @failed, $topdir.$subdir;
233 if ($ret >> 8 != 0) {
234 print STDERR "mr $action: command failed\n";
237 print STDERR "mr $action: command died ($ret)\n";
241 push @successful, $dir;
251 return "$count ".($count > 1 ? $plural : $singular);
255 print "\nmr $action: finished (".join("; ",
256 showstat($#successful+1, "successful", "successful"),
257 showstat($#failed+1, "failed", "failed"),
258 showstat($#skipped+1, "skipped", "skipped"),
260 exit @failed ? 1 : 0;
270 if (ref $f eq 'GLOB') {
275 # $f might be a symlink
276 my $absf=abs_path($f);
277 if ($loaded{$absf}) {
282 print "mr: loading config $f\n" if $verbose;
283 open($in, "<", $f) || die "mr: open $f: $!\n";
284 ($dir)=$f=~/^(.*\/)[^\/]+$/;
285 if (! defined $dir) {
288 $dir=abs_path($dir)."/";
290 # copy in defaults from first parent
292 while ($parent=~s/^(.*)\/[^\/]+\/?$/$1/) {
293 if (exists $config{$parent} &&
294 exists $config{$parent}{default}) {
295 $config{$dir}{default}={ %{$config{$parent}{default}} };
304 next if /^\s*\#/ || /^\s*$/;
305 if (/^\s*\[([^\]]*)\]\s*$/) {
307 if (length $dir && $section ne "default" &&
308 -e $dir.$section."/.mrconfig") {
309 push @toload, $dir.$section."/.mrconfig";
312 elsif (/^\s*(\w+)\s*=\s*(.*)/) {
317 while ($value=~/(.*)\\$/) {
322 if (! defined $section) {
323 die "$f line $.: parameter ($parameter) not in section\n";
325 if (! exists $config{$dir}{$section} &&
326 exists $config{$dir}{default}) {
328 $config{$dir}{$section}={ %{$config{$dir}{default}} };
330 $config{$dir}{$section}{$parameter}=$value;
331 $knownactions{$parameter}=1;
334 die "$f line $.: parse error\n";
345 # Some useful actions that mr knows about by default.
346 # These can be overridden in ~/.mrconfig.
349 if [ -d .svn ]; then \
351 elif [ -d .git ]; then \
352 git pull origin master; \
354 echo "mr update: unknown repo type"; \
358 if [ -d .svn ]; then \
360 elif [ -d .git ]; then \
361 git status || true; \
363 echo "mr status: unknown repo type"; \
367 if [ -d .svn ]; then \
369 elif [ -d .git ]; then \
370 git commit -a "$@" && git push --all; \
372 echo "mr commit: unknown repo type"; \