The optional -m parameter allows specifying a commit message.
+=back
+
+Actions can be abbreviated to any unambiguous subsctring, so
+"mr st" is equivilant to "mr status".
+
=head1 OPTIONS
=over 4
=head1 FILES
-B<mr> is configured by .mrconfig files. It searches for .mrconfig files in
-your home directory, and in the root directory of each repository specified
-in a .mrconfig file. So you could have a ~/.mrconfig that registers a
+B<mr> is configured by .mrconfig files. It starts by reading the .mrconfig
+file in your home directory. Each repository specified in a .mrconfig file
+can also have its own .mrconfig file in its root directory that can
+optionally be used as well. So you could have a ~/.mrconfig that registers a
repository ~/src, that itself contains a ~/src/.mrconfig file, that in turn
registers several additional repositories.
a command, except for the "checkout" command, which is run in the parent
of the repository directory, since the repository isn't checked out yet.
-There are two special parameters. If the "skip" parameter is set and
+There are three special parameters. If the "skip" parameter is set and
its command returns nonzero, then B<mr> will skip acting on that repository.
+If the "chain" parameter is set and its command returns nonzero, then B<mr>
+will try to load a .mrconfig file from the root of the repository. (You
+should avoid chaining from repositories with untrusted committers.) The
+"lib" parameter can specify some shell code that will be run before each
+command, this can be a useful way to define shell functions other commands
+can use.
The "default" section allows setting up default handlers for each action,
and is overridden by the contents of other sections. mr contains default
[src]
checkout = svn co svn://svn.example.com/src/trunk src
+ chain = true
[src/linux-2.6]
- # only check this out on kodama
- skip = test $(hostname) != kodama
+ skip = small
checkout = git clone git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux-2.6.git
+ [default]
+ lib = \
+ small() {
+ case "$(hostname)" in; \
+ slug|snail); \
+ return 0; ;; ; \
+ esac; \
+ return 1; \
+ }
+
=head1 AUTHOR
Copyright 2007 Joey Hess <joey@kitenet.net>
my $config="$ENV{HOME}/.mrconfig";
my $verbose=0;
my %config;
+my %knownactions;
Getopt::Long::Configure("no_permute");
my $result=GetOptions(
if (! $result || @ARGV < 1) {
die("Usage: mr [-d directory] action [params ...]\n");
}
-my $action=shift @ARGV;
loadconfig(\*DATA);
loadconfig($config);
#use Data::Dumper;
#print Dumper(\%config);
+my $action=shift @ARGV;
+if (! $knownactions{$action}) {
+ my @matches = grep { /^\Q$action\E/ } keys %knownactions;
+ if (@matches == 1) {
+ $action=$matches[0];
+ }
+ else {
+ die "mr: ambiguous action \"$action\" (matches @matches)\n";
+ }
+}
+
my (@failed, @successful, @skipped);
my $first=1;
foreach my $topdir (sort keys %config) {
print "\n" unless $first;
$first=0;
- if (exists $config{$topdir}{$subdir}{skip}) {
- my $ret=system($config{$topdir}{$subdir}{skip});
- if ($ret >> 8 == 0) {
- print "mr $action: $dir skipped per config file\n" if $verbose;
- push @skipped, $dir;
- next;
- }
- }
-
action($action, $dir, $topdir, $subdir);
}
sub action {
my ($action, $dir, $topdir, $subdir) = @_;
+
+ my $lib= exists $config{$topdir}{$subdir}{lib} ?
+ $config{$topdir}{$subdir}{lib} : "";
if ($action eq 'checkout') {
if (-d $dir) {
- print "mr $action: $dir already exists, skipping checkout\n";
+ print "mr $action: $dir already exists, skipping checkout\n" if $verbose;
push @skipped, $dir;
- next;
+ return;
}
$dir=~s/^(.*)\/[^\/]+\/?$/$1/;
}
- if ($action eq 'update') {
+ elsif ($action eq 'update') {
if (! -d $dir) {
return action("checkout", $dir, $topdir, $subdir);
}
}
-
+
if (! chdir($dir)) {
print STDERR "mr $action: failed to chdir to $dir: $!\n";
push @skipped, $dir;
}
- elsif (! exists $config{$topdir}{$subdir}{$action}) {
+
+ if (exists $config{$topdir}{$subdir}{skip}) {
+ my $ret=system($lib.$config{$topdir}{$subdir}{skip});
+ if ($ret >> 8 == 0) {
+ print "mr $action: $dir skipped per config file\n" if $verbose;
+ push @skipped, $dir;
+ return;
+ }
+ }
+
+ if (! exists $config{$topdir}{$subdir}{$action}) {
print STDERR "mr $action: no defined $action command for $topdir$subdir, skipping\n";
push @skipped, $dir;
}
else {
print "mr $action: in $dir\n";
- my $command="set -e; my_action(){ $config{$topdir}{$subdir}{$action} ; }; my_action @ARGV";
+ my $command="set -e; ".$lib.
+ "my_action(){ $config{$topdir}{$subdir}{$action} ; }; my_action ".
+ join(" ", map { s/\//\/\//g; s/"/\"/g; '"'.$_.'"' } @ARGV);
my $ret=system($command);
if ($ret != 0) {
print STDERR "mr $action: failed to run: $command\n" if $verbose;
showstat($#failed+1, "failed", "failed"),
showstat($#skipped+1, "skipped", "skipped"),
).")\n";
-exit @failed ? 1 : 0;
+if (@failed) {
+ exit 1;
+}
+elsif (! @successful && @skipped) {
+ exit 1;
+}
+exit 0;
my %loaded;
sub loadconfig {
next if /^\s*\#/ || /^\s*$/;
if (/^\s*\[([^\]]*)\]\s*$/) {
$section=$1;
- if (length $dir && $section ne "default" &&
- -e $dir.$section."/.mrconfig") {
- push @toload, $dir.$section."/.mrconfig";
- }
}
elsif (/^\s*(\w+)\s*=\s*(.*)/) {
my $parameter=$1;
# copy in defaults
$config{$dir}{$section}={ %{$config{$dir}{default}} };
}
- $config{$dir}{$section}{$parameter}=$value;
+ if ($parameter ne 'lib') {
+ $config{$dir}{$section}{$parameter}=$value;
+ $knownactions{$parameter}=1;
+ }
+ else {
+ $config{$dir}{$section}{$parameter}.=$value." ; ";
+ }
+
+ if ($parameter eq 'chain' &&
+ length $dir && $section ne "default" &&
+ -e $dir.$section."/.mrconfig" &&
+ system($value) >> 8 == 0) {
+ push @toload, $dir.$section."/.mrconfig";
+ }
}
else {
die "$f line $.: parse error\n";
}
}
-__DATA__
-# Some useful actions that mr knows about by default.
+# Finally, some useful actions that mr knows about by default.
# These can be overridden in ~/.mrconfig.
+__DATA__
[default]
+lib = \
+ error() { \
+ echo "mr: $@" >&2; \
+ exit 1; \
+ }
update = \
if [ -d .svn ]; then \
svn update; \
elif [ -d .git ]; then \
git pull origin master; \
else \
- echo "mr update: unknown RCS"; \
- exit 1; \
+ error "unknown repo type"; \
fi
status = \
if [ -d .svn ]; then \
elif [ -d .git ]; then \
git status || true; \
else \
- echo "mr status: unknown RCS"; \
- exit 1; \
+ error "unknown repo type"; \
fi
commit = \
if [ -d .svn ]; then \
svn commit "$@"; \
elif [ -d .git ]; then \
- git commit -a "$@"; \
+ git commit -a "$@" && git push --all; \
else \
- echo "mr commit: unknown RCS"; \
- exit 1; \
+ error "unknown repo type"; \
fi