Skip to content

Commit 1be6ebe

Browse files
tnm-oaigitster
authored andcommitted
stash: avoid sparse-index expansion for in-cone paths
`git stash push -- <pathspec>` expands a sparse index before checking whether the pathspec matches any tracked paths. This is unnecessary when the pathspec is wholly inside the sparse-checkout cone and makes a path-limited stash proportional to the size of the full index. Use `pathspec_needs_expanded_index()` to expand only when a pathspec can match part of a sparse-directory entry, as `git rm` and `git reset` already do. Keep the full-index behavior for pathspecs that need it. Add compatibility coverage for literal, prefixed, wildcard, file, multiple, staged, and missing pathspecs. Add the corresponding path-limited stash case to p2000. On a cone-mode repository with 349,525 tracked paths and 49 sparse index entries, the best of three runs changed from 18.87s to 0.06s. Trace2 reported four index expansions before this change and none after it. Signed-off-by: Ted Nyman <tnyman@openai.com> Reviewed-by: Taylor Blau <ttaylorr@openai.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
1 parent dd674df commit 1be6ebe

3 files changed

Lines changed: 58 additions & 2 deletions

File tree

builtin/stash.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1702,8 +1702,8 @@ static int do_push_stash(const struct pathspec *ps, const char *stash_msg, int q
17021702
if (!include_untracked && ps->nr) {
17031703
char *ps_matched = xcalloc(ps->nr, 1);
17041704

1705-
/* TODO: audit for interaction with sparse-index. */
1706-
ensure_full_index(the_repository->index);
1705+
if (pathspec_needs_expanded_index(the_repository->index, ps))
1706+
ensure_full_index(the_repository->index);
17071707
for (size_t i = 0; i < the_repository->index->cache_nr; i++)
17081708
ce_path_match(the_repository->index, the_repository->index->cache[i], ps,
17091709
ps_matched);

t/perf/p2000-sparse-operations.sh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,7 @@ test_perf_on_all () {
108108

109109
test_perf_on_all git status
110110
test_perf_on_all 'git stash && git stash pop'
111+
test_perf_on_all "git stash push -- $SPARSE_CONE/a && git stash pop"
111112
test_perf_on_all 'echo >>new && git stash -u && git stash pop'
112113
test_perf_on_all git add -A
113114
test_perf_on_all git add .

t/t1092-sparse-checkout-compatibility.sh

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1598,6 +1598,61 @@ test_expect_success 'sparse-index is not expanded: stash' '
15981598
ensure_not_expanded stash pop
15991599
'
16001600

1601+
test_expect_success 'sparse-index is not expanded: stash in-cone pathspec' '
1602+
init_repos &&
1603+
1604+
echo unrelated >>sparse-index/deep/e &&
1605+
echo literal >>sparse-index/deep/a &&
1606+
ensure_not_expanded stash push -- deep/a &&
1607+
test_grep ! literal sparse-index/deep/a &&
1608+
test_grep unrelated sparse-index/deep/e &&
1609+
ensure_not_expanded stash pop &&
1610+
test_grep literal sparse-index/deep/a &&
1611+
1612+
echo prefixed >>sparse-index/deep/a &&
1613+
ensure_not_expanded -C deep stash push -- a &&
1614+
test_grep ! prefixed sparse-index/deep/a &&
1615+
test_grep unrelated sparse-index/deep/e &&
1616+
ensure_not_expanded stash pop &&
1617+
test_grep prefixed sparse-index/deep/a &&
1618+
1619+
echo wildcard >>sparse-index/deep/a &&
1620+
ensure_not_expanded stash push -- "deep/a*" &&
1621+
test_grep ! wildcard sparse-index/deep/a &&
1622+
test_grep unrelated sparse-index/deep/e &&
1623+
ensure_not_expanded stash pop &&
1624+
test_grep wildcard sparse-index/deep/a &&
1625+
1626+
echo pathspec-file >>sparse-index/deep/a &&
1627+
echo deep/a >pathspec-file &&
1628+
ensure_not_expanded stash push --pathspec-from-file=../pathspec-file &&
1629+
test_grep ! pathspec-file sparse-index/deep/a &&
1630+
test_grep unrelated sparse-index/deep/e &&
1631+
ensure_not_expanded stash pop &&
1632+
test_grep pathspec-file sparse-index/deep/a &&
1633+
1634+
echo multiple-a >>sparse-index/deep/a &&
1635+
echo multiple-e >>sparse-index/deep/e &&
1636+
ensure_not_expanded stash push -- deep/a deep/e &&
1637+
test_grep ! multiple-a sparse-index/deep/a &&
1638+
test_grep ! multiple-e sparse-index/deep/e &&
1639+
ensure_not_expanded stash pop &&
1640+
test_grep multiple-a sparse-index/deep/a &&
1641+
test_grep multiple-e sparse-index/deep/e &&
1642+
1643+
echo staged >>sparse-index/deep/a &&
1644+
git -C sparse-index add deep/a &&
1645+
ensure_not_expanded stash push --staged -- deep/a &&
1646+
test_grep ! staged sparse-index/deep/a &&
1647+
test_grep unrelated sparse-index/deep/e &&
1648+
ensure_not_expanded stash pop --index &&
1649+
test_grep staged sparse-index/deep/a &&
1650+
test_must_fail git -C sparse-index diff --cached --quiet -- deep/a &&
1651+
1652+
ensure_not_expanded ! stash push -- deep/does-not-exist &&
1653+
test_grep "did not match any file" sparse-index-error
1654+
'
1655+
16011656
test_expect_success 'describe tested on all' '
16021657
init_repos &&
16031658

0 commit comments

Comments
 (0)