X-Git-Url: https://git.madduck.net/code/myrepos.git/blobdiff_plain/32808ba79347c860fd3acfa3f8b731795170bc93..c1903b7c2e589ce4379f1f9fb63b36b2fab274eb:/mr?ds=sidebyside diff --git a/mr b/mr index 823e3df..3131690 100755 --- a/mr +++ b/mr @@ -16,17 +16,25 @@ B [options] commit [-m "message"] B [options] diff +B [options] log + 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". +B 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 +contains no other registered repositories, it will stay in that directory, +and work on only that repository, + The predefined commands should be fairly familiar to users of any revision control system: @@ -58,7 +66,11 @@ The optional -m parameter allows specifying a commit message. Show a diff of uncommitted changes. -=item list +=item log + +Show the commit log. + +=item list (or ls) List the repositories that mr will act on. @@ -69,11 +81,8 @@ 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, it will act on -only that repository. +"mr st" is equivilant to "mr status", and "mr up" is equivilant to "mr +update" Additional parameters can be passed to other commands than "commit", they will be passed on unchanged to the underlying revision control system. @@ -87,12 +96,11 @@ revision control system. =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 -Use the specified mrconfig file, instead of looking for on in your home +Use the specified mrconfig file, instead of looking for one in your home directory. =item -v @@ -104,60 +112,65 @@ Be verbose. =head1 FILES B is configured by .mrconfig files. It starts by reading the .mrconfig -file in your home directory. Each repository specified in a .mrconfig file -can also have its own .mrconfig file in its root directory that can -optionally be used as well. So you could have a ~/.mrconfig that registers a -repository ~/src, that itself contains a ~/src/.mrconfig file, that in turn -registers several additional repositories. +file in your home directory, and this can in turn chain load .mrconfig files +from repositories. + +Here is an example .mrconfig file: + + [src] + checkout = svn co svn://svn.example.com/src/trunk src + chain = true + + [src/linux-2.6] + checkout = git clone git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux-2.6.git 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. -Sections specify where each repository is located, relative to the + +The "default" section allows setting default values for the sections that +come after it. + +The "alias" section allows adding aliases for actions. Each parameter +is an alias, and its value is the action to use. + +All other sections specify where each repository is located, relative to the 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 +given action. mr contains default handlers for the "update", "status", and +"commit" actions, so normally you only need to specify what to do for +"checkout". + +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. -If the "chain" parameter is set and its command returns nonzero, then B -will try to load a .mrconfig file from the root of the repository. (You -should avoid chaining from repositories with untrusted committers.) 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 other commands -can use. +A few parameters have special meanings: -The "default" section allows setting up default handlers for each action, -and is overridden by the contents of other sections. mr contains default -handlers for the "update", "status", and "commit" actions, so normally -you only need to specify what to do for "checkout". +=over 4 -The "alias" section allows adding aliases for commands. Each parameter -is an alias, and its value is the command to run. +=item skip -For example: +If the "skip" parameter is set and its command returns nonzero, then B +will skip acting on that repository. - [src] - checkout = svn co svn://svn.example.com/src/trunk src - chain = true +=item chain - [src/linux-2.6] - skip = small - checkout = git clone git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux-2.6.git +If the "chain" parameter is set and its command returns nonzero, then B +will try to load a .mrconfig file from the root of the repository. (You +should avoid chaining from repositories with untrusted committers.) + +=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. - [default] - lib = \ - small() { - case "$(hostname)" in; \ - slug|snail); \ - return 0; ;; ; \ - esac; \ - return 1; \ - } +=back =head1 AUTHOR @@ -183,9 +196,9 @@ my %alias; Getopt::Long::Configure("no_permute"); my $result=GetOptions( - "d=s" => sub { $directory=abs_path($_[1]) }, - "c=s" => \$config, - "v" => \$verbose, + "d|directory=s" => sub { $directory=abs_path($_[1]) }, + "c|config=s" => \$config, + "verbose" => \$verbose, ); if (! $result || @ARGV < 1) { die("Usage: mr [-d directory] action [params ...]\n". @@ -205,19 +218,22 @@ eval { # alias expansion and command stemming my $action=shift @ARGV; +if (exists $alias{$action}) { + $action=$alias{$action}; +} if (! exists $knownactions{$action}) { - if (exists $alias{$action}) { - $action=$alias{$action}; + my @matches = grep { /^\Q$action\E/ } + keys %knownactions, keys %alias; + if (@matches == 1) { + $action=$matches[0]; + } + elsif (@matches == 0) { + die "mr: unknown action \"$action\" (known actions: ". + join(", ", sort keys %knownactions).")\n"; } else { - my @matches = grep { /^\Q$action\E/ } - keys %knownactions, keys %alias; - if (@matches == 1) { - $action=$matches[0]; - } - else { - die "mr: ambiguous action \"$action\" (matches @matches)\n"; - } + die "mr: ambiguous action \"$action\" (matches: ". + join(", ", @matches).")\n"; } } @@ -225,30 +241,38 @@ if ($action eq 'help') { exec($config{''}{default}{help}); } -# handle being in a subdir of a repository +# 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}}) { - if ($directory =~ /^\Q$topdir$subdir\E\//) { - $directory=$topdir.$subdir; + next if $subdir eq 'default'; + my $dir=$topdir.$subdir; + 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; + } } } + $nochdir=1; } my (@failed, @successful, @skipped); -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\//) { - next; - } - - action($action, $dir, $topdir, $subdir); - } +foreach my $repo (@repos) { + action($action, @$repo); } sub action { @@ -271,32 +295,41 @@ sub action { } } - if (! chdir($dir)) { - print STDERR "mr $action: failed to chdir to $dir: $!\n"; - push @skipped, $dir; - } + $ENV{MR_REPO}=$dir; if (exists $config{$topdir}{$subdir}{skip}) { - my $ret=system($lib.$config{$topdir}{$subdir}{skip}); + my $test="set -e;".$lib.$config{$topdir}{$subdir}{skip}; + print "mr $action: running skip test $test\n" if $verbose; + my $ret=system($test); if ($ret >> 8 == 0) { print "mr $action: $dir skipped per config file\n" if $verbose; push @skipped, $dir; return; } } - - if (! exists $config{$topdir}{$subdir}{$action}) { + + if (! $nochdir && ! chdir($dir)) { + print STDERR "mr $action: failed to chdir to $dir: $!\n"; + push @failed, $dir; + } + elsif (! exists $config{$topdir}{$subdir}{$action}) { print STDERR "mr $action: no defined $action command for $topdir$subdir, skipping\n"; push @skipped, $dir; } else { - print "mr $action: $dir\n"; + if (! $nochdir) { + print "mr $action: $dir\n"; + } + else { + print "mr $action: $dir (in subdir $directory)\n"; + } my $command="set -e; ".$lib. "my_action(){ $config{$topdir}{$subdir}{$action} ; }; my_action ". join(" ", map { s/\//\/\//g; s/"/\"/g; '"'.$_.'"' } @ARGV); + print STDERR "mr $action: running $command\n" if $verbose; my $ret=system($command); if ($ret != 0) { - print STDERR "mr $action: failed to run: $command\n" if $verbose; + print STDERR "mr $action: failed ($ret)\n" if $verbose; push @failed, $topdir.$subdir; if ($ret >> 8 != 0) { print STDERR "mr $action: command failed\n"; @@ -322,6 +355,9 @@ sub showstat { } return; } +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"), @@ -434,48 +470,83 @@ __DATA__ [alias] co = checkout ci = commit + ls = list + [default] lib = \ error() { \ echo "mr: $@" >&2; \ exit 1; \ } + update = \ - if [ -d .svn ]; then \ + if [ -d "$MR_REPO"/.svn ]; then \ svn update "$@"; \ - elif [ -d .git ]; then \ + 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 .svn ]; then \ + if [ -d "$MR_REPO"/.svn ]; then \ svn status "$@"; \ - elif [ -d .git ]; then \ + 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 .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; \ + elif [ -d "$MR_REPO"/.bzr ]; then \ + bzr commit "$@" && bzr push; \ + elif [ -d "$MR_REPO"/CVS ]; then \ + cvs commit "$@"; \ else \ error "unknown repo type"; \ fi diff = \ - if [ -d .svn ]; then \ + if [ -d "$MR_REPO"/.svn ]; then \ svn diff "$@"; \ - elif [ -d .git ]; then \ + 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 +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 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" + +ed = echo "A horse is a horse, of course, of course.." +T = echo "I pity the fool."