sparse-index: skip indexes with unmerged entries

The sparse-index format is designed to be compatible with merge conflicts, even those outside the sparse-checkout definition. The reason is that when converting a full index to a sparse one, a cache entry with nonzero stage will not be collapsed into a sparse directory entry. However, this behavior was not tested, and a different behavior within convert_to_sparse() fails in this scenario. Specifically, cache_tree_update() will fail when unmerged entries exist. convert_to_sparse_rec() uses the cache-tree data to recursively walk the tree structure, but also to compute the OIDs used in the sparse-directory entries. Add an index scan to convert_to_sparse() that will detect if these merge conflict entries exist and skip the conversion before trying to update the cache-tree. This is marked as NEEDSWORK because this can be removed with a suitable update to cache_tree_update() or a similar method that can construct a cache-tree with invalid nodes, but still allow creating the nodes necessary for creating sparse directory entries. It is possible that in the future we will not need to make such an update, since if we do not expand a sparse-index into a full one, this conversion does not need to happen. Thus, this can be deferred until the merge machinery is made to integrate with the sparse-index. Reviewed-by: Elijah Newren <newren@gmail.com> Signed-off-by: Derrick Stolee <dstolee@microsoft.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Derrick Stolee committed Jul 14, 2021 at 13:12 UTC fc6609d198c47bf511f3388975c7c94e0aa1af2b
2 files changed +40
sparse-index.c
+18
@@ -116,6 +116,17 @@ int set_sparse_index_config(struct repository *repo, int enable)
116 return res;
117 }
118
119 +static int index_has_unmerged_entries(struct index_state *istate)
120 +{
121 + int i;
122 + for (i = 0; i < istate->cache_nr; i++) {
123 + if (ce_stage(istate->cache[i]))
124 + return 1;
125 + }
126 +
127 + return 0;
128 +}
129 +
130 int convert_to_sparse(struct index_state *istate)
131 {
132 int test_env;
@@ -152,6 +163,13 @@ int convert_to_sparse(struct index_state *istate)
163 return -1;
164 }
165
166 + /*
167 + * NEEDSWORK: If we have unmerged entries, then stay full.
168 + * Unmerged entries prevent the cache-tree extension from working.
169 + */
170 + if (index_has_unmerged_entries(istate))
171 + return 0;
172 +
173 if (cache_tree_update(istate, 0)) {
174 warning(_("unable to update cache-tree, staying full"));
175 return -1;
t/t1092-sparse-checkout-compatibility.sh
+22
@@ -352,6 +352,28 @@ test_expect_success 'merge with outside renames' '
352 done
353 '
354
355 +# Sparse-index fails to convert the index in the
356 +# final 'git cherry-pick' command.
357 +test_expect_success 'cherry-pick with conflicts' '
358 + init_repos &&
359 +
360 + write_script edit-conflict <<-\EOF &&
361 + echo $1 >conflict
362 + EOF
363 +
364 + test_all_match git checkout -b to-cherry-pick &&
365 + run_on_all ../edit-conflict ABC &&
366 + test_all_match git add conflict &&
367 + test_all_match git commit -m "conflict to pick" &&
368 +
369 + test_all_match git checkout -B base HEAD~1 &&
370 + run_on_all ../edit-conflict DEF &&
371 + test_all_match git add conflict &&
372 + test_all_match git commit -m "conflict in base" &&
373 +
374 + test_all_match test_must_fail git cherry-pick to-cherry-pick
375 +'
376 +
377 test_expect_success 'clean' '
378 init_repos &&
379