X-Git-Url: https://git.madduck.net/code/myrepos.git/blobdiff_plain/fb4c5ab3cf865b15d2092ccbc90aa5f7bd416c16..15c4f456e2c42b5653112ca3c0e86e0130316176:/mr?ds=sidebyside

diff --git a/mr b/mr
index b65e623..29e5fad 100755
--- a/mr
+++ b/mr
@@ -26,13 +26,11 @@ B<mr> [options] action [params ...]
 
 =head1 DESCRIPTION
 
-B<mr> is a Multiple Repository management tool. It allows you to register a
-set of repositories in a .mrconfig file, and then checkout, update, or
-perform other actions on the repositories as if they were one big
-respository.
-
-Any mix of revision control systems can be used with B<mr>, and you can
-define arbitrary actions for commands like "update", "checkout", or "commit".
+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 respository. It
+supports any combination of subversion, git, cvs, and bzr repositories, 
+and support for other revision control systems can easily be added.
 
 B<mr> cds into and operates on all registered repsitories at or below your
 working directory. Or, if you are in a subdirectory of a repository that
@@ -86,14 +84,19 @@ directory to register.
 
 =item config
 
-Modifies the mrconfig file. The next parameter is the name of the section
-to add or modify, and it is followed by one or more instances of
-"parameter=value". Use "parameter=" to remove a parameter. 
+Adds, modifies, removes, or prints a value from the mrconfig file. The next
+parameter is the name of the section the value is in. To add or modify
+values, use one or more instances of "parameter=value". Use "parameter=" to
+remove a parameter. Use just "parameter" to get the value of a parameter.
 
 For example, to add (or edit) a repository in src/foo:
 
   mr config src/foo checkout="svn co svn://example.com/foo/trunk foo"
 
+To show the command that mr uses to update the repository in src/foo:
+
+  mr config src/foo update
+
 =item help
 
 Displays this help.
@@ -142,10 +145,13 @@ Here is an example .mrconfig file:
   chain = true
 
   [src/linux-2.6]
-  checkout = git clone git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux-2.6.git
+  checkout = git clone git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux-2.6.git &&
+  	cd linux-2.6 &&
+  	git checkout -b mybranch origin/master
 
 The .mrconfig file uses a variant of the INI file format. Lines starting with
-"#" are comments. Lines ending with "\" are continued on to the next line.
+"#" are comments. Values can be continued to the following line by
+indenting the line with whitespace.
 
 The "DEFAULT" section allows setting default values for the sections that
 come after it.
@@ -178,7 +184,16 @@ A few parameters have special meanings:
 =item skip
 
 If the "skip" parameter is set and its command returns nonzero, then B<mr>
-will skip acting on that repository.
+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
+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.
+
+  skip = test $(whoami) != joey
+  skip = [ "$1" = update ] && [ $(hours_since "$1") -lt 12 ]
 
 =item chain
 
@@ -282,16 +297,26 @@ elsif ($action eq 'config') {
 			$section=$1;
 		}
 	}
-	my %fields;
+	my %changefields;
 	foreach (@ARGV) {
 		if (/^([^=]+)=(.*)$/) {
-			$fields{$1}=$2;
+			$changefields{$1}=$2;
 		}
 		else {
-			die "mr config: expected parameter=value, not \"$_\"\n";
+			my $found=0;
+			foreach my $topdir (sort keys %config) {
+				if (exists $config{$topdir}{$section} &&
+				    exists $config{$topdir}{$section}{$_}) {
+					print $config{$topdir}{$section}{$_}."\n";
+					$found=1;
+				}
+			}
+			if (! $found) {
+				die "mr $action: $section $_ not set\n";
+			}
 		}
 	}
-	modifyconfig($config, $section, %fields);
+	modifyconfig($config, $section, %changefields) if %changefields;
 	exit 0;
 }
 elsif ($action eq 'register') {
@@ -334,7 +359,7 @@ if (! @repos) {
 	$nochdir=1;
 }
 
-my (@failed, @successful, @skipped);
+my (@failed, @ok, @skipped);
 foreach my $repo (@repos) {
 	action($action, @$repo);
 }
@@ -378,7 +403,8 @@ sub action {
 	$ENV{MR_REPO}=$dir;
 
 	if (exists $config{$topdir}{$subdir}{skip}) {
-		my $test="set -e;".$lib.$config{$topdir}{$subdir}{skip};
+		my $test="set -e;".$lib.
+			"my_action(){ $config{$topdir}{$subdir}{skip}\n }; my_action '$action'";
 		print "mr $action: running skip test >>$test<<\n" if $verbose;
 		my $ret=system($test);
 		if ($ret >> 8 == 0) {
@@ -419,7 +445,7 @@ sub action {
 			}
 		}
 		else {
-			push @successful, $dir;
+			push @ok, $dir;
 		}
 
 		print "\n";
@@ -435,18 +461,18 @@ sub showstat {
 	}
 	return;
 }
-if (! @successful && ! @failed && ! @skipped) {
+if (! @ok && ! @failed && ! @skipped) {
 	die "mr $action: no repositories found to work on\n";
 }
 print "mr $action: finished (".join("; ",
-	showstat($#successful+1, "successful", "successful"),
+	showstat($#ok+1, "ok", "ok"),
 	showstat($#failed+1, "failed", "failed"),
 	showstat($#skipped+1, "skipped", "skipped"),
 ).")\n";
 if (@failed) {
 	exit 1;
 }
-elsif (! @successful && @skipped) {
+elsif (! @ok && @skipped) {
 	exit 1;
 }
 exit 0;
@@ -460,8 +486,8 @@ sub loadconfig {
 	my $in;
 	my $dir;
 	if (ref $f eq 'GLOB') {
-		$in=$f;	
 		$dir="";
+		$in=$f;	
 	}
 	else {
 		if (! -e $f) {
@@ -474,8 +500,6 @@ sub loadconfig {
 		}
 		$loaded{$absf}=1;
 
-		print "mr: loading config $f\n" if $verbose;
-		open($in, "<", $f) || die "mr: open $f: $!\n";
 		($dir)=$f=~/^(.*\/)[^\/]+$/;
 		if (! defined $dir) {
 			$dir=".";
@@ -491,22 +515,29 @@ sub loadconfig {
 				last;
 			}
 		}
+		
+		print "mr: loading config $f\n" if $verbose;
+		open($in, "<", $f) || die "mr: open $f: $!\n";
 	}
+	my @lines=<$in>;
+	close $in;
 
 	my $section;
-	while (<$in>) {
+	while (@lines) {
+		$_=shift @lines;
 		chomp;
 		next if /^\s*\#/ || /^\s*$/;
-		if (/^\s*\[([^\]]*)\]\s*$/) {
+		if (/^\[([^\]]*)\]\s*$/) {
 			$section=$1;
 		}
-		elsif (/^\s*(\w+)\s*=\s*(.*)/) {
+		elsif (/^(\w+)\s*=\s*(.*)/) {
 			my $parameter=$1;
 			my $value=$2;
 
-			# continuation line
-			while ($value=~/(.*)\\$/s) {
-				$value=$1."\n".<$in>;
+			# continued value
+			while (@lines && $lines[0]=~/^\s(.+)/) {
+				shift(@lines);
+				$value.="\n$1";
 				chomp $value;
 			}
 
@@ -540,7 +571,6 @@ sub loadconfig {
 			die "$f line $.: parse error\n";
 		}
 	}
-	close $in;
 
 	foreach (@toload) {
 		loadconfig($_);
@@ -564,6 +594,13 @@ sub modifyconfig {
 		close $in;
 	}
 
+	my $formatfield=sub {
+		my $field=shift;
+		my @value=split(/\n/, shift);
+
+		return "$field = ".shift(@value)."\n".
+			join("", map { "\t$_\n" } @value);
+	};
 	my $addfields=sub {
 		my @blanks;
 		while ($out[$#out] =~ /^\s*$/) {
@@ -572,6 +609,7 @@ sub modifyconfig {
 		foreach my $field (sort keys %changefields) {
 			if (length $changefields{$field}) {
 				push @out, "$field = $changefields{$field}\n";
+				delete $changefields{$field};
 			}
 		}
 		push @out, @blanks;
@@ -584,7 +622,7 @@ sub modifyconfig {
 		if (/^\s*\#/ || /^\s*$/) {
 			push @out, $_;
 		}
-		elsif (/^\s*\[([^\]]*)\]\s*$/) {
+		elsif (/^\[([^\]]*)\]\s*$/) {
 			if (defined $section && 
 			    $section eq $targetsection) {
 				$addfields->();
@@ -594,13 +632,14 @@ sub modifyconfig {
 
 			push @out, $_;
 		}
-		elsif (/^\s*(\w+)\s*=\s(.*)/) {
+		elsif (/^(\w+)\s*=\s(.*)/) {
 			my $parameter=$1;
 			my $value=$2;
 
-			# continuation line
-			while ($value=~/(.*\\)$/s) {
-				$value=$1."\n".shift(@lines);
+			# continued value
+			while (@lines && $lines[0]=~/^\s(.+)/) {
+				shift(@lines);
+				$value.="\n$1";
 				chomp $value;
 			}
 
@@ -613,19 +652,19 @@ sub modifyconfig {
 				}
 			}
 
-			push @out, "$parameter = $value\n";
+			push @out, $formatfield->($parameter, $value);
 		}
 	}
 
 	if (defined $section && 
 	    $section eq $targetsection) {
-			$addfields->();
+		$addfields->();
 	}
 	elsif (%changefields) {
 		push @out, "\n[$targetsection]\n";
 		foreach my $field (sort keys %changefields) {
 			if (length $changefields{$field}) {
-				push @out, "$field = $changefields{$field}\n";
+				push @out, $formatfield->($field, $changefields{$field});
 			}
 		}
 	}
@@ -639,115 +678,126 @@ sub modifyconfig {
 # These can be overridden in ~/.mrconfig.
 __DATA__
 [ALIAS]
-	co = checkout
-	ci = commit
-	ls = list
+co = checkout
+ci = commit
+ls = list
 
 [DEFAULT]
-lib =							\
-	error() {					\
-		echo "mr: $@" >&2			\
-		exit 1					\
+lib =
+	error() {
+		echo "mr: $@" >&2
+		exit 1
+	}
+	hours_since() {
+		for dir in .git .svn .bzr CVS; do
+			if [ -e "$MR_REPO/$dir" ]; then
+				flagfile="$MR_REPO/$dir/.mr_last$1"
+				break
+			fi
+		done
+		if [ -z "$flagfile" ]; then
+			error "cannot determine flag filename"
+		fi
+		perl -wle 'print -f shift() ? int((-M _) * 24) : 9999' "$flagfile"
+		touch "$flagfile"
 	}
 
-update =						\
-	if [ -d "$MR_REPO"/.svn ]; then			\
-		svn update "$@"				\
-	elif [ -d "$MR_REPO"/.git ]; then		\
-		git pull origin master "$@"		\
-	elif [ -d "$MR_REPO"/.bzr ]; then		\
-		bzr merge "$@"				\
-	elif [ -d "$MR_REPO"/CVS ]; then		\
-		cvs update "$@"				\
-	else						\
-		error "unknown repo type"		\
+update =
+	if [ -d "$MR_REPO"/.svn ]; then
+		svn update "$@"
+	elif [ -d "$MR_REPO"/.git ]; then
+		git pull origin master "$@"
+	elif [ -d "$MR_REPO"/.bzr ]; then
+		bzr merge "$@"
+	elif [ -d "$MR_REPO"/CVS ]; then
+		cvs update "$@"
+	else
+		error "unknown repo type"
+	fi
+status =
+	if [ -d "$MR_REPO"/.svn ]; then
+		svn status "$@"
+	elif [ -d "$MR_REPO"/.git ]; then
+		git status "$@" || true
+	elif [ -d "$MR_REPO"/.bzr ]; then
+		bzr status "$@"
+	elif [ -d "$MR_REPO"/CVS ]; then
+		cvs status "$@"
+	else
+		error "unknown repo type"
+	fi
+commit =
+	if [ -d "$MR_REPO"/.svn ]; then
+		svn commit "$@"
+	elif [ -d "$MR_REPO"/.git ]; then
+		git commit -a "$@" && git push --all
+	elif [ -d "$MR_REPO"/.bzr ]; then
+		bzr commit "$@" && bzr push
+	elif [ -d "$MR_REPO"/CVS ]; then
+		cvs commit "$@"
+	else
+		error "unknown repo type"
 	fi
-status =						\
-	if [ -d "$MR_REPO"/.svn ]; then			\
-		svn status "$@"				\
-	elif [ -d "$MR_REPO"/.git ]; then		\
-		git status "$@" || true			\
-	elif [ -d "$MR_REPO"/.bzr ]; then		\
-		bzr status "$@"				\
-	elif [ -d "$MR_REPO"/CVS ]; then		\
-		cvs status "$@"				\
-	else						\
-		error "unknown repo type"		\
+diff =
+	if [ -d "$MR_REPO"/.svn ]; then
+		svn diff "$@"
+	elif [ -d "$MR_REPO"/.git ]; then
+		git diff "$@"
+	elif [ -d "$MR_REPO"/.bzr ]; then
+		bzr diff "$@"
+	elif [ -d "$MR_REPO"/CVS ]; then
+		cvs diff "$@"
+	else
+		error "unknown repo type"
 	fi
-commit =						\
-	if [ -d "$MR_REPO"/.svn ]; then			\
-		svn commit "$@"				\
-	elif [ -d "$MR_REPO"/.git ]; then		\
-		git commit -a "$@" && git push --all	\
-	elif [ -d "$MR_REPO"/.bzr ]; then		\
-		bzr commit "$@" && bzr push		\
-	elif [ -d "$MR_REPO"/CVS ]; then		\
-		cvs commit "$@"				\
-	else						\
-		error "unknown repo type"		\
+log =
+	if [ -d "$MR_REPO"/.svn ]; then
+		svn log"$@"
+	elif [ -d "$MR_REPO"/.git ]; then
+		git log "$@"
+	elif [ -d "$MR_REPO"/.bzr ]; then
+		bzr log "$@"
+	elif [ -d "$MR_REPO"/CVS ]; then
+		cvs log "$@"
+	else
+		error "unknown repo type"
 	fi
-diff =							\
-	if [ -d "$MR_REPO"/.svn ]; then			\
-		svn diff "$@"				\
-	elif [ -d "$MR_REPO"/.git ]; then		\
-		git diff "$@"				\
-	elif [ -d "$MR_REPO"/.bzr ]; then		\
-		bzr diff "$@"				\
-	elif [ -d "$MR_REPO"/CVS ]; then		\
-		cvs diff "$@"				\
-	else						\
-		error "unknown repo type"		\
+register =
+	if [ -n "$1" ]; then
+		cd "$1"
 	fi
-log =							\
-	if [ -d "$MR_REPO"/.svn ]; then			\
-		svn log"$@"				\
-	elif [ -d "$MR_REPO"/.git ]; then		\
-		git log "$@"				\
-	elif [ -d "$MR_REPO"/.bzr ]; then		\
-		bzr log "$@"				\
-	elif [ -d "$MR_REPO"/CVS ]; then		\
-		cvs log "$@"				\
-	else						\
-		error "unknown repo type"		\
+	basedir="$(basename $(pwd))"
+	if [ -d .svn ]; then
+		url=$(LANG=C svn info . | grep -i ^URL: | cut -d ' ' -f 2)
+		if [ -z "$url" ]; then
+			error "cannot determine svn url"
+		fi
+		echo "Registering svn url: $url"
+		mr config "$(pwd)" checkout="svn co $url $basedir"
+	elif [ -d .git ]; then
+		url=$(LANG=C git-config --get remote.origin.url)
+		if [ -z "$url" ]; then
+			error "cannot determine git url"
+		fi
+		echo "Registering git url: $url"
+		mr config "$(pwd)" checkout="git clone $url $basedir"
+	elif [ -d .bzr ]; then
+		url=$(cat .bzr/branch/parent)
+		if [ -z "$url" ]; then
+			error "cannot determine bzr url"
+		fi
+		echo "Registering bzr url: $url"
+		mr config "$(pwd)" checkout="bzr clone $url $basedir"
+	else
+		error "unable to register this repo type"
 	fi
-register =								\
-	if [ -n "$1" ]; then						\
-		cd "$1"							\
-	fi								\
-	basedir="$(basename $(pwd))"					\
-	if [ -d .svn ]; then						\
-		url=$(LANG=C svn info . |				\
-		      grep -i ^URL: | cut -d ' ' -f 2)			\
-		if [ -z "$url" ]; then					\
-			error "cannot determine svn url"		\
-		fi							\
-		echo "Registering svn url: $url"			\
-		mr config "$(pwd)" checkout="svn co $url $basedir"	\
-	elif [ -d .git ]; then						\
-		url=$(LANG=C git-config --get remote.origin.url)	\
-		if [ -z "$url" ]; then					\
-			error "cannot determine git url"		\
-		fi							\
-		echo "Registering git url: $url"			\
-		mr config "$(pwd)" checkout="git clone $url $basedir"	\
-	elif [ -d .bzr ]; then						\
-		url=$(cat .bzr/branch/parent)				\
-		if [ -z "$url" ]; then					\
-			error "cannot determine bzr url"		\
-		fi							\
-		echo "Registering bzr url: $url"			\
-		mr config "$(pwd)" checkout="bzr clone $url $basedir"	\
-	else								\
-		error "unable to register this repo type"		\
+help =
+	if [ ! -e "$MR_PATH" ]; then
+		error "cannot find program path"
 	fi
+	(pod2man -c mr "$MR_PATH" | man -l -) || error "pod2man or man failed"
 list = true
 config = 
-help =							\
-	if [ ! -e "$MR_PATH" ]; then			\
-		error "cannot find program path"	\
-	fi						\
-	(pod2man -c mr "$MR_PATH" | man -l -) ||	\
-		error "pod2man or man failed"
 
 ed = echo "A horse is a horse, of course, of course.."
 T = echo "I pity the fool."