chiark / gitweb /
git-branchmove: new script, still work in progress
[chiark-utils.git] / scripts / git-branchmove
index 49fd370ec86fc74393e949aca37ec46d2a7eeb7d..029992dd776d9cb9067b8bc9b526bb792907a69a 100755 (executable)
@@ -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.'