#!/usr/bin/perl
-#man{{{
-
=head1 NAME
mr - a Multiple Repository management tool
=head1 SYNOPSIS
-/
+
B<mr> [options] checkout
B<mr> [options] update
The optional -m parameter allows specifying a commit message.
+=item push
+
+Pushes committed local changes to the remote repository. A no-op for
+centralized revision control systems.
+
=item diff
Show a diff of uncommitted changes.
It is not recommended for interactive operations.
Note that running more than 10 jobs at a time is likely to run afoul of
-ssh connection limits. Running between 3 and 5 jobs at a time will yeild
+ssh connection limits. Running between 3 and 5 jobs at a time will yield
a good speedup in updates without loading the machine too much.
=back
=cut
-#}}}
-
use warnings;
use strict;
use Getopt::Long;
my %knownactions;
my %alias;
my (@ok, @failed, @skipped);
-my @SWITCHES=@ARGV;
main();
my %rcs;
-sub rcs_test { #{{{
+sub rcs_test {
my ($action, $dir, $topdir, $subdir) = @_;
if (exists $rcs{$dir}) {
chomp $rcs;
if ($rcs=~/\n/s) {
$rcs=~s/\n/, /g;
- print STDERR "mr $action: found multiple possible repository types ($rcs) for $topdir$subdir\n";
+ print STDERR "mr $action: found multiple possible repository types ($rcs) for $dir\n";
return undef;
}
if (! length $rcs) {
else {
return $rcs{$dir}=$rcs;
}
-} #}}}
+}
-sub findcommand { #{{{
+sub findcommand {
my ($action, $dir, $topdir, $subdir, $is_checkout) = @_;
if (exists $config{$topdir}{$subdir}{$action}) {
else {
return undef;
}
-} #}}}
+}
-sub action { #{{{
+sub action {
my ($action, $dir, $topdir, $subdir) = @_;
-
+
$ENV{MR_CONFIG}=$configfiles{$topdir};
my $lib=exists $config{$topdir}{$subdir}{lib} ?
$config{$topdir}{$subdir}{lib}."\n" : "";
elsif (! defined $command) {
my $rcs=rcs_test(@_);
if (! defined $rcs) {
- print STDERR "mr $action: unknown repository type and no defined $action command for $topdir$subdir\n";
+ print STDERR "mr $action: unknown repository type and no defined $action command for $dir\n";
return FAILED;
}
else {
- print STDERR "mr $action: no defined action for $rcs repository $topdir$subdir, skipping\n";
+ print STDERR "mr $action: no defined action for $rcs repository $dir, skipping\n";
return SKIPPED;
}
}
else {
if (! $no_chdir) {
- print "mr $action: $topdir$subdir\n" unless $quiet;
+ print "mr $action: $dir\n" unless $quiet;
}
else {
my $s=$directory;
- $s=~s/^\Q$topdir$subdir\E\/?//;
- print "mr $action: $topdir$subdir (in subdir $s)\n" unless $quiet;
+ $s=~s/^\Q$dir\E\/?//;
+ print "mr $action: $dir (in subdir $s)\n" unless $quiet;
}
$command="set -e; ".$lib.
"my_action(){ $command\n }; my_action ".
if ($ret >> 8 != 0) {
print STDERR "mr $action: command failed\n";
if (-e "$ENV{HOME}/.mrlog" && $action ne 'remember') {
- @ARGV=(@SWITCHES, $action);
+ # recreate original command line to
+ # remember, and avoid recursing
+ my @orig=@ARGV;
+ @ARGV=('-n', $action, @orig);
action("remember", $dir, $topdir, $subdir);
+ @ARGV=@orig;
}
}
elsif ($ret != 0) {
return OK;
}
}
-} #}}}
+}
# run actions on multiple repos, in parallel
-sub mrs { #{{{
+sub mrs {
my $action=shift;
my @repos=@_;
}
}
}
-} #}}}
+}
-sub record { #{{{
+sub record {
my $dir=shift()->[0];
my $ret=shift;
else {
die "unknown exit status $ret";
}
-} #}}}
+}
-sub showstats { #{{{
+sub showstats {
my $action=shift;
if (! @ok && ! @failed && ! @skipped) {
die "mr $action: no repositories found to work on\n";
print STDERR "mr $action: (failed: ".join(" ", @failed).")\n";
}
}
-} #}}}
+}
-sub showstat { #{{{
+sub showstat {
my $count=shift;
my $singular=shift;
my $plural=shift;
return "$count ".($count > 1 ? $plural : $singular);
}
return;
-} #}}}
+}
# an ordered list of repos
-sub repolist { #{{{
+sub repolist {
my @list;
foreach my $topdir (sort keys %config) {
foreach my $subdir (sort keys %{$config{$topdir}}) {
||
$a->{subdir} cmp $b->{subdir}
} @list;
-} #}}}
+}
# figure out which repos to act on
-sub selectrepos { #{{{
+sub selectrepos {
my @repos;
foreach my $repo (repolist()) {
my $topdir=$repo->{topdir};
$no_chdir=1;
}
return @repos;
-} #}}}
+}
-sub expandenv { #{{{
+sub expandenv {
my $val=shift;
}
return $val;
-} #}}}
+}
my %loaded;
-sub loadconfig { #{{{
+sub loadconfig {
my $f=shift;
my @toload;
if ($parameter eq "include") {
print "mr: including output of \"$value\"\n" if $verbose;
unshift @lines, `$value`;
+ if ($?) {
+ print STDERR "mr: include command exited nonzero ($?)\n";
+ }
next;
}
foreach (@toload) {
loadconfig($_);
}
-} #}}}
+}
-sub modifyconfig { #{{{
+sub modifyconfig {
my $f=shift;
# the section to modify or add
my $targetsection=shift;
open(my $out, ">", $f) || die "mr: write $f: $!\n";
print $out @out;
close $out;
-} #}}}
+}
-sub dispatch { #{{{
+sub dispatch {
my $action=shift;
# actions that do not operate on all repos
elsif ($action eq 'register') {
register(@ARGV);
}
- elsif ($action eq 'remember') {
+ elsif ($action eq 'remember' ||
+ $action eq 'offline' ||
+ $action eq 'online') {
my @repos=selectrepos;
action($action, @{$repos[0]}) if @repos;
exit 0;
record($repo, action($action, @$repo));
}
}
-} #}}}
+}
-sub help { #{{{
+sub help {
exec($config{''}{DEFAULT}{help}) || die "exec: $!";
-} #}}}
+}
-sub config { #{{{
+sub config {
if (@_ < 2) {
die "mr config: not enough parameters\n";
}
}
modifyconfig($ENV{MR_CONFIG}, $section, %changefields) if %changefields;
exit 0;
-} #}}}
+}
-sub register { #{{{
+sub register {
if ($config_overridden) {
# Find the directory that the specified config file is
# located in.
join(" ", map { s/\//\/\//g; s/"/\"/g; '"'.$_.'"' } @ARGV);
print "mr register: running >>$command<<\n" if $verbose;
exec($command) || die "exec: $!";
-} #}}}
+}
# alias expansion and command stemming
-sub expandaction { #{{{
+sub expandaction {
my $action=shift;
if (exists $alias{$action}) {
$action=$alias{$action};
}
}
return $action;
-} #}}}
+}
-sub getopts { #{{{
+sub getopts {
+ my @saved=@ARGV;
Getopt::Long::Configure("bundling", "no_permute");
my $result=GetOptions(
"d|directory=s" => sub { $directory=abs_path($_[1]) },
die("Usage: mr [-d directory] action [params ...]\n".
"(Use mr help for man page.)\n");
}
-} #}}}
+
+ $ENV{MR_SWITCHES}="";
+ foreach my $option (@saved) {
+ last if $option eq $ARGV[0];
+ $ENV{MR_SWITCHES}.="$option ";
+ }
+}
-sub init { #{{{
+sub init {
$SIG{INT}=sub {
print STDERR "mr: interrupted\n";
exit 2;
use FindBin qw($Bin $Script);
$ENV{MR_PATH}=$Bin."/".$Script;
};
-} #}}}
+}
-sub main { #{{{
+sub main {
getopts();
init();
loadconfig($ENV{MR_CONFIG});
#use Data::Dumper; print Dumper(\%config);
- my $action=shift @ARGV;
- @SWITCHES = grep { $_ ne $action } @SWITCHES;
- $action=expandaction($action);
-
+ my $action=expandaction(shift @ARGV);
dispatch($action);
showstats($action);
else {
exit 0;
}
-} #}}}
+}
# Finally, some useful actions that mr knows about by default.
# These can be overridden in ~/.mrconfig.
-#DATA{{{
__DATA__
[ALIAS]
co = checkout
hg_record = hg commit -m "$@"
darcs_record = darcs record -a -m "$@"
+svn_push = :
+git_push = git push "$@"
+bzr_push = bzr push "$@"
+cvs_push = :
+hg_push = hg push "$@"
+darcs_push = darcs push -a "$@"
+
svn_diff = svn diff "$@"
git_diff = git diff "$@"
bzr_diff = bzr diff "$@"
config =
online =
- if [ -e ~/.mrlog ]; then
+ if [ -s ~/.mrlog ]; then
info "running offline commands"
mv -f ~/.mrlog ~/.mrlog.old
- if ! sh ~/.mrlog.old; then
- error "offline commands failed; left in ~/.mrlog.old"
+ if ! sh -e ~/.mrlog.old; then
+ error "offline command failed; left in ~/.mrlog.old"
fi
rm -f ~/.mrlog.old
else
info "no offline commands to run"
fi
offline =
+ umask 077
touch ~/.mrlog
info "offline mode enabled"
remember =
info "remembering command: 'mr $@'"
- command="mr -d '$(pwd)'"
+ command="mr -d '$(pwd)' $MR_SWITCHES"
for w in "$@"; do
command="$command '$w'"
done
ed = echo "A horse is a horse, of course, of course.."
T = echo "I pity the fool."
right = echo "Not found."
-#}}}
# vim:sw=8:sts=0:ts=8:noet