X-Git-Url: https://git.madduck.net/code/myrepos.git/blobdiff_plain/c1903b7c2e589ce4379f1f9fb63b36b2fab274eb..2f861a437094ba6d5106b3b96823788b935f82f2:/mr?ds=sidebyside diff --git a/mr b/mr index 3131690..96f3a16 100755 --- a/mr +++ b/mr @@ -18,6 +18,10 @@ B [options] diff B [options] log +B [options] register repository + +B [options] config section [parameter=[value] ...] + B [options] action [params ...] =head1 DESCRIPTION @@ -74,6 +78,21 @@ Show the commit log. List the repositories that mr will act on. +=item register + +The next parameter is the directory of an existing repository. The +repository will be registered in the mrconfig file. + +=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. + +For example, to add (or edit) a repository in src/foo: + + mr config src/foo checkout="svn co svn://example.com/foo/trunk foo" + =item help Displays this help. @@ -84,10 +103,10 @@ Actions can be abbreviated to any unambiguous subsctring, so "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. -This is mostly useful if the repositories mr will act on all use the same -revision control system. +Additional parameters can be passed to most commands, and are 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 @@ -127,14 +146,16 @@ Here is an example .mrconfig file: 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. -The "default" section allows setting default values for the sections that +The "DEFAULT" section allows setting default values for the sections that come after it. -The "alias" section allows adding aliases for actions. Each parameter +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. +All other sections add repositories. The section header specifies the +directory where the repository is located. This is relative to the directory +that contains the mrconfig file, but you can also choose to use absolute +paths. Within a section, each parameter defines a shell command to run to handle a given action. mr contains default handlers for the "update", "status", and @@ -164,6 +185,14 @@ 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 deleted + +If the "deleted" parameter is set and its command returns nonzero, then +B will treat the repository as deleted. It won't ever actually delete +the repository, but it will warn if it sees the repsoitory's directory. +This is useful when one mrconfig file is shared amoung multiple machines, +to keep track of and remember to delete old repositories. + =item lib The "lib" parameter can specify some shell code that will be run before each @@ -238,7 +267,38 @@ if (! exists $knownactions{$action}) { } if ($action eq 'help') { - exec($config{''}{default}{help}); + exec($config{''}{DEFAULT}{$action}) || die "exec: $!"; +} +elsif ($action eq 'config') { + if (@ARGV < 2) { + die "mr config: not enough parameters\n"; + } + my $section=shift; + if ($section=~/^\//) { + # try to convert to a path relative to $config's dir + my ($dir)=$config=~/^(.*\/)[^\/]+$/; + if ($section=~/^\Q$dir\E(.*)/) { + $section=$1; + } + } + my %fields; + foreach (@ARGV) { + if (/^([^=]+)=(.*)$/) { + $fields{$1}=$2; + } + else { + die "mr config: expected parameter=value, not \"$_\"\n"; + } + } + modifyconfig($config, $section, %fields); + exit 0; +} +elsif ($action eq 'register') { + my $command="set -e; ".$config{''}{DEFAULT}{lib}."\n". + "my_action(){ $config{''}{DEFAULT}{$action}\n }; my_action ". + join(" ", map { s/\//\/\//g; s/"/\"/g; '"'.$_.'"' } @ARGV); + print STDERR "mr $action: running >>$command<<\n" if $verbose; + exec($command) || die "exec: $!"; } # work out what repos to act on @@ -246,9 +306,12 @@ 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; - next if $dir ne $directory && $dir !~ /^\Q$directory\E\//; + next if $subdir eq 'DEFAULT'; + my $dir=($subdir =~/^\//) ? $subdir : $topdir.$subdir; + my $d=$directory; + $dir.="/" unless $dir=~/\/$/; + $d.="/" unless $d=~/\/$/; + next if $dir ne $directory && $dir !~ /^\Q$directory\E/; push @repos, [$dir, $topdir, $subdir]; } } @@ -256,8 +319,8 @@ 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; + next if $subdir eq 'DEFAULT'; + my $dir=($subdir =~/^\//) ? $subdir : $topdir.$subdir; my $d=$directory; $dir.="/" unless $dir=~/\/$/; $d.="/" unless $d=~/\/$/; @@ -279,7 +342,23 @@ sub action { my ($action, $dir, $topdir, $subdir) = @_; my $lib= exists $config{$topdir}{$subdir}{lib} ? - $config{$topdir}{$subdir}{lib} : ""; + $config{$topdir}{$subdir}{lib}."\n" : ""; + + if (exists $config{$topdir}{$subdir}{deleted}) { + if (! -d $dir) { + return; + } + else { + my $test="set -e;".$lib.$config{$topdir}{$subdir}{deleted}; + print "mr $action: running deleted test >>$test<<\n" if $verbose; + my $ret=system($test); + if ($ret >> 8 == 0) { + print STDERR "mr error: $dir should be deleted yet still exists\n\n"; + push @failed, $dir; + return; + } + } + } if ($action eq 'checkout') { if (-d $dir) { @@ -299,7 +378,7 @@ sub action { if (exists $config{$topdir}{$subdir}{skip}) { my $test="set -e;".$lib.$config{$topdir}{$subdir}{skip}; - print "mr $action: running skip test $test\n" if $verbose; + 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; @@ -318,19 +397,19 @@ sub action { } else { if (! $nochdir) { - print "mr $action: $dir\n"; + print "mr $action: $topdir$subdir\n"; } else { - print "mr $action: $dir (in subdir $directory)\n"; + print "mr $action: $topdir$subdir (in subdir $directory)\n"; } my $command="set -e; ".$lib. - "my_action(){ $config{$topdir}{$subdir}{$action} ; }; my_action ". + "my_action(){ $config{$topdir}{$subdir}{$action}\n }; my_action ". join(" ", map { s/\//\/\//g; s/"/\"/g; '"'.$_.'"' } @ARGV); - print STDERR "mr $action: running $command\n" if $verbose; + print STDERR "mr $action: running >>$command<<\n" if $verbose; my $ret=system($command); if ($ret != 0) { print STDERR "mr $action: failed ($ret)\n" if $verbose; - push @failed, $topdir.$subdir; + push @failed, $dir; if ($ret >> 8 != 0) { print STDERR "mr $action: command failed\n"; } @@ -384,7 +463,10 @@ sub loadconfig { $dir=""; } else { - # $f might be a symlink + if (! -e $f) { + return; + } + my $absf=abs_path($f); if ($loaded{$absf}) { return; @@ -403,8 +485,8 @@ sub loadconfig { my $parent=$dir; while ($parent=~s/^(.*)\/[^\/]+\/?$/$1/) { if (exists $config{$parent} && - exists $config{$parent}{default}) { - $config{$dir}{default}={ %{$config{$parent}{default}} }; + exists $config{$parent}{DEFAULT}) { + $config{$dir}{DEFAULT}={ %{$config{$parent}{DEFAULT}} }; last; } } @@ -422,31 +504,31 @@ sub loadconfig { my $value=$2; # continuation line - while ($value=~/(.*)\\$/) { - $value=$1.<$in>; + while ($value=~/(.*)\\$/s) { + $value=$1."\n".<$in>; chomp $value; } if (! defined $section) { die "$f line $.: parameter ($parameter) not in section\n"; } - if ($section ne 'alias' && + if ($section ne 'ALIAS' && ! exists $config{$dir}{$section} && - exists $config{$dir}{default}) { + exists $config{$dir}{DEFAULT}) { # copy in defaults - $config{$dir}{$section}={ %{$config{$dir}{default}} }; + $config{$dir}{$section}={ %{$config{$dir}{DEFAULT}} }; } - if ($section eq 'alias') { + if ($section eq 'ALIAS') { $alias{$parameter}=$value; } elsif ($parameter eq 'lib') { - $config{$dir}{$section}{lib}.=$value." ; "; + $config{$dir}{$section}{lib}.=$value."\n"; } else { $config{$dir}{$section}{$parameter}=$value; $knownactions{$parameter}=1; if ($parameter eq 'chain' && - length $dir && $section ne "default" && + length $dir && $section ne "DEFAULT" && -e $dir.$section."/.mrconfig" && system($value) >> 8 == 0) { push @toload, $dir.$section."/.mrconfig"; @@ -464,88 +546,200 @@ sub loadconfig { } } +sub modifyconfig { + my $f=shift; + # the section to modify or add + my $targetsection=shift; + # fields to change in the section + # To remove a field, set its value to "". + my %changefields=@_; + + my @lines; + my @out; + + if (-e $f) { + open(my $in, "<", $f) || die "mr: open $f: $!\n"; + @lines=<$in>; + close $in; + } + + my $addfields=sub { + my @blanks; + while ($out[$#out] =~ /^\s*$/) { + unshift @blanks, pop @out; + } + foreach my $field (sort keys %changefields) { + if (length $changefields{$field}) { + push @out, "$field = $changefields{$field}\n"; + } + } + push @out, @blanks; + }; + + my $section; + while (@lines) { + $_=shift(@lines); + + if (/^\s*\#/ || /^\s*$/) { + push @out, $_; + } + elsif (/^\s*\[([^\]]*)\]\s*$/) { + if (defined $section && + $section eq $targetsection) { + $addfields->(); + } + + $section=$1; + + push @out, $_; + } + elsif (/^\s*(\w+)\s*=\s(.*)/) { + my $parameter=$1; + my $value=$2; + + # continuation line + while ($value=~/(.*\\)$/s) { + $value=$1."\n".shift(@lines); + chomp $value; + } + + if ($section eq $targetsection) { + if (exists $changefields{$parameter}) { + if (length $changefields{$parameter}) { + $value=$changefields{$parameter}; + } + delete $changefields{$parameter}; + } + } + + push @out, "$parameter = $value\n"; + } + } + + if (defined $section && + $section eq $targetsection) { + $addfields->(); + } + elsif (%changefields) { + push @out, "\n[$targetsection]\n"; + foreach my $field (sort keys %changefields) { + if (length $changefields{$field}) { + push @out, "$field = $changefields{$field}\n"; + } + } + } + + open(my $out, ">", $f) || die "mr: write $f: $!\n"; + print $out @out; + close $out; +} + # Finally, some useful actions that mr knows about by default. # These can be overridden in ~/.mrconfig. __DATA__ -[alias] +[ALIAS] co = checkout ci = commit ls = list -[default] -lib = \ - error() { \ - echo "mr: $@" >&2; \ - exit 1; \ +[DEFAULT] +lib = \ + error() { \ + echo "mr: $@" >&2 \ + exit 1 \ } -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"; \ +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"; \ +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 -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"; \ +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 -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"; \ +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 +register = \ + if [ -z "$1" ]; then \ + error "repository directory not specified" \ + fi \ + cd "$1" \ + basedir="$(basename $(pwd))" \ + if [ -d .svn ]; then \ + url=$(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=$(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" \ + else \ + error "unable to register this repo type" \ fi list = true - -help = \ - if [ ! -e "$MR_PATH" ]; then \ - error "cannot find program path";\ - fi; \ - (pod2man -c mr "$MR_PATH" | man -l -) || \ +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.."