From fc03c1b3ff52751bbcf645502ee04250f7564ba7 Mon Sep 17 00:00:00 2001 From: Victoria Dye Date: Wed, 20 Sep 2023 12:24:40 -0700 Subject: [PATCH 1/3] fixup! Add virtual file system settings and hook proc The 'dtype' arg to 'path_matches_pattern_list()' is not assumed to be initialized, and in a few callers it is not. However, the changes in 2cff5e9e9e0 (Add virtual file system settings and hook proc, 2018-01-11) _do_ assume 'dtype' is initialized. This can cause indeterminate behavior. For example, seemingly unrelated changes from 33b1b4c7681 (sparse-checkout: avoid using internal API of unpack-trees, take 2, 2023-02-27) changed the initial value of 'dtype' in 'expand_index()' from non-zero to zero. That caused 'path_matches_pattern_list()' to call 'resolve_dtype()', which initialized the 'name_hash' and 'dir_hash' of 'istate', which triggered another bug that ultimately led to a segfault. Update the callers with uninitialized 'dtype' values to intialize with a value appropriate to their respective use cases. --- sparse-index.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sparse-index.c b/sparse-index.c index ad337d5c158658..3cff0213071660 100644 --- a/sparse-index.c +++ b/sparse-index.c @@ -242,7 +242,7 @@ static int add_path_to_index(const struct object_id *oid, size_t len = base->len; if (S_ISDIR(mode)) { - int dtype; + int dtype = DT_DIR; size_t baselen = base->len; if (!ctx->pl) return READ_TREE_RECURSIVE; @@ -360,7 +360,7 @@ void expand_index(struct index_state *istate, struct pattern_list *pl) struct cache_entry *ce = istate->cache[i]; struct tree *tree; struct pathspec ps; - int dtype; + int dtype = DT_UNKNOWN; if (!S_ISSPARSEDIR(ce->ce_mode)) { set_index_entry(full, full->cache_nr++, ce); From 433c5e9adc934eae99933d01ec3c9226e1933603 Mon Sep 17 00:00:00 2001 From: Victoria Dye Date: Tue, 19 Sep 2023 18:20:32 -0700 Subject: [PATCH 2/3] sparse-index.c: fix use of index hashes in expand_index In ac8acb4f2c7 (sparse-index: complete partial expansion, 2022-05-23), 'expand_index()' was updated to expand the index to a given pathspec. However, the 'path_matches_pattern_list()' method used to facilitate this has the side effect of initializing or updating the index hash variables ('name_hash', 'dir_hash', and 'name_hash_initialized'). This operation is performed on 'istate', though, not 'full'; as a result, the initialized hashes are later overwritten when copied from 'full'. To ensure the correct hashes are in 'istate' after the index expansion, change the arg used in 'path_matches_pattern_list()' from 'istate' to 'full'. Note that this does not fully solve the problem. If 'istate' does not have an initialized 'name_hash' when its contents are copied to 'full', initialized hashes will be copied back into 'istate' but 'name_hash_initialized' will be 0. Therefore, we also need to copy 'full->name_hash_initialized' back to 'istate' after the index expansion is complete. Signed-off-by: Victoria Dye --- sparse-index.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/sparse-index.c b/sparse-index.c index 3cff0213071660..924dc4c10e41e7 100644 --- a/sparse-index.c +++ b/sparse-index.c @@ -371,7 +371,7 @@ void expand_index(struct index_state *istate, struct pattern_list *pl) if (pl && path_matches_pattern_list(ce->name, ce->ce_namelen, NULL, &dtype, - pl, istate) == NOT_MATCHED) { + pl, full) == NOT_MATCHED) { set_index_entry(full, full->cache_nr++, ce); continue; } @@ -399,6 +399,7 @@ void expand_index(struct index_state *istate, struct pattern_list *pl) } /* Copy back into original index. */ + istate->name_hash_initialized = full->name_hash_initialized; memcpy(&istate->name_hash, &full->name_hash, sizeof(full->name_hash)); memcpy(&istate->dir_hash, &full->dir_hash, sizeof(full->dir_hash)); istate->sparse_index = pl ? INDEX_PARTIALLY_SPARSE : INDEX_EXPANDED; From c9f342c864e3acce3a288b28269ccdec4d95e74a Mon Sep 17 00:00:00 2001 From: Victoria Dye Date: Wed, 20 Sep 2023 13:12:30 -0700 Subject: [PATCH 3/3] t1092: add test for untracked files and directories Add a test verifying that sparse-checkout (with and without sparse index enabled) treat untracked files & directories correctly when changing sparse patterns. Specifically, it ensures that 'git sparse-checkout set' * deletes empty directories outside the sparse cone * does _not_ delete untracked files outside the sparse cone Signed-off-by: Victoria Dye --- t/t1092-sparse-checkout-compatibility.sh | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/t/t1092-sparse-checkout-compatibility.sh b/t/t1092-sparse-checkout-compatibility.sh index 542bb96c82982a..e857d3587a147e 100755 --- a/t/t1092-sparse-checkout-compatibility.sh +++ b/t/t1092-sparse-checkout-compatibility.sh @@ -311,6 +311,22 @@ test_expect_success 'root directory cannot be sparse' ' test_cmp expect actual ' +test_expect_success 'sparse-checkout with untracked files and dirs' ' + init_repos && + + # Empty directories outside sparse cone are deleted + run_on_sparse mkdir -p deep/empty && + test_sparse_match git sparse-checkout set folder1 && + test_must_be_empty sparse-checkout-err && + run_on_sparse test_path_is_missing deep && + + # Untracked files outside sparse cone are not deleted + run_on_sparse touch folder1/another && + test_sparse_match git sparse-checkout set folder2 && + grep "directory ${SQ}folder1/${SQ} contains untracked files" sparse-checkout-err && + run_on_sparse test_path_exists folder1/another +' + test_expect_success 'status with options' ' init_repos && test_sparse_match ls &&