add: skip tracked paths outside sparse-checkout cone

When 'git add' adds a tracked file that is outside of the sparse-checkout cone, it checks the SKIP_WORKTREE bit to see if the file exists outside of the sparse-checkout cone. This is usually correct, except in the case of a merge conflict outside of the cone. Modify add_pathspec_matched_against_index() to be more careful about paths by checking the sparse-checkout patterns in addition to the SKIP_WORKTREE bit. This causes 'git add' to no longer allow files outside of the cone that removed the SKIP_WORKTREE bit due to a merge conflict. With only this change, users will only be able to add the file after adding the file to the sparse-checkout cone. A later change will allow users to force adding even though the file is outside of the sparse-checkout cone. Signed-off-by: Derrick Stolee <dstolee@microsoft.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Derrick Stolee committed Sep 24, 2021 at 15:39 UTC 49fdd51a235fe2ca91a6d1b16315204f0f016a96
5 files changed +34 -10
builtin/add.c
+4
@@ -94,6 +94,10 @@ static void update_callback(struct diff_queue_struct *q,
94 for (i = 0; i < q->nr; i++) {
95 struct diff_filepair *p = q->queue[i];
96 const char *path = p->one->path;
97 +
98 + if (!path_in_sparse_checkout(path, &the_index))
99 + continue;
100 +
101 switch (fix_unmerged_status(p, data)) {
102 default:
103 die(_("unexpected diff status %c"), p->status);
pathspec.c
+3 -2
@@ -39,7 +39,8 @@ void add_pathspec_matches_against_index(const struct pathspec *pathspec,
39 return;
40 for (i = 0; i < istate->cache_nr; i++) {
41 const struct cache_entry *ce = istate->cache[i];
42 - if (sw_action == PS_IGNORE_SKIP_WORKTREE && ce_skip_worktree(ce))
42 + if (sw_action == PS_IGNORE_SKIP_WORKTREE &&
43 + (ce_skip_worktree(ce) || !path_in_sparse_checkout(ce->name, istate)))
44 continue;
45 ce_path_match(istate, ce, pathspec, seen);
46 }
@@ -70,7 +71,7 @@ char *find_pathspecs_matching_skip_worktree(const struct pathspec *pathspec)
71
72 for (i = 0; i < istate->cache_nr; i++) {
73 struct cache_entry *ce = istate->cache[i];
73 - if (ce_skip_worktree(ce))
74 + if (ce_skip_worktree(ce) || !path_in_sparse_checkout(ce->name, istate))
75 ce_path_match(istate, ce, pathspec, seen);
76 }
77
t/t1091-sparse-checkout-builtin.sh
+3 -1
@@ -406,7 +406,7 @@ test_expect_success 'sparse-checkout (init|set|disable) warns with unmerged stat
406 git -C unmerged sparse-checkout disable
407 '
408
409 -test_expect_success 'sparse-checkout reapply' '
409 +test_expect_failure 'sparse-checkout reapply' '
410 git clone repo tweak &&
411
412 echo dirty >tweak/deep/deeper2/a &&
@@ -438,6 +438,8 @@ test_expect_success 'sparse-checkout reapply' '
438 test_i18ngrep "warning.*The following paths are unmerged" err &&
439 test_path_is_file tweak/folder1/a &&
440
441 + # NEEDSWORK: We are asking to update a file outside of the
442 + # sparse-checkout cone, but this is no longer allowed.
443 git -C tweak add folder1/a &&
444 git -C tweak sparse-checkout reapply 2>err &&
445 test_must_be_empty err &&
t/t1092-sparse-checkout-compatibility.sh
+12 -7
@@ -546,10 +546,9 @@ test_expect_failure 'merge with conflict outside cone' '
546 test_all_match git status --porcelain=v2 &&
547
548 # 2. Add the file with conflict markers
549 - # NEEDSWORK: Even though the merge conflict removed the
550 - # SKIP_WORKTREE bit from the index entry for folder1/a, we should
551 - # warn that this is a problematic add.
552 - test_all_match git add folder1/a &&
549 + test_sparse_match test_must_fail git add folder1/a &&
550 + grep "Disable or modify the sparsity rules" sparse-checkout-err &&
551 + test_sparse_unstaged folder1/a &&
552 test_all_match git status --porcelain=v2 &&
553
554 # 3. Rename the file to another sparse filename and
@@ -558,7 +557,9 @@ test_expect_failure 'merge with conflict outside cone' '
557 # NEEDSWORK: This mode now fails, because folder2/z is
558 # outside of the sparse-checkout cone and does not match an
559 # existing index entry with the SKIP_WORKTREE bit cleared.
561 - test_all_match git add folder2 &&
560 + test_sparse_match test_must_fail git add folder2 &&
561 + grep "Disable or modify the sparsity rules" sparse-checkout-err &&
562 + test_sparse_unstaged folder2/z &&
563 test_all_match git status --porcelain=v2 &&
564
565 test_all_match git merge --continue &&
@@ -586,7 +587,9 @@ test_expect_failure 'cherry-pick/rebase with conflict outside cone' '
587 # NEEDSWORK: Even though the merge conflict removed the
588 # SKIP_WORKTREE bit from the index entry for folder1/a, we should
589 # warn that this is a problematic add.
589 - test_all_match git add folder1/a &&
590 + test_sparse_match test_must_fail git add folder1/a &&
591 + grep "Disable or modify the sparsity rules" sparse-checkout-err &&
592 + test_sparse_unstaged folder1/a &&
593 test_all_match git status --porcelain=v2 &&
594
595 # 3. Rename the file to another sparse filename and
@@ -595,7 +598,9 @@ test_expect_failure 'cherry-pick/rebase with conflict outside cone' '
598 # outside of the sparse-checkout cone and does not match an
599 # existing index entry with the SKIP_WORKTREE bit cleared.
600 run_on_all mv folder2/a folder2/z &&
598 - test_all_match git add folder2 &&
601 + test_sparse_match test_must_fail git add folder2 &&
602 + grep "Disable or modify the sparsity rules" sparse-checkout-err &&
603 + test_sparse_unstaged folder2/z &&
604 test_all_match git status --porcelain=v2 &&
605
606 test_all_match git $OPERATION --continue &&
t/t3705-add-sparse-checkout.sh
+12
@@ -158,6 +158,18 @@ test_expect_success 'do not warn when pathspec matches dense entries' '
158 git ls-files --error-unmatch dense_entry
159 '
160
161 +test_expect_success 'git add fails outside of sparse-checkout definition' '
162 + test_when_finished git sparse-checkout disable &&
163 + test_commit a &&
164 + git sparse-checkout init &&
165 + git sparse-checkout set a &&
166 + echo >>sparse_entry &&
167 +
168 + git update-index --no-skip-worktree sparse_entry &&
169 + test_must_fail git add sparse_entry &&
170 + test_sparse_entry_unstaged
171 +'
172 +
173 test_expect_success 'add obeys advice.updateSparsePath' '
174 setup_sparse_entry &&
175 test_must_fail git -c advice.updateSparsePath=false add sparse_entry 2>stderr &&