sparse-checkout: integrate with sparse index

When modifying the sparse-checkout definition, the sparse-checkout
builtin calls update_sparsity() to modify the SKIP_WORKTREE bits of all
cache entries in the index. Before, we needed the index to be fully
expanded in order to ensure we had the full list of files necessary that
match the new patterns.

Insert a call to reset_sparse_directories() that expands sparse
directories that are within the new pattern list, but only far enough
that every necessary file path now exists as a cache entry. The
remaining logic within update_sparsity() will modify the SKIP_WORKTREE
bits appropriately.

This allows us to disable command_requires_full_index within the
sparse-checkout builtin. Add tests that demonstrate that we are not
expanding to a full index unnecessarily.

We can see the improved performance in the p2000 test script:

Test                           HEAD~1            HEAD
2000.24: git ... (sparse-v3)   2.14(1.55+0.58)   1.57(1.03+0.53) -26.6%
2000.25: git ... (sparse-v4)   2.20(1.62+0.57)   1.58(0.98+0.59) -28.2%

These reductions of 26-28% are small compared to most examples, but the
time is dominated by writing a new copy of the base repository to the
worktree and then deleting it again. The fact that the previous index
expansion was such a large portion of the time is telling how important
it is to complete this sparse index integration.

Signed-off-by: Derrick Stolee <>
Signed-off-by: Junio C Hamano <>
Derrick Stolee 5 months ago committed by Junio C Hamano
parent b0b40c0468
commit 598b1e7d09
  1. 3
  2. 25
  3. 4

@ -937,6 +937,9 @@ int cmd_sparse_checkout(int argc, const char **argv, const char *prefix)
git_config(git_default_config, NULL);
the_repository->settings.command_requires_full_index = 0;
if (argc > 0) {
if (!strcmp(argv[0], "list"))
return sparse_checkout_list(argc, argv);

@ -1548,6 +1548,31 @@ test_expect_success 'ls-files' '
ensure_not_expanded ls-files --sparse
test_expect_success 'sparse index is not expanded: sparse-checkout' '
init_repos &&
ensure_not_expanded sparse-checkout set deep/deeper2 &&
ensure_not_expanded sparse-checkout set deep/deeper1 &&
ensure_not_expanded sparse-checkout set deep &&
ensure_not_expanded sparse-checkout add folder1 &&
ensure_not_expanded sparse-checkout set deep/deeper1 &&
ensure_not_expanded sparse-checkout set folder2 &&
# Demonstrate that the checks that "folder1/a" is a file
# do not cause a sparse-index expansion (since it is in the
# sparse-checkout cone).
echo >>sparse-index/folder2/a &&
git -C sparse-index add folder2/a &&
ensure_not_expanded sparse-checkout add folder1 &&
# Skip checks here, since deep/deeper1 is inside a sparse directory
# that must be expanded to check whether `deep/deeper1` is a file
# or not.
ensure_not_expanded sparse-checkout set --skip-checks deep/deeper1 &&
ensure_not_expanded sparse-checkout set
# NEEDSWORK: a sparse-checkout behaves differently from a full checkout
# in this scenario, but it shouldn't.
test_expect_success 'reset mixed and checkout orphan' '

@ -18,6 +18,7 @@
#include "promisor-remote.h"
#include "entry.h"
#include "parallel-checkout.h"
#include "sparse-index.h"
* Error messages expected by scripts out of plumbing commands such as
@ -2018,6 +2019,9 @@ enum update_sparsity_result update_sparsity(struct unpack_trees_options *o)
goto skip_sparse_checkout;
/* Expand sparse directories as needed */
expand_index(o->src_index, o->pl);
/* Set NEW_SKIP_WORKTREE on existing entries. */
mark_new_skip_worktree(o->pl, o->src_index, 0,