X-Git-Url: https://git.madduck.net/code/myrepos.git/blobdiff_plain/832fadc6fc84658766e966137633bcfff6aa35c2..ebc88f26c7cbe202c2738bfc30423280c80f172b:/mr

diff --git a/mr b/mr
index 2bb3efa..17f9585 100755
--- a/mr
+++ b/mr
@@ -16,10 +16,14 @@ B<mr> [options] commit [-m "message"]
 
 B<mr> [options] record [-m "message"]
 
+B<mr> [options] push
+
 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]
@@ -47,7 +51,9 @@ and work on only that repository,
 
 B<mr> is configured by .mrconfig files, which list the repositories. It
 starts by reading the .mrconfig file in your home directory, and this can
-in turn chain load .mrconfig files from repositories.
+in turn chain load .mrconfig files from repositories. It also automatically
+looks for a .mrconfig file in the current directory, or in one of its
+parent directories.
 
 These predefined commands should be fairly familiar to users of any revision
 control system:
@@ -98,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:
@@ -126,7 +136,7 @@ repository in the current directory is registered, or you can specify a
 directory to register.
 
 The mrconfig file that is modified is chosen by either the -c option, or by
-looking for the closest known one at or below the current directory.
+looking for the closest known one at or in a parent of the current directory.
 
 =item config
 
@@ -147,8 +157,8 @@ To see the built-in library of shell functions contained in mr:
 
   mr config DEFAULT lib
 
-The ~/.mrconfig file is used by default. To use a different config file,
-use the -c option.
+The mrconfig file that is used is chosen by either the -c option, or by
+looking for the closest known one at or in a parent of the current directory.
 
 =item offline
 
@@ -198,14 +208,9 @@ the current working directory.
 
 =item --config mrconfig
 
-Use the specified mrconfig file. The default is B<~/.mrconfig>
-
-=item -p
-
-=item --path
-
-Search in the current directory, and its parent directories and use
-the first B<.mrconfig> found, instead of the default B<~/.mrconfig>.
+Use the specified mrconfig file. The default is to use both B<~/.mrconfig>
+as well as look for a .mrconfig file in the current directory, or in one
+of its parent directories.
 
 =item -v
 
@@ -217,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
 
@@ -270,6 +277,12 @@ a good speedup in updates without loading the machine too much.
 Trust all mrconfig files even if they are not listed in ~/.mrtrust.
 Use with caution.
 
+=item -p
+
+=item --path
+
+This obsolete flag is ignored.
+
 =back
 
 =head1 MRCONFIG FILES
@@ -398,26 +411,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
 
@@ -425,9 +440,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 <joey@kitenet.net>
+Copyright 2007-2011 Joey Hess <joey@kitenet.net>
 
 Licensed under the GNU GPL version 2 or higher.
 
@@ -460,7 +479,8 @@ my $no_chdir=0;
 my $jobs=1;
 my $trust_all=0;
 my $directory=getcwd();
-$ENV{MR_CONFIG}="$ENV{HOME}/.mrconfig";
+
+$ENV{MR_CONFIG}=find_mrconfig();
 
 # globals :-(
 my %config;
@@ -609,23 +629,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";
@@ -681,7 +714,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";
@@ -919,22 +962,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 (<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};
@@ -1018,6 +1055,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;
@@ -1091,7 +1133,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;
@@ -1118,10 +1160,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";
 				}
 			}
 
@@ -1400,7 +1442,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: $!";
 }
@@ -1479,7 +1521,7 @@ sub expandaction {
 	return $action;
 }
 
-sub find_nearest_mrconfig {
+sub find_mrconfig {
 	my $dir=getcwd();
 	while (length $dir) {
 		if (-e "$dir/.mrconfig") {
@@ -1487,7 +1529,7 @@ sub find_nearest_mrconfig {
 		}
 		$dir=~s/\/[^\/]*$//;
 	}
-	die "no .mrconfig found in path\n";
+	return "$ENV{HOME}/.mrconfig";
 }
 
 sub getopts {
@@ -1496,7 +1538,7 @@ sub getopts {
 	my $result=GetOptions(
 		"d|directory=s" => sub { $directory=abs_path($_[1]) },
 		"c|config=s" => sub { $ENV{MR_CONFIG}=$_[1]; $config_overridden=1 },
-		"p|path" => sub { $ENV{MR_CONFIG}=find_nearest_mrconfig(); $config_overridden=1 },
+		"p|path" => sub { }, # now default, ignore
 		"v|verbose" => \$verbose,
 		"q|quiet" => \$quiet,
 		"s|stats" => \$stats,
@@ -1546,9 +1588,6 @@ sub exitstats {
 	if (@failed) {
 		exit 1;
 	}
-	elsif (! @ok && @skipped) {
-		exit 1;
-	}
 	else {
 		exit 0;
 	}
@@ -1559,6 +1598,7 @@ sub main {
 	init();
 
 	startingconfig();
+	loadconfig("$ENV{HOME}/.mrconfig");
 	loadconfig($ENV{MR_CONFIG});
 	#use Data::Dumper; print Dumper(\%config);
 	
@@ -1679,6 +1719,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