]> 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:

Fix display when absolute directories are configured in mrconfig.
[code/myrepos.git] / mr
diff --git a/mr b/mr
index f7509c90b715fb211d02280296b47dc0f91fbd3f..2bb3efa38c77a4ddc6bf0169d2c1077e5c21c1a9 100755 (executable)
--- a/mr
+++ b/mr
@@ -37,8 +37,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
@@ -115,9 +115,6 @@ specified, the current directory will be used.
 If the .mrconfig file includes a repository named ".", that
 is checked out into the top of the specified directory.
 
 If the .mrconfig file includes a repository named ".", that
 is checked out into the top of the specified directory.
 
-To bootstrap from a SSL url with an untrusted certificate, you may
-need to specify the -k option.
-
 =item list (or ls)
 
 List the repositories that mr will act on.
 =item list (or ls)
 
 List the repositories that mr will act on.
@@ -275,7 +272,7 @@ Use with caution.
 
 =back
 
 
 =back
 
-=head1 "MRCONFIG FILES"
+=head1 MRCONFIG FILES
 
 Here is an example .mrconfig file:
 
 
 Here is an example .mrconfig file:
 
@@ -373,6 +370,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
@@ -392,10 +403,10 @@ 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.
 
 or even to add other commands for 'mr online' to run. If the file is
 present, mr assumes it is in offline mode.
 
-=head1 "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
 inside a repository that your main ~/.mrconfig checks out and chains to. To
 avoid worries about evil commands in a mrconfig file, mr
 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
@@ -416,7 +427,7 @@ the documentation in the files for details about using them.
 
 =head1 AUTHOR
 
 
 =head1 AUTHOR
 
-Copyright 2007-2009 Joey Hess <joey@kitenet.net>
+Copyright 2007-2010 Joey Hess <joey@kitenet.net>
 
 Licensed under the GNU GPL version 2 or higher.
 
 
 Licensed under the GNU GPL version 2 or higher.
 
@@ -487,7 +498,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) {
@@ -520,13 +531,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;
 
@@ -540,7 +558,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);
                }
@@ -582,23 +600,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);
@@ -631,16 +653,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;
@@ -790,6 +845,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;
@@ -798,7 +862,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=~/\/$/;
@@ -818,7 +882,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=~/\/$/;
@@ -1031,6 +1095,12 @@ sub loadconfig {
                                }
                        }
                        $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;
@@ -1067,12 +1137,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;
                        }
@@ -1353,7 +1417,7 @@ 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();
-       my @curlargs = ("curl", "-A", "mr", "-s", $url, "-o", $tmpconfig);
+       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;
        push(@curlargs, "-k") if $insecure;
        my $curlstatus = system(@curlargs);
        die "mr bootstrap: invalid SSL certificate for $url (consider -k)\n" if $curlstatus >> 8 == 60;
@@ -1436,7 +1500,7 @@ sub getopts {
                "v|verbose" => \$verbose,
                "q|quiet" => \$quiet,
                "s|stats" => \$stats,
                "v|verbose" => \$verbose,
                "q|quiet" => \$quiet,
                "s|stats" => \$stats,
-               "k|insecure" => \$insecure,   
+               "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,
@@ -1530,7 +1594,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
@@ -1541,10 +1605,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
        }
 
@@ -1554,6 +1618,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 &&
@@ -1565,13 +1630,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
@@ -1579,11 +1646,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 "$@"
@@ -1591,6 +1660,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 "$@"
@@ -1598,6 +1668,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 "$@"
@@ -1606,6 +1677,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`
@@ -1651,6 +1723,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
@@ -1660,6 +1737,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 =