will try to load a .mrconfig file from the root of the repository. (You
should avoid chaining from repositories with untrusted committers.)
+=item include
+
+If the "include" parameter is set, its command is ran, and should output
+additional mrconfig file content. The content is included as if it were
+part of the including file.
+
+Unlike all other parameters, this parameter does not need to be placed
+within a section.
+
=item lib
The "lib" parameter can specify some shell code that will be run before each
my $verbose=0;
my $stats=0;
my $no_recurse=0;
+my $no_chdir=0;
my $jobs=1;
my %config;
my %configfiles;
my %alias;
my $directory=getcwd();
-Getopt::Long::Configure("no_permute");
-my $result=GetOptions(
- "d|directory=s" => sub { $directory=abs_path($_[1]) },
- "c|config=s" => sub { $ENV{MR_CONFIG}=$_[1]; $config_overridden=1 },
- "v|verbose" => \$verbose,
- "s|stats" => \$stats,
- "n|no-recurse" => \$no_recurse,
- "j|jobs=i" => \$jobs,
-);
-if (! $result || @ARGV < 1) {
- die("Usage: mr [-d directory] action [params ...]\n".
- "(Use mr help for man page.)\n");
+getopts();
-}
+# This can happen if it's run in a directory that was removed
+# or other strangeness.
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
# in is significant.
#use Data::Dumper;
#print Dumper(\%config);
-# alias expansion and command stemming
-my $action=shift @ARGV;
-if (exists $alias{$action}) {
- $action=$alias{$action};
-}
-if (! exists $knownactions{$action}) {
- my @matches = grep { /^\Q$action\E/ }
- keys %knownactions, keys %alias;
- if (@matches == 1) {
- $action=$matches[0];
- }
- elsif (@matches == 0) {
- die "mr: unknown action \"$action\" (known actions: ".
- join(", ", sort keys %knownactions).")\n";
- }
- else {
- die "mr: ambiguous action \"$action\" (matches: ".
- join(", ", @matches).")\n";
- }
-}
+my $action=expandaction(shift @ARGV);
-# commands that do not operate on all repos
+# actions that do not operate on all repos
if ($action eq 'help') {
- exec($config{''}{DEFAULT}{$action}) || die "exec: $!";
+ help(@ARGV);
}
elsif ($action eq 'config') {
- if (@ARGV < 2) {
- die "mr config: not enough parameters\n";
- }
- my $section=shift;
- if ($section=~/^\//) {
- # try to convert to a path relative to the config file
- my ($dir)=$ENV{MR_CONFIG}=~/^(.*\/)[^\/]+$/;
- $dir=abs_path($dir);
- $dir.="/" unless $dir=~/\/$/;
- if ($section=~/^\Q$dir\E(.*)/) {
- $section=$1;
- }
- }
- my %changefields;
- foreach (@ARGV) {
- if (/^([^=]+)=(.*)$/) {
- $changefields{$1}=$2;
- }
- else {
- my $found=0;
- foreach my $topdir (sort keys %config) {
- if (exists $config{$topdir}{$section} &&
- exists $config{$topdir}{$section}{$_}) {
- print $config{$topdir}{$section}{$_}."\n";
- $found=1;
- last if $section eq 'DEFAULT';
- }
- }
- if (! $found) {
- die "mr $action: $section $_ not set\n";
- }
- }
- }
- modifyconfig($ENV{MR_CONFIG}, $section, %changefields) if %changefields;
- exit 0;
+ config(@ARGV);
}
elsif ($action eq 'register') {
- if (! $config_overridden) {
- # Find the closest known mrconfig file to the current
- # directory.
- $directory.="/" unless $directory=~/\/$/;
- foreach my $topdir (reverse sort keys %config) {
- next unless length $topdir;
- if ($directory=~/^\Q$topdir\E/) {
- $ENV{MR_CONFIG}=$configfiles{$topdir};
- last;
- }
- }
- }
- if (@ARGV) {
- my $subdir=shift @ARGV;
- if (! chdir($subdir)) {
- print STDERR "mr $action: failed to chdir to $subdir: $!\n";
- }
- }
-
- $ENV{MR_REPO}=getcwd();
- my $command=findcommand("register", '', '', 'DEFAULT');
- if (! defined $command) {
- die "mr $action: unknown repository type\n";
- }
-
- $ENV{MR_REPO}=~s/.*\/(.*)/$1/;
- $command="set -e; ".$config{''}{DEFAULT}{lib}."\n".
- "my_action(){ $command\n }; my_action ".
- join(" ", map { s/\//\/\//g; s/"/\"/g; '"'.$_.'"' } @ARGV);
- print STDERR "mr $action: running >>$command<<\n" if $verbose;
- 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 $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=~/\/$/;
- if ($d=~/^\Q$dir\E/) {
- push @repos, [$dir, $topdir, $subdir];
- last;
- }
- }
- $nochdir=1;
+ register(@ARGV);
}
-# run the action on each repository and print stats
my (@ok, @failed, @skipped);
if ($jobs > 1) {
- mrs(@repos);
+ mrs(selectrepos());
}
else {
- foreach my $repo (@repos) {
+ foreach my $repo (selectrepos()) {
record($repo, action($action, @$repo));
}
}
-if (! @ok && ! @failed && ! @skipped) {
- die "mr $action: no repositories found to work on\n";
-}
-print "mr $action: finished (".join("; ",
- showstat($#ok+1, "ok", "ok"),
- showstat($#failed+1, "failed", "failed"),
- showstat($#skipped+1, "skipped", "skipped"),
-).")\n";
-if ($stats) {
- if (@skipped) {
- print "mr $action: (skipped: ".join(" ", @skipped).")\n";
- }
- if (@failed) {
- print STDERR "mr $action: (failed: ".join(" ", @failed).")\n";
- }
-}
+
+showstats();
+
if (@failed) {
exit 1;
}
length $a <=> length $b
||
$a cmp $b
- } grep { /_test/ } keys %{$config{$topdir}{$subdir}}) {
+ } grep { /_test$/ } keys %{$config{$topdir}{$subdir}}) {
my ($rcs)=$rcs_test=~/(.*)_test/;
$test="my_$rcs_test() {\n$config{$topdir}{$subdir}{$rcs_test}\n}\n".$test;
$test.="if my_$rcs_test; then echo $rcs; fi\n";
sub findcommand { #{{{
my ($action, $dir, $topdir, $subdir) = @_;
+
+ if (exists $config{$topdir}{$subdir}{$action}) {
+ return $config{$topdir}{$subdir}{$action};
+ }
my $rcs=rcs_test(@_);
exists $config{$topdir}{$subdir}{$rcs."_".$action}) {
return $config{$topdir}{$subdir}{$rcs."_".$action};
}
- elsif (exists $config{$topdir}{$subdir}{$action}) {
- return $config{$topdir}{$subdir}{$action};
- }
else {
return undef;
}
}
}
- if (! $nochdir && ! chdir($dir)) {
+ if (! $no_chdir && ! chdir($dir)) {
print STDERR "mr $action: failed to chdir to $dir: $!\n";
return FAILED;
}
}
}
else {
- if (! $nochdir) {
+ if (! $no_chdir) {
print "mr $action: $topdir$subdir\n";
}
else {
$command="set -e; ".$lib.
"my_action(){ $command\n }; my_action ".
join(" ", map { s/\//\/\//g; s/"/\"/g; '"'.$_.'"' } @ARGV);
- print STDERR "mr $action: running >>$command<<\n" if $verbose;
+ print "mr $action: running >>$command<<\n" if $verbose;
my $ret=system($command);
if ($ret != 0) {
if (($? & 127) == 2) {
# run actions on multiple repos, in parallel
sub mrs { #{{{
+ my @repos=@_;
+
$| = 1;
my @active;
my @fhs;
}
} #}}}
+sub showstats { #{{{
+ if (! @ok && ! @failed && ! @skipped) {
+ die "mr $action: no repositories found to work on\n";
+ }
+ print "mr $action: finished (".join("; ",
+ showstat($#ok+1, "ok", "ok"),
+ showstat($#failed+1, "failed", "failed"),
+ showstat($#skipped+1, "skipped", "skipped"),
+ ).")\n";
+ if ($stats) {
+ if (@skipped) {
+ print "mr $action: (skipped: ".join(" ", @skipped).")\n";
+ }
+ if (@failed) {
+ print STDERR "mr $action: (failed: ".join(" ", @failed).")\n";
+ }
+ }
+} #}}}
+
sub showstat { #{{{
my $count=shift;
my $singular=shift;
return;
} #}}}
+# an ordered list of repos
+sub repolist { #{{{
+ 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},
+ };
+ }
+ }
+ return sort {
+ $a->{order} <=> $b->{order}
+ ||
+ $a->{topdir} cmp $b->{topdir}
+ ||
+ $a->{subdir} cmp $b->{subdir}
+ } @list;
+} #}}}
+
+# figure out which repos to act on
+sub selectrepos { #{{{
+ my @repos;
+ foreach my $repo (repolist()) {
+ 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 repolist()) {
+ 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=~/\/$/;
+ if ($d=~/^\Q$dir\E/) {
+ push @repos, [$dir, $topdir, $subdir];
+ last;
+ }
+ }
+ $no_chdir=1;
+ }
+ return @repos;
+} #}}}
+
my %loaded;
sub loadconfig { #{{{
my $f=shift;
chomp $value;
}
+ if ($parameter eq "include") {
+ print "mr: including output of \"$value\"\n" if $verbose;
+ unshift @lines, `$value`;
+ next;
+ }
+
if (! defined $section) {
die "$f line $.: parameter ($parameter) not in section\n";
}
print $out @out;
close $out;
} #}}}
+
+sub help { #{{{
+ exec($config{''}{DEFAULT}{$action}) || die "exec: $!";
+} #}}}
+
+sub config { #{{{
+ if (@_ < 2) {
+ die "mr config: not enough parameters\n";
+ }
+ my $section=shift;
+ if ($section=~/^\//) {
+ # try to convert to a path relative to the config file
+ my ($dir)=$ENV{MR_CONFIG}=~/^(.*\/)[^\/]+$/;
+ $dir=abs_path($dir);
+ $dir.="/" unless $dir=~/\/$/;
+ if ($section=~/^\Q$dir\E(.*)/) {
+ $section=$1;
+ }
+ }
+ my %changefields;
+ foreach (@_) {
+ if (/^([^=]+)=(.*)$/) {
+ $changefields{$1}=$2;
+ }
+ else {
+ my $found=0;
+ foreach my $topdir (sort keys %config) {
+ if (exists $config{$topdir}{$section} &&
+ exists $config{$topdir}{$section}{$_}) {
+ print $config{$topdir}{$section}{$_}."\n";
+ $found=1;
+ last if $section eq 'DEFAULT';
+ }
+ }
+ if (! $found) {
+ die "mr $action: $section $_ not set\n";
+ }
+ }
+ }
+ modifyconfig($ENV{MR_CONFIG}, $section, %changefields) if %changefields;
+ exit 0;
+} #}}}
+
+sub register { #{{{
+ if (! $config_overridden) {
+ # Find the closest known mrconfig file to the current
+ # directory.
+ $directory.="/" unless $directory=~/\/$/;
+ foreach my $topdir (reverse sort keys %config) {
+ next unless length $topdir;
+ if ($directory=~/^\Q$topdir\E/) {
+ $ENV{MR_CONFIG}=$configfiles{$topdir};
+ $directory=$topdir;
+ last;
+ }
+ }
+ }
+ if (@ARGV) {
+ my $subdir=shift @ARGV;
+ if (! chdir($subdir)) {
+ print STDERR "mr $action: failed to chdir to $subdir: $!\n";
+ }
+ }
+
+ $ENV{MR_REPO}=getcwd();
+ my $command=findcommand("register", $ENV{MR_REPO}, $directory, 'DEFAULT');
+ if (! defined $command) {
+ die "mr $action: unknown repository type\n";
+ }
+
+ $ENV{MR_REPO}=~s/.*\/(.*)/$1/;
+ $command="set -e; ".$config{$directory}{DEFAULT}{lib}."\n".
+ "my_action(){ $command\n }; my_action ".
+ join(" ", map { s/\//\/\//g; s/"/\"/g; '"'.$_.'"' } @ARGV);
+ print "mr $action: running >>$command<<\n" if $verbose;
+ exec($command) || die "exec: $!";
+} #}}}
+
+# alias expansion and command stemming
+sub expandaction { #{{{
+ my $action=shift;
+ if (exists $alias{$action}) {
+ $action=$alias{$action};
+ }
+ if (! exists $knownactions{$action}) {
+ my @matches = grep { /^\Q$action\E/ }
+ keys %knownactions, keys %alias;
+ if (@matches == 1) {
+ $action=$matches[0];
+ }
+ elsif (@matches == 0) {
+ die "mr: unknown action \"$action\" (known actions: ".
+ join(", ", sort keys %knownactions).")\n";
+ }
+ else {
+ die "mr: ambiguous action \"$action\" (matches: ".
+ join(", ", @matches).")\n";
+ }
+ }
+ return $action;
+} #}}}
+
+sub getopts { #{{{
+ Getopt::Long::Configure("no_permute");
+ my $result=GetOptions(
+ "d|directory=s" => sub { $directory=abs_path($_[1]) },
+ "c|config=s" => sub { $ENV{MR_CONFIG}=$_[1]; $config_overridden=1 },
+ "v|verbose" => \$verbose,
+ "s|stats" => \$stats,
+ "n|no-recurse" => \$no_recurse,
+ "j|jobs=i" => \$jobs,
+ );
+ if (! $result || @ARGV < 1) {
+ die("Usage: mr [-d directory] action [params ...]\n".
+ "(Use mr help for man page.)\n");
+ }
+} #}}}
# Finally, some useful actions that mr knows about by default.
# These can be overridden in ~/.mrconfig.
test -d "$MR_REPO"/refs/heads && test -d "$MR_REPO"/refs/tags &&
test -d "$MR_REPO"/objects && test -f "$MR_REPO"/config &&
test "$(GIT_CONFIG="$MR_REPO"/config git-config --get core.bare)" = true
-git_fake_bare_test =
- test -d "$MR_REPO"/refs/heads && test -d "$MR_REPO"/refs/tags &&
- test -d "$MR_REPO"/objects && test -f "$MR_REPO"/config &&
- test "$(GIT_CONFIG="$MR_REPO"/config git-config --get core.bare)" = false
svn_update = svn update "$@"
git_update = if [ "$@" ]; then git pull "$@"; else git pull -t origin master; fi
cvs_update = cvs update "$@"
hg_update = hg pull "$@" && hg update "$@"
darcs_update = darcs pull -a "$@"
-git_fake_bare_update =
- # all this is because of a bug in git-fetch, which requires GIT_DIR set
- local git_dir_override; git_dir_override=.git
- case "$(get_git_repo_type "$MR_REPO")" in
- fake-bare) git_dir_override="$MR_REPO";;
- esac
- args="$@"
- [ -z "$args" ] && args="-t origin master"
- eval GIT_DIR="$git_dir_override" git pull "$args"
svn_status = svn status "$@"
git_status = git status "$@" || true
cvs_status = cvs status "$@"
hg_status = hg status "$@"
darcs_status = darcs whatsnew -ls "$@"
-git_fake_bare_status = git status "$@" || true
svn_commit = svn commit "$@"
git_commit = git commit -a "$@" && git push --all
cvs_commit = cvs commit "$@"
hg_commit = hg commit -m "$@" && hg push
darcs_commit = darcs commit -a -m "$@" && darcs push -a
-git_fake_bare_commit = error "commit does not work for fake bare git repositories (yet)."
svn_diff = svn diff "$@"
git_diff = git diff "$@"
cvs_diff = cvs diff "$@"
hg_diff = hg diff "$@"
darcs_diff = darcs diff "$@"
-git_fake_bare_diff = error "diff does not work for fake bare git repositories (yet)."
svn_log = svn log "$@"
git_log = git log "$@"
hg_log = hg log "$@"
darcs_log = darcs changes "$@"
git_bare_log = git log "$@"
-git_fake_bare_log = git log "$@"
svn_register =
url=$(LANG=C svn info . | grep -i ^URL: | cut -d ' ' -f 2)
error "cannot determine svn url"
fi
echo "Registering svn url: $url in $MR_CONFIG"
- mr -c "$MR_CONFIG" config "`pwd`" checkout="svn co $url $MR_REPO"
+ mr -c "$MR_CONFIG" config "`pwd`" checkout="svn co '$url' '$MR_REPO'"
git_register =
url="$(LANG=C git-config --get remote.origin.url)" || true
if [ -z "$url" ]; then
error "cannot determine git url"
fi
echo "Registering git url: $url in $MR_CONFIG"
- mr -c "$MR_CONFIG" config "`pwd`" checkout="git clone $url $MR_REPO"
+ mr -c "$MR_CONFIG" config "`pwd`" checkout="git clone '$url' '$MR_REPO'"
bzr_register =
url=$(cat .bzr/branch/parent)
if [ -z "$url" ]; then
error "cannot determine bzr url"
fi
echo "Registering bzr url: $url in $MR_CONFIG"
- mr -c "$MR_CONFIG" config "`pwd`" checkout="bzr clone $url $MR_REPO"
+ mr -c "$MR_CONFIG" config "`pwd`" checkout="bzr clone '$url' '$MR_REPO'"
cvs_register =
repo=$(cat CVS/Repository)
root=$(cat CVS/Root)
error "cannot determine cvs root"
fi
echo "Registering cvs repository $repo at root $root"
- mr -c "$MR_CONFIG" config "`pwd`" checkout="cvs -d '$root' co -d $MR_REPO $repo"
+ mr -c "$MR_CONFIG" config "`pwd`" checkout="cvs -d '$root' co -d '$MR_REPO' '$repo'"
hg_register =
url=$(hg showconfig paths.default)
echo "Registering mercurial repo url: $url in $MR_CONFIG"
- mr -c "$MR_CONFIG" config "`pwd`" checkout="hg clone $url $MR_REPO"
+ mr -c "$MR_CONFIG" config "`pwd`" checkout="hg clone '$url' '$MR_REPO'"
darcs_register =
url=$(cat _darcs/prefs/defaultrepo)
echo "Registering darcs repository $url in $MR_CONFIG"
- mr -c "$MR_CONFIG" config "`pwd`" checkout="darcs get $url $MR_REPO"
+ mr -c "$MR_CONFIG" config "`pwd`" checkout="darcs get '$url'p '$MR_REPO'"
git_bare_register =
url="$(LANG=C GIT_CONFIG=config git-config --get remote.origin.url)" || true
if [ -z "$url" ]; then
error "cannot determine git url"
fi
echo "Registering git url: $url in $MR_CONFIG"
- mr -c "$MR_CONFIG" config "`pwd`" checkout="git clone --bare $url $MR_REPO"
-git_fake_bare_register =
- url="$(LANG=C GIT_CONFIG=config git-config --get remote.origin.url)" || true
- if [ -z "$url" ]; then
- error "cannot determine git url"
- fi
- worktree="$(git-config --get core.worktree)" || true
- if [ -z "$worktree" ]; then
- error "git worktree is not set"
- fi
- worktree="${worktree%%/}/"
- if [ ! -d "$worktree" ]; then
- error "git worktree $worktree does not exist"
- fi
- mr -c "$MR_CONFIG" config "`pwd`" \
- lib="GIT_WORK_TREE=$work_tree; export GIT_WORK_TREE"
- echo "Registering git url: $url in $MR_CONFIG (with worktree $worktree)"
- mr -c "$MR_CONFIG" config "`pwd`" \
- checkout=" \
- git clone --no-checkout $url $MR_REPO && \
- cd $MR_REPO && \
- git read-tree HEAD && \
- git checkout-index -a --prefix='$work_tree' || true; \
- git config core.worktree '$worktree' && \
- mv .git/* . && \
- rmdir .git"
+ mr -c "$MR_CONFIG" config "`pwd`" checkout="git clone --bare '$url' '$MR_REPO'"
help =
if [ ! -e "$MR_PATH" ]; then