X-Git-Url: https://git.madduck.net/code/myrepos.git/blobdiff_plain/de3cf4eb2025f65ee6b809277adbcaa287345da9..b7a3b1615f17991e400b5fa93c7e8a8cfffd348c:/mr?ds=sidebyside diff --git a/mr b/mr index 4350dad..41a5362 100755 --- a/mr +++ b/mr @@ -16,6 +16,8 @@ B [options] commit [-m "message"] B [options] record [-m "message"] +B [options] push + B [options] diff B [options] log @@ -370,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. +=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 @@ -384,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. -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 UNTRUSTED MRCONFIG FILES Since mrconfig files can contain arbitrary shell commands, they can do 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 @@ -411,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. +=head1 EXIT STATUS + +mr returns nonzero if a command failed in any of the repositories. + =head1 AUTHOR -Copyright 2007-2010 Joey Hess +Copyright 2007-2011 Joey Hess Licensed under the GNU GPL version 2 or higher. @@ -484,7 +506,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) { @@ -517,13 +539,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; @@ -537,7 +566,7 @@ sub action { $dir=~s/^(.*)\/[^\/]+\/?$/$1/; } } - elsif ($action =~ /update/) { + elsif ($is_update) { if (! -d $dir) { return action("checkout", $dir, $topdir, $subdir); } @@ -579,23 +608,27 @@ 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 { if (! $no_chdir) { - print "mr $action: $topdir$subdir\n" unless $quiet; + print "mr $action: $fulldir\n" unless $quiet; } 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); @@ -628,16 +661,49 @@ 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=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; @@ -861,22 +927,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 () { - chomp; - s/^~\//$ENV{HOME}\//; - $trusted{abs_path($_)}=1; + if (open (TRUST, "<", $trustfile)) { + while () { + chomp; + s/^~\//$ENV{HOME}\//; + $trusted{abs_path($_)}=1; + } + close TRUST; } - close TRUST; } return $trusted{$config}; @@ -960,6 +1020,11 @@ sub is_trusted_checkout { return 0; } +sub trusterror { + die shift()."\n". + "(To trust this file, list it in ~/.mrtrust.)\n"; +} + my %loaded; sub loadconfig { my $f=shift; @@ -1033,7 +1098,7 @@ 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]\" in untrusted $f line $line"; } } $section=expandenv($section) if $trusted; @@ -1060,10 +1125,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\" in untrusted $f line $line"; } 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"; } } @@ -1488,9 +1553,6 @@ sub exitstats { if (@failed) { exit 1; } - elsif (! @ok && @skipped) { - exit 1; - } else { exit 0; } @@ -1559,12 +1621,12 @@ git_test = test -d "$MR_REPO"/.git 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 && test "`GIT_CONFIG="$MR_REPO"/config git config --get core.bare`" = true -darcs_test = test -d "$MR_REPO"/_darcs svn_update = svn update "$@" git_update = git pull "$@" @@ -1594,7 +1656,7 @@ git_record = git commit -a "$@" bzr_record = bzr commit "$@" hg_record = hg commit -m "$@" darcs_record = darcs record -a -m "$@" -fossil_record = git commit "$@" +fossil_record = fossil commit "$@" svn_push = : git_push = git push "$@" @@ -1667,7 +1729,7 @@ git_bare_register = 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` + 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'"