X-Git-Url: http://www.chiark.greenend.org.uk/ucgi/~ian/git?p=chiark-utils.git;a=blobdiff_plain;f=scripts%2Fgit-branchmove;fp=scripts%2Fgit-branchmove;h=029992dd776d9cb9067b8bc9b526bb792907a69a;hp=49fd370ec86fc74393e949aca37ec46d2a7eeb7d;hb=f4c74ad52db38f9f467207d3c1ea952e9b9441c8;hpb=07abfd57eb09934ad42dd3b2b12c46ee237b4b89 diff --git a/scripts/git-branchmove b/scripts/git-branchmove index 49fd370..029992d 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,6 +29,7 @@ esac case "$remote" in *:*) remoteurl="$remote" ;; +[/.]*) remoteurl="$remote" ;; *) remoteurl="$( git config remote."$remote".pushurl || git config remote."$remote".url || @@ -50,8 +43,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 +61,112 @@ get) src_path="$remote_path" dst_rune="sh -c" dst_path=. + msg="git-branchmove: moved to $remote ($remoteurl)" ;; + push_fetch=fetch ;; put) dst_rune="$remote_rune" dst_path="$remote_path" src_rune="sh -c" src_path=. + msg="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 + +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" "$message" + for src_rm in "${src_branches[@]}"; do printf "%s\n" "$src_rm"; done +) | on_src ' + read message + while read src_rm; do + src_ref="${src_rm%=*}" + src_obj="${src_rm##*=}" + git update-ref -m "$message" -d "$src_ref" "$src_obj" + echo "move complete: $src_ref" + done +' + +echo 'moved ${#src_branches[@]} branches.'