chiark / gitweb /
cgi-fcgi-interp: Actually get run in ~ right
[chiark-utils.git] / scripts / git-branchmove
index 49fd370ec86fc74393e949aca37ec46d2a7eeb7d..6952727b7421614ecb8b9f1ffee9c167110b7e6e 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,11 +29,13 @@ esac
 
 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 '
@@ -50,8 +44,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 +62,140 @@ get)
        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."