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.
63 Specifies the topmost directory that B<mr> should work in. The default is
64 the current working directory. B<mr> will operate on all registered
65 repositories at or under the directory.
69 Use the specified mrconfig file, instead of looking for on in your home
80 B<mr> is configured by .mrconfig files. It searches for .mrconfig files in
81 your home directory, and in the root directory of each repository specified
82 in a .mrconfig file. So you could have a ~/.mrconfig that registers a
83 repository ~/src, that itself contains a ~/src/.mrconfig file, that in turn
84 registers several additional repositories.
86 The .mrconfig file uses a variant of the INI file format. Lines starting with
87 "#" are comments. Lines ending with "\" are continued on to the next line.
88 Sections specify where each repository is located, relative to the
89 directory that contains the .mrconfig file.
91 Within a section, each parameter defines a shell command to run to handle a
92 given action. Note that these shell commands are run in a "set -e" shell
93 environment, where any additional parameters you pass are available in
94 "$@". B<mr> cds into the repository directory before running
95 a command, except for the "checkout" command, which is run in the parent
96 of the repository directory, since the repository isn't checked out yet.
98 There are two special parameters. If the "skip" parameter is set and
99 its command returns nonzero, then B<mr> will skip acting on that repository.
101 The "default" section allows setting up default handlers for each action,
102 and is overridden by the contents of other sections. mr contains default
103 handlers for the "update", "status", and "commit" actions, so normally
104 you only need to specify what to do for "checkout".
109 checkout = svn co svn://svn.example.com/src/trunk src
112 # only check this out on kodama
113 skip = test $(hostname) != kodama
114 checkout = git clone git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux-2.6.git
118 Copyright 2007 Joey Hess <joey@kitenet.net>
120 Licensed under the GNU GPL version 2 or higher.
122 http://kitenet.net/~joey/code/mr/
129 use Cwd qw(getcwd abs_path);
131 my $directory=getcwd();
132 my $config="$ENV{HOME}/.mrconfig";
136 Getopt::Long::Configure("no_permute");
137 my $result=GetOptions(
138 "d=s" => sub { $directory=abs_path($_[1]) },
142 if (! $result || @ARGV < 1) {
143 die("Usage: mr [-d directory] action [params ...]\n");
145 my $action=shift @ARGV;
150 #print Dumper(\%config);
152 my (@failed, @successful, @skipped);
154 foreach my $topdir (sort keys %config) {
155 foreach my $subdir (sort keys %{$config{$topdir}}) {
156 next if $subdir eq 'default';
158 my $dir=$topdir.$subdir;
160 if (defined $directory &&
161 $dir ne $directory &&
162 $dir !~ /^\Q$directory\E\//) {
163 print "mr $action: $dir skipped per -d parameter ($directory)\n" if $verbose;
168 print "\n" unless $first;
171 if (exists $config{$topdir}{$subdir}{skip}) {
172 my $ret=system($config{$topdir}{$subdir}{skip});
173 if ($ret >> 8 == 0) {
174 print "mr $action: $dir skipped per config file\n" if $verbose;
180 action($action, $dir, $topdir, $subdir);
186 my ($action, $dir, $topdir, $subdir) = @_;
188 if ($action eq 'checkout') {
190 print "mr $action: $dir already exists, skipping checkout\n";
194 $dir=~s/^(.*)\/[^\/]+\/?$/$1/;
196 if ($action eq 'update') {
198 return action("checkout", $dir, $topdir, $subdir);
203 print STDERR "mr $action: failed to chdir to $dir: $!\n";
206 elsif (! exists $config{$topdir}{$subdir}{$action}) {
207 print STDERR "mr $action: no defined $action command for $topdir$subdir, skipping\n";
211 print "mr $action: in $dir\n";
212 my $command="set -e; my_action(){ $config{$topdir}{$subdir}{$action} ; }; my_action @ARGV";
213 my $ret=system($command);
215 print STDERR "mr $action: failed to run: $command\n" if $verbose;
216 push @failed, $topdir.$subdir;
217 if ($ret >> 8 != 0) {
218 print STDERR "mr $action: command failed\n";
221 print STDERR "mr $action: command died ($ret)\n";
225 push @successful, $dir;
235 return "$count ".($count > 1 ? $plural : $singular);
239 print "\nmr $action: finished (".join("; ",
240 showstat($#successful+1, "successful", "successful"),
241 showstat($#failed+1, "failed", "failed"),
242 showstat($#skipped+1, "skipped", "skipped"),
244 exit @failed ? 1 : 0;
254 if (ref $f eq 'GLOB') {
259 # $f might be a symlink
260 my $absf=abs_path($f);
261 if ($loaded{$absf}) {
266 print "mr: loading config $f\n" if $verbose;
267 open($in, "<", $f) || die "mr: open $f: $!\n";
268 ($dir)=$f=~/^(.*\/)[^\/]+$/;
269 if (! defined $dir) {
272 $dir=abs_path($dir)."/";
274 # copy in defaults from first parent
276 while ($parent=~s/^(.*)\/[^\/]+\/?$/$1/) {
277 if (exists $config{$parent} &&
278 exists $config{$parent}{default}) {
279 $config{$dir}{default}={ %{$config{$parent}{default}} };
288 next if /^\s*\#/ || /^\s*$/;
289 if (/^\s*\[([^\]]*)\]\s*$/) {
291 if (length $dir && $section ne "default" &&
292 -e $dir.$section."/.mrconfig") {
293 push @toload, $dir.$section."/.mrconfig";
296 elsif (/^\s*(\w+)\s*=\s*(.*)/) {
301 while ($value=~/(.*)\\$/) {
306 if (! defined $section) {
307 die "$f line $.: parameter ($parameter) not in section\n";
309 if (! exists $config{$dir}{$section} &&
310 exists $config{$dir}{default}) {
312 $config{$dir}{$section}={ %{$config{$dir}{default}} };
314 $config{$dir}{$section}{$parameter}=$value;
317 die "$f line $.: parse error\n";
328 # Some useful actions that mr knows about by default.
329 # These can be overridden in ~/.mrconfig.
332 if [ -d .svn ]; then \
334 elif [ -d .git ]; then \
335 git pull origin master; \
337 echo "mr update: unknown repo type"; \
341 if [ -d .svn ]; then \
343 elif [ -d .git ]; then \
344 git status || true; \
346 echo "mr status: unknown repo type"; \
350 if [ -d .svn ]; then \
352 elif [ -d .git ]; then \
353 git commit -a "$@" && git push --all; \
355 echo "mr commit: unknown repo type"; \