X-Git-Url: https://git.madduck.net/code/myrepos.git/blobdiff_plain/deb8ddcf307273f8cbf7252e796f6769c83be427..a34380f45799d68e6523785350127748bb2604d1:/mr?ds=inline

diff --git a/mr b/mr
index a431e8a..4235151 100755
--- a/mr
+++ b/mr
@@ -16,10 +16,14 @@ B<mr> [options] commit [-m "message"]
 
 B<mr> [options] record [-m "message"]
 
+B<mr> [options] push
+
 B<mr> [options] diff
 
 B<mr> [options] log
 
+B<mr> [options] run command [param ...]
+
 B<mr> [options] bootstrap url [directory]
 
 B<mr> [options] register [repository]
@@ -37,8 +41,8 @@ B<mr> [options] remember action [params ...]
 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
 repository. It supports any combination of subversion, git, cvs, mercurial,
-bzr and darcs repositories, and support for other revision control systems can
-easily be added.
+bzr, darcs and fossil 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
@@ -47,7 +51,9 @@ and work on only that repository,
 
 B<mr> is configured by .mrconfig files, which list the repositories. It
 starts by reading the .mrconfig file in your home directory, and this can
-in turn chain load .mrconfig files from repositories.
+in turn chain load .mrconfig files from repositories. It also automatically
+looks for a .mrconfig file in the current directory, or in one of its
+parent directories.
 
 These predefined commands should be fairly familiar to users of any revision
 control system:
@@ -98,6 +104,10 @@ Show a diff of uncommitted changes.
 
 Show the commit log.
 
+=item run command [param ...]
+
+Runs the specified command in each repository.
+
 =back
 
 These commands are also available:
@@ -126,7 +136,7 @@ repository in the current directory is registered, or you can specify a
 directory to register.
 
 The mrconfig file that is modified is chosen by either the -c option, or by
-looking for the closest known one at or below the current directory.
+looking for the closest known one at or in a parent of the current directory.
 
 =item config
 
@@ -147,8 +157,8 @@ To see the built-in library of shell functions contained in mr:
 
   mr config DEFAULT lib
 
-The ~/.mrconfig file is used by default. To use a different config file,
-use the -c option.
+The mrconfig file that is used is chosen by either the -c option, or by
+looking for the closest known one at or in a parent of the current directory.
 
 =item offline
 
@@ -198,14 +208,9 @@ the current working directory.
 
 =item --config mrconfig
 
-Use the specified mrconfig file. The default is B<~/.mrconfig>
-
-=item -p
-
-=item --path
-
-Search in the current directory, and its parent directories and use
-the first B<.mrconfig> found, instead of the default B<~/.mrconfig>.
+Use the specified mrconfig file. The default is to use both B<~/.mrconfig>
+as well as look for a .mrconfig file in the current directory, or in one
+of its parent directories.
 
 =item -v
 
@@ -217,7 +222,15 @@ Be verbose.
 
 =item --quiet
 
-Be quiet.
+Be quiet. This supresses mr's usual output, as well as any output from
+commands that are run (including stderr output). If a command fails,
+the output will be shown.
+
+=item -k
+
+=item --insecure
+
+Accept untrusted SSL certificates when bootstrapping.
 
 =item -s
 
@@ -264,9 +277,15 @@ a good speedup in updates without loading the machine too much.
 Trust all mrconfig files even if they are not listed in ~/.mrtrust.
 Use with caution.
 
+=item -p
+
+=item --path
+
+This obsolete flag is ignored.
+
 =back
 
-=head1 "MRCONFIG FILES"
+=head1 MRCONFIG FILES
 
 Here is an example .mrconfig file:
 
@@ -364,6 +383,20 @@ 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 for other commands
 to use.
 
+=item fixups
+
+If the "fixups" parameter is set, its command is run whenever a repository
+is checked out, or updated. This provides an easy way to do things
+like permissions fixups, or other tweaks to the repository content,
+whenever the repository is changed.
+
+=item pre_ and post_
+
+If a "pre_action" parameter is set, its command is run before mr performs the
+specified action. Similarly, "post_action" parameters are run after mr
+successfully performs the specified action. For example, "pre_commit" is
+run before committing; "post_update" is run after updating.
+
 =back
 
 When looking for a command to run for a given action, mr first looks for
@@ -378,26 +411,28 @@ the action that is performed for a given revision control system, you can
 override these rcs specific actions. To add a new revision control system,
 you can just add rcs specific actions for it.
 
-The ~/.mrlog file contains commands that mr has remembered to run later,
-due to being offline. You can delete or edit this file to remove commands,
-or even to add other commands for 'mr online' to run. If the file is
-present, mr assumes it is in offline mode.
-
-=head "UNTRUSTED MRCONFIG FILES"
+=head1 UNTRUSTED MRCONFIG FILES
 
 Since mrconfig files can contain arbitrary shell commands, they can do
-anything. This flexability is good, but it also allows a malicious mrconfig
+anything. This flexibility is good, but it also allows a malicious mrconfig
 file to delete your whole home directory. Such a file might be contained
-inside a repository that your main ~/.mrconfig checks out and chains to. To
-avoid worries about evil commands in a mrconfig file, mr
-has the ability to read mrconfig files in untrusted mode. Such files are
-limited to running only known safe commands (like "git clone") in a
-carefully checked manner.
+inside a repository that your main ~/.mrconfig checks out. To
+avoid worries about evil commands in a mrconfig file, mr defaults to
+reading all mrconfig files other than the main ~/.mrconfig in untrusted
+mode. In untrusted mode, mrconfig files are limited to running only known
+safe commands (like "git clone") in a carefully checked manner.
+
+To configure mr to trust other mrconfig files, list them in ~/.mrtrust.
+One mrconfig file should be listed per line. Either the full pathname
+should be listed, or the pathname can start with "~/" to specify a file
+relative to your home directory.
 
-By default, mr trusts all mrconfig files. (This default will change in a
-future release!) But if you have a ~/.mrtrust file, mr will only trust
-mrconfig files that are listed within it. (One file per line.) All other
-files will be treated as untrusted.
+=head1 OFFLINE LOG FILE
+
+The ~/.mrlog file contains commands that mr has remembered to run later,
+due to being offline. You can delete or edit this file to remove commands,
+or even to add other commands for 'mr online' to run. If the file is
+present, mr assumes it is in offline mode.
 
 =head1 EXTENSIONS
 
@@ -405,9 +440,13 @@ mr can be extended to support things such as unison and git-svn. Some
 files providing such extensions are available in /usr/share/mr/. See
 the documentation in the files for details about using them.
 
+=head1 EXIT STATUS
+
+mr returns nonzero if a command failed in any of the repositories.
+
 =head1 AUTHOR
 
-Copyright 2007-2009 Joey Hess <joey@kitenet.net>
+Copyright 2007-2011 Joey Hess <joey@kitenet.net>
 
 Licensed under the GNU GPL version 2 or higher.
 
@@ -433,13 +472,15 @@ my $config_overridden=0;
 my $verbose=0;
 my $quiet=0;
 my $stats=0;
+my $insecure=0;
 my $interactive=0;
 my $max_depth;
 my $no_chdir=0;
 my $jobs=1;
 my $trust_all=0;
 my $directory=getcwd();
-$ENV{MR_CONFIG}="$ENV{HOME}/.mrconfig";
+
+$ENV{MR_CONFIG}=find_mrconfig();
 
 # globals :-(
 my %config;
@@ -477,7 +518,7 @@ sub rcs_test {
 	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 ".fulldir($topdir, $subdir)."\n";
 		return undef;
 	}
 	if (! length $rcs) {
@@ -510,13 +551,20 @@ sub findcommand {
 	}
 }
 
+sub fulldir {
+	my ($topdir, $subdir) = @_;
+	return $subdir =~ /^\// ? $subdir : $topdir.$subdir;
+}
+
 sub action {
 	my ($action, $dir, $topdir, $subdir, $force_checkout) = @_;
-	
+	my $fulldir=fulldir($topdir, $subdir);
+
 	$ENV{MR_CONFIG}=$configfiles{$topdir};
 	my $lib=exists $config{$topdir}{$subdir}{lib} ?
 	               $config{$topdir}{$subdir}{lib}."\n" : "";
 	my $is_checkout=($action eq 'checkout');
+	my $is_update=($action =~ /update/);
 
 	$ENV{MR_REPO}=$dir;
 
@@ -530,7 +578,7 @@ sub action {
 			$dir=~s/^(.*)\/[^\/]+\/?$/$1/;
 		}
 	}
-	elsif ($action =~ /update/) {
+	elsif ($is_update) {
 		if (! -d $dir) {
 			return action("checkout", $dir, $topdir, $subdir);
 		}
@@ -572,28 +620,45 @@ sub action {
 	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 $fulldir\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 $fulldir, skipping\n";
 			return SKIPPED;
 		}
 	}
 	else {
+		my $actionmsg;
 		if (! $no_chdir) {
-			print "mr $action: $topdir$subdir\n" unless $quiet;
+			$actionmsg="mr $action: $fulldir";
 		}
 		else {
 			my $s=$directory;
-			$s=~s/^\Q$topdir$subdir\E\/?//;
-			print "mr $action: $topdir$subdir (in subdir $s)\n" unless $quiet;
+			$s=~s/^\Q$fulldir\E\/?//;
+			$actionmsg="mr $action: $fulldir (in subdir $s)";
 		}
+		print "$actionmsg\n" unless $quiet;
+
+		my $hookret=hook("pre_$action", $topdir, $subdir);
+		return $hookret if $hookret != OK;
+
 		$command="set -e; ".$lib.
 			"my_action(){ $command\n }; my_action ".
-			join(" ", map { s/\//\/\//g; s/"/\"/g; '"'.$_.'"' } @ARGV);
+			join(" ", map { s/\\/\\\\/g; s/"/\"/g; '"'.$_.'"' } @ARGV);
 		print "mr $action: running >>$command<<\n" if $verbose;
-		my $ret=system($command);
+		my $ret;
+		if ($quiet) {
+			my $output = qx/$command 2>&1/;
+			$ret = $?;
+			if ($ret != 0) {
+				print "$actionmsg\n";
+				print STDERR $output;
+			}
+		}
+		else {
+			$ret=system($command);
+		}
 		if ($ret != 0) {
 			if (($? & 127) == 2) {
 				print STDERR "mr $action: interrupted\n";
@@ -621,16 +686,59 @@ sub action {
 			return FAILED;
 		}
 		else {
-			if ($action eq 'checkout' && ! -d $dir) {
+			if ($is_checkout && ! -d $dir) {
 				print STDERR "mr $action: $dir missing after checkout\n";;
 				return FAILED;
 			}
 
+			my $ret=hook("post_$action", $topdir, $subdir);
+			return $ret if $ret != OK;
+			
+			if (($is_checkout || $is_update)) {
+				my $ret=hook("fixups", $topdir, $subdir);
+				return $ret if $ret != OK;
+			}
+			
 			return OK;
 		}
 	}
 }
 
+sub hook {
+	my ($hook, $topdir, $subdir) = @_;
+
+	my $command=$config{$topdir}{$subdir}{$hook};
+	return OK unless defined $command;
+	my $lib=exists $config{$topdir}{$subdir}{lib} ?
+	               $config{$topdir}{$subdir}{lib}."\n" : "";
+	my $shell="set -e;".$lib.
+		"my_hook(){ $command\n }; my_hook";
+	print "mr $hook: running >>$shell<<\n" if $verbose;
+	my $ret;
+	if ($quiet) {
+		my $output = qx/$shell 2>&1/;
+		$ret = $?;
+		if ($ret != 0) {
+			print STDERR $output;
+		}
+	}
+	else {
+		$ret=system($shell);
+	}
+	if ($ret != 0) {
+		if (($? & 127) == 2) {
+			print STDERR "mr $hook: interrupted\n";
+			return ABORT;
+		}
+		elsif ($? & 127) {
+			print STDERR "mr $hook: received signal ".($? & 127)."\n";
+			return ABORT;
+		}
+	}
+
+	return OK;
+}
+
 # run actions on multiple repos, in parallel
 sub mrs {
 	my $action=shift;
@@ -707,7 +815,7 @@ sub record {
 
 	if ($ret == OK) {
 		push @ok, $dir;
-		print "\n";
+		print "\n" unless $quiet;
 	}
 	elsif ($ret == FAILED) {
 		if ($interactive) {
@@ -716,7 +824,7 @@ sub record {
 			system((getpwuid($<))[8], "-i");
 		}
 		push @failed, $dir;
-		print "\n";
+		print "\n" unless $quiet;
 	}
 	elsif ($ret == SKIPPED) {
 		push @skipped, $dir;
@@ -780,6 +888,15 @@ sub repolist {
 	} @list;
 }
 
+sub repodir {
+	my $repo=shift;
+	my $topdir=$repo->{topdir};
+	my $subdir=$repo->{subdir};
+	my $ret=($subdir =~/^\//) ? $subdir : $topdir.$subdir;
+	$ret=~s/\/\.$//;
+	return $ret;
+}
+
 # figure out which repos to act on
 sub selectrepos {
 	my @repos;
@@ -788,7 +905,7 @@ sub selectrepos {
 		my $subdir=$repo->{subdir};
 
 		next if $subdir eq 'DEFAULT';
-		my $dir=($subdir =~/^\//) ? $subdir : $topdir.$subdir;
+		my $dir=repodir($repo);
 		my $d=$directory;
 		$dir.="/" unless $dir=~/\/$/;
 		$d.="/" unless $d=~/\/$/;
@@ -808,7 +925,7 @@ sub selectrepos {
 			my $subdir=$repo->{subdir};
 			
 			next if $subdir eq 'DEFAULT';
-			my $dir=($subdir =~/^\//) ? $subdir : $topdir.$subdir;
+			my $dir=repodir($repo);
 			my $d=$directory;
 			$dir.="/" unless $dir=~/\/$/;
 			$d.="/" unless $d=~/\/$/;
@@ -845,22 +962,16 @@ sub is_trusted_config {
 
 	my $trustfile=$ENV{HOME}."/.mrtrust";
 
-	if (! -e $trustfile) {
-		print "mr: Assuming $config is trusted.\n";
-		print "mr: For better security, you are encouraged to create ~/.mrtrust\n";
-		print "mr: and list all trusted mrconfig files in it.\n";
-		return 1;
-	}
-
 	if (! %trusted) {
 		$trusted{"$ENV{HOME}/.mrconfig"}=1;
-		open (TRUST, "<", $trustfile) || die "$trustfile: $!";
-		while (<TRUST>) {
-			chomp;
-			s/^~\//$ENV{HOME}\//;
-			$trusted{abs_path($_)}=1;
+		if (open (TRUST, "<", $trustfile)) {
+			while (<TRUST>) {
+				chomp;
+				s/^~\//$ENV{HOME}\//;
+				$trusted{abs_path($_)}=1;
+			}
+			close TRUST;
 		}
-		close TRUST;
 	}
 
 	return $trusted{$config};
@@ -944,10 +1055,25 @@ sub is_trusted_checkout {
 	return 0;
 }
 
+sub trusterror {
+	my ($err, $file, $line, $url)=@_;
+	
+	if (defined $url) {
+		die "$err in untrusted $url line $line\n".
+			"(To trust this url, --trust-all can be used; but please use caution;\n".
+			"this can allow arbitrary code execution!)\n";
+	}
+	else {
+		die "$err in untrusted $file line $line\n".
+			"(To trust this file, list it in ~/.mrtrust.)\n";
+	}
+}
+
 my %loaded;
 sub loadconfig {
 	my $f=shift;
 	my $dir=shift;
+	my $bootstrap_url=shift;
 
 	my @toload;
 
@@ -959,10 +1085,6 @@ sub loadconfig {
 		$trusted=1;
 	}
 	else {
-		if (! -e $f) {
-			return;
-		}
-
 		my $absf=abs_path($f);
 		if ($loaded{$absf}) {
 			return;
@@ -997,11 +1119,15 @@ sub loadconfig {
 			}
 		}
 		
+		if (! -e $f) {
+			return;
+		}
+
 		print "mr: loading config $f\n" if $verbose;
 		open($in, "<", $f) || die "mr: open $f: $!\n";
 	}
 	my @lines=<$in>;
-	close $in;
+	close $in unless ref $f eq 'GLOB';
 
 	my $section;
 	my $line=0;
@@ -1017,10 +1143,16 @@ sub loadconfig {
 				if (! is_trusted_repo($section) ||
 				    $section eq 'ALIAS' ||
 				    $section eq 'DEFAULT') {
-					die "mr: illegal section \"[$section]\" in untrusted $f line $line\n";
+					trusterror("mr: illegal section \"[$section]\"", $f, $line, $bootstrap_url)
 				}
 			}
 			$section=expandenv($section) if $trusted;
+			if ($section ne 'ALIAS' &&
+			    ! exists $config{$dir}{$section} &&
+			    exists $config{$dir}{DEFAULT}) {
+				# copy in defaults
+				$config{$dir}{$section}={ %{$config{$dir}{DEFAULT}} };
+			}
 		}
 		elsif (/^(\w+)\s*=\s*(.*)/) {
 			my $parameter=$1;
@@ -1038,10 +1170,10 @@ sub loadconfig {
 				# Untrusted files can only contain checkout
 				# parameters.
 				if ($parameter ne 'checkout') {
-					die "mr: illegal setting \"$parameter=$value\" in untrusted $f line $line\n";
+					trusterror("mr: illegal setting \"$parameter=$value\"", $f, $line, $bootstrap_url);
 				}
 				if (! is_trusted_checkout($value)) {
-					die "mr: illegal checkout command \"$value\" in untrusted $f line $line\n";
+					trusterror("mr: illegal checkout command \"$value\"", $f, $line, $bootstrap_url);
 				}
 			}
 
@@ -1057,12 +1189,6 @@ sub loadconfig {
 			if (! defined $section) {
 				die "$f line $.: parameter ($parameter) not in section\n";
 			}
-			if ($section ne 'ALIAS' &&
-			    ! exists $config{$dir}{$section} &&
-			    exists $config{$dir}{DEFAULT}) {
-				# copy in defaults
-				$config{$dir}{$section}={ %{$config{$dir}{DEFAULT}} };
-			}
 			if ($section eq 'ALIAS') {
 				$alias{$parameter}=$value;
 			}
@@ -1106,6 +1232,13 @@ sub loadconfig {
 	}
 }
 
+sub startingconfig {
+	%alias=%config=%configfiles=%knownactions=%loaded=();
+	my $datapos=tell(DATA);
+	loadconfig(\*DATA);
+	seek(DATA,$datapos,0); # rewind
+}
+
 sub modifyconfig {
 	my $f=shift;
 	# the section to modify or add
@@ -1319,7 +1452,7 @@ sub register {
 	$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);
+		join(" ", map { s/\\/\\\\/g; s/"/\"/g; '"'.$_.'"' } @ARGV);
 	print "mr register: running >>$command<<\n" if $verbose;
 	exec($command) || die "exec: $!";
 }
@@ -1336,9 +1469,11 @@ sub bootstrap {
 	eval q{use File::Temp};
 	die $@ if $@;
 	my $tmpconfig=File::Temp->new();
-	if (system("curl", "-A", "mr", "-s", $url, "-o", $tmpconfig) != 0) {
-		die "mr: download of $url failed\n";
-	}
+	my @curlargs = ("curl", "-A", "mr", "-L", "-s", $url, "-o", $tmpconfig);
+	push(@curlargs, "-k") if $insecure;
+	my $curlstatus = system(@curlargs);
+	die "mr bootstrap: invalid SSL certificate for $url (consider -k)\n" if $curlstatus >> 8 == 60;
+	die "mr bootstrap: download of $url failed\n" if $curlstatus != 0;
 
 	if (! -e $dir) {
 		system("mkdir", "-p", $dir);
@@ -1349,12 +1484,12 @@ sub bootstrap {
 	# would normally be skipped.
 	my $topdir=abs_path(".")."/";
 	my @repo=($topdir, $topdir, ".");
-	loadconfig($tmpconfig, $topdir);
+	loadconfig($tmpconfig, $topdir, $url);
 	record(\@repo, action("checkout", @repo, 1))
 		if exists $config{$topdir}{"."}{"checkout"};
 
 	if (-e ".mrconfig") {
-		print STDERR "mr: .mrconfig file already exists, not overwriting with $url\n";
+		print STDERR "mr bootstrap: .mrconfig file already exists, not overwriting with $url\n";
 	}
 	else {
 		eval q{use File::Copy};
@@ -1362,8 +1497,14 @@ sub bootstrap {
 		move($tmpconfig, ".mrconfig") || die "rename: $!";
 	}
 
-	exec("mr $ENV{MR_SWITCHES} -c .mrconfig checkout");
-	die "failed to run mr checkout";
+	# Reload the config file (in case we got a different version)
+	# and checkout everything else.
+	startingconfig();
+	loadconfig(".mrconfig");
+	dispatch("checkout");
+	@skipped=grep { abs_path($_) ne abs_path($topdir) } @skipped;
+	showstats("bootstrap");
+	exitstats();
 }
 
 # alias expansion and command stemming
@@ -1390,7 +1531,7 @@ sub expandaction {
 	return $action;
 }
 
-sub find_nearest_mrconfig {
+sub find_mrconfig {
 	my $dir=getcwd();
 	while (length $dir) {
 		if (-e "$dir/.mrconfig") {
@@ -1398,7 +1539,7 @@ sub find_nearest_mrconfig {
 		}
 		$dir=~s/\/[^\/]*$//;
 	}
-	die "no .mrconfig found in path\n";
+	return "$ENV{HOME}/.mrconfig";
 }
 
 sub getopts {
@@ -1407,10 +1548,11 @@ sub getopts {
 	my $result=GetOptions(
 		"d|directory=s" => sub { $directory=abs_path($_[1]) },
 		"c|config=s" => sub { $ENV{MR_CONFIG}=$_[1]; $config_overridden=1 },
-		"p|path" => sub { $ENV{MR_CONFIG}=find_nearest_mrconfig(); $config_overridden=1 },
+		"p|path" => sub { }, # now default, ignore
 		"v|verbose" => \$verbose,
 		"q|quiet" => \$quiet,
 		"s|stats" => \$stats,
+		"k|insecure" => \$insecure,
 		"i|interactive" => \$interactive,
 		"n|no-recurse:i" => \$max_depth,
 		"j|jobs:i" => \$jobs,
@@ -1451,28 +1593,30 @@ sub init {
 		$ENV{MR_PATH}=$Bin."/".$Script;
 	};
 }
+	
+sub exitstats {
+	if (@failed) {
+		exit 1;
+	}
+	else {
+		exit 0;
+	}
+}
 
 sub main {
 	getopts();
 	init();
 
-	loadconfig(\*DATA);
+	startingconfig();
+	loadconfig("$ENV{HOME}/.mrconfig");
 	loadconfig($ENV{MR_CONFIG});
 	#use Data::Dumper; print Dumper(\%config);
 	
 	my $action=expandaction(shift @ARGV);
 	dispatch($action);
-	showstats($action);
 
-	if (@failed) {
-		exit 1;
-	}
-	elsif (! @ok && @skipped) {
-		exit 1;
-	}
-	else {
-		exit 0;
-	}
+	showstats($action);
+	exitstats();
 }
 
 # Finally, some useful actions that mr knows about by default.
@@ -1500,7 +1644,7 @@ lib =
 		if [ -z "$1" ] || [ -z "$2" ]; then
 			error "mr: usage: hours_since action num"
 		fi
-		for dir in .git .svn .bzr CVS .hg _darcs; do
+		for dir in .git .svn .bzr CVS .hg _darcs _FOSSIL_; do
 			if [ -e "$MR_REPO/$dir" ]; then
 				flagfile="$MR_REPO/$dir/.mr_last$1"
 				break
@@ -1511,10 +1655,10 @@ lib =
 		fi
 		delta=`perl -wle 'print -f shift() ? int((-M _) * 24) : 9999' "$flagfile"`
 		if [ "$delta" -lt "$2" ]; then
-			exit 0
+			return 1
 		else
 			touch "$flagfile"
-			exit 1
+			return 0
 		fi
 	}
 
@@ -1524,6 +1668,7 @@ bzr_test = test -d "$MR_REPO"/.bzr
 cvs_test = test -d "$MR_REPO"/CVS
 hg_test  = test -d "$MR_REPO"/.hg
 darcs_test = test -d "$MR_REPO"/_darcs
+fossil_test = test -f "$MR_REPO"/_FOSSIL_
 git_bare_test =
 	test -d "$MR_REPO"/refs/heads && test -d "$MR_REPO"/refs/tags &&
 	test -d "$MR_REPO"/objects && test -f "$MR_REPO"/config &&
@@ -1535,13 +1680,15 @@ bzr_update = bzr merge --pull "$@"
 cvs_update = cvs update "$@"
 hg_update  = hg pull "$@" && hg update "$@"
 darcs_update = darcs pull -a "$@"
+fossil_update = fossil pull "$@"
 
 svn_status = svn status "$@"
-git_status = git status "$@" || true
-bzr_status = bzr status "$@"
+git_status = git status -s "$@" || true
+bzr_status = bzr status --short "$@"
 cvs_status = cvs status "$@"
 hg_status  = hg status "$@"
 darcs_status = darcs whatsnew -ls "$@" || true
+fossil_status = fossil changes "$@"
 
 svn_commit = svn commit "$@"
 git_commit = git commit -a "$@" && git push --all
@@ -1549,11 +1696,13 @@ bzr_commit = bzr commit "$@" && bzr push
 cvs_commit = cvs commit "$@"
 hg_commit  = hg commit -m "$@" && hg push
 darcs_commit = darcs record -a -m "$@" && darcs push -a
+fossil_commit = fossil commit "$@"
 
 git_record = git commit -a "$@"
 bzr_record = bzr commit "$@"
 hg_record  = hg commit -m "$@"
 darcs_record = darcs record -a -m "$@"
+fossil_record = fossil commit "$@"
 
 svn_push = :
 git_push = git push "$@"
@@ -1561,6 +1710,7 @@ bzr_push = bzr push "$@"
 cvs_push = :
 hg_push = hg push "$@"
 darcs_push = darcs push -a "$@"
+fossil_push = fossil push "$@"
 
 svn_diff = svn diff "$@"
 git_diff = git diff "$@"
@@ -1568,6 +1718,7 @@ bzr_diff = bzr diff "$@"
 cvs_diff = cvs diff "$@"
 hg_diff  = hg diff "$@"
 darcs_diff = darcs diff -u "$@"
+fossil_diff = fossil diff "$@"
 
 svn_log = svn log "$@"
 git_log = git log "$@"
@@ -1576,6 +1727,9 @@ cvs_log = cvs log "$@"
 hg_log  = hg log "$@"
 darcs_log = darcs changes "$@"
 git_bare_log = git log "$@"
+fossil_log = fossil timeline "$@"
+
+run = "$@"
 
 svn_register =
 	url=`LC_ALL=C svn info . | grep -i '^URL:' | cut -d ' ' -f 2`
@@ -1621,6 +1775,11 @@ git_bare_register =
 	fi
 	echo "Registering git url: $url in $MR_CONFIG"
 	mr -c "$MR_CONFIG" config "`pwd`" checkout="git clone --bare '$url' '$MR_REPO'"
+fossil_register =
+	url=`fossil remote-url`
+	repo=`fossil info | grep repository | sed -e 's/repository:*.//g' -e 's/ //g'`
+	echo "Registering fossil repository $url in $MR_CONFIG"
+	mr -c "$MR_CONFIG" config "`pwd`" checkout="mkdir -p '$MR_REPO' && cd '$MR_REPO' && fossil open '$repo'"
 
 svn_trusted_checkout = svn co $url $repo
 svn_alt_trusted_checkout = svn checkout $url $repo
@@ -1630,6 +1789,7 @@ bzr_trusted_checkout = bzr clone $url $repo
 hg_trusted_checkout = hg clone $url $repo
 darcs_trusted_checkout = darcs get $url $repo
 git_bare_trusted_checkout = git clone --bare $url $repo
+# fossil: messy to do
 
 
 help =