diff --git a/contrib/subtree/git-subtree.sh b/contrib/subtree/git-subtree.sh index 3ebe88cbea..17106d1a72 100755 --- a/contrib/subtree/git-subtree.sh +++ b/contrib/subtree/git-subtree.sh @@ -325,12 +325,6 @@ check_parents () { done } -# Usage: get_notree REV -get_notree () { - assert test $# = 1 - test -r "$cachedir/notree/$1" -} - # Usage: set_notree REV set_notree () { assert test $# = 1 @@ -517,71 +511,6 @@ find_existing_splits () { done || exit $? } -# Usage: find_other_splits DIR REV UNREVS... -# -# Scan history in REV UNREVS for other `git subtree split --rejoin` -# merge commits belonging to prefixes outside of DIR. These -# "other splits" don't contribute to DIR and can be ignored. -# -# If any such rejoins are found, -# -# * emit their second-parent as an UNREV, avoiding a -# potentially costly history traversal -# -# * mark the merge commit as "notree" to ignore it -find_other_splits () { - assert test $# -ge 2 - dir="${1%/}" - rev="$2" - shift 2 - debug "Looking for other splits with dir != $dir..." - - git log \ - --grep '^git-subtree-mainline:' \ - --no-patch \ - --no-show-signature \ - --format='hash: %H%nparents: %P%n%(trailers:key=git-subtree-dir,key=git-subtree-mainline,key=git-subtree-split)%nEND' \ - "$rev" ${@:+"$@"} | - while read -r key val - do - case "$key" in - hash:) - commit_hash="${val}" - commit_parents= - subtree_dir= - subtree_mainline= - subtree_split= - ;; - parents:) - commit_parents="${val}" ;; - git-subtree-dir:) - subtree_dir="${val%/}/" ;; - git-subtree-mainline:) - subtree_mainline="${val}" ;; - git-subtree-split:) - subtree_split="${val}" ;; - END) - # verify: - # * all git-subtree-* trailers are present - # * this subtree is outside of $dir - # * the first parent is the git-subtree-mainline: - # * the commit has at least two parents - if test -n "${subtree_dir}" && - test -n "${subtree_split}" && - test -n "${subtree_mainline}" && - test "${subtree_dir}" = "${subtree_dir#"${dir}/"}" && - test "${commit_parents}" != "${commit_parents#"$subtree_mainline "}" && - rev_exists "${commit_hash}^2" - then - debug "find_other_splits excluding dir=$subtree_dir merged in ${commit_hash}" - echo "^${commit_hash}^2" - set_notree "${commit_hash}" - fi - ;; - esac - done -} - # Usage: copy_commit REV TREE FLAGS_STR copy_commit () { assert test $# = 3 @@ -856,6 +785,42 @@ ensure_valid_ref_format () { die "fatal: '$1' does not look like a ref" } +# Usage: should_ignore_subtree_split_commit REV +# +# Check if REV is a commit from another subtree and should be +# ignored from processing for splits +should_ignore_subtree_split_commit () { + assert test $# = 1 + + git show \ + --no-patch \ + --no-show-signature \ + --format='%(trailers:key=git-subtree-dir,key=git-subtree-mainline)' \ + "$1" | + ( + have_mainline= + subtree_dir= + + while read -r trailer val + do + case "$trailer" in + git-subtree-dir:) + subtree_dir="${val%/}" ;; + git-subtree-mainline:) + have_mainline=y ;; + esac + done + + if test -n "${subtree_dir}" && + test -z "${have_mainline}" && + test "${subtree_dir}" != "$arg_prefix" + then + return 0 + fi + return 1 + ) +} + # Usage: process_split_commit REV PARENTS process_split_commit () { assert test $# = 2 @@ -1029,39 +994,31 @@ cmd_split () { fi unrevs="$(find_existing_splits "$dir" "$rev" "$repository")" || exit $? - (find_other_splits >"$cachedir/prune" "$dir" "$rev" $unrevs) || exit $? # We can't restrict rev-list to only $dir here, because some of our # parents have the $dir contents the root, and those won't match. # (and rev-list --follow doesn't seem to solve this) - revmax="$(git rev-list \ - <"$cachedir/prune" \ - --topo-order \ - --reverse \ - --parents \ - --stdin \ - --count \ - "$rev" \ - $unrevs - )" + grl='git rev-list --topo-order --reverse --parents $rev $unrevs' + revmax=$(eval "$grl" | wc -l) revcount=0 createcount=0 extracount=0 - git rev-list \ - <"$cachedir/prune" \ - --topo-order \ - --reverse \ - --parents \ - --stdin \ - "$rev" \ - $unrevs | + eval "$grl" | while read rev parents do - if get_notree "$rev" + if should_ignore_subtree_split_commit "$rev" then continue fi - process_split_commit "$rev" "$parents" + parsedparents='' + for parent in $parents + do + if ! should_ignore_subtree_split_commit "$parent" + then + parsedparents="$parsedparents$parent " + fi + done + process_split_commit "$rev" "$parsedparents" done || exit $? latest_new=$(cache_get latest_new) || exit $? diff --git a/contrib/subtree/t/t7900-subtree.sh b/contrib/subtree/t/t7900-subtree.sh index 4db3a6eff3..316dc5269e 100755 --- a/contrib/subtree/t/t7900-subtree.sh +++ b/contrib/subtree/t/t7900-subtree.sh @@ -411,9 +411,8 @@ test_expect_success 'split sub dir/ with --rejoin' ' git fetch ./"sub proj" HEAD && git subtree merge --prefix="sub dir" FETCH_HEAD && split_hash=$(git subtree split --prefix="sub dir" --annotate="*") && - git subtree split --prefix="sub dir" --annotate="*" -b spl --rejoin && - test "$(last_commit_subject)" = "Split '\''sub dir/'\'' into commit '\''$split_hash'\''" && - test "$(git rev-list --count spl)" -eq 5 + git subtree split --prefix="sub dir" --annotate="*" --rejoin && + test "$(last_commit_subject)" = "Split '\''sub dir/'\'' into commit '\''$split_hash'\''" ) ' @@ -443,25 +442,18 @@ test_expect_success 'split with multiple subtrees' ' git -C "$test_count" subtree add --prefix=subADir FETCH_HEAD && git -C "$test_count" fetch ./subB HEAD && git -C "$test_count" subtree add --prefix=subBDir FETCH_HEAD && - test "$(git -C "$test_count" rev-list --count main)" -eq 7 && test_create_commit "$test_count" subADir/main-subA1 && test_create_commit "$test_count" subBDir/main-subB1 && git -C "$test_count" subtree split --prefix=subADir \ - --squash --rejoin -m "Sub A Split 1" -b a1 && - test "$(git -C "$test_count" rev-list --count main..a1)" -eq 1 && + --squash --rejoin -m "Sub A Split 1" && git -C "$test_count" subtree split --prefix=subBDir \ - --squash --rejoin -m "Sub B Split 1" -b b1 && - test "$(git -C "$test_count" rev-list --count main..b1)" -eq 1 && + --squash --rejoin -m "Sub B Split 1" && test_create_commit "$test_count" subADir/main-subA2 && test_create_commit "$test_count" subBDir/main-subB2 && git -C "$test_count" subtree split --prefix=subADir \ - --squash --rejoin -m "Sub A Split 2" -b a2 && - test "$(git -C "$test_count" rev-list --count main..a2)" -eq 2 && - test "$(git -C "$test_count" rev-list --count a1..a2)" -eq 1 && + --squash --rejoin -m "Sub A Split 2" && test "$(git -C "$test_count" subtree split --prefix=subBDir \ - --squash --rejoin -d -m "Sub B Split 1" -b b2 2>&1 | grep -w "\[1\]")" = "" && - test "$(git -C "$test_count" rev-list --count main..b2)" -eq 2 && - test "$(git -C "$test_count" rev-list --count b1..b2)" -eq 1 + --squash --rejoin -d -m "Sub B Split 1" 2>&1 | grep -w "\[1\]")" = "" ' # When subtree split-ing a directory that has other subtree @@ -485,7 +477,6 @@ do test_path_is_file subA/file1.t && test_path_is_file subA/subB/file2.t && git subtree split --prefix=subA --branch=bsplit && - test "$(git rev-list --count bsplit)" -eq 2 && git checkout bsplit && test_path_is_file file1.t && test_path_is_file subB/file2.t && @@ -498,7 +489,6 @@ do --prefix=subA/subB mksubtree && test_path_is_file subA/subB/file3.t && git subtree split --prefix=subA --branch=bsplit && - test "$(git rev-list --count bsplit)" -eq 3 && git checkout bsplit && test_path_is_file file1.t && test_path_is_file subB/file2.t && @@ -507,67 +497,6 @@ do ' done -# Usually, -# -# git subtree merge -P subA --squash f00... -# -# makes two commits, in this order: -# -# 1. Squashed 'subA/' content from commit f00... -# 2. Merge commit (1) as 'subA' -# -# Commit 1 updates the subtree but does *not* rewrite paths. -# Commit 2 rewrites all trees to start with `subA/` -# -# Commit 1 either has no parents or depends only on other -# "Squashed 'subA/' content" commits. -# -# For merge without --squash, subtree produces just one commit: -# a merge commit with git-subtree trailers. -# -# In either case, if the user rebases these commits, they will -# still have the git-subtree-* trailers… but will NOT have -# the layout described above. -# -# Test that subsequent `git subtree split` are not confused by this. -test_expect_success 'split with rebased subtree commit' ' - subtree_test_create_repo "$test_count" && - ( - cd "$test_count" && - test_commit file0 && - test_create_subtree_add \ - . mksubtree subA file1 --squash && - test_path_is_file subA/file1.t && - mkdir subB && - test_commit subB/bfile && - git commit --amend -F - <<'EOF' && -Squashed '\''subB/'\'' content from commit '\''badf00da911bbe895347b4b236f5461d55dc9877'\'' - -Simulate a cherry-picked or rebased subtree commit. - -git-subtree-dir: subB -git-subtree-split: badf00da911bbe895347b4b236f5461d55dc9877 -EOF - test_commit subA/file2 && - test_commit subB/bfile2 && - git commit --amend -F - <<'EOF' && -Split '\''subB/'\'' into commit '\''badf00da911bbe895347b4b236f5461d55dc9877'\'' - -Simulate a cherry-picked or rebased subtree commit. - -git-subtree-dir: subB -git-subtree-mainline: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa -git-subtree-split: badf00da911bbe895347b4b236f5461d55dc9877 -EOF - git subtree split --prefix=subA --branch=bsplit && - git checkout bsplit && - test_path_is_file file1.t && - test_path_is_file file2.t && - test "$(last_commit_subject)" = "subA/file2" && - test "$(git rev-list --count bsplit)" -eq 2 - ) -' - test_expect_success 'split sub dir/ with --rejoin from scratch' ' subtree_test_create_repo "$test_count" && test_create_commit "$test_count" main1 &&