X-Git-Url: https://git.madduck.net/code/myrepos.git/blobdiff_plain/1b7137c19b38431c79b41d1dfde73ef76f826836..fd59f443a2cd4d0b83b21e6488875bea9f1f2d54:/mr?ds=sidebyside

diff --git a/mr b/mr
index 4fc7971..e2f9dae 100755
--- a/mr
+++ b/mr
@@ -1,7 +1,5 @@
 #!/usr/bin/perl
 
-#man{{{
-
 =head1 NAME
 
 mr - a Multiple Repository management tool
@@ -28,6 +26,10 @@ B<mr> [options] config section ["parameter=[value]" ...]
 
 B<mr> [options] action [params ...]
 
+B<mr> [options] [online|offline]
+
+B<mr> [options] remember action [params ...]
+
 =head1 DESCRIPTION
 
 B<mr> is a Multiple Repository management tool. It can checkout, update, or
@@ -77,6 +79,11 @@ remote repository. Only supported for distributed revision control systems.
 
 The optional -m parameter allows specifying a commit message.
 
+=item push
+
+Pushes committed local changes to the remote repository. A no-op for
+centralized revision control systems.
+
 =item diff
 
 Show a diff of uncommitted changes.
@@ -126,6 +133,24 @@ To see the built-in library of shell functions contained in mr:
 The ~/.mrconfig file is used by default. To use a different config file,
 use the -c option.
 
+=item offline
+
+Advises mr that it is in offline mode. Any commands that fail in
+offline mode will be remembered, and retried when mr is told it's online.
+
+=item online
+
+Advices mr that it is in online mode again. Commands that failed while in
+offline mode will be re-run.
+
+=item remember
+
+Remember a command, to be run later when mr re-enters online mode. This
+implicitly puts mr into offline mode. The command can be any regular mr
+command. This is useful when you know that a command will fail due to being
+offline, and so don't want to run it right now at all, but just remember
+to run it when you go back online.
+
 =item help
 
 Displays this help.
@@ -189,7 +214,7 @@ with no number specified. This can greatly speed up operations such as updates.
 It is not recommended for interactive operations.
 
 Note that running more than 10 jobs at a time is likely to run afoul of
-ssh connection limits. Running between 3 and 5 jobs at a time will yeild
+ssh connection limits. Running between 3 and 5 jobs at a time will yield
 a good speedup in updates without loading the machine too much.
 
 =back
@@ -311,6 +336,17 @@ 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 EXTENSIONS
+
+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 AUTHOR
 
 Copyright 2007 Joey Hess <joey@kitenet.net>
@@ -321,8 +357,6 @@ http://kitenet.net/~joey/code/mr/
 
 =cut
 
-#}}}
-
 use warnings;
 use strict;
 use Getopt::Long;
@@ -358,7 +392,7 @@ my (@ok, @failed, @skipped);
 main();
 
 my %rcs;
-sub rcs_test { #{{{
+sub rcs_test {
 	my ($action, $dir, $topdir, $subdir) = @_;
 
 	if (exists $rcs{$dir}) {
@@ -393,9 +427,9 @@ sub rcs_test { #{{{
 	else {
 		return $rcs{$dir}=$rcs;
 	}
-} #}}}
+}
 	
-sub findcommand { #{{{
+sub findcommand {
 	my ($action, $dir, $topdir, $subdir, $is_checkout) = @_;
 	
 	if (exists $config{$topdir}{$subdir}{$action}) {
@@ -415,11 +449,11 @@ sub findcommand { #{{{
 	else {
 		return undef;
 	}
-} #}}}
+}
 
-sub action { #{{{
+sub action {
 	my ($action, $dir, $topdir, $subdir) = @_;
-
+	
 	$ENV{MR_CONFIG}=$configfiles{$topdir};
 	my $lib=exists $config{$topdir}{$subdir}{lib} ?
 	               $config{$topdir}{$subdir}{lib}."\n" : "";
@@ -511,6 +545,14 @@ sub action { #{{{
 			print STDERR "mr $action: failed ($ret)\n" if $verbose;
 			if ($ret >> 8 != 0) {
 				print STDERR "mr $action: command failed\n";
+				if (-e "$ENV{HOME}/.mrlog" && $action ne 'remember') {
+					# recreate original command line to
+					# remember, and avoid recursing
+					my @orig=@ARGV;
+					@ARGV=('-n', $action, @orig);
+					action("remember", $dir, $topdir, $subdir);
+					@ARGV=@orig;
+				}
 			}
 			elsif ($ret != 0) {
 				print STDERR "mr $action: command died ($ret)\n";
@@ -526,10 +568,10 @@ sub action { #{{{
 			return OK;
 		}
 	}
-} #}}}
+}
 
 # run actions on multiple repos, in parallel
-sub mrs { #{{{
+sub mrs {
 	my $action=shift;
 	my @repos=@_;
 
@@ -596,9 +638,9 @@ sub mrs { #{{{
 			}
 		}
 	}
-} #}}}
+}
 
-sub record { #{{{
+sub record {
 	my $dir=shift()->[0];
 	my $ret=shift;
 
@@ -624,9 +666,9 @@ sub record { #{{{
 	else {
 		die "unknown exit status $ret";
 	}
-} #}}}
+}
 
-sub showstats { #{{{
+sub showstats {
 	my $action=shift;
 	if (! @ok && ! @failed && ! @skipped) {
 		die "mr $action: no repositories found to work on\n";
@@ -644,9 +686,9 @@ sub showstats { #{{{
 			print STDERR "mr $action: (failed: ".join(" ", @failed).")\n";
 		}
 	}
-} #}}}
+}
 
-sub showstat { #{{{
+sub showstat {
 	my $count=shift;
 	my $singular=shift;
 	my $plural=shift;
@@ -654,10 +696,10 @@ sub showstat { #{{{
 		return "$count ".($count > 1 ? $plural : $singular);
 	}
 	return;
-} #}}}
+}
 
 # an ordered list of repos
-sub repolist { #{{{
+sub repolist {
 	my @list;
 	foreach my $topdir (sort keys %config) {
 		foreach my $subdir (sort keys %{$config{$topdir}}) {
@@ -675,10 +717,10 @@ sub repolist { #{{{
 		             ||
 		$a->{subdir} cmp $b->{subdir}
 	} @list;
-} #}}}
+}
 
 # figure out which repos to act on
-sub selectrepos { #{{{
+sub selectrepos {
 	my @repos;
 	foreach my $repo (repolist()) {
 		my $topdir=$repo->{topdir};
@@ -717,9 +759,9 @@ sub selectrepos { #{{{
 		$no_chdir=1;
 	}
 	return @repos;
-} #}}}
+}
 
-sub expandenv { #{{{
+sub expandenv {
 	my $val=shift;
 	
 
@@ -729,10 +771,10 @@ sub expandenv { #{{{
 	}
 	
 	return $val;
-} #}}}
+}
 
 my %loaded;
-sub loadconfig { #{{{
+sub loadconfig {
 	my $f=shift;
 
 	my @toload;
@@ -808,6 +850,9 @@ sub loadconfig { #{{{
 			if ($parameter eq "include") {
 				print "mr: including output of \"$value\"\n" if $verbose;
 				unshift @lines, `$value`;
+				if ($?) {
+					print STDERR "mr: include command exited nonzero ($?)\n";
+				}
 				next;
 			}
 
@@ -861,9 +906,9 @@ sub loadconfig { #{{{
 	foreach (@toload) {
 		loadconfig($_);
 	}
-} #}}}
+}
 
-sub modifyconfig { #{{{
+sub modifyconfig {
 	my $f=shift;
 	# the section to modify or add
 	my $targetsection=shift;
@@ -958,9 +1003,9 @@ sub modifyconfig { #{{{
 	open(my $out, ">", $f) || die "mr: write $f: $!\n";
 	print $out @out;
 	close $out;	
-} #}}}
+}
 
-sub dispatch { #{{{
+sub dispatch {
 	my $action=shift;
 
 	# actions that do not operate on all repos
@@ -973,6 +1018,13 @@ sub dispatch { #{{{
 	elsif ($action eq 'register') {
 		register(@ARGV);
 	}
+	elsif ($action eq 'remember' ||
+	       $action eq 'offline' ||
+	       $action eq 'online') {
+		my @repos=selectrepos;
+		action($action, @{$repos[0]}) if @repos;
+		exit 0;
+	}
 
 	if (!$jobs || $jobs > 1) {
 		mrs($action, selectrepos());
@@ -982,13 +1034,13 @@ sub dispatch { #{{{
 			record($repo, action($action, @$repo));
 		}
 	}
-} #}}}
+}
 
-sub help { #{{{
+sub help {
 	exec($config{''}{DEFAULT}{help}) || die "exec: $!";
-} #}}}
-	
-sub config { #{{{
+}
+
+sub config {
 	if (@_ < 2) {
 		die "mr config: not enough parameters\n";
 	}
@@ -1024,12 +1076,15 @@ sub config { #{{{
 	}
 	modifyconfig($ENV{MR_CONFIG}, $section, %changefields) if %changefields;
 	exit 0;
-} #}}}
+}
 
-sub register { #{{{
+sub register {
 	if ($config_overridden) {
-		($directory)=$ENV{MR_CONFIG}=~/^(.*\/)[^\/]+$/;
-	} else {
+		# Find the directory that the specified config file is
+		# located in.
+		($directory)=abs_path($ENV{MR_CONFIG})=~/^(.*\/)[^\/]+$/;
+	}
+	else {
 		# Find the closest known mrconfig file to the current
 		# directory.
 		$directory.="/" unless $directory=~/\/$/;
@@ -1066,10 +1121,10 @@ sub register { #{{{
 		join(" ", map { s/\//\/\//g; s/"/\"/g; '"'.$_.'"' } @ARGV);
 	print "mr register: running >>$command<<\n" if $verbose;
 	exec($command) || die "exec: $!";
-} #}}}
+}
 
 # alias expansion and command stemming
-sub expandaction { #{{{
+sub expandaction {
 	my $action=shift;
 	if (exists $alias{$action}) {
 		$action=$alias{$action};
@@ -1090,9 +1145,10 @@ sub expandaction { #{{{
 		}
 	}
 	return $action;
-} #}}}
+}
 
-sub getopts { #{{{
+sub getopts {
+	my @saved=@ARGV;
 	Getopt::Long::Configure("bundling", "no_permute");
 	my $result=GetOptions(
 		"d|directory=s" => sub { $directory=abs_path($_[1]) },
@@ -1108,9 +1164,15 @@ sub getopts { #{{{
 		die("Usage: mr [-d directory] action [params ...]\n".
 		    "(Use mr help for man page.)\n");
 	}
-} #}}}
+	
+	$ENV{MR_SWITCHES}="";
+	foreach my $option (@saved) {
+		last if $option eq $ARGV[0];
+		$ENV{MR_SWITCHES}.="$option ";
+	}
+}
 
-sub init { #{{{
+sub init {
 	$SIG{INT}=sub {
 		print STDERR "mr: interrupted\n";
 		exit 2;
@@ -1132,15 +1194,16 @@ sub init { #{{{
 		use FindBin qw($Bin $Script);
 		$ENV{MR_PATH}=$Bin."/".$Script;
 	};
-} #}}}
+}
 
-sub main { #{{{
+sub main {
 	getopts();
 	init();
+
 	loadconfig(\*DATA);
 	loadconfig($ENV{MR_CONFIG});
 	#use Data::Dumper; print Dumper(\%config);
-
+	
 	my $action=expandaction(shift @ARGV);
 	dispatch($action);
 	showstats($action);
@@ -1154,11 +1217,10 @@ sub main { #{{{
 	else {
 		exit 0;
 	}
-} #}}}
+}
 
 # Finally, some useful actions that mr knows about by default.
 # These can be overridden in ~/.mrconfig.
-#DATA{{{
 __DATA__
 [ALIAS]
 co = checkout
@@ -1237,6 +1299,13 @@ bzr_record = bzr commit "$@"
 hg_record  = hg commit -m "$@"
 darcs_record = darcs record -a -m "$@"
 
+svn_push = :
+git_push = git push "$@"
+bzr_push = bzr push "$@"
+cvs_push = :
+hg_push = hg push "$@"
+darcs_push = darcs push -a "$@"
+
 svn_diff = svn diff "$@"
 git_diff = git diff "$@"
 bzr_diff = bzr diff "$@"
@@ -1308,9 +1377,33 @@ help =
 list = true
 config = 
 
+online =
+	if [ -s ~/.mrlog ]; then
+		info "running offline commands"
+		mv -f ~/.mrlog ~/.mrlog.old
+		if ! sh -e ~/.mrlog.old; then
+			error "offline command failed; left in ~/.mrlog.old"
+		fi
+		rm -f ~/.mrlog.old
+	else
+		info "no offline commands to run"
+	fi
+offline =
+	umask 077
+	touch ~/.mrlog
+	info "offline mode enabled"
+remember =
+	info "remembering command: 'mr $@'"
+	command="mr -d '$(pwd)' $MR_SWITCHES"
+	for w in "$@"; do
+		command="$command '$w'"
+	done
+	if [ ! -e ~/.mrlog ] || ! grep -q -F "$command" ~/.mrlog; then
+		echo "$command" >> ~/.mrlog
+	fi
+
 ed = echo "A horse is a horse, of course, of course.."
 T = echo "I pity the fool."
 right = echo "Not found."
-#}}}
 
 # vim:sw=8:sts=0:ts=8:noet