Hmm... can't actually filter rev-list on the subdir name.
[gitweb.git] / git-subtree.sh
index c59759baa63c241170fd112de907b32da65d6ccb..1e1237f52006db612a4e69d1565672456bc07854 100755 (executable)
@@ -2,10 +2,10 @@
 #
 # git-subtree.sh: split/join git repositories in subdirectories of this one
 #
-# Copyright (c) 2009 Avery Pennarun <apenwarr@gmail.com>
+# Copyright (C) 2009 Avery Pennarun <apenwarr@gmail.com>
 #
 OPTS_SPEC="\
-git subtree split <revisions> -- <subdir>
+git subtree split [--rejoin] [--onto rev] <commit...> -- <path>
 git subtree merge 
 
 git subtree does foo and bar!
@@ -13,6 +13,8 @@ git subtree does foo and bar!
 h,help   show the help
 q        quiet
 v        verbose
+onto=    existing subtree revision to search for parent
+rejoin   merge the new branch back into HEAD
 "
 eval $(echo "$OPTS_SPEC" | git rev-parse --parseopt -- "$@" || echo exit $?)
 . git-sh-setup
@@ -20,6 +22,8 @@ require_work_tree
 
 quiet=
 command=
+onto=
+rejoin=
 
 debug()
 {
@@ -45,6 +49,8 @@ while [ $# -gt 0 ]; do
        shift
        case "$opt" in
                -q) quiet=1 ;;
+               --onto) onto="$1"; shift ;;
+               --rejoin) rejoin=1 ;;
                --) break ;;
        esac
 done
@@ -77,7 +83,6 @@ cache_setup()
        rm -rf "$cachedir" || die "Can't delete old cachedir: $cachedir"
        mkdir -p "$cachedir" || die "Can't create new cachedir: $cachedir"
        debug "Using cachedir: $cachedir" >&2
-       echo "$cachedir"
 }
 
 cache_get()
@@ -94,22 +99,105 @@ cache_set()
 {
        oldrev="$1"
        newrev="$2"
-       if [ -e "$cachedir/$oldrev" ]; then
+       if [ "$oldrev" != "latest_old" \
+            -a "$oldrev" != "latest_new" \
+            -a -e "$cachedir/$oldrev" ]; then
                die "cache for $oldrev already exists!"
        fi
        echo "$newrev" >"$cachedir/$oldrev"
 }
 
+find_existing_splits()
+{
+       debug "Looking for prior splits..."
+       dir="$1"
+       revs="$2"
+       git log --grep="^git-subtree-dir: $dir\$" \
+               --pretty=format:'%s%n%n%b%nEND' "$revs" |
+       while read a b junk; do
+               case "$a" in
+                       git-subtree-mainline:) main="$b" ;;
+                       git-subtree-split:) sub="$b" ;;
+                       *)
+                               if [ -n "$main" -a -n "$sub" ]; then
+                                       debug "  Prior: $main -> $sub"
+                                       cache_set $main $sub
+                                       echo "^$main^ ^$sub^"
+                                       main=
+                                       sub=
+                               fi
+                               ;;
+               esac
+       done
+}
+
+copy_commit()
+{
+       # We're doing to set some environment vars here, so
+       # do it in a subshell to get rid of them safely later
+       git log -1 --pretty=format:'%an%n%ae%n%ad%n%cn%n%ce%n%cd%n%s%n%n%b' "$1" |
+       (
+               read GIT_AUTHOR_NAME
+               read GIT_AUTHOR_EMAIL
+               read GIT_AUTHOR_DATE
+               read GIT_COMMITTER_NAME
+               read GIT_COMMITTER_EMAIL
+               read GIT_COMMITTER_DATE
+               export  GIT_AUTHOR_NAME \
+                       GIT_AUTHOR_EMAIL \
+                       GIT_AUTHOR_DATE \
+                       GIT_COMMITTER_NAME \
+                       GIT_COMMITTER_EMAIL \
+                       GIT_COMMITTER_DATE
+               git commit-tree "$2" $3  # reads the rest of stdin
+       ) || die "Can't copy commit $1"
+}
+
+merge_msg()
+{
+       dir="$1"
+       latest_old="$2"
+       latest_new="$3"
+       cat <<-EOF
+               Split '$dir/' into commit '$latest_new'
+               
+               git-subtree-dir: $dir
+               git-subtree-mainline: $latest_old
+               git-subtree-split: $latest_new
+       EOF
+}
+
 cmd_split()
 {
        debug "Splitting $dir..."
        cache_setup || exit $?
        
-       git rev-list --reverse --parents $revs -- "$dir" |
+       if [ -n "$onto" ]; then
+               echo "Reading history for --onto=$onto..."
+               git rev-list $onto |
+               while read rev; do
+                       # the 'onto' history is already just the subdir, so
+                       # any parent we find there can be used verbatim
+                       debug "  cache: $rev"
+                       cache_set $rev $rev
+               done
+       fi
+       
+       unrevs="$(find_existing_splits "$dir" "$revs")"
+       
+       debug "git rev-list --reverse $revs $unrevs"
+       git rev-list --reverse --parents $revs $unrevsx |
        while read rev parents; do
-               newparents=$(cache_get $parents)
                debug
-               debug "Processing commit: $rev / $newparents"
+               debug "Processing commit: $rev"
+               exists=$(cache_get $rev)
+               if [ -n "$exists" ]; then
+                       debug "  prior: $exists"
+                       continue
+               fi
+               debug "  parents: $parents"
+               newparents=$(cache_get $parents)
+               debug "  newparents: $newparents"
                
                git ls-tree $rev -- "$dir" |
                while read mode type tree name; do
@@ -119,13 +207,27 @@ cmd_split()
                        for parent in $newparents; do
                                p="$p -p $parent"
                        done
-                       newrev=$(echo synthetic | git commit-tree $tree $p) \
-                               || die "Can't create new commit for $rev / $tree"
-                       echo "  newrev is: $newrev"
+                       
+                       newrev=$(copy_commit $rev $tree "$p") || exit $?
+                       debug "  newrev is: $newrev"
                        cache_set $rev $newrev
+                       cache_set latest_new $newrev
+                       cache_set latest_old $rev
                done || exit $?
        done || exit $?
+       latest_new=$(cache_get latest_new)
+       if [ -z "$latest_new" ]; then
+               die "No new revisions were found"
+       fi
        
+       if [ -n "$rejoin" ]; then
+               debug "Merging split branch into HEAD..."
+               latest_old=$(cache_get latest_old)
+               git merge -s ours \
+                       -m "$(merge_msg $dir $latest_old $latest_new)" \
+                       $latest_new
+       fi
+       echo $latest_new
        exit 0
 }