sparse-checkout: toggle sparse index from builtin

The sparse index extension is used to signal that index writes should be in sparse mode. This was only updated using GIT_TEST_SPARSE_INDEX=1. Add a '--[no-]sparse-index' option to 'git sparse-checkout init' that specifies if the sparse index should be used. It also updates the index to use the correct format, either way. Add a warning in the documentation that the use of a repository extension might reduce compatibility with third-party tools. 'git sparse-checkout init' already sets extension.worktreeConfig, which places most sparse-checkout users outside of the scope of most third-party tools. Update t1092-sparse-checkout-compatibility.sh to use this CLI instead of GIT_TEST_SPARSE_INDEX=1. Signed-off-by: Derrick Stolee <dstolee@microsoft.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Derrick Stolee committed Mar 30, 2021 at 13:11 UTC 122ba1f7b52612e197db76aded1f9681b80f3085
5 files changed +80 -34
Documentation/git-sparse-checkout.txt
+14
@@ -45,6 +45,20 @@ To avoid interfering with other worktrees, it first enables the
45 When `--cone` is provided, the `core.sparseCheckoutCone` setting is
46 also set, allowing for better performance with a limited set of
47 patterns (see 'CONE PATTERN SET' below).
48 ++
49 +Use the `--[no-]sparse-index` option to toggle the use of the sparse
50 +index format. This reduces the size of the index to be more closely
51 +aligned with your sparse-checkout definition. This can have significant
52 +performance advantages for commands such as `git status` or `git add`.
53 +This feature is still experimental. Some commands might be slower with
54 +a sparse index until they are properly integrated with the feature.
55 ++
56 +**WARNING:** Using a sparse index requires modifying the index in a way
57 +that is not completely understood by external tools. If you have trouble
58 +with this compatibility, then run `git sparse-checkout init --no-sparse-index`
59 +to rewrite your index to not be sparse. Older versions of Git will not
60 +understand the sparse directory entries index extension and may fail to
61 +interact with your repository until it is disabled.
62
63 'set'::
64 Write a set of patterns to the sparse-checkout file, as given as
builtin/sparse-checkout.c
+16 -1
@@ -14,6 +14,7 @@
14 #include "unpack-trees.h"
15 #include "wt-status.h"
16 #include "quote.h"
17 +#include "sparse-index.h"
18
19 static const char *empty_base = "";
20
@@ -283,12 +284,13 @@ static int set_config(enum sparse_checkout_mode mode)
284 }
285
286 static char const * const builtin_sparse_checkout_init_usage[] = {
286 - N_("git sparse-checkout init [--cone]"),
287 + N_("git sparse-checkout init [--cone] [--[no-]sparse-index]"),
288 NULL
289 };
290
291 static struct sparse_checkout_init_opts {
292 int cone_mode;
293 + int sparse_index;
294 } init_opts;
295
296 static int sparse_checkout_init(int argc, const char **argv)
@@ -303,11 +305,15 @@ static int sparse_checkout_init(int argc, const char **argv)
305 static struct option builtin_sparse_checkout_init_options[] = {
306 OPT_BOOL(0, "cone", &init_opts.cone_mode,
307 N_("initialize the sparse-checkout in cone mode")),
308 + OPT_BOOL(0, "sparse-index", &init_opts.sparse_index,
309 + N_("toggle the use of a sparse index")),
310 OPT_END(),
311 };
312
313 repo_read_index(the_repository);
314
315 + init_opts.sparse_index = -1;
316 +
317 argc = parse_options(argc, argv, NULL,
318 builtin_sparse_checkout_init_options,
319 builtin_sparse_checkout_init_usage, 0);
@@ -326,6 +332,15 @@ static int sparse_checkout_init(int argc, const char **argv)
332 sparse_filename = get_sparse_checkout_filename();
333 res = add_patterns_from_file_to_list(sparse_filename, "", 0, &pl, NULL);
334
335 + if (init_opts.sparse_index >= 0) {
336 + if (set_sparse_index_config(the_repository, init_opts.sparse_index) < 0)
337 + die(_("failed to modify sparse-index config"));
338 +
339 + /* force an index rewrite */
340 + repo_read_index(the_repository);
341 + the_repository->index->updated_workdir = 1;
342 + }
343 +
344 /* If we already have a sparse-checkout file, use it. */
345 if (res >= 0) {
346 free(sparse_filename);
sparse-index.c
+21 -12
@@ -102,21 +102,32 @@ static int convert_to_sparse_rec(struct index_state *istate,
102 return num_converted - start_converted;
103 }
104
105 -static int enable_sparse_index(struct repository *repo)
105 +static int set_index_sparse_config(struct repository *repo, int enable)
106 {
107 - const char *config_path = repo_git_path(repo, "config.worktree");
108 -
109 - git_config_set_in_file_gently(config_path,
110 - "index.sparse",
111 - "true");
107 + int res;
108 + char *config_path = repo_git_path(repo, "config.worktree");
109 + res = git_config_set_in_file_gently(config_path,
110 + "index.sparse",
111 + enable ? "true" : NULL);
112 + free(config_path);
113
114 prepare_repo_settings(repo);
115 repo->settings.sparse_index = 1;
115 - return 0;
116 + return res;
117 +}
118 +
119 +int set_sparse_index_config(struct repository *repo, int enable)
120 +{
121 + int res = set_index_sparse_config(repo, enable);
122 +
123 + prepare_repo_settings(repo);
124 + repo->settings.sparse_index = enable;
125 + return res;
126 }
127
128 int convert_to_sparse(struct index_state *istate)
129 {
130 + int test_env;
131 if (istate->split_index || istate->sparse_index ||
132 !core_apply_sparse_checkout || !core_sparse_checkout_cone)
133 return 0;
@@ -128,11 +139,9 @@ int convert_to_sparse(struct index_state *istate)
139 * The GIT_TEST_SPARSE_INDEX environment variable triggers the
140 * index.sparse config variable to be on.
141 */
131 - if (git_env_bool("GIT_TEST_SPARSE_INDEX", 0)) {
132 - int err = enable_sparse_index(istate->repo);
133 - if (err < 0)
134 - return err;
135 - }
142 + test_env = git_env_bool("GIT_TEST_SPARSE_INDEX", -1);
143 + if (test_env >= 0)
144 + set_sparse_index_config(istate->repo, test_env);
145
146 /*
147 * Only convert to sparse if index.sparse is set.
sparse-index.h
+3
@@ -5,4 +5,7 @@ struct index_state;
5 void ensure_full_index(struct index_state *istate);
6 int convert_to_sparse(struct index_state *istate);
7
8 +struct repository;
9 +int set_sparse_index_config(struct repository *repo, int enable);
10 +
11 #endif
t/t1092-sparse-checkout-compatibility.sh
+26 -21
@@ -6,6 +6,7 @@ test_description='compare full workdir to sparse workdir'
6 # So, disable the check until that integration is complete.
7 GIT_TEST_CHECK_CACHE_TREE=0
8 GIT_TEST_SPLIT_INDEX=0
9 +GIT_TEST_SPARSE_INDEX=
10
11 . ./test-lib.sh
12
@@ -100,25 +101,26 @@ init_repos () {
101 # initialize sparse-checkout definitions
102 git -C sparse-checkout sparse-checkout init --cone &&
103 git -C sparse-checkout sparse-checkout set deep &&
103 - GIT_TEST_SPARSE_INDEX=1 git -C sparse-index sparse-checkout init --cone &&
104 - GIT_TEST_SPARSE_INDEX=1 git -C sparse-index sparse-checkout set deep
104 + git -C sparse-index sparse-checkout init --cone --sparse-index &&
105 + test_cmp_config -C sparse-index true index.sparse &&
106 + git -C sparse-index sparse-checkout set deep
107 }
108
109 run_on_sparse () {
110 (
111 cd sparse-checkout &&
110 - GIT_TEST_SPARSE_INDEX=0 "$@" >../sparse-checkout-out 2>../sparse-checkout-err
112 + "$@" >../sparse-checkout-out 2>../sparse-checkout-err
113 ) &&
114 (
115 cd sparse-index &&
114 - GIT_TEST_SPARSE_INDEX=1 "$@" >../sparse-index-out 2>../sparse-index-err
116 + "$@" >../sparse-index-out 2>../sparse-index-err
117 )
118 }
119
120 run_on_all () {
121 (
122 cd full-checkout &&
121 - GIT_TEST_SPARSE_INDEX=0 "$@" >../full-checkout-out 2>../full-checkout-err
123 + "$@" >../full-checkout-out 2>../full-checkout-err
124 ) &&
125 run_on_sparse "$@"
126 }
@@ -148,7 +150,7 @@ test_expect_success 'sparse-index contents' '
150 || return 1
151 done &&
152
151 - GIT_TEST_SPARSE_INDEX=1 git -C sparse-index sparse-checkout set folder1 &&
153 + git -C sparse-index sparse-checkout set folder1 &&
154
155 test-tool -C sparse-index read-cache --table >cache &&
156 for dir in deep folder2 x
@@ -158,7 +160,7 @@ test_expect_success 'sparse-index contents' '
160 || return 1
161 done &&
162
161 - GIT_TEST_SPARSE_INDEX=1 git -C sparse-index sparse-checkout set deep/deeper1 &&
163 + git -C sparse-index sparse-checkout set deep/deeper1 &&
164
165 test-tool -C sparse-index read-cache --table >cache &&
166 for dir in deep/deeper2 folder1 folder2 x
@@ -166,7 +168,14 @@ test_expect_success 'sparse-index contents' '
168 TREE=$(git -C sparse-index rev-parse HEAD:$dir) &&
169 grep "040000 tree $TREE $dir/" cache \
170 || return 1
169 - done
171 + done &&
172 +
173 + # Disabling the sparse-index removes tree entries with full ones
174 + git -C sparse-index sparse-checkout init --no-sparse-index &&
175 +
176 + test-tool -C sparse-index read-cache --table >cache &&
177 + ! grep "040000 tree" cache &&
178 + test_sparse_match test-tool read-cache --table
179 '
180
181 test_expect_success 'expanded in-memory index matches full index' '
@@ -396,19 +405,15 @@ test_expect_success 'submodule handling' '
405 test_expect_success 'sparse-index is expanded and converted back' '
406 init_repos &&
407
399 - (
400 - GIT_TEST_SPARSE_INDEX=1 &&
401 - export GIT_TEST_SPARSE_INDEX &&
402 - GIT_TRACE2_EVENT="$(pwd)/trace2.txt" GIT_TRACE2_EVENT_NESTING=10 \
403 - git -C sparse-index -c core.fsmonitor="" reset --hard &&
404 - test_region index convert_to_sparse trace2.txt &&
405 - test_region index ensure_full_index trace2.txt &&
406 -
407 - rm trace2.txt &&
408 - GIT_TRACE2_EVENT="$(pwd)/trace2.txt" GIT_TRACE2_EVENT_NESTING=10 \
409 - git -C sparse-index -c core.fsmonitor="" status -uno &&
410 - test_region index ensure_full_index trace2.txt
411 - )
408 + GIT_TRACE2_EVENT="$(pwd)/trace2.txt" GIT_TRACE2_EVENT_NESTING=10 \
409 + git -C sparse-index -c core.fsmonitor="" reset --hard &&
410 + test_region index convert_to_sparse trace2.txt &&
411 + test_region index ensure_full_index trace2.txt &&
412 +
413 + rm trace2.txt &&
414 + GIT_TRACE2_EVENT="$(pwd)/trace2.txt" GIT_TRACE2_EVENT_NESTING=10 \
415 + git -C sparse-index -c core.fsmonitor="" status -uno &&
416 + test_region index ensure_full_index trace2.txt
417 '
418
419 test_done