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 ".
229 join(" ", map { s/\//\/\//g; s/"/\"/g; '"'.$_.'"' } @ARGV);
230 my $ret=system($command);
232 print STDERR "mr $action: failed to run: $command\n" if $verbose;
233 push @failed, $topdir.$subdir;
234 if ($ret >> 8 != 0) {
235 print STDERR "mr $action: command failed\n";
238 print STDERR "mr $action: command died ($ret)\n";
242 push @successful, $dir;
252 return "$count ".($count > 1 ? $plural : $singular);
256 print "\nmr $action: finished (".join("; ",
257 showstat($#successful+1, "successful", "successful"),
258 showstat($#failed+1, "failed", "failed"),
259 showstat($#skipped+1, "skipped", "skipped"),
261 exit @failed ? 1 : 0;
271 if (ref $f eq 'GLOB') {
276 # $f might be a symlink
277 my $absf=abs_path($f);
278 if ($loaded{$absf}) {
283 print "mr: loading config $f\n" if $verbose;
284 open($in, "<", $f) || die "mr: open $f: $!\n";
285 ($dir)=$f=~/^(.*\/)[^\/]+$/;
286 if (! defined $dir) {
289 $dir=abs_path($dir)."/";
291 # copy in defaults from first parent
293 while ($parent=~s/^(.*)\/[^\/]+\/?$/$1/) {
294 if (exists $config{$parent} &&
295 exists $config{$parent}{default}) {
296 $config{$dir}{default}={ %{$config{$parent}{default}} };
305 next if /^\s*\#/ || /^\s*$/;
306 if (/^\s*\[([^\]]*)\]\s*$/) {
308 if (length $dir && $section ne "default" &&
309 -e $dir.$section."/.mrconfig") {
310 push @toload, $dir.$section."/.mrconfig";
313 elsif (/^\s*(\w+)\s*=\s*(.*)/) {
318 while ($value=~/(.*)\\$/) {
323 if (! defined $section) {
324 die "$f line $.: parameter ($parameter) not in section\n";
326 if (! exists $config{$dir}{$section} &&
327 exists $config{$dir}{default}) {
329 $config{$dir}{$section}={ %{$config{$dir}{default}} };
331 $config{$dir}{$section}{$parameter}=$value;
332 $knownactions{$parameter}=1;
335 die "$f line $.: parse error\n";
346 # Some useful actions that mr knows about by default.
347 # These can be overridden in ~/.mrconfig.
350 if [ -d .svn ]; then \
352 elif [ -d .git ]; then \
353 git pull origin master; \
355 echo "mr update: unknown repo type"; \
359 if [ -d .svn ]; then \
361 elif [ -d .git ]; then \
362 git status || true; \
364 echo "mr status: unknown repo type"; \
368 if [ -d .svn ]; then \
370 elif [ -d .git ]; then \
371 git commit -a "$@" && git push --all \
373 echo "mr commit: unknown repo type"; \