X-Git-Url: https://git.madduck.net/code/myrepos.git/blobdiff_plain/d977f4f0abb8e9c61e0fda75fd5684ad6e265c8e..bdb2e6d37714e82fddeb435a9768b4fbbe31037f:/mr diff --git a/mr b/mr index 8883aab..d59dc39 100755 --- a/mr +++ b/mr @@ -1,8 +1,8 @@ -#!/usr/bin/perl +#!/usr/bin/env perl =head1 NAME -mr - a Multiple Repository management tool +mr - a tool to manage all your version control repos =head1 SYNOPSIS @@ -16,15 +16,19 @@ B [options] commit [-m "message"] B [options] record [-m "message"] +B [options] fetch + B [options] push B [options] diff B [options] log +B [options] grep pattern + B [options] run command [param ...] -B [options] bootstrap url [directory] +B [options] bootstrap src [directory] B [options] register [repository] @@ -38,11 +42,11 @@ B [options] remember action [params ...] =head1 DESCRIPTION -B 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 -repository. It supports any combination of subversion, git, cvs, mercurial, -bzr, darcs and fossil repositories, and support for other version -control systems can easily be added. +B is a tool to manage all your version control repos. It can checkout, +update, or perform other actions on a set of repositories as if they were +one combined repository. It supports any combination of subversion, git, +cvs, mercurial, bzr, darcs, fossil and veracity repositories, and support +for other version control systems can easily be added. B cds into and operates on all registered repositories at or below your working directory. Or, if you are in a subdirectory of a repository that @@ -73,7 +77,8 @@ If a repository isn't checked out yet, it will first check it out. =item status Displays a status report for each repository, showing what -uncommitted changes are present in the repository. +uncommitted changes are present in the repository. For distributed version +control systems, also shows unpushed local branches. =item commit (or ci) @@ -91,6 +96,12 @@ remote repository. Only supported for distributed version control systems. The optional -m parameter allows specifying a commit message. +=item fetch + +Fetches from each repository's remote repository, but does not +update the working copy. Only supported for some distributed version +control systems. + =item push Pushes committed local changes to the remote repository. A no-op for @@ -104,6 +115,11 @@ Show a diff of uncommitted changes. Show the commit log. +=item grep pattern + +Searches for a pattern in each repository using the grep subcommand. Uses +ack-grep on VCS that do not have their own. + =item run command [param ...] Runs the specified command in each repository. @@ -114,15 +130,34 @@ These commands are also available: =over 4 -=item bootstrap url [directory] +=item bootstrap src [directory] + +Causes mr to retrieve the source C and use it as a .mrconfig file to +checkout the repositories listed in it, into the specified directory. + +B understands several types of sources: + +=over 4 + +=item URL for curl + +C may be an URL understood by B. + +=item copy via ssh + +To use B to download, the C may have the form +C. -Causes mr to download the url, and use it as a .mrconfig file -to checkout the repositories listed in it, into the specified directory. +=item local file + +You can retrieve the config file by other means and pass its B as C. + +=back The directory will be created if it does not exist. If no directory is specified, the current directory will be used. -If the .mrconfig file includes a repository named ".", that +As a special case, if source C includes a repository named ".", that is checked out into the top of the specified directory. =item list (or ls) @@ -405,7 +440,7 @@ Unlike all other parameters, this parameter does not need to be placed within a section. B ships several libraries that can be included to add support for -additional version control type things (unison, git-svn, vcsh, git-fake-bare, +additional version control type things (unison, git-svn, git-fake-bare, git-subtree). To include them all, you could use: include = cat /usr/share/mr/* @@ -467,13 +502,6 @@ can be constructed accumulatively. The name of the version control system is itself determined by running each defined "VCS_test" action, until one succeeds. -=item VCS_dir_test - -This is a more optimised way to test for the version control system. -Each "VCS_dir_test" action is run once, and can output lines consisting -of the name of a VCS, and a directory to look for in the top of a repo -to detect that VCS. - =back =head1 UNTRUSTED MRCONFIG FILES @@ -515,7 +543,7 @@ Copyright 2007-2011 Joey Hess Licensed under the GNU GPL version 2 or higher. -http://kitenet.net/~joey/code/mr/ +http://myrepos.branchable.com/ =cut @@ -594,17 +622,22 @@ sub runsh { $runner->($shellcode); } -sub runshpipe { - runsh @_, sub { - my $sh=shift; - my $ret=`$sh`; - chomp $ret; - return $ret; - }; +my %perl_cache; +sub perl { + my $id=shift; + my $s=shift; + if ($s =~ m/^perl:\s+(.*)/s) { + return $perl_cache{$1} if exists $perl_cache{$1}; + my $sub=eval "sub {$1}"; + if (! defined $sub) { + print STDERR "mr: bad perl code in $id: $@\n"; + } + return $perl_cache{$1} = $sub; + } + return undef; } my %vcs; -my %vcs_dir_test; sub vcs_test { my ($action, $dir, $topdir, $subdir) = @_; @@ -613,53 +646,46 @@ sub vcs_test { } my $test=""; - my $dir_test=""; + my %perltest; foreach my $vcs_test ( sort { length $a <=> length $b || $a cmp $b } grep { /_test$/ } keys %{$config{$topdir}{$subdir}}) { - if ($vcs_test =~ /(.*)_dir_test/) { - my $vcs=$1; - if (! defined $vcs_dir_test{$vcs}) { - $dir_test.=$config{$topdir}{$subdir}{$vcs_test}."\n"; - } - next; + my ($vcs)=$vcs_test =~ /(.*)_test/; + my $p=perl($vcs_test, $config{$topdir}{$subdir}{$vcs_test}); + if (defined $p) { + $perltest{$vcs}=$p; } - my $vcs=$vcs_test =~ /(.*)_test/; - $test="my_$vcs_test() {\n$config{$topdir}{$subdir}{$vcs_test}\n}\n".$test; - $test.="if my_$vcs_test; then echo $vcs; fi\n"; - } - - if (length $dir_test) { - runsh "vcs dir test", $topdir, $subdir, $dir_test, [], sub { - my $sh=shift; - foreach my $line (`$sh`) { - chomp $line; - my ($vcs, $dir)=split(" ", $line); - $vcs_dir_test{$vcs}=$dir; - } + else { + $test="my_$vcs_test() {\n$config{$topdir}{$subdir}{$vcs_test}\n}\n".$test; + $test.="if my_$vcs_test; then echo $vcs; fi\n"; } } - foreach my $vcs (keys %vcs_dir_test) { - if (-d "$ENV{MR_REPO}/$vcs_dir_test{$vcs}") { - return $vcs{$dir}=$vcs; + my @vcs; + foreach my $vcs (keys %perltest) { + if ($perltest{$vcs}->()) { + push @vcs, $vcs; } } - my $vcs=runshpipe "vcs test", $topdir, $subdir, $test, []; - if ($vcs=~/\n/s) { - $vcs=~s/\n/, /g; - print STDERR "mr $action: found multiple possible repository types ($vcs) for ".fulldir($topdir, $subdir)."\n"; + push @vcs, split(/\n/, + runsh("vcs test", $topdir, $subdir, $test, [], sub { + my $sh=shift; + my $ret=`$sh`; + return $ret; + })) if length $test; + if (@vcs > 1) { + print STDERR "mr $action: found multiple possible repository types (@vcs) for ".fulldir($topdir, $subdir)."\n"; return undef; } - if (! length $vcs) { + if (! @vcs) { return $vcs{$dir}=undef; } else { - return $vcs{$dir}=$vcs; + return $vcs{$dir}=$vcs[0]; } } @@ -693,6 +719,7 @@ sub fulldir { sub action { my ($action, $dir, $topdir, $subdir, $force_checkout) = @_; my $fulldir=fulldir($topdir, $subdir); + my $checkout_dir; $ENV{MR_CONFIG}=$configfiles{$topdir}; my $is_checkout=($action eq 'checkout'); @@ -734,6 +761,7 @@ sub action { } if ($is_checkout) { + $checkout_dir=$dir; if (! $force_checkout) { if (-d $dir) { print "mr $action: $dir already exists, skipping checkout\n" if $verbose; @@ -837,7 +865,13 @@ sub action { my $ret=hook("post_$action", $topdir, $subdir); return $ret if $ret != OK; - if (($is_checkout || $is_update)) { + if ($is_checkout || $is_update) { + if ($is_checkout && ! $no_chdir) { + if (! chdir($checkout_dir)) { + print STDERR "mr $action: failed to chdir to $checkout_dir: $!\n"; + return FAILED; + } + } my $ret=hook("fixups", $topdir, $subdir); return $ret if $ret != OK; } @@ -1206,7 +1240,7 @@ my %loaded; sub loadconfig { my $f=shift; my $dir=shift; - my $bootstrap_url=shift; + my $bootstrap_src=shift; my @toload; @@ -1293,15 +1327,14 @@ sub loadconfig { }; my $trusterror = sub { my $msg=shift; - my ($err, $file, $lineno, $url)=@_; - if (defined $bootstrap_url) { - die "mr: $err in untrusted $bootstrap_url line $lineno\n". + if (defined $bootstrap_src) { + die "mr: $msg in untrusted $bootstrap_src line $lineno\n". "(To trust this url, --trust-all can be used; but please use caution;\n". "this can allow arbitrary code execution!)\n"; } else { - die "mr: $err in untrusted $file line $lineno\n". + die "mr: $msg in untrusted $f line $lineno\n". "(To trust this file, list it in ~/.mrtrust.)\n"; } }; @@ -1309,11 +1342,12 @@ sub loadconfig { while (@lines) { $_=$nextline->(); + next if /^\s*\#/ || /^\s*$/; + if (! $trusted && /[[:cntrl:]]/) { $trusterror->("illegal control character"); } - next if /^\s*\#/ || /^\s*$/; if (/^\[([^\]]*)\]\s*$/) { $section=$1; @@ -1652,23 +1686,47 @@ sub register { } sub bootstrap { - my $url=shift @ARGV; + eval q{use File::Copy}; + die $@ if $@; + + my $src=shift @ARGV; my $dir=shift @ARGV || "."; - if (! defined $url || ! length $url) { - die "mr: bootstrap requires url\n"; + if (! defined $src || ! length $src) { + die "mr: bootstrap requires source\n"; } - - # Download the config file to a temporary location. + + # Retrieve config file. eval q{use File::Temp}; die $@ if $@; my $tmpconfig=File::Temp->new(); - my @curlargs = ("curl", "-A", "mr", "-L", "-s", $url, "-o", $tmpconfig); - push(@curlargs, "-k") if $insecure; - my $curlstatus = system(@curlargs); - die "mr bootstrap: invalid SSL certificate for $url (consider -k)\n" if $curlstatus >> 8 == 60; - die "mr bootstrap: download of $url failed\n" if $curlstatus != 0; + if ($src =~ m!^[\w\d]+://!) { + # Download the config file to a temporary location. + my @downloader; + if ($src =~ m!^ssh://(.*)!) { + @downloader = ("scp", $1, $tmpconfig); + } + else { + @downloader = ("curl", "-A", "mr", "-L", "-s", $src, "-o", $tmpconfig); + push(@downloader, "-k") if $insecure; + } + my $status = system(@downloader); + die "mr bootstrap: invalid SSL certificate for $src (consider -k)\n" + if $downloader[0] eq 'curl' && $status >> 8 == 60; + die "mr bootstrap: download of $src failed\n" if $status != 0; + } + elsif ($src eq '-') { + # Config file is read from stdin. + copy(\*STDIN, $tmpconfig) || die "stdin: $!"; + } + else { + # Config file is local. + die "mr bootstrap: cannot read file '$src'" + unless -r $src; + copy($src, $tmpconfig) || die "copy: $!"; + } + # Sanity check on destination directory. if (! -e $dir) { system("mkdir", "-p", $dir); } @@ -1678,16 +1736,14 @@ sub bootstrap { # would normally be skipped. my $topdir=abs_path(".")."/"; my @repo=($topdir, $topdir, "."); - loadconfig($tmpconfig, $topdir, $url); + loadconfig($tmpconfig, $topdir, $src); record(\@repo, action("checkout", @repo, 1)) if exists $config{$topdir}{"."}{"checkout"}; if (-e ".mrconfig") { - print STDERR "mr bootstrap: .mrconfig file already exists, not overwriting with $url\n"; + print STDERR "mr bootstrap: .mrconfig file already exists, not overwriting with $src\n"; } else { - eval q{use File::Copy}; - die $@ if $@; move($tmpconfig, ".mrconfig") || die "rename: $!"; } @@ -1867,17 +1923,22 @@ lib = fi } -svn_dir_test = echo svn .svn -git_dir_test = echo git .git -bzr_dir_test = echo bzr .bzr -cvs_dir_test = echo cvs CVS -hg_dir_test = echo hg .hg -darcs_dir_test = echo darcs _darcs -fossil_test = test -f "$MR_REPO"/_FOSSIL_ -git_bare_test = - test -d "$MR_REPO"/refs/heads && test -d "$MR_REPO"/refs/tags && - test -d "$MR_REPO"/objects && test -f "$MR_REPO"/config && - test "`GIT_CONFIG="$MR_REPO"/config git config --get core.bare`" = true +svn_test = perl: -d "$ENV{MR_REPO}/.svn" +git_test = perl: -e "$ENV{MR_REPO}/.git" +bzr_test = perl: -d "$ENV{MR_REPO}/.bzr" +cvs_test = perl: -d "$ENV{MR_REPO}/CVS" +hg_test = perl: -d "$ENV{MR_REPO}/.hg" +darcs_test = perl: -d "$ENV{MR_REPO}/_darcs" +fossil_test = perl: -f "$ENV{MR_REPO}/_FOSSIL_" +git_bare_test = perl: + -d "$ENV{MR_REPO}/refs/heads" && -d "$ENV{MR_REPO}/refs/tags" && + -d "$ENV{MR_REPO}/objects" && -f "$ENV{MR_REPO}/config" && + `GIT_CONFIG="$ENV{MR_REPO}"/config git config --get core.bare` =~ /true/ +vcsh_test = perl: + -d "$ENV{MR_REPO}/refs/heads" && -d "$ENV{MR_REPO}/refs/tags" && + -d "$ENV{MR_REPO}/objects" && -f "$ENV{MR_REPO}/config" && + `GIT_CONFIG="$ENV{MR_REPO}"/config git config --get vcsh.vcsh` =~ /true/ +veracity_test = perl: -d "$ENV{MR_REPO}/.sgdrawer" svn_update = svn update "$@" git_update = git pull "$@" @@ -1887,18 +1948,27 @@ bzr_update = else bzr merge --pull "$@" fi -cvs_update = cvs update "$@" -hg_update = hg pull "$@" && hg update "$@" +cvs_update = cvs -q update "$@" +hg_update = hg pull "$@"; hg update "$@" darcs_update = darcs pull -a "$@" fossil_update = fossil pull "$@" +vcsh_update = vcsh run "$MR_REPO" git pull "$@" +veracity_update = vv pull "$@" && vv update "$@" + +git_fetch = git fetch --all --prune --tags +git_svn_fetch = git svn fetch +darcs_fetch = darcs fetch +hg_fetch = hg pull svn_status = svn status "$@" -git_status = git status -s "$@" || true -bzr_status = bzr status --short "$@" -cvs_status = cvs status "$@" -hg_status = hg status "$@" +git_status = git status -s "$@" || true; git --no-pager log --branches --not --remotes --simplify-by-decoration --decorate --oneline || true +bzr_status = bzr status --short "$@"; bzr missing +cvs_status = cvs -q status | grep -E '^(File:.*Status:|\?)' | grep -v 'Status: Up-to-date' +hg_status = hg status "$@"; hg summary --quiet | grep -v 'parent: 0:' darcs_status = darcs whatsnew -ls "$@" || true fossil_status = fossil changes "$@" +vcsh_status = vcsh run "$MR_REPO" git -c status.relativePaths=false status -s "$@" || true +veracity_status = vv status "$@" svn_commit = svn commit "$@" git_commit = git commit -a "$@" && git push --all @@ -1909,9 +1979,11 @@ bzr_commit = bzr commit "$@" && bzr push fi cvs_commit = cvs commit "$@" -hg_commit = hg commit -m "$@" && hg push -darcs_commit = darcs record -a -m "$@" && darcs push -a +hg_commit = hg commit "$@" && hg push +darcs_commit = darcs record -a "$@" && darcs push -a fossil_commit = fossil commit "$@" +vcsh_commit = vcsh run "$MR_REPO" git commit -a "$@" && vcsh run "$MR_REPO" git push --all +veracity_commit = vv commit "$@" && vv push git_record = git commit -a "$@" bzr_record = @@ -1920,9 +1992,11 @@ bzr_record = else bzr commit "$@" fi -hg_record = hg commit -m "$@" -darcs_record = darcs record -a -m "$@" +hg_record = hg commit "$@" +darcs_record = darcs record -a "$@" fossil_record = fossil commit "$@" +vcsh_record = vcsh run "$MR_REPO" git commit -a "$@" +veracity_record = vv commit "$@" svn_push = : git_push = git push "$@" @@ -1931,14 +2005,18 @@ cvs_push = : hg_push = hg push "$@" darcs_push = darcs push -a "$@" fossil_push = fossil push "$@" +vcsh_push = vcsh run "$MR_REPO" git push "$@" +veracity_push = vv push "$@" svn_diff = svn diff "$@" git_diff = git diff "$@" bzr_diff = bzr diff "$@" -cvs_diff = cvs diff "$@" +cvs_diff = cvs -q diff "$@" hg_diff = hg diff "$@" darcs_diff = darcs diff -u "$@" fossil_diff = fossil diff "$@" +vcsh_diff = vcsh run "$MR_REPO" git diff "$@" +veracity_diff = vv diff "$@" svn_log = svn log "$@" git_log = git log "$@" @@ -1948,6 +2026,15 @@ hg_log = hg log "$@" darcs_log = darcs changes "$@" git_bare_log = git log "$@" fossil_log = fossil timeline "$@" +vcsh_log = vcsh run "$MR_REPO" git log "$@" +veracity_log = vv log "$@" + +hg_grep = hg grep "$@" +cvs_grep = ack-grep "$@" +svn_grep = ack-grep "$@" +git_svn_grep = git grep "$@" +git_grep = git grep "$@" +bzr_grep = ack-grep "$@" run = "$@" @@ -1966,7 +2053,7 @@ git_register = echo "Registering git url: $url in $MR_CONFIG" mr -c "$MR_CONFIG" config "`pwd`" checkout="git clone '$url' '$MR_REPO'" bzr_register = - url="`LC_ALL=C bzr info . | egrep -i 'checkout of branch|parent branch' | awk '{print $NF}'`" + url="`LC_ALL=C bzr info . | egrep -i 'checkout of branch|parent branch' | awk '{print $NF}' | head -n 1`" if [ -z "$url" ]; then error "cannot determine bzr url" fi @@ -1995,11 +2082,23 @@ git_bare_register = fi echo "Registering git url: $url in $MR_CONFIG" mr -c "$MR_CONFIG" config "`pwd`" checkout="git clone --bare '$url' '$MR_REPO'" +vcsh_register = + url="`LC_ALL=C vcsh run "$MR_REPO" git config --get remote.origin.url`" || true + if [ -z "$url" ]; then + error "cannot determine git url" + fi + echo "Registering git url: $url in $MR_CONFIG" + mr -c "$MR_CONFIG" config "`pwd`" checkout="vcsh clone '$url' '$MR_REPO'" fossil_register = url=`fossil remote-url` repo=`fossil info | grep repository | sed -e 's/repository:*.//g' -e 's/ //g'` echo "Registering fossil repository $url in $MR_CONFIG" mr -c "$MR_CONFIG" config "`pwd`" checkout="mkdir -p '$MR_REPO' && cd '$MR_REPO' && fossil open '$repo'" +veracity_register = + url=`vv config | grep sync_targets | sed -e 's/sync_targets:*.//g' -e 's/ //g'` + repo=`vv repo info | grep repository | sed -e 's/Current repository:*.//g' -e 's/ //g'` + echo "Registering veracity repository $url in $MR_CONFIG" + mr -c "$MR_CONFIG" config "`pwd`" checkout="mkdir -p '$MR_REPO' && cd '$MR_REPO' && vv checkout '$repo'" svn_trusted_checkout = svn co $url $repo svn_alt_trusted_checkout = svn checkout $url $repo @@ -2009,7 +2108,9 @@ bzr_trusted_checkout = bzr checkout|clone|branch|get $url $repo hg_trusted_checkout = hg clone $url $repo darcs_trusted_checkout = darcs get $url $repo git_bare_trusted_checkout = git clone --bare $url $repo +vcsh_trusted_checkout = vcsh run "$MR_REPO" git clone $url $repo # fossil: messy to do +veracity_trusted_checkout = vv clone $url $repo help = @@ -2021,7 +2122,7 @@ help = SHOWMANFILE="man" ;; *) - SHOWMANFILE="man -l" + SHOWMANFILE="man" ;; esac if [ ! -e "$MR_PATH" ]; then