From 7f3f0ac18f0d86c1972bfe1aec512e45b0c6ac22 Mon Sep 17 00:00:00 2001 From: Kristofer Karlsson Date: Mon, 1 Jun 2026 13:08:12 +0200 Subject: [PATCH 1/2] config: mark as sparse-index compatible `git config --blob :.lfsconfig` (and similar :path lookups) triggers ensure_full_index() in sparse checkouts, expanding the entire sparse index to a full index. On large repositories this takes seconds even though the command only needs to look up a single path. The root cause has two parts: 1. `git config` uses RUN_SETUP_GENTLY and never calls repo_config(), so the sparse checkout globals (core.sparseCheckout, core.sparseCheckoutCone) are unset when the index is first read. 2. Without those globals, is_sparse_index_allowed() returns false, causing ensure_correct_sparsity() to call ensure_full_index() during do_read_index(). Fix this by loading repo config and setting command_requires_full_index to 0 in cmd_config(). This is the standard opt-in pattern used by the ~30 other commands that are sparse-index compatible. The repo_config() call is needed because RUN_SETUP_GENTLY does not load the default config callbacks that populate the sparse checkout globals. Other sparse-index-compatible commands use RUN_SETUP, which handles this during setup_git_directory(). Note that this does not introduce new failure modes for broken .git/config: the setup phase under RUN_SETUP_GENTLY already parses enough config to fail on syntax errors before cmd_config() runs. The flag is safe to set unconditionally because git config only accesses the index when resolving --blob :path specs. For non-blob invocations, the flag has no effect. For --blob :path, in-cone paths resolve directly from the sparse index; out-of-cone paths trigger on-demand expansion through index_name_pos() with EXPAND_SPARSE. Signed-off-by: Kristofer Karlsson --- builtin/config.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/builtin/config.c b/builtin/config.c index cf4ba0f7cc6f22..97bb16ceb93dbc 100644 --- a/builtin/config.c +++ b/builtin/config.c @@ -1618,6 +1618,12 @@ int cmd_config(int argc, */ argc = parse_options(argc, argv, prefix, subcommand_opts, builtin_config_usage, PARSE_OPT_SUBCOMMAND_OPTIONAL|PARSE_OPT_KEEP_ARGV0|PARSE_OPT_KEEP_UNKNOWN_OPT); + if (startup_info->have_repository) { + repo_config(repo, git_default_config, NULL); + prepare_repo_settings(repo); + repo->settings.command_requires_full_index = 0; + } + if (subcommand) { argc = parse_options(argc, argv, prefix, subcommand_opts, builtin_config_usage, PARSE_OPT_SUBCOMMAND_OPTIONAL|PARSE_OPT_KEEP_UNKNOWN_OPT); From 21e2edd62be421dc5b0fdcd21785aff8cb79dae1 Mon Sep 17 00:00:00 2001 From: Kristofer Karlsson Date: Mon, 1 Jun 2026 13:08:27 +0200 Subject: [PATCH 2/2] t1092: add tests for config --blob with sparse index Verify that `git config --blob :path` does not expand the sparse index when the path is in-cone, and does expand when the path is behind a sparse directory entry. Uses valid config blobs (staged via git add) for the in-cone cases to verify end-to-end correctness including config value parsing. Add a separate test verifying that `git config --file` does not trigger sparse index expansion, since it does not access the index at all. Signed-off-by: Kristofer Karlsson --- t/t1092-sparse-checkout-compatibility.sh | 63 ++++++++++++++++++++++++ 1 file changed, 63 insertions(+) diff --git a/t/t1092-sparse-checkout-compatibility.sh b/t/t1092-sparse-checkout-compatibility.sh index d98cb4ac113c67..477899dc0a193a 100755 --- a/t/t1092-sparse-checkout-compatibility.sh +++ b/t/t1092-sparse-checkout-compatibility.sh @@ -2573,4 +2573,67 @@ test_expect_success 'sparse-index is not expanded: merge-ours' ' ensure_not_expanded merge -s ours merge-right ' +test_expect_success 'sparse-index is not expanded: config --blob with index path' ' + init_repos && + + # Stage valid config blobs at in-cone locations. + for repo in full-checkout sparse-checkout sparse-index + do + test_write_lines "[test]" "value = root" \ + >"$repo/test-config" && + git -C "$repo" add test-config && + test_write_lines "[test]" "value = deep" \ + >"$repo/deep/test-config" && + git -C "$repo" add deep/test-config || return 1 + done && + + # Nonexistent file: must not expand. + rm -f trace2.txt && + GIT_TRACE2_EVENT="$(pwd)/trace2.txt" GIT_TRACE2_EVENT_NESTING=10 \ + test_must_fail \ + git -C sparse-index config --blob ":.lfsconfig" -l && + test_region ! index ensure_full_index trace2.txt && + + # Root-level file (always in-cone): must not expand, and must + # successfully parse the config values. + rm -f trace2.txt && + GIT_TRACE2_EVENT="$(pwd)/trace2.txt" GIT_TRACE2_EVENT_NESTING=10 \ + git -C sparse-index config --blob ":test-config" -l >actual && + echo "test.value=root" >expect && + test_cmp expect actual && + test_region ! index ensure_full_index trace2.txt && + + # File inside sparse cone (deep/): must not expand. + rm -f trace2.txt && + GIT_TRACE2_EVENT="$(pwd)/trace2.txt" GIT_TRACE2_EVENT_NESTING=10 \ + git -C sparse-index config --blob ":deep/test-config" -l >actual && + echo "test.value=deep" >expect && + test_cmp expect actual && + test_region ! index ensure_full_index trace2.txt && + + # File outside sparse cone (folder1/a): must expand because + # it is behind a sparse directory entry. The blob contains + # "a\n" from the initial setup, which git config parses as + # a valueless key. + rm -f trace2.txt && + GIT_TRACE2_EVENT="$(pwd)/trace2.txt" GIT_TRACE2_EVENT_NESTING=10 \ + git -C sparse-index config --blob ":folder1/a" -l && + test_region index ensure_full_index trace2.txt +' + +test_expect_success 'config --file and --blob do not expand sparse index' ' + init_repos && + + test_write_lines "[test]" "value = external" >external-config && + + # --file reads an external config file; the sparse index + # should not be read or expanded. + rm -f trace2.txt && + GIT_TRACE2_EVENT="$(pwd)/trace2.txt" GIT_TRACE2_EVENT_NESTING=10 \ + git -C sparse-index config --file ../external-config -l >actual && + echo "test.value=external" >expect && + test_cmp expect actual && + test_region ! index ensure_full_index trace2.txt +' + test_done