Merge branch 'cs/subtree-squash-split-fix'

"git subtree" (in contrib/) did not work correctly when splitting
squashed subtrees, which has been improved.

* cs/subtree-squash-split-fix:
  contrib/subtree: fix split with squashed subtrees
main
Junio C Hamano 2025-09-23 11:53:40 -07:00
commit 3e0e2e3a5c
2 changed files with 99 additions and 8 deletions

View File

@ -785,20 +785,40 @@ ensure_valid_ref_format () {
die "fatal: '$1' does not look like a ref"
}

# Usage: check if a commit from another subtree should be
# 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
local rev="$1"
if test -n "$(git log -1 --grep="git-subtree-dir:" $rev)"
then
if test -z "$(git log -1 --grep="git-subtree-mainline:" $rev)" &&
test -z "$(git log -1 --grep="git-subtree-dir: $arg_prefix$" $rev)"

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
fi
return 1
)
}

# Usage: process_split_commit REV PARENTS

View File

@ -9,6 +9,9 @@ This test verifies the basic operation of the add, merge, split, pull,
and push subcommands of git subtree.
'

GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main
export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME

TEST_DIRECTORY=$(pwd)/../../../t
. "$TEST_DIRECTORY"/test-lib.sh
. "$TEST_DIRECTORY"/lib-gpg.sh
@ -68,6 +71,33 @@ test_create_pre2_32_repo () {
git -C "$1-clone" replace HEAD^2 $new_commit
}

# test_create_subtree_add REPO ORPHAN PREFIX FILENAME ...
#
# Create a simple subtree on a new branch named ORPHAN in REPO.
# The subtree is then merged into the current branch of REPO,
# under PREFIX. The generated subtree has has one commit
# with subject and tag FILENAME with a single file "FILENAME.t"
#
# When this method returns:
# - the current branch of REPO will have file PREFIX/FILENAME.t
# - REPO will have a branch named ORPHAN with subtree history
#
# additional arguments are forwarded to "subtree add"
test_create_subtree_add () {
(
cd "$1" &&
orphan="$2" &&
prefix="$3" &&
filename="$4" &&
shift 4 &&
last="$(git branch --show-current)" &&
git switch --orphan "$orphan" &&
test_commit "$filename" &&
git checkout "$last" &&
git subtree add --prefix="$prefix" "$@" "$orphan"
)
}

test_expect_success 'shows short help text for -h' '
test_expect_code 129 git subtree -h >out 2>err &&
test_must_be_empty err &&
@ -426,6 +456,47 @@ test_expect_success 'split with multiple subtrees' '
--squash --rejoin -d -m "Sub B Split 1" 2>&1 | grep -w "\[1\]")" = ""
'

# When subtree split-ing a directory that has other subtree
# *merges* underneath it, the split must include those subtrees.
# This test creates a nested subtree, `subA/subB`, and tests
# that the tree is correct after a subtree split of `subA/`.
# The test covers:
# - An initial `subtree add`; and
# - A follow-up `subtree merge`
# both with and without `--squashed`.
for is_squashed in '' 'y'
do
test_expect_success "split keeps nested ${is_squashed:+--squash }subtrees that are part of the split" '
subtree_test_create_repo "$test_count" &&
(
cd "$test_count" &&
mkdir subA &&
test_commit subA/file1 &&
test_create_subtree_add \
. mksubtree subA/subB file2 ${is_squashed:+--squash} &&
test_path_is_file subA/file1.t &&
test_path_is_file subA/subB/file2.t &&
git subtree split --prefix=subA --branch=bsplit &&
git checkout bsplit &&
test_path_is_file file1.t &&
test_path_is_file subB/file2.t &&
git checkout mksubtree &&
git branch -D bsplit &&
test_commit file3 &&
git checkout main &&
git subtree merge \
${is_squashed:+--squash} \
--prefix=subA/subB mksubtree &&
test_path_is_file subA/subB/file3.t &&
git subtree split --prefix=subA --branch=bsplit &&
git checkout bsplit &&
test_path_is_file file1.t &&
test_path_is_file subB/file2.t &&
test_path_is_file subB/file3.t
)
'
done

test_expect_success 'split sub dir/ with --rejoin from scratch' '
subtree_test_create_repo "$test_count" &&
test_create_commit "$test_count" main1 &&