# 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
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
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 '
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";
}
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."