Weird, I forgot to have 'make test' call test.sh.
[gitweb.git] / git-subtree.sh
index ea0294fb79d969b37bf15ca6b051227feb13f822..8baa376fe5b99dff91fa6cfc9f8fedab69644acb 100755 (executable)
@@ -23,8 +23,11 @@ b,branch=     create a new branch from the split subtree
 ignore-joins  ignore prior --rejoin commits
 onto=         try connecting new tree to an existing one
 rejoin        merge the new branch back into HEAD
+ options for 'add', 'merge', and 'pull'
+squash        merge subtree changes as a single commit
 "
 eval $(echo "$OPTS_SPEC" | git rev-parse --parseopt -- "$@" || echo exit $?)
+PATH=$(git --exec-path):$PATH
 . git-sh-setup
 require_work_tree
 
@@ -36,6 +39,7 @@ onto=
 rejoin=
 ignore_joins=
 annotate=
+squash=
 
 debug()
 {
@@ -80,6 +84,8 @@ while [ $# -gt 0 ]; do
                --no-rejoin) rejoin= ;;
                --ignore-joins) ignore_joins=1 ;;
                --no-ignore-joins) ignore_joins= ;;
+               --squash) squash=1 ;;
+               --no-squash) squash= ;;
                --) break ;;
                *) die "Unexpected option: $opt" ;;
        esac
@@ -96,7 +102,7 @@ esac
 if [ -z "$prefix" ]; then
        die "You must provide the --prefix option."
 fi
-dir="$prefix"
+dir="$(dirname "$prefix/.")"
 
 if [ "$command" != "pull" ]; then
        revs=$(git rev-parse $default --revs-only "$@") || exit $?
@@ -162,26 +168,70 @@ try_remove_previous()
        fi
 }
 
+find_latest_squash()
+{
+       debug "Looking for latest squash ($dir)..."
+       dir="$1"
+       sq=
+       main=
+       sub=
+       git log --grep="^git-subtree-dir: $dir/*\$" \
+               --pretty=format:'START %H%n%s%n%n%b%nEND%n' HEAD |
+       while read a b junk; do
+               debug "$a $b $junk"
+               debug "{{$sq/$main/$sub}}"
+               case "$a" in
+                       START) sq="$b" ;;
+                       git-subtree-mainline:) main="$b" ;;
+                       git-subtree-split:) sub="$b" ;;
+                       END)
+                               if [ -n "$sub" ]; then
+                                       if [ -n "$main" ]; then
+                                               # a rejoin commit?
+                                               # Pretend its sub was a squash.
+                                               sq="$sub"
+                                       fi
+                                       debug "Squash found: $sq $sub"
+                                       echo "$sq" "$sub"
+                                       break
+                               fi
+                               sq=
+                               main=
+                               sub=
+                               ;;
+               esac
+       done
+}
+
 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 |
+       main=
+       sub=
+       git log --grep="^git-subtree-dir: $dir/*\$" \
+               --pretty=format:'START %H%n%s%n%n%b%nEND%n' $revs |
        while read a b junk; do
                case "$a" in
+                       START) sq="$b" ;;
                        git-subtree-mainline:) main="$b" ;;
                        git-subtree-split:) sub="$b" ;;
-                       *)
+                       END)
+                               debug "  Main is: '$main'"
+                               if [ -z "$main" -a -n "$sub" ]; then
+                                       # squash commits refer to a subtree
+                                       debug "  Squash: $sq from $sub"
+                                       cache_set "$sq" "$sub"
+                               fi
                                if [ -n "$main" -a -n "$sub" ]; then
                                        debug "  Prior: $main -> $sub"
                                        cache_set $main $sub
                                        try_remove_previous "$main"
                                        try_remove_previous "$sub"
-                                       main=
-                                       sub=
                                fi
+                               main=
+                               sub=
                                ;;
                esac
        done
@@ -225,7 +275,7 @@ add_msg()
        EOF
 }
 
-merge_msg()
+rejoin_msg()
 {
        dir="$1"
        latest_old="$2"
@@ -239,6 +289,28 @@ merge_msg()
        EOF
 }
 
+squash_msg()
+{
+       dir="$1"
+       oldsub="$2"
+       newsub="$3"
+       newsub_short=$(git rev-parse --short "$newsub")
+       
+       if [ -n "$oldsub" ]; then
+               oldsub_short=$(git rev-parse --short "$oldsub")
+               echo "Squashed '$dir/' changes from $oldsub_short..$newsub_short"
+               echo
+               git log --pretty=tformat:'%h %s' "$oldsub..$newsub"
+               git log --pretty=tformat:'REVERT: %h %s' "$newsub..$oldsub"
+       else
+               echo "Squashed '$dir/' content from commit $newsub_short"
+       fi
+       
+       echo
+       echo "git-subtree-dir: $dir"
+       echo "git-subtree-split: $newsub"
+}
+
 toptree_for_commit()
 {
        commit="$1"
@@ -252,6 +324,7 @@ subtree_for_commit()
        git ls-tree "$commit" -- "$dir" |
        while read mode type tree name; do
                assert [ "$name" = "$dir" ]
+               assert [ "$type" = "tree" ]
                echo $tree
                break
        done
@@ -273,6 +346,21 @@ tree_changed()
        fi
 }
 
+new_squash_commit()
+{
+       old="$1"
+       oldsub="$2"
+       newsub="$3"
+       tree=$(toptree_for_commit $newsub) || exit $?
+       if [ -n "$old" ]; then
+               squash_msg "$dir" "$oldsub" "$newsub" | 
+                       git commit-tree "$tree" -p "$old" || exit $?
+       else
+               squash_msg "$dir" "" "$newsub" |
+                       git commit-tree "$tree" || exit $?
+       fi
+}
+
 copy_or_skip()
 {
        rev="$1"
@@ -341,7 +429,7 @@ cmd_add()
        
        debug "Adding $dir as '$rev'..."
        git read-tree --prefix="$dir" $rev || exit $?
-       git checkout "$dir" || exit $?
+       git checkout -- "$dir" || exit $?
        tree=$(git write-tree) || exit $?
        
        headrev=$(git rev-parse HEAD) || exit $?
@@ -350,9 +438,18 @@ cmd_add()
        else
                headp=
        fi
-       commit=$(add_msg "$dir" "$headrev" "$rev" |
-                git commit-tree $tree $headp -p "$rev") || exit $?
+       
+       if [ -n "$squash" ]; then
+               rev=$(new_squash_commit "" "" "$rev") || exit $?
+               commit=$(echo "Merge commit '$rev' as '$dir'" |
+                        git commit-tree $tree $headp -p "$rev") || exit $?
+       else
+               commit=$(add_msg "$dir" "$headrev" "$rev" |
+                        git commit-tree $tree $headp -p "$rev") || exit $?
+       fi
        git reset "$commit" || exit $?
+       
+       say "Added dir '$dir'"
 }
 
 cmd_split()
@@ -405,6 +502,9 @@ cmd_split()
                
                tree=$(subtree_for_commit $rev "$dir")
                debug "  tree is: $tree"
+               
+               # ugly.  is there no better way to tell if this is a subtree
+               # vs. a mainline commit?  Does it matter?
                [ -z $tree ] && continue
 
                newrev=$(copy_or_skip "$rev" "$tree" "$newparents") || exit $?
@@ -422,7 +522,7 @@ cmd_split()
                debug "Merging split branch into HEAD..."
                latest_old=$(cache_get latest_old)
                git merge -s ours \
-                       -m "$(merge_msg $dir $latest_old $latest_new)" \
+                       -m "$(rejoin_msg $dir $latest_old $latest_new)" \
                        $latest_new >&2 || exit $?
        fi
        if [ -n "$branch" ]; then
@@ -444,14 +544,32 @@ cmd_merge()
        fi
        rev="$1"
        
+       if [ -n "$squash" ]; then
+               first_split="$(find_latest_squash "$dir")"
+               if [ -z "$first_split" ]; then
+                       die "Can't squash-merge: '$dir' was never added."
+               fi
+               set $first_split
+               old=$1
+               sub=$2
+               if [ "$sub" = "$rev" ]; then
+                       say "Subtree is already at commit $rev."
+                       exit 0
+               fi
+               new=$(new_squash_commit "$old" "$sub" "$rev") || exit $?
+               debug "New squash commit: $new"
+               rev="$new"
+       fi
+       
        git merge -s subtree $rev
 }
 
 cmd_pull()
 {
        ensure_clean
-       set -x
-       git pull -s subtree "$@"
+       git fetch "$@" || exit $?
+       revs=FETCH_HEAD
+       cmd_merge
 }
 
 "cmd_$command" "$@"