X-Git-Url: https://git.madduck.net/code/myrepos.git/blobdiff_plain/1c1951da6ff532d6d5961858181a87d34b78b346..48204df369a5931a4bf3c9e153e54727ada7c9f1:/mr?ds=inline diff --git a/mr b/mr index b503fa7..38bf2b7 100755 --- a/mr +++ b/mr @@ -415,9 +415,13 @@ 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 -command, this can be a useful way to define shell functions for other commands -to use. +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. + +Unlike most other parameters, this can be specified multiple times, in +which case the chunks of shell code are accumulatively concatenated +together. =item fixups @@ -426,14 +430,7 @@ 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 +=item VCS_action When looking for a command to run for a given action, mr first looks for a parameter with the same name as the action. If that is not found, it @@ -447,6 +444,21 @@ the action that is performed for a given version control system, you can override these VCS specific actions. To add a new version control system, you can just add VCS specific actions for it. +=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. + +=item _append + +Any parameter can be suffixed with C<_append>, to add an additional value +to the existing value of the parameter. In this way, actions +can be constructed accumulatively. + +=back + =head1 UNTRUSTED MRCONFIG FILES Since mrconfig files can contain arbitrary shell commands, they can do @@ -527,6 +539,42 @@ my (@ok, @failed, @skipped); main(); +sub shellquote { + my $i=shift; + $i=~s/'/'"'"'/g; + return "'$i'"; +} + +# Runs a shell command using a supplied function. +# The lib will be included in the shell command line, and any params +# will be available in the shell as $1, $2, etc. +my $lastlib; +sub runsh { + my ($action, $topdir, $subdir, $command, $params, $runner) = @_; + + # optimisation: avoid running the shell for true and false + if ($command =~ /^\s*true\s*$/) { + $?=0; + return 0; + } + elsif ($command =~ /^\s*false\s*$/) { + $?=0; + return 1; + } + + my $quotedparams=join(" ", (map { shellquote($_) } @$params)); + my $lib=exists $config{$topdir}{$subdir}{lib} ? + $config{$topdir}{$subdir}{lib}."\n" : ""; + if ($verbose && (! defined $lastlib || $lastlib ne $lib)) { + print "mr library now: >>$lib<<\n"; + $lastlib=$lib; + } + my $shellcode="set -e;".$lib. + "my_sh(){ $command\n }; my_sh $quotedparams"; + print "mr $action: running $action >>$command<<\n" if $verbose; + $runner->($shellcode); +} + my %vcs; sub vcs_test { my ($action, $dir, $topdir, $subdir) = @_; @@ -535,7 +583,7 @@ sub vcs_test { return $vcs{$dir}; } - my $test="set -e\n"; + my $test=""; foreach my $vcs_test ( sort { length $a <=> length $b @@ -546,12 +594,13 @@ sub vcs_test { $test="my_$vcs_test() {\n$config{$topdir}{$subdir}{$vcs_test}\n}\n".$test; $test.="if my_$vcs_test; then echo $vcs; fi\n"; } - $test=$config{$topdir}{$subdir}{lib}."\n".$test - if exists $config{$topdir}{$subdir}{lib}; - - print "mr $action: running vcs test >>$test<<\n" if $verbose; - my $vcs=`$test`; - chomp $vcs; + + my $vcs=runsh "vcs test", $topdir, $subdir, $test, [], sub { + my $sh=shift; + my $ret=`$sh`; + chomp $ret; + return $ret; + }; if ($vcs=~/\n/s) { $vcs=~s/\n/, /g; print STDERR "mr $action: found multiple possible repository types ($vcs) for ".fulldir($topdir, $subdir)."\n"; @@ -597,8 +646,6 @@ sub action { 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/); @@ -609,10 +656,9 @@ sub action { 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); + my $ret=runsh "$testname test", $topdir, $subdir, + $testcommand, [$action], + sub { system(shift()) }; if ($ret != 0) { if (($? & 127) == 2) { print STDERR "mr $action: interrupted\n"; @@ -689,22 +735,22 @@ sub action { 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); - print "mr $action: running >>$command<<\n" if $verbose; - my $ret; - if ($quiet) { - my $output = qx/$command 2>&1/; - $ret = $?; - if ($ret != 0) { - print "$actionmsg\n"; - print STDERR $output; - } - } - else { - $ret=system($command); - } + my $ret=runsh $action, $topdir, $subdir, + $command, \@ARGV, sub { + my $sh=shift; + if ($quiet) { + my $output = qx/$sh 2>&1/; + my $ret = $?; + if ($ret != 0) { + print "$actionmsg\n"; + print STDERR $output; + } + return $ret; + } + else { + system($sh); + } + }; if ($ret != 0) { if (($? & 127) == 2) { print STDERR "mr $action: interrupted\n"; @@ -755,22 +801,20 @@ sub hook { 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); - } + my $ret=runsh $hook, $topdir, $subdir, $command, [], sub { + my $sh=shift; + if ($quiet) { + my $output = qx/$sh 2>&1/; + my $ret = $?; + if ($ret != 0) { + print STDERR $output; + } + return $ret; + } + else { + system($sh); + } + }; if ($ret != 0) { if (($? & 127) == 2) { print STDERR "mr $hook: interrupted\n"; @@ -1107,20 +1151,6 @@ 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; @@ -1185,30 +1215,43 @@ sub loadconfig { # Keep track of the current line in the config file; # when a file is included track the current line from the include. - my $line=0; + my $lineno=0; my $included=undef; - my $includeline=0; + + my $line; my $nextline = sub { if ($included) { - $includeline++; $included--; } else { $included=undef; - $includeline=0; - $line++; + $lineno++; } - my $l=shift @lines; - chomp $l; - return $l + $line=shift @lines; + chomp $line; + return $line; }; my $lineerror = sub { my $msg=shift; if (defined $included) { - die "mr: $f line $line include line $includeline: $msg\n"; + die "mr: $msg at $f line $lineno, included line: $line\n"; + } + else { + die "mr: $msg at $f line $lineno\n"; + } + }; + my $trusterror = sub { + my $msg=shift; + my ($err, $file, $lineno, $url)=@_; + + if (defined $bootstrap_url) { + die "mr: $err in untrusted $bootstrap_url line $lineno\n". + "(To trust this url, --trust-all can be used; but please use caution;\n". + "this can allow arbitrary code execution!)\n"; } else { - die "mr: $f line $line: $msg\n"; + die "mr: $err in untrusted $file line $lineno\n". + "(To trust this file, list it in ~/.mrtrust.)\n"; } }; @@ -1216,7 +1259,7 @@ sub loadconfig { $_=$nextline->(); if (! $trusted && /[[:cntrl:]]/) { - trusterror("mr: illegal control character", $f, $line, $bootstrap_url); + $trusterror->("illegal control character"); } next if /^\s*\#/ || /^\s*$/; @@ -1227,7 +1270,7 @@ sub loadconfig { if (! is_trusted_repo($section) || $section eq 'ALIAS' || $section eq 'DEFAULT') { - trusterror("mr: illegal section \"[$section]\"", $f, $line, $bootstrap_url) + $trusterror->("illegal section \"[$section]\""); } } $section=expandenv($section) if $trusted; @@ -1254,7 +1297,7 @@ sub loadconfig { # 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); + $trusterror->("illegal checkout command \"$value\""); } } elsif ($parameter eq 'order') { @@ -1266,7 +1309,7 @@ sub loadconfig { # safe. } else { - trusterror("mr: illegal setting \"$parameter=$value\"", $f, $line, $bootstrap_url); + $trusterror->("illegal setting \"$parameter=$value\""); } } @@ -1287,8 +1330,8 @@ sub loadconfig { if ($section eq 'ALIAS') { $alias{$parameter}=$value; } - elsif ($parameter eq 'lib') { - $config{$dir}{$section}{lib}.=$value."\n"; + elsif ($parameter eq 'lib' or $parameter =~ s/_append$//) { + $config{$dir}{$section}{$parameter}.="\n".$value."\n"; } else { $config{$dir}{$section}{$parameter}=$value;