Merge branch 'ds/sparse-index-ita-crash' into jch

A crash in the 'sparse-index' collapse code when encountering an
invalidated cache-tree node (due to an intent-to-add path) has been
fixed by avoiding collapsing such subtrees.

* ds/sparse-index-ita-crash:
  sparse-index: avoid crash on intent-to-add entry outside the cone
jch
Junio C Hamano 2026-07-24 16:11:57 -07:00
commit 4ebf614c48
3 changed files with 82 additions and 1 deletions

View File

@ -113,10 +113,17 @@ static int convert_to_sparse_rec(struct index_state *istate,
continue;
}

span = ct->down[pos]->cache_tree->entry_count;
if (span < 0) {
/* cache-tree entry is invalidated, cannot collapse. */
istate->cache[num_converted++] = ce;
i++;
continue;
}

strbuf_setlen(&child_path, 0);
strbuf_add(&child_path, ce->name, slash - ce->name + 1);

span = ct->down[pos]->cache_tree->entry_count;
count = convert_to_sparse_rec(istate,
num_converted, i, i + span,
child_path.buf, child_path.len,

View File

@ -384,6 +384,54 @@ test_expect_success 'add, commit, checkout' '
test_all_match git checkout -
'

test_expect_success 'intent-to-add entries outside sparse-checkout' '
init_repos &&

write_script edit-contents <<-\EOF &&
echo text >>$1
EOF

test_sparse_match git sparse-checkout set deep folder1 &&
run_on_sparse mkdir -p folder1 &&
run_on_all ../edit-contents folder1/newita &&
test_sparse_match git add -N folder1/newita &&

test_sparse_match git sparse-checkout set deep &&
test_sparse_match git status --porcelain=v2 &&
test_sparse_match git ls-files --stage
'

test_expect_success 'intent-to-add with --sparse outside sparse-checkout' '
init_repos &&

write_script edit-contents <<-\EOF &&
echo text >>$1
EOF

run_on_all mkdir -p folder1 &&
run_on_all ../edit-contents folder1/newita &&
test_all_match git add --sparse --intent-to-add folder1/newita &&

test_all_match git status --porcelain=v2 &&
test_all_match git ls-files --stage &&
test_all_match git diff --cached --stat &&

# Ensure sparse index stores correct sparse directories and
# intent-to-add path.
git -C sparse-index ls-files --format="%(path)" --sparse >out &&

# These paths should be present in index as-is.
test_grep "^before/\$" out &&
test_grep "^folder1/newita\$" out &&
test_grep "^folder2/\$" out &&
test_grep "^x/\$" out &&

# folder/0/ could theoretically be collapsed to a sparse
# directory entry, but the current implementation avoids the
# reduction because of folder1/newita
test_grep "^folder1/0/0/0\$" out
'

test_expect_success 'git add, checkout, and reset with -p' '
init_repos &&


View File

@ -233,4 +233,30 @@ test_expect_success 'refuse to add non-skip-worktree file from sparse dir' '
test_cmp expect stderr
'

test_expect_success 'intent-to-add entry and sparse index' '
test_when_finished "git sparse-checkout disable" &&
test_when_finished "git reset --hard" &&

git sparse-checkout disable &&
mkdir -p in out &&
echo base >in/file &&
echo base >out/file &&
git add in/file out/file &&
git commit -m "in and out directories" &&

# enable sparse-checkout, but with all child directories.
git config index.sparse true &&
git sparse-checkout set in out &&

# create a new path and set intent-to-add bit
echo new >out/newita &&
git add -N out/newita &&

# collapse sparse-checkout, and make sure that the sparse index
# maintains the intent-to-add bit.
git sparse-checkout set in &&
git ls-files --error-unmatch out/newita &&
git status --porcelain
'

test_done