B<mr> is a Multiple Repository management tool. It can checkout, update, or
perform other actions on a set of repositories as if they were one combined
-respository. It supports any combination of subversion, git, cvs, mecurial and
-bzr repositories, and support for other revision control systems can easily be
-added.
+respository. It supports any combination of subversion, git, cvs, mecurial,
+bzr and darcs repositories, and support for other revision control systems can
+easily be added.
B<mr> cds into and operates on all registered repositories at or below your
working directory. Or, if you are in a subdirectory of a repository that
been at least 12 hours since the last update.
skip = test $(whoami) != joey
- skip = [ "$1" = update ] && [ $(hours_since "$1") -lt 12 ]
+ skip = [ "$1" = update ] && ! hours_since "$1" 12
+
+=item order
+
+The "order" parameter can be used to override the default ordering of
+repositories. The default order value is 10. Use smaller values to make
+repositories be processed earlier, and larger values to make repositories
+be processed later.
+
+Note that if a repository is located in a subdirectory of another
+repository, ordering it to be processed earlier is not recommended.
=item chain
$ENV{MR_CONFIG}="$ENV{HOME}/.mrconfig";
my $config_overridden=0;
-my $directory=getcwd();
my $verbose=0;
my $stats=0;
my $no_recurse=0;
my %configfiles;
my %knownactions;
my %alias;
+my $directory=getcwd();
Getopt::Long::Configure("no_permute");
my $result=GetOptions(
"(Use mr help for man page.)\n");
}
+if (! defined $directory) {
+ die("mr: failed to determine working directory\n");
+}
# Make sure MR_CONFIG is an absolute path, but don't use abs_path since
# the config file might be a symlink to elsewhere, and the directory it's
exec($command) || die "exec: $!";
}
+# an ordered list of repos
+my @list;
+foreach my $topdir (sort keys %config) {
+ foreach my $subdir (sort keys %{$config{$topdir}}) {
+ push @list, {
+ topdir => $topdir,
+ subdir => $subdir,
+ order => $config{$topdir}{$subdir}{order},
+ };
+ }
+}
+@list = sort {
+ $a->{order} <=> $b->{order}
+ ||
+ $a->{topdir} cmp $b->{topdir}
+ ||
+ $a->{subdir} cmp $b->{subdir}
+ } @list;
+
# work out what repos to act on
my @repos;
my $nochdir=0;
-foreach my $topdir (sort keys %config) {
- foreach my $subdir (sort keys %{$config{$topdir}}) {
+foreach my $repo (@list) {
+ my $topdir=$repo->{topdir};
+ my $subdir=$repo->{subdir};
+
+ next if $subdir eq 'DEFAULT';
+ my $dir=($subdir =~/^\//) ? $subdir : $topdir.$subdir;
+ my $d=$directory;
+ $dir.="/" unless $dir=~/\/$/;
+ $d.="/" unless $d=~/\/$/;
+ next if $no_recurse && $d ne $dir;
+ next if $dir ne $d && $dir !~ /^\Q$d\E/;
+ push @repos, [$dir, $topdir, $subdir];
+}
+if (! @repos) {
+ # fallback to find a leaf repo
+ foreach my $repo (reverse @list) {
+ my $topdir=$repo->{topdir};
+ my $subdir=$repo->{subdir};
+
next if $subdir eq 'DEFAULT';
my $dir=($subdir =~/^\//) ? $subdir : $topdir.$subdir;
my $d=$directory;
$dir.="/" unless $dir=~/\/$/;
$d.="/" unless $d=~/\/$/;
- next if $no_recurse && $d ne $dir;
- next if $dir ne $d && $dir !~ /^\Q$d\E/;
- push @repos, [$dir, $topdir, $subdir];
- }
-}
-if (! @repos) {
- # fallback to find a leaf repo
- LEAF: foreach my $topdir (reverse sort keys %config) {
- foreach my $subdir (reverse sort keys %{$config{$topdir}}) {
- next if $subdir eq 'DEFAULT';
- my $dir=($subdir =~/^\//) ? $subdir : $topdir.$subdir;
- my $d=$directory;
- $dir.="/" unless $dir=~/\/$/;
- $d.="/" unless $d=~/\/$/;
- if ($d=~/^\Q$dir\E/) {
- push @repos, [$dir, $topdir, $subdir];
- last LEAF;
- }
+ if ($d=~/^\Q$dir\E/) {
+ push @repos, [$dir, $topdir, $subdir];
+ last;
}
}
$nochdir=1;
else {
foreach my $repo (@repos) {
record($repo, action($action, @$repo));
- print "\n";
}
}
if (! @ok && ! @failed && ! @skipped) {
system("mkdir", "-p", $dir);
}
}
- elsif ($action eq 'update') {
+ elsif ($action =~ /update/) {
if (! -d $dir) {
return action("checkout", $dir, $topdir, $subdir);
}
print "mr $action: $topdir$subdir\n";
}
else {
- print "mr $action: $topdir$subdir (in subdir $directory)\n";
+ my $s=$directory;
+ $s=~s/^\Q$topdir$subdir\E\/?//;
+ print "mr $action: $topdir$subdir (in subdir $s)\n";
}
my $command="set -e; ".$lib.
"my_action(){ $config{$topdir}{$subdir}{$action}\n }; my_action ".
my $repo = shift @repos;
pipe(my $outfh, CHILD_STDOUT);
pipe(my $errfh, CHILD_STDERR);
- unless (my $pid = fork) {
+ my $pid;
+ unless ($pid = fork) {
die "mr $action: cannot fork: $!" unless defined $pid;
open(STDOUT, ">&CHILD_STDOUT") || die "mr $action cannot reopen stdout: $!";
open(STDERR, ">&CHILD_STDERR") || die "mr $action cannot reopen stderr: $!";
}
close CHILD_STDOUT;
close CHILD_STDERR;
- push @active, $repo;
+ push @active, [$pid, $repo];
push @fhs, [$outfh, $errfh];
push @out, ['', ''];
}
$fhs[$i][$channel] = undef;
if (! defined $fhs[$i][0] &&
! defined $fhs[$i][1]) {
+ waitpid($active[$i][0], 0);
print STDOUT $out[$i][0];
print STDERR $out[$i][1];
- print "\n";
- record($active[$i], $? >> 8);
+ record($active[$i][1], $? >> 8);
splice(@fhs, $i, 1);
splice(@active, $i, 1);
splice(@out, $i, 1);
if ($ret == OK) {
push @ok, $dir;
+ print "\n";
}
elsif ($ret == FAILED) {
push @failed, $dir;
+ print "\n";
}
elsif ($ret == SKIPPED) {
push @skipped, $dir;
ls = list
[DEFAULT]
+order = 10
lib =
error() {
echo "mr: $@" >&2
exit 1
}
hours_since() {
- for dir in .git .svn .bzr CVS .hg; do
+ if [ -z "$1" ] || [ -z "$2" ]; then
+ error "mr: usage: hours_since action num"
+ fi
+ for dir in .git .svn .bzr CVS .hg _darcs; do
if [ -e "$MR_REPO/$dir" ]; then
flagfile="$MR_REPO/$dir/.mr_last$1"
break
if [ -z "$flagfile" ]; then
error "cannot determine flag filename"
fi
- perl -wle 'print -f shift() ? int((-M _) * 24) : 9999' "$flagfile"
- touch "$flagfile"
+ delta=$(perl -wle 'print -f shift() ? int((-M _) * 24) : 9999' "$flagfile")
+ if [ "$delta" -lt "$2" ]; then
+ exit 0
+ else
+ touch "$flagfile"
+ exit 1
+ fi
}
update =
if [ -d "$MR_REPO"/.svn ]; then
svn update "$@"
- elif [ -d "$MR_REPO"/.git ]; then
+ elif [ -d "$MR_REPO"/.git ] || [ "${MR_REPO%.git/}" != "$MR_REPO" ]; then
+ # this is a bug in git-fetch, which requires GIT_DIR set
+ [ "${MR_REPO%.git/}" != "$MR_REPO" ] && GIT_DIR="$MR_REPO" && export GIT_DIR
if [ -z "$@" ]; then
git pull -t origin master
else
git pull "$@"
fi
+ unset GIT_DIR
elif [ -d "$MR_REPO"/.bzr ]; then
bzr merge "$@"
elif [ -d "$MR_REPO"/CVS ]; then
cvs update "$@"
elif [ -d "$MR_REPO"/.hg ]; then
hg pull "$@" && hg update "$@"
+ elif [ -d "$MR_REPO"/_darcs ]; then
+ darcs pull -a "$@"
else
error "unknown repo type"
fi
status =
if [ -d "$MR_REPO"/.svn ]; then
svn status "$@"
- elif [ -d "$MR_REPO"/.git ]; then
+ elif [ -d "$MR_REPO"/.git ] || [ "${MR_REPO%.git/}" != "$MR_REPO" ]; then
git status "$@" || true
elif [ -d "$MR_REPO"/.bzr ]; then
bzr status "$@"
cvs status "$@"
elif [ -d "$MR_REPO"/.hg ]; then
hg status "$@"
+ elif [ -d "$MR_REPO"/_darcs ]; then
+ darcs whatsnew -ls "$@"
else
error "unknown repo type"
fi
svn commit "$@"
elif [ -d "$MR_REPO"/.git ]; then
git commit -a "$@" && git push --all
+ elif [ "${MR_REPO%.git/}" != "$MR_REPO" ]; then
+ error "commit does not work for fake bare git repositories (yet)."
elif [ -d "$MR_REPO"/.bzr ]; then
bzr commit "$@" && bzr push
elif [ -d "$MR_REPO"/CVS ]; then
cvs commit "$@"
elif [ -d "$MR_REPO"/.hg ]; then
hg commit -m "$@" && hg push
+ elif [ -d "$MR_REPO"/_darcs ]; then
+ darcs commit -a -m "$@" && darcs push -a
else
error "unknown repo type"
fi
svn diff "$@"
elif [ -d "$MR_REPO"/.git ]; then
git diff "$@"
+ elif [ "${MR_REPO%.git/}" != "$MR_REPO" ]; then
+ error "commit does not work for fake bare git repositories (yet)."
elif [ -d "$MR_REPO"/.bzr ]; then
bzr diff "$@"
elif [ -d "$MR_REPO"/CVS ]; then
cvs diff "$@"
elif [ -d "$MR_REPO"/.hg ]; then
hg diff "$@"
+ elif [ -d "$MR_REPO"/_darcs ]; then
+ darcs diff "$@"
else
error "unknown repo type"
fi
log =
if [ -d "$MR_REPO"/.svn ]; then
svn log"$@"
- elif [ -d "$MR_REPO"/.git ]; then
+ elif [ -d "$MR_REPO"/.git ] || [ "${MR_REPO%.git/}" != "$MR_REPO" ]; then
git log "$@"
elif [ -d "$MR_REPO"/.bzr ]; then
bzr log "$@"
cvs log "$@"
elif [ -d "$MR_REPO"/.hg ]; then
hg log "$@"
+ elif [ -d "$MR_REPO"/_darcs ]; then
+ darcs changes "$@"
else
error "unknown repo type"
fi
fi
echo "Registering svn url: $url in $MR_CONFIG"
mr -c "$MR_CONFIG" config "$(pwd)" checkout="svn co $url $basedir"
- elif [ -d .git ]; then
- url=$(LANG=C git-config --get remote.origin.url)
+ elif [ -d .git ] || [ "${basedir%.git}" != "$basedir" ]; then
+ GIT_CONFIG=.git/config
+ [ ! -f $GIT_CONFIG ] && GIT_CONFIG=config
+ export GIT_CONFIG
+ url="$(LANG=C git-config --get remote.origin.url)"
if [ -z "$url" ]; then
error "cannot determine git url"
fi
- echo "Registering git url: $url in $MR_CONFIG"
+ if [ $GIT_CONFIG = config ]; then
+ # this seems like a bare repo as it has no worktree.
+ # mr needs a worktree and cannot work with bare
+ # repositories, so ensure that it's "fake bare" and
+ # has a worktree configured.
+ case "$(git-config --get core.bare)" in
+ true) error "cannot register a bare git repository";;
+ esac
+ GIT_WORK_TREE="$(git-config --get core.worktree)" || :
+ case "$GIT_WORK_TREE" in
+ '') error "cannot register a fake bare git repository without core.worktree";;
+ *)
+ if [ ! -d "$GIT_WORK_TREE" ]; then
+ error "git worktree $GIT_WORK_TREE does not exist"
+ fi;;
+ esac
+ suffix=" (with worktree $GIT_WORK_TREE)"
+ mr -c "$MR_CONFIG" config "$(pwd)" \
+ lib="GIT_WORK_TREE=$GIT_WORK_TREE; export GIT_WORK_TREE"
+ unset GIT_WORK_TREE
+ fi
+ echo "Registering git url: $url in $MR_CONFIG${suffix:-}"
+ unset GIT_CONFIG
mr -c "$MR_CONFIG" config "$(pwd)" checkout="git clone $url $basedir"
elif [ -d .bzr ]; then
url=$(cat .bzr/branch/parent)
echo "Registering mercurial repo url: $url in $MR_CONFIG"
mr -c "$MR_CONFIG" config "$(pwd)" \
checkout="hg clone $url $basedir"
+ elif [ -d _darcs ]; then
+ url=$(cat _darcs/prefs/defaultrepo)
+ echo "Registering darcs repository $url in $MR_CONFIG"
+ mr -c "$MR_CONFIG" config "$(pwd)" \
+ checkout="darcs get $url $basedir"
else
error "unable to register this repo type"
fi
T = echo "I pity the fool."
right = echo "Not found."
#}}}
+
+# vim:sw=8:sts=0:ts=8:noet