X-Git-Url: https://git.madduck.net/code/myrepos.git/blobdiff_plain/f096cfec16f6b36046862cdf84c88a426948a6dc..4e609667a9b2545726b8076da2f4504fa0d9c9b1:/mr?ds=sidebyside diff --git a/mr b/mr index f2e4af5..3c998f3 100755 --- a/mr +++ b/mr @@ -14,13 +14,16 @@ B [options] status B [options] commit [-m "message"] +B [options] diff + B [options] action [params ...] =head1 DESCRIPTION B 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 all of the repositories at once. +perform other actions on the repositories as if they were one big +respository. Any mix of revision control systems can be used with B, and you can define arbitrary actions for commands like "update", "checkout", or "commit". @@ -32,33 +35,52 @@ control system: =item checkout (or co) -Checks out all the registered repositories that are not already checked -out. +Checks out any repositories that are not already checked out. =item update -Updates each registered repository from its configured remote repository. +Updates each repository from its configured remote repository. If a repository isn't checked out yet, it will first check it out. =item status -Displays a status report for each registered repository, showing what +Displays a status report for each repository, showing what uncommitted changes are present in the repository. =item commit (or ci) -Commits changes to each registered repository. (By default, changes -are pushed to the remote repository too, when using distributed systems -like git.) +Commits changes to each repository. (By default, changes are pushed to the +remote repository too, when using distributed systems like git.) The optional -m parameter allows specifying a commit message. +=item diff + +Show a diff of uncommitted changes. + +=item list + +List the repositories that mr will act on. + +=item help + +Displays this help. + =back Actions can be abbreviated to any unambiguous subsctring, so "mr st" is equivilant to "mr status". +B operates on all registered repsitories at or below your working +directory. Or, if you are in a subdirectory of a repository that contains +no other registered repositories, it will act on only that repository. + +Additional parameters can be passed to other commands than "commit", they +will be passed on unchanged to the underlying revision control system. +This is mostly useful if the repositories mr will act on all use the same +revision control system. + =head1 OPTIONS =over 4 @@ -66,8 +88,7 @@ Actions can be abbreviated to any unambiguous subsctring, so =item -d directory Specifies the topmost directory that B should work in. The default is -the current working directory. B will operate on all registered -repositories at or under the directory. +the current working directory. =item -c mrconfig @@ -97,9 +118,11 @@ directory that contains the .mrconfig file. Within a section, each parameter defines a shell command to run to handle a given action. Note that these shell commands are run in a "set -e" shell environment, where any additional parameters you pass are available in -"$@". B cds into the repository directory before running -a command, except for the "checkout" command, which is run in the parent -of the repository directory, since the repository isn't checked out yet. +"$@". The "checkout" command is run in the parent of the repository +directory, since the repository isn't checked out yet. All other commands +are run inside the repository, though not necessarily at the top of it. +The "MR_REPO" environment variable is set to the path to the top of the +repository. There are three special parameters. If the "skip" parameter is set and its command returns nonzero, then B will skip acting on that repository. @@ -167,7 +190,9 @@ my $result=GetOptions( "v" => \$verbose, ); if (! $result || @ARGV < 1) { - die("Usage: mr [-d directory] action [params ...]\n"); + die("Usage: mr [-d directory] action [params ...]\n". + "(Use mr help for man page.)\n"); + } loadconfig(\*DATA); @@ -175,6 +200,12 @@ loadconfig($config); #use Data::Dumper; #print Dumper(\%config); +eval { + use FindBin qw($Bin $Script); + $ENV{MR_PATH}=$Bin."/".$Script; +}; + +# alias expansion and command stemming my $action=shift @ARGV; if (! exists $knownactions{$action}) { if (exists $alias{$action}) { @@ -192,28 +223,42 @@ if (! exists $knownactions{$action}) { } } -my (@failed, @successful, @skipped); -my $first=1; +if ($action eq 'help') { + exec($config{''}{default}{help}); +} + +# work out what repos to act on +my @repos; +my $nochdir=0; foreach my $topdir (sort keys %config) { foreach my $subdir (sort keys %{$config{$topdir}}) { next if $subdir eq 'default'; - my $dir=$topdir.$subdir; - - if (defined $directory && - $dir ne $directory && - $dir !~ /^\Q$directory\E\//) { - print "mr $action: $dir skipped per -d parameter ($directory)\n" if $verbose; - push @skipped, $dir; - next; + next if $dir ne $directory && $dir !~ /^\Q$directory\E\//; + push @repos, [$dir, $topdir, $subdir]; + } +} +if (! @repos) { + # fallback to find a leaf repo + LEAF: foreach my $topdir (reverse sort keys %config) { + foreach my $subdir (reverse sort keys %{$config{$topdir}}) { + next if $subdir eq 'default'; + my $dir=$topdir.$subdir; + my $d=$directory; + $dir.="/" unless $dir=~/\/$/; + $d.="/" unless $d=~/\/$/; + if ($d=~/^\Q$dir\E/) { + push @repos, [$dir, $topdir, $subdir]; + last LEAF; + } } - - print "\n" unless $first; - $first=0; - - action($action, $dir, $topdir, $subdir); - } + $nochdir=1; +} + +my (@failed, @successful, @skipped); +foreach my $repo (@repos) { + action($action, @$repo); } sub action { @@ -236,7 +281,8 @@ sub action { } } - if (! chdir($dir)) { + $ENV{MR_REPO}=$dir; + if (! $nochdir && ! chdir($dir)) { print STDERR "mr $action: failed to chdir to $dir: $!\n"; push @skipped, $dir; } @@ -255,7 +301,7 @@ sub action { push @skipped, $dir; } else { - print "mr $action: in $dir\n"; + print "mr $action: $dir\n"; my $command="set -e; ".$lib. "my_action(){ $config{$topdir}{$subdir}{$action} ; }; my_action ". join(" ", map { s/\//\/\//g; s/"/\"/g; '"'.$_.'"' } @ARGV); @@ -273,6 +319,8 @@ sub action { else { push @successful, $dir; } + + print "\n"; } } @@ -285,7 +333,10 @@ sub showstat { } return; } -print "\nmr $action: finished (".join("; ", +if (! @successful && ! @failed && ! @skipped) { + die "mr $action: no repositories found to work on\n"; +} +print "mr $action: finished (".join("; ", showstat($#successful+1, "successful", "successful"), showstat($#failed+1, "failed", "failed"), showstat($#skipped+1, "skipped", "skipped"), @@ -404,26 +455,41 @@ lib = \ exit 1; \ } update = \ - if [ -d .svn ]; then \ - svn update; \ - elif [ -d .git ]; then \ - git pull origin master; \ + if [ -d "$MR_REPO"/.svn ]; then \ + svn update "$@"; \ + elif [ -d "$MR_REPO"/.git ]; then \ + git pull origin master "$@"; \ else \ error "unknown repo type"; \ fi status = \ - if [ -d .svn ]; then \ - svn status; \ - elif [ -d .git ]; then \ - git status || true; \ + if [ -d "$MR_REPO"/.svn ]; then \ + svn status "$@"; \ + elif [ -d "$MR_REPO"/.git ]; then \ + git status "$@" || true; \ else \ error "unknown repo type"; \ fi commit = \ - if [ -d .svn ]; then \ + if [ -d "$MR_REPO"/.svn ]; then \ svn commit "$@"; \ - elif [ -d .git ]; then \ + elif [ -d "$MR_REPO"/.git ]; then \ git commit -a "$@" && git push --all; \ else \ error "unknown repo type"; \ fi +diff = \ + if [ -d "$MR_REPO"/.svn ]; then \ + svn diff "$@"; \ + elif [ -d "$MR_REPO"/.git ]; then \ + git diff "$@"; \ + else \ + error "unknown repo type"; \ + fi +list = true +help = \ + if [ ! -e "$MR_PATH" ]; then \ + error "cannot find program path";\ + fi; \ + (pod2man -c mr "$MR_PATH" | man -l -) || \ + error "pod2man or man failed"