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 starts by reading the .mrconfig
86 file in your home directory. Each repository specified in a .mrconfig file
87 can also have its own .mrconfig file in its root directory that can
88 optionally be used as well. So you could have a ~/.mrconfig that registers a
89 repository ~/src, that itself contains a ~/src/.mrconfig file, that in turn
90 registers several additional repositories.
92 The .mrconfig file uses a variant of the INI file format. Lines starting with
93 "#" are comments. Lines ending with "\" are continued on to the next line.
94 Sections specify where each repository is located, relative to the
95 directory that contains the .mrconfig file.
97 Within a section, each parameter defines a shell command to run to handle a
98 given action. Note that these shell commands are run in a "set -e" shell
99 environment, where any additional parameters you pass are available in
100 "$@". B<mr> cds into the repository directory before running
101 a command, except for the "checkout" command, which is run in the parent
102 of the repository directory, since the repository isn't checked out yet.
104 There are two special parameters. If the "skip" parameter is set and
105 its command returns nonzero, then B<mr> will skip acting on that repository.
106 If the "chain" parameter is set and its command returns nonzero, then B<mr>
107 will try to load a .mrconfig file from the root of the repository. (You
108 should avoid chaining from repositories with untrusted committers.)
110 The "default" section allows setting up default handlers for each action,
111 and is overridden by the contents of other sections. mr contains default
112 handlers for the "update", "status", and "commit" actions, so normally
113 you only need to specify what to do for "checkout".
118 checkout = svn co svn://svn.example.com/src/trunk src
122 # only check this out on kodama
123 skip = test $(hostname) != kodama
124 checkout = git clone git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux-2.6.git
128 Copyright 2007 Joey Hess <joey@kitenet.net>
130 Licensed under the GNU GPL version 2 or higher.
132 http://kitenet.net/~joey/code/mr/
139 use Cwd qw(getcwd abs_path);
141 my $directory=getcwd();
142 my $config="$ENV{HOME}/.mrconfig";
147 Getopt::Long::Configure("no_permute");
148 my $result=GetOptions(
149 "d=s" => sub { $directory=abs_path($_[1]) },
153 if (! $result || @ARGV < 1) {
154 die("Usage: mr [-d directory] action [params ...]\n");
160 #print Dumper(\%config);
162 my $action=shift @ARGV;
163 if (! $knownactions{$action}) {
164 my @matches = grep { /^\Q$action\E/ } keys %knownactions;
169 die "mr: ambiguous action \"$action\" (matches @matches)\n";
173 my (@failed, @successful, @skipped);
175 foreach my $topdir (sort keys %config) {
176 foreach my $subdir (sort keys %{$config{$topdir}}) {
177 next if $subdir eq 'default';
179 my $dir=$topdir.$subdir;
181 if (defined $directory &&
182 $dir ne $directory &&
183 $dir !~ /^\Q$directory\E\//) {
184 print "mr $action: $dir skipped per -d parameter ($directory)\n" if $verbose;
189 print "\n" unless $first;
192 if (exists $config{$topdir}{$subdir}{skip}) {
193 my $ret=system($config{$topdir}{$subdir}{skip});
194 if ($ret >> 8 == 0) {
195 print "mr $action: $dir skipped per config file\n" if $verbose;
201 action($action, $dir, $topdir, $subdir);
207 my ($action, $dir, $topdir, $subdir) = @_;
209 if ($action eq 'checkout') {
211 print "mr $action: $dir already exists, skipping checkout\n";
215 $dir=~s/^(.*)\/[^\/]+\/?$/$1/;
217 if ($action eq 'update') {
219 return action("checkout", $dir, $topdir, $subdir);
224 print STDERR "mr $action: failed to chdir to $dir: $!\n";
227 elsif (! exists $config{$topdir}{$subdir}{$action}) {
228 print STDERR "mr $action: no defined $action command for $topdir$subdir, skipping\n";
232 print "mr $action: in $dir\n";
233 my $command="set -e; my_action(){ $config{$topdir}{$subdir}{$action} ; }; my_action ".
234 join(" ", map { s/\//\/\//g; s/"/\"/g; '"'.$_.'"' } @ARGV);
235 my $ret=system($command);
237 print STDERR "mr $action: failed to run: $command\n" if $verbose;
238 push @failed, $topdir.$subdir;
239 if ($ret >> 8 != 0) {
240 print STDERR "mr $action: command failed\n";
243 print STDERR "mr $action: command died ($ret)\n";
247 push @successful, $dir;
257 return "$count ".($count > 1 ? $plural : $singular);
261 print "\nmr $action: finished (".join("; ",
262 showstat($#successful+1, "successful", "successful"),
263 showstat($#failed+1, "failed", "failed"),
264 showstat($#skipped+1, "skipped", "skipped"),
266 exit @failed ? 1 : 0;
276 if (ref $f eq 'GLOB') {
281 # $f might be a symlink
282 my $absf=abs_path($f);
283 if ($loaded{$absf}) {
288 print "mr: loading config $f\n" if $verbose;
289 open($in, "<", $f) || die "mr: open $f: $!\n";
290 ($dir)=$f=~/^(.*\/)[^\/]+$/;
291 if (! defined $dir) {
294 $dir=abs_path($dir)."/";
296 # copy in defaults from first parent
298 while ($parent=~s/^(.*)\/[^\/]+\/?$/$1/) {
299 if (exists $config{$parent} &&
300 exists $config{$parent}{default}) {
301 $config{$dir}{default}={ %{$config{$parent}{default}} };
310 next if /^\s*\#/ || /^\s*$/;
311 if (/^\s*\[([^\]]*)\]\s*$/) {
314 elsif (/^\s*(\w+)\s*=\s*(.*)/) {
319 while ($value=~/(.*)\\$/) {
324 if (! defined $section) {
325 die "$f line $.: parameter ($parameter) not in section\n";
327 if (! exists $config{$dir}{$section} &&
328 exists $config{$dir}{default}) {
330 $config{$dir}{$section}={ %{$config{$dir}{default}} };
332 $config{$dir}{$section}{$parameter}=$value;
333 $knownactions{$parameter}=1;
335 if ($parameter eq 'chain' &&
336 length $dir && $section ne "default" &&
337 -e $dir.$section."/.mrconfig" &&
338 system($value) >> 8 == 0) {
339 push @toload, $dir.$section."/.mrconfig";
343 die "$f line $.: parse error\n";
354 # Some useful actions that mr knows about by default.
355 # These can be overridden in ~/.mrconfig.
358 if [ -d .svn ]; then \
360 elif [ -d .git ]; then \
361 git pull origin master; \
363 echo "mr update: unknown repo type"; \
367 if [ -d .svn ]; then \
369 elif [ -d .git ]; then \
370 git status || true; \
372 echo "mr status: unknown repo type"; \
376 if [ -d .svn ]; then \
378 elif [ -d .git ]; then \
379 echo git commit -a "$@" && echo git push --all \
380 git commit -a "$@" && git push --all \
382 echo "mr commit: unknown repo type"; \