]> git.madduck.net Git - code/myrepos.git/blobdiff - mr

madduck's git repository

Every one of the projects in this repository is available at the canonical URL git://git.madduck.net/madduck/pub/<projectpath> — see each project's metadata for the exact URL.

All patches and comments are welcome. Please squash your changes to logical commits before using git-format-patch and git-send-email to patches@git.madduck.net. If you'd read over the Git project's submission guidelines and adhered to them, I'd be especially grateful.

SSH access, as well as push access can be individually arranged.

If you use my repositories frequently, consider adding the following snippet to ~/.gitconfig and using the third clone URL listed for each project:

[url "git://git.madduck.net/madduck/"]
  insteadOf = madduck:

Trust flag day.
[code/myrepos.git] / mr
diff --git a/mr b/mr
index a431e8ac82ac989fdbfe1e5b1231e449d8508682..41a5362e95cec0f5f66f601fba2792c5e264f725 100755 (executable)
--- a/mr
+++ b/mr
@@ -16,6 +16,8 @@ B<mr> [options] commit [-m "message"]
 
 B<mr> [options] record [-m "message"]
 
 
 B<mr> [options] record [-m "message"]
 
+B<mr> [options] push
+
 B<mr> [options] diff
 
 B<mr> [options] log
 B<mr> [options] diff
 
 B<mr> [options] log
@@ -37,8 +39,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,
 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
 
 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
@@ -219,6 +221,12 @@ Be verbose.
 
 Be quiet.
 
 
 Be quiet.
 
+=item -k
+
+=item --insecure
+
+Accept untrusted SSL certificates when bootstrapping.
+
 =item -s
 
 =item --stats
 =item -s
 
 =item --stats
@@ -266,7 +274,7 @@ Use with caution.
 
 =back
 
 
 =back
 
-=head1 "MRCONFIG FILES"
+=head1 MRCONFIG FILES
 
 Here is an example .mrconfig file:
 
 
 Here is an example .mrconfig file:
 
@@ -364,6 +372,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.
 
 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
 =back
 
 When looking for a command to run for a given action, mr first looks for
@@ -378,26 +400,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.
 
 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
 
 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
 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
 
 
 =head1 EXTENSIONS
 
@@ -405,9 +429,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.
 
 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
 
 =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.
 
 
 Licensed under the GNU GPL version 2 or higher.
 
@@ -433,6 +461,7 @@ my $config_overridden=0;
 my $verbose=0;
 my $quiet=0;
 my $stats=0;
 my $verbose=0;
 my $quiet=0;
 my $stats=0;
+my $insecure=0;
 my $interactive=0;
 my $max_depth;
 my $no_chdir=0;
 my $interactive=0;
 my $max_depth;
 my $no_chdir=0;
@@ -477,7 +506,7 @@ sub rcs_test {
        chomp $rcs;
        if ($rcs=~/\n/s) {
                $rcs=~s/\n/, /g;
        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) {
                return undef;
        }
        if (! length $rcs) {
@@ -510,13 +539,20 @@ sub findcommand {
        }
 }
 
        }
 }
 
+sub fulldir {
+       my ($topdir, $subdir) = @_;
+       return $subdir =~ /^\// ? $subdir : $topdir.$subdir;
+}
+
 sub action {
        my ($action, $dir, $topdir, $subdir, $force_checkout) = @_;
 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');
        $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;
 
 
        $ENV{MR_REPO}=$dir;
 
@@ -530,7 +566,7 @@ sub action {
                        $dir=~s/^(.*)\/[^\/]+\/?$/$1/;
                }
        }
                        $dir=~s/^(.*)\/[^\/]+\/?$/$1/;
                }
        }
-       elsif ($action =~ /update/) {
+       elsif ($is_update) {
                if (! -d $dir) {
                        return action("checkout", $dir, $topdir, $subdir);
                }
                if (! -d $dir) {
                        return action("checkout", $dir, $topdir, $subdir);
                }
@@ -572,23 +608,27 @@ sub action {
        elsif (! defined $command) {
                my $rcs=rcs_test(@_);
                if (! defined $rcs) {
        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 {
                        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 {
                if (! $no_chdir) {
                        return SKIPPED;
                }
        }
        else {
                if (! $no_chdir) {
-                       print "mr $action: $topdir$subdir\n" unless $quiet;
+                       print "mr $action: $fulldir\n" unless $quiet;
                }
                else {
                        my $s=$directory;
                }
                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\/?//;
+                       print "mr $action: $fulldir (in subdir $s)\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);
                $command="set -e; ".$lib.
                        "my_action(){ $command\n }; my_action ".
                        join(" ", map { s/\//\/\//g; s/"/\"/g; '"'.$_.'"' } @ARGV);
@@ -621,16 +661,49 @@ sub action {
                        return FAILED;
                }
                else {
                        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;
                        }
 
                                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;
                }
        }
 }
 
                        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=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;
 # run actions on multiple repos, in parallel
 sub mrs {
        my $action=shift;
@@ -707,7 +780,7 @@ sub record {
 
        if ($ret == OK) {
                push @ok, $dir;
 
        if ($ret == OK) {
                push @ok, $dir;
-               print "\n";
+               print "\n" unless $quiet;
        }
        elsif ($ret == FAILED) {
                if ($interactive) {
        }
        elsif ($ret == FAILED) {
                if ($interactive) {
@@ -716,7 +789,7 @@ sub record {
                        system((getpwuid($<))[8], "-i");
                }
                push @failed, $dir;
                        system((getpwuid($<))[8], "-i");
                }
                push @failed, $dir;
-               print "\n";
+               print "\n" unless $quiet;
        }
        elsif ($ret == SKIPPED) {
                push @skipped, $dir;
        }
        elsif ($ret == SKIPPED) {
                push @skipped, $dir;
@@ -780,6 +853,15 @@ sub repolist {
        } @list;
 }
 
        } @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;
 # figure out which repos to act on
 sub selectrepos {
        my @repos;
@@ -788,7 +870,7 @@ sub selectrepos {
                my $subdir=$repo->{subdir};
 
                next if $subdir eq 'DEFAULT';
                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=~/\/$/;
                my $d=$directory;
                $dir.="/" unless $dir=~/\/$/;
                $d.="/" unless $d=~/\/$/;
@@ -808,7 +890,7 @@ sub selectrepos {
                        my $subdir=$repo->{subdir};
                        
                        next if $subdir eq 'DEFAULT';
                        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=~/\/$/;
                        my $d=$directory;
                        $dir.="/" unless $dir=~/\/$/;
                        $d.="/" unless $d=~/\/$/;
@@ -845,22 +927,16 @@ sub is_trusted_config {
 
        my $trustfile=$ENV{HOME}."/.mrtrust";
 
 
        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;
        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};
        }
 
        return $trusted{$config};
@@ -944,6 +1020,11 @@ sub is_trusted_checkout {
        return 0;
 }
 
        return 0;
 }
 
+sub trusterror {
+       die shift()."\n".
+               "(To trust this file, list it in ~/.mrtrust.)\n";
+}
+
 my %loaded;
 sub loadconfig {
        my $f=shift;
 my %loaded;
 sub loadconfig {
        my $f=shift;
@@ -1001,7 +1082,7 @@ sub loadconfig {
                open($in, "<", $f) || die "mr: open $f: $!\n";
        }
        my @lines=<$in>;
                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;
 
        my $section;
        my $line=0;
@@ -1017,10 +1098,16 @@ sub loadconfig {
                                if (! is_trusted_repo($section) ||
                                    $section eq 'ALIAS' ||
                                    $section eq 'DEFAULT') {
                                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]\" in untrusted $f line $line";
                                }
                        }
                        $section=expandenv($section) if $trusted;
                                }
                        }
                        $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;
                }
                elsif (/^(\w+)\s*=\s*(.*)/) {
                        my $parameter=$1;
@@ -1038,10 +1125,10 @@ sub loadconfig {
                                # Untrusted files can only contain checkout
                                # parameters.
                                if ($parameter ne 'checkout') {
                                # 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\" in untrusted $f line $line";
                                }
                                if (! is_trusted_checkout($value)) {
                                }
                                if (! is_trusted_checkout($value)) {
-                                       die "mr: illegal checkout command \"$value\" in untrusted $f line $line\n";
+                                       trusterror "mr: illegal checkout command \"$value\" in untrusted $f line $line";
                                }
                        }
 
                                }
                        }
 
@@ -1057,12 +1144,6 @@ sub loadconfig {
                        if (! defined $section) {
                                die "$f line $.: parameter ($parameter) not in section\n";
                        }
                        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;
                        }
                        if ($section eq 'ALIAS') {
                                $alias{$parameter}=$value;
                        }
@@ -1106,6 +1187,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
 sub modifyconfig {
        my $f=shift;
        # the section to modify or add
@@ -1336,9 +1424,11 @@ sub bootstrap {
        eval q{use File::Temp};
        die $@ if $@;
        my $tmpconfig=File::Temp->new();
        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);
 
        if (! -e $dir) {
                system("mkdir", "-p", $dir);
@@ -1354,7 +1444,7 @@ sub bootstrap {
                if exists $config{$topdir}{"."}{"checkout"};
 
        if (-e ".mrconfig") {
                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};
        }
        else {
                eval q{use File::Copy};
@@ -1362,8 +1452,14 @@ sub bootstrap {
                move($tmpconfig, ".mrconfig") || die "rename: $!";
        }
 
                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
 }
 
 # alias expansion and command stemming
@@ -1411,6 +1507,7 @@ sub getopts {
                "v|verbose" => \$verbose,
                "q|quiet" => \$quiet,
                "s|stats" => \$stats,
                "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,
                "i|interactive" => \$interactive,
                "n|no-recurse:i" => \$max_depth,
                "j|jobs:i" => \$jobs,
@@ -1451,28 +1548,29 @@ sub init {
                $ENV{MR_PATH}=$Bin."/".$Script;
        };
 }
                $ENV{MR_PATH}=$Bin."/".$Script;
        };
 }
+       
+sub exitstats {
+       if (@failed) {
+               exit 1;
+       }
+       else {
+               exit 0;
+       }
+}
 
 sub main {
        getopts();
        init();
 
 
 sub main {
        getopts();
        init();
 
-       loadconfig(\*DATA);
+       startingconfig();
        loadconfig($ENV{MR_CONFIG});
        #use Data::Dumper; print Dumper(\%config);
        
        my $action=expandaction(shift @ARGV);
        dispatch($action);
        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.
 }
 
 # Finally, some useful actions that mr knows about by default.
@@ -1500,7 +1598,7 @@ lib =
                if [ -z "$1" ] || [ -z "$2" ]; then
                        error "mr: usage: hours_since action num"
                fi
                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
                        if [ -e "$MR_REPO/$dir" ]; then
                                flagfile="$MR_REPO/$dir/.mr_last$1"
                                break
@@ -1511,10 +1609,10 @@ lib =
                fi
                delta=`perl -wle 'print -f shift() ? int((-M _) * 24) : 9999' "$flagfile"`
                if [ "$delta" -lt "$2" ]; then
                fi
                delta=`perl -wle 'print -f shift() ? int((-M _) * 24) : 9999' "$flagfile"`
                if [ "$delta" -lt "$2" ]; then
-                       exit 0
+                       return 1
                else
                        touch "$flagfile"
                else
                        touch "$flagfile"
-                       exit 1
+                       return 0
                fi
        }
 
                fi
        }
 
@@ -1524,6 +1622,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
 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 &&
 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 +1634,15 @@ bzr_update = bzr merge --pull "$@"
 cvs_update = cvs update "$@"
 hg_update  = hg pull "$@" && hg update "$@"
 darcs_update = darcs pull -a "$@"
 cvs_update = cvs update "$@"
 hg_update  = hg pull "$@" && hg update "$@"
 darcs_update = darcs pull -a "$@"
+fossil_update = fossil pull "$@"
 
 svn_status = svn status "$@"
 
 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
 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
 
 svn_commit = svn commit "$@"
 git_commit = git commit -a "$@" && git push --all
@@ -1549,11 +1650,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
 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 "$@"
 
 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 "$@"
 
 svn_push = :
 git_push = git push "$@"
@@ -1561,6 +1664,7 @@ bzr_push = bzr push "$@"
 cvs_push = :
 hg_push = hg push "$@"
 darcs_push = darcs push -a "$@"
 cvs_push = :
 hg_push = hg push "$@"
 darcs_push = darcs push -a "$@"
+fossil_push = fossil push "$@"
 
 svn_diff = svn diff "$@"
 git_diff = git diff "$@"
 
 svn_diff = svn diff "$@"
 git_diff = git diff "$@"
@@ -1568,6 +1672,7 @@ bzr_diff = bzr diff "$@"
 cvs_diff = cvs diff "$@"
 hg_diff  = hg diff "$@"
 darcs_diff = darcs diff -u "$@"
 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 "$@"
 
 svn_log = svn log "$@"
 git_log = git log "$@"
@@ -1576,6 +1681,7 @@ cvs_log = cvs log "$@"
 hg_log  = hg log "$@"
 darcs_log = darcs changes "$@"
 git_bare_log = git log "$@"
 hg_log  = hg log "$@"
 darcs_log = darcs changes "$@"
 git_bare_log = git log "$@"
+fossil_log = fossil timeline "$@"
 
 svn_register =
        url=`LC_ALL=C svn info . | grep -i '^URL:' | cut -d ' ' -f 2`
 
 svn_register =
        url=`LC_ALL=C svn info . | grep -i '^URL:' | cut -d ' ' -f 2`
@@ -1621,6 +1727,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'"
        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
 
 svn_trusted_checkout = svn co $url $repo
 svn_alt_trusted_checkout = svn checkout $url $repo
@@ -1630,6 +1741,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
 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 =
 
 
 help =