X-Git-Url: http://www.chiark.greenend.org.uk/ucgi/~ian/git?a=blobdiff_plain;f=scripts%2Fgit-branchmove;h=6952727b7421614ecb8b9f1ffee9c167110b7e6e;hb=30cbd6a4e31c37e9726f1d6f1eaab7a60ba7aa76;hp=49fd370ec86fc74393e949aca37ec46d2a7eeb7d;hpb=07abfd57eb09934ad42dd3b2b12c46ee237b4b89;p=chiark-utils.git diff --git a/scripts/git-branchmove b/scripts/git-branchmove index 49fd370..6952727 100755 --- a/scripts/git-branchmove +++ b/scripts/git-branchmove @@ -3,7 +3,7 @@ # Moves a branch to or from the current git tree to or from # another git tree # -# usage: git-branchmove get|put REMOTE BRANCH +# usage: git-branchmove get|put REMOTE PATTERN set -e set -o posix @@ -11,25 +11,17 @@ set -o posix fail () { echo >&2 "git-branchmove: $*"; exit 16; } badusage () { fail "bad usage: $*"; } -case "$#.$1" in -3.get) - op=get - remote="$2" - branch="$3" - ;; -3.put) - op=put - branch="$3" - ;; -*) - badusage "wrong number of arguments or wrong operation" - ;; -esac +if [ $# -lt 3 ]; then badusage "too few arguments"; fi + +op="$1"; shift +case "$op" in get|put) ;; *) badusage "unknown operation \`$op'"; esac + +remote="$1"; shift # Plan of attack: # determine execute-sh runes for src and dst trees -# check that source branch is not checked out # list affected branches on source +# check that source branches are not checked out # list affected branches on destination and moan if any nonequal overlap # transfer src->dst refs/heads/BRANCH:refs/heads/BRANCH # transfer and merge reflog(s) xxx todo @@ -37,11 +29,13 @@ esac case "$remote" in *:*) remoteurl="$remote" ;; +[/.]*) remoteurl="$remote" ;; *) remoteurl="$( git config remote."$remote".pushurl || git config remote."$remote".url || fail "no pushurl or url defined for remote $remote" )" + remotename="$remote" esac remote_spec="$(perl -e ' @@ -50,8 +44,10 @@ remote_spec="$(perl -e ' print "$'\''|ssh "; print " -p $3" if $2; print "$1\n"; - } elsif (m#^([-+_.0-9a-zA-Z\@]+):(?!//)#) { + } elsif (m#^([-+_.0-9a-zA-Z\@]+):(?!//|:)#) { print "$'\''|ssh $1\n"; + } elsif (m#^[/.]#) { + print "$_|sh -c $1\n"; } else { die "git-branchmove: unsupported remote url \`$_'\''\n"; } @@ -66,12 +62,140 @@ get) src_path="$remote_path" dst_rune="sh -c" dst_path=. + updatemsg="git-branchmove: moved to $remote ($remoteurl)" + push_fetch=fetch ;; put) dst_rune="$remote_rune" dst_path="$remote_path" src_rune="sh -c" src_path=. + updatemsg="git-branchmove; moved to `hostname -f` by `whoami`" + push_fetch=push ;; esac +on_src () { $src_rune "set -e; cd $src_path; $*"; } +on_dst () { $dst_rune "set -e; cd $dst_path; $*"; } + + +#----- fetch the current refs from both sides ----- + +branch_pats='' +for branch_pat in "$@"; do + branch_pats+=" '[r]efs/heads/$branch_pat'" +done + +get_branches_rune=' + git for-each-ref --format="%(refname)=%(objectname)" '"$branch_pats"' +' + +src_branches=( $( + on_src ' + printf H + git symbolic-ref -q HEAD || test $? = 1 + echo " " + '"$get_branches_rune"' + ' +)) + +src_head="${src_branches[0]}" +unset src_branches[0] +: "${src_branches[@]}" + +case "$src_head" in +H) ;; # already detached +*) + src_head="${src_head#H}" + for check in "${src_branches[@]}"; do + case "$check" in + "$src_head"=*) + fail "would delete checked-out branch $src_head" + ;; + esac + done + ;; +esac + + +if [ "${#src_branches[@]}" = 0 ]; then + echo >&2 "git-branchmove: nothing to do" + exit 1 +fi + +dst_branches=( $(on_dst "$get_branches_rune") ) +: "${dst_branches[@]}" + + +#----- check for nonequal overlaps ----- + +ok=true +for dst_check in "${dst_branches[@]}"; do + dst_ref="${dst_check%=*}" + for src_check in "${src_branches[@]}"; do + case "$src_check" in + "$dst_check") ;; + "$dst_ref"=*) + ok=false + echo >&2 "src: $src_check dst: $dst_check" + ;; + esac + done +done + +$ok || fail "would overwrite some destination branch(es)" + + +#----- do the transfer ----- + +refspecs=() +for src_xfer in "${src_branches[@]}"; do + src_ref="${src_xfer%=*}" + refspecs+=("$src_ref:$src_ref") +done + +case "$op" in +put) git push "$remote" "${refspecs[@]}" ;; +get) git fetch "$remote" "${refspecs[@]}" ;; +*) fail "unknown $op ???" ;; +esac + + +#----- delete the refs on the source ----- + +( + printf "%s\n" "$updatemsg" + for src_rm in "${src_branches[@]}"; do printf "%s\n" "$src_rm"; done +) | on_src ' + read updatemsg + while read src_rm; do + src_ref="${src_rm%=*}" + src_obj="${src_rm##*=}" + git update-ref -m "$updatemsg" -d "$src_ref" "$src_obj" + echo "moved: $src_ref" + done +' + +#----- update the remote tracking branches ----- + +if [ "x$remotename" != x ]; then + for src_rm in "${src_branches[@]}"; do + src_ref="${src_rm%=*}" + src_obj="${src_rm##*=}" + + case "$src_ref" in + refs/heads/*) ;; + *) continue ;; + esac + + branch="${src_ref#refs/heads/}" + track_ref="refs/remotes/$remotename/$branch" + case $op in + get) git update-ref -d "$track_ref" ;; + put) git update-ref "$track_ref" "$src_obj" ;; + *) fail "unknown $op ???" + esac + done +fi + +echo "git-repomove: moved ${#src_branches[@]} branches."