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"
41 remote_spec="$(perl -e '
43 if (m#^ssh://([^:/]+)(?:\:(\w+))?#) {
47 } elsif (m#^([-+_.0-9a-zA-Z\@]+):(?!//|:)#) {
48 print "$'\''|ssh $1\n";
50 print "$_|sh -c $1\n";
52 die "git-branchmove: unsupported remote url \`$_'\''\n";
56 remote_path="${remote_spec%%|*}"
57 remote_rune="${remote_spec#*|}"
61 src_rune="$remote_rune"
62 src_path="$remote_path"
65 updatemsg="git-branchmove: moved to $remote ($remoteurl)"
69 dst_rune="$remote_rune"
70 dst_path="$remote_path"
73 updatemsg="git-branchmove; moved to `hostname -f` by `whoami`"
78 on_src () { $src_rune "set -e; cd $src_path; $*"; }
79 on_dst () { $dst_rune "set -e; cd $dst_path; $*"; }
82 #----- fetch the current refs from both sides -----
85 for branch_pat in "$@"; do
86 branch_pats+=" '[r]efs/heads/$branch_pat'"
90 git for-each-ref --format="%(refname)=%(objectname)" '"$branch_pats"'
96 git symbolic-ref -q HEAD || test $? = 1
98 '"$get_branches_rune"'
102 src_head="${src_branches[0]}"
103 unset src_branches[0]
104 : "${src_branches[@]}"
107 H) ;; # already detached
109 src_head="${src_head#H}"
110 for check in "${src_branches[@]}"; do
113 fail "would delete checked-out branch $src_head"
121 if [ "${#src_branches[@]}" = 0 ]; then
122 echo >&2 "git-branchmove: nothing to do"
126 dst_branches=( $(on_dst "$get_branches_rune") )
127 : "${dst_branches[@]}"
130 #----- check for nonequal overlaps -----
133 for dst_check in "${dst_branches[@]}"; do
134 dst_ref="${dst_check%=*}"
135 for src_check in "${src_branches[@]}"; do
140 echo >&2 "src: $src_check dst: $dst_check"
146 $ok || fail "would overwrite some destination branch(es)"
149 #----- do the transfer -----
152 for src_xfer in "${src_branches[@]}"; do
153 src_ref="${src_xfer%=*}"
154 refspecs+=("$src_ref:$src_ref")
158 put) git push "$remote" "${refspecs[@]}" ;;
159 get) git fetch "$remote" "${refspecs[@]}" ;;
160 *) fail "unknown $op ???" ;;
164 #----- delete the refs on the source -----
167 printf "%s\n" "$updatemsg"
168 for src_rm in "${src_branches[@]}"; do printf "%s\n" "$src_rm"; done
171 while read src_rm; do
172 src_ref="${src_rm%=*}"
173 src_obj="${src_rm##*=}"
174 git update-ref -m "$updatemsg" -d "$src_ref" "$src_obj"
175 echo "moved: $src_ref"
179 #----- update the remote tracking branches -----
181 if [ "x$remotename" != x ]; then
182 for src_rm in "${src_branches[@]}"; do
183 src_ref="${src_rm%=*}"
184 src_obj="${src_rm##*=}"
191 branch="${src_ref#refs/heads/}"
192 track_ref="refs/remotes/$remotename/$branch"
194 get) git update-ref -d "$track_ref" ;;
195 put) git update-ref "$track_ref" "$src_obj" ;;
196 *) fail "unknown $op ???"
201 echo "git-repomove: moved ${#src_branches[@]} branches."