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

Add support for skip = lazy, a mode where mr only operates on repositories that are...
[code/myrepos.git] / mr
diff --git a/mr b/mr
index 3f04b5b95041eb9b4e8d24458c64cdb2b45d1a3d..d3f8efb0a750dba737ad9463fa9bf3d594309e72 100755 (executable)
--- a/mr
+++ b/mr
@@ -22,6 +22,8 @@ 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]
@@ -102,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:
@@ -216,7 +222,9 @@ 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
 
@@ -282,7 +290,7 @@ This obsolete flag is ignored.
 Here is an example .mrconfig file:
 
   [src]
-  checkout = svn co svn://svn.example.com/src/trunk src
+  checkout = svn checkout svn://svn.example.com/src/trunk src
   chain = true
 
   [src/linux-2.6]
@@ -309,14 +317,18 @@ will be passed through the shell for expansion. For example,
 
 Within a section, each parameter defines a shell command to run to handle a
 given action. mr contains default handlers for "update", "status",
-"commit", and other standard actions. Normally you only need to specify what
-to do for "checkout".
+"commit", and other standard actions.
+
+Normally you only need to specify what to do for "checkout". Here you
+specify the command to run in order to create a checkout of the repository.
+The command will be run in the parent directory, and must create the
+repository's directory. So use "git clone", "svn checkout", "bzr branch"
+or "bzr checkout" (for a bound branch), etc.
 
 Note that these shell commands are run in a "set -e" shell
 environment, where any additional parameters you pass are available in
-"$@". The "checkout" command is run in the parent of the repository
-directory, since the repository isn't checked out yet. All other commands
-are run inside the repository, though not necessarily at the top of it.
+"$@". All commands other than "checkout" are run inside the repository,
+though not necessarily at the top of it.
 
 The "MR_REPO" environment variable is set to the path to the top of the
 repository. (For the "register" action, "MR_REPO" is instead set to the 
@@ -327,6 +339,9 @@ The "MR_CONFIG" environment variable is set to the .mrconfig file
 that defines the repo being acted on, or, if the repo is not yet in a config
 file, the .mrconfig file that should be modified to register the repo.
 
+The "MR_ACTION" environment variable is set to the command being run
+(update, checkout, etc).
+
 A few parameters have special meanings:
 
 =over 4
@@ -337,13 +352,15 @@ If the "skip" parameter is set and its command returns true, then B<mr>
 will skip acting on that repository. The command is passed the action
 name in $1.
 
-Here are two examples. The first skips the repo unless
+Here are three examples. The first skips the repo unless
 mr is run by joey. The second uses the hours_since function
 (included in mr's built-in library) to skip updating the repo unless it's
-been at least 12 hours since the last update.
+been at least 12 hours since the last update. The third skips operating
+on the repo unless it already exists (use mr checkout to enable the repo).
 
   skip = test `whoami` != joey
   skip = [ "$1" = update ] && ! hours_since "$1" 12
+  skip = lazy
 
 =item order
 
@@ -369,6 +386,14 @@ part of the including file.
 Unlike all other parameters, this parameter does not need to be placed
 within a section.
 
+=item deleted
+
+If the "deleted" parameter is set and its command returns true, then
+B<mr> will treat the repository as deleted. It won't ever actually delete
+the repository, but it will warn if it sees the repository's directory.
+This is useful when one mrconfig file is shared amoung multiple machines,
+to keep track of and remember to delete old repositories.
+
 =item lib
 
 The "lib" parameter can specify some shell code that will be run before each
@@ -559,6 +584,38 @@ sub action {
        my $is_update=($action =~ /update/);
 
        $ENV{MR_REPO}=$dir;
+       $ENV{MR_ACTION}=$action;
+       
+       foreach my $testname ("skip", "deleted") {
+               my $testcommand=findcommand($testname, $dir, $topdir, $subdir, $is_checkout);
+
+               if (defined $testcommand) {
+                       my $test="set -e;".$lib.
+                               "my_action(){ $testcommand\n }; my_action '$action'";
+                       print "mr $action: running $testname test >>$test<<\n" if $verbose;
+                       my $ret=system($test);
+                       if ($ret != 0) {
+                               if (($? & 127) == 2) {
+                                       print STDERR "mr $action: interrupted\n";
+                                       return ABORT;
+                               }
+                               elsif ($? & 127) {
+                                       print STDERR "mr $action: $testname test received signal ".($? & 127)."\n";
+                                       return ABORT;
+                               }
+                       }
+                       if ($ret >> 8 == 0) {
+                               if ($testname eq "deleted") {
+                                       if (-d $dir) {
+                                               print STDERR "mr error: $dir should be deleted yet still exists\n";
+                                               return FAILED;
+                                       }
+                               }
+                               print "mr $action: skip $dir skipped\n" if $verbose;
+                               return SKIPPED;
+                       }
+               }
+       }
 
        if ($is_checkout) {
                if (! $force_checkout) {
@@ -576,30 +633,8 @@ sub action {
                }
        }
 
-       my $skiptest=findcommand("skip", $dir, $topdir, $subdir, $is_checkout);
        my $command=findcommand($action, $dir, $topdir, $subdir, $is_checkout);
 
-       if (defined $skiptest) {
-               my $test="set -e;".$lib.
-                       "my_action(){ $skiptest\n }; my_action '$action'";
-               print "mr $action: running skip test >>$test<<\n" if $verbose;
-               my $ret=system($test);
-               if ($ret != 0) {
-                       if (($? & 127) == 2) {
-                               print STDERR "mr $action: interrupted\n";
-                               return ABORT;
-                       }
-                       elsif ($? & 127) {
-                               print STDERR "mr $action: skip test received signal ".($? & 127)."\n";
-                               return ABORT;
-                       }
-               }
-               if ($ret >> 8 == 0) {
-                       print "mr $action: $dir skipped per config file\n" if $verbose;
-                       return SKIPPED;
-               }
-       }
-
        if ($is_checkout && ! -d $dir) {
                print "mr $action: creating parent directory $dir\n" if $verbose;
                system("mkdir", "-p", $dir);
@@ -621,23 +656,36 @@ sub action {
                }
        }
        else {
+               my $actionmsg;
                if (! $no_chdir) {
-                       print "mr $action: $fulldir\n" unless $quiet;
+                       $actionmsg="mr $action: $fulldir";
                }
                else {
                        my $s=$directory;
                        $s=~s/^\Q$fulldir\E\/?//;
-                       print "mr $action: $fulldir (in subdir $s)\n" unless $quiet;
+                       $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";
@@ -693,7 +741,17 @@ sub hook {
        my $shell="set -e;".$lib.
                "my_hook(){ $command\n }; my_hook";
        print "mr $hook: running >>$shell<<\n" if $verbose;
-       my $ret=system($shell);
+       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";
@@ -703,6 +761,9 @@ sub hook {
                        print STDERR "mr $hook: received signal ".($? & 127)."\n";
                        return ABORT;
                }
+               else {
+                       return FAILED;
+               }
        }
 
        return OK;
@@ -1011,7 +1072,7 @@ sub is_trusted_checkout {
                                        is_trusted_repo($words[$c])
                                );
                        }
-                       elsif (defined $words[$c] && $twords[$c] eq $words[$c]) {
+                       elsif (defined $words[$c] && $words[$c]=~/^($twords[$c])$/) {
                                $match=1;
                        }
                        else {
@@ -1025,14 +1086,24 @@ sub is_trusted_checkout {
 }
 
 sub trusterror {
-       die shift()."\n".
-               "(To trust this file, list it in ~/.mrtrust.)\n";
+       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;
 
@@ -1044,10 +1115,6 @@ sub loadconfig {
                $trusted=1;
        }
        else {
-               if (! -e $f) {
-                       return;
-               }
-
                my $absf=abs_path($f);
                if ($loaded{$absf}) {
                        return;
@@ -1082,6 +1149,10 @@ sub loadconfig {
                        }
                }
                
+               if (! -e $f) {
+                       return;
+               }
+
                print "mr: loading config $f\n" if $verbose;
                open($in, "<", $f) || die "mr: open $f: $!\n";
        }
@@ -1102,7 +1173,7 @@ sub loadconfig {
                                if (! is_trusted_repo($section) ||
                                    $section eq 'ALIAS' ||
                                    $section eq 'DEFAULT') {
-                                       trusterror "mr: illegal section \"[$section]\" in untrusted $f line $line";
+                                       trusterror("mr: illegal section \"[$section]\"", $f, $line, $bootstrap_url)
                                }
                        }
                        $section=expandenv($section) if $trusted;
@@ -1126,13 +1197,23 @@ sub loadconfig {
                        }
 
                        if (! $trusted) {
-                               # Untrusted files can only contain checkout
-                               # parameters.
-                               if ($parameter ne 'checkout') {
-                                       trusterror "mr: illegal setting \"$parameter=$value\" in untrusted $f line $line";
+                               # Untrusted files can only contain a few
+                               # settings in specific known-safe formats.
+                               if ($parameter eq 'checkout') {
+                                       if (! is_trusted_checkout($value)) {
+                                               trusterror("mr: illegal checkout command \"$value\"", $f, $line, $bootstrap_url);
+                                       }
                                }
-                               if (! is_trusted_checkout($value)) {
-                                       trusterror "mr: illegal checkout command \"$value\" in untrusted $f line $line";
+                               elsif ($parameter eq 'order') {
+                                       # not interpreted as a command, so
+                                       # safe.
+                               }
+                               elsif ($value eq 'true' || $value eq 'false') {
+                                       # skip=true , deleted=true etc are
+                                       # safe.
+                               }
+                               else {
+                                       trusterror("mr: illegal setting \"$parameter=$value\"", $f, $line, $bootstrap_url);
                                }
                        }
 
@@ -1163,21 +1244,26 @@ sub loadconfig {
                                        $knownactions{$parameter}=1;
                                }
                                if ($parameter eq 'chain' &&
-                                   length $dir && $section ne "DEFAULT" &&
-                                   -e $dir.$section."/.mrconfig") {
-                                       my $ret=system($value);
-                                       if ($ret != 0) {
-                                               if (($? & 127) == 2) {
-                                                       print STDERR "mr: chain test interrupted\n";
-                                                       exit 2;
+                                   length $dir && $section ne "DEFAULT") {
+                                       my $chaindir="$section";
+                                       if ($chaindir !~ m!/!) {
+                                               $chaindir=$dir.$chaindir;
+                                       }
+                                       if (-e "$chaindir/.mrconfig") {
+                                               my $ret=system($value);
+                                               if ($ret != 0) {
+                                                       if (($? & 127) == 2) {
+                                                               print STDERR "mr: chain test interrupted\n";
+                                                               exit 2;
+                                                       }
+                                                       elsif ($? & 127) {
+                                                               print STDERR "mr: chain test received signal ".($? & 127)."\n";
+                                                       }
                                                }
-                                               elsif ($? & 127) {
-                                                       print STDERR "mr: chain test received signal ".($? & 127)."\n";
+                                               else {
+                                                       push @toload, ["$chaindir/.mrconfig", $chaindir];
                                                }
                                        }
-                                       else {
-                                               push @toload, $dir.$section."/.mrconfig";
-                                       }
                                }
                        }
                }
@@ -1186,8 +1272,8 @@ sub loadconfig {
                }
        }
 
-       foreach (@toload) {
-               loadconfig($_);
+       foreach my $c (@toload) {
+               loadconfig(@$c);
        }
 }
 
@@ -1411,7 +1497,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: $!";
 }
@@ -1443,7 +1529,7 @@ 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"};
 
@@ -1620,6 +1706,16 @@ lib =
                        return 0
                fi
        }
+       is_bzr_checkout() {
+               LANG=C bzr info | egrep -q '^Checkout'
+       }
+       lazy() {
+               if [ "$MR_ACTION" = checkout ] || [ -d "$MR_REPO" ]; then
+                       return 1
+               else
+                       return 0
+               fi
+       }
 
 svn_test = test -d "$MR_REPO"/.svn
 git_test = test -d "$MR_REPO"/.git
@@ -1635,7 +1731,12 @@ git_bare_test =
 
 svn_update = svn update "$@"
 git_update = git pull "$@"
-bzr_update = bzr merge --pull "$@"
+bzr_update = 
+       if is_bzr_checkout; then
+               bzr update "$@"
+       else
+               bzr merge --pull "$@"
+       fi
 cvs_update = cvs update "$@"
 hg_update  = hg pull "$@" && hg update "$@"
 darcs_update = darcs pull -a "$@"
@@ -1651,14 +1752,24 @@ fossil_status = fossil changes "$@"
 
 svn_commit = svn commit "$@"
 git_commit = git commit -a "$@" && git push --all
-bzr_commit = bzr commit "$@" && bzr push
+bzr_commit = 
+       if is_bzr_checkout; then
+               bzr commit "$@"
+       else
+               bzr commit "$@" && bzr push
+       fi
 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 "$@"
+bzr_record =
+       if is_bzr_checkout; then
+               bzr commit --local "$@"
+       else
+               bzr commit "$@"
+       fi
 hg_record  = hg commit -m "$@"
 darcs_record = darcs record -a -m "$@"
 fossil_record = fossil commit "$@"
@@ -1688,6 +1799,8 @@ 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`
        if [ -z "$url" ]; then
@@ -1708,7 +1821,7 @@ bzr_register =
                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 branch '$url' '$MR_REPO'"
 cvs_register =
        repo=`cat CVS/Repository`
        root=`cat CVS/Root`
@@ -1741,7 +1854,7 @@ fossil_register =
 svn_trusted_checkout = svn co $url $repo
 svn_alt_trusted_checkout = svn checkout $url $repo
 git_trusted_checkout = git clone $url $repo
-bzr_trusted_checkout = bzr clone $url $repo
+bzr_trusted_checkout = bzr checkout|clone|branch|get $url $repo
 # cvs: too hard
 hg_trusted_checkout = hg clone $url $repo
 darcs_trusted_checkout = darcs get $url $repo