3 # Moves a branch to or from the current git tree to or from
6 # usage: git-branchmove get|put REMOTE PATTERN
11 fail () { echo >&2 "git-branchmove: $*"; exit 16; }
12 badusage () { fail "bad usage: $*"; }
14 if [ $# -lt 3 ]; then badusage "too few arguments"; fi
17 case "$op" in get|put) ;; *) badusage "unknown operation \`$op'"; esac
22 # determine execute-sh runes for src and dst trees
23 # list affected branches on source
24 # check that source branches are not checked out
25 # list affected branches on destination and moan if any nonequal overlap
26 # transfer src->dst refs/heads/BRANCH:refs/heads/BRANCH
27 # transfer and merge reflog(s) xxx todo
31 *:*) remoteurl="$remote" ;;
32 [/.]*) remoteurl="$remote" ;;
34 git config remote."$remote".pushurl ||
35 git config remote."$remote".url ||
36 fail "no pushurl or url defined for remote $remote"
40 remote_spec="$(perl -e '
42 if (m#^ssh://([^:/]+)(?:\:(\w+))?#) {
46 } elsif (m#^([-+_.0-9a-zA-Z\@]+):(?!//|:)#) {
47 print "$'\''|ssh $1\n";
49 print "$&|sh -c $1\n";
51 die "git-branchmove: unsupported remote url \`$_'\''\n";
55 remote_path="${remote_spec%%|*}"
56 remote_rune="${remote_spec#*|}"
60 src_rune="$remote_rune"
61 src_path="$remote_path"
64 updatemsg="git-branchmove: moved to $remote ($remoteurl)"
68 dst_rune="$remote_rune"
69 dst_path="$remote_path"
72 updatemsg="git-branchmove; moved to `hostname -f` by `whoami`"
77 on_src () { $src_rune "set -e; cd $src_path; $*"; }
78 on_dst () { $dst_rune "set -e; cd $dst_path; $*"; }
81 #----- fetch the current refs from both sides -----
84 for branch_pat in "$@"; do
85 branch_pats+=" '[r]efs/heads/$branch_pat'"
89 git for-each-ref --format="%(refname)=%(objectname)" '"$branch_pats"'
95 git symbolic-ref -q HEAD || test $? = 1
97 '"$get_branches_rune"'
101 src_head="${src_branches[0]}"
102 unset src_branches[0]
103 : "${src_branches[@]}"
106 H) ;; # already detached
108 src_head="${src_head#H}"
109 for check in "${src_branches[@]}"; do
112 fail "would delete checked-out branch $src_head"
120 if [ "${#src_branches[@]}" = 0 ]; then
121 echo >&2 "git-branchmove: nothing to do"
125 dst_branches=( $(on_dst "$get_branches_rune") )
126 : "${dst_branches[@]}"
129 #----- check for nonequal overlaps -----
132 for dst_check in "${dst_branches[@]}"; do
133 dst_ref="${dst_check%=*}"
134 for src_check in "${src_branches[@]}"; do
139 echo >&2 "src: $src_check dst: $dst_check"
145 $ok || fail "would overwrite some destination branch(es)"
148 #----- do the transfer -----
151 for src_xfer in "${src_branches[@]}"; do
152 src_ref="${src_xfer%=*}"
153 refspecs+=("$src_ref:$src_ref")
157 put) git push "$remote" "${refspecs[@]}" ;;
158 get) git fetch "$remote" "${refspecs[@]}" ;;
159 *) fail "unknown $op ???" ;;
163 #----- delete the refs on the source -----
166 printf "%s\n" "$updatemsg"
167 for src_rm in "${src_branches[@]}"; do printf "%s\n" "$src_rm"; done
170 while read src_rm; do
171 src_ref="${src_rm%=*}"
172 src_obj="${src_rm##*=}"
173 git update-ref -m "$updatemsg" -d "$src_ref" "$src_obj"
174 echo "move complete: $src_ref"
178 echo "moved ${#src_branches[@]} branches."