status: fix nested sparse directory diff in sparse index

Enable the 'recursive' diff option for the diff executed as part of 'git status'. Without the 'recursive' enabled, 'git status' reports index changes incorrectly when the following conditions were met: * sparse index is enabled * there is a difference between the index and HEAD in a file inside a *subdirectory* of a sparse directory * the sparse directory index entry is *not* expanded in-core Because it is not recursive by default, the diff in 'git status' reports changes only at the level of files and directories that are immediate children of a sparse directory, rather than recursing into directories with changes to identify the modified file(s). As a result, 'git status' reports the immediate subdirectory itself as "modified". Example: $ git init $ mkdir -p sparse/sub $ echo test >sparse/sub/foo $ git add . $ git commit -m "commit 1" $ echo somethingelse >sparse/sub/foo $ git add . $ git commit -a -m "commit 2" $ git sparse-checkout set --cone --sparse-index 'sparse' $ git reset --soft HEAD~1 $ git status On branch master You are in a sparse checkout. Changes to be committed: (use "git restore --staged <file>..." to unstage) modified: sparse/sub Enabling the 'recursive' diff option in 'wt_status_collect_changes_index' corrects this issue by allowing the diff to recurse into subdirectories of sparse directories to find modified files. Given the same repository setup as the example above, the corrected result of `git status` is: $ git status On branch master You are in a sparse checkout. Changes to be committed: (use "git restore --staged <file>..." to unstage) modified: sparse/sub/foo Signed-off-by: Victoria Dye <vdye@github.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Victoria Dye committed Mar 1, 2022 at 20:24 UTC 2c521b0e4900d8e7ff1f611ed956cfdd67f03eb0
2 files changed +16
t/t1092-sparse-checkout-compatibility.sh
+7
@@ -278,6 +278,13 @@ test_expect_success 'status with options' '
278 test_all_match git status --porcelain=v2 -uno
279 '
280
281 +test_expect_success 'status with diff in unexpanded sparse directory' '
282 + init_repos &&
283 + test_all_match git checkout rename-base &&
284 + test_all_match git reset --soft rename-out-to-out &&
285 + test_all_match git status --porcelain=v2
286 +'
287 +
288 test_expect_success 'status reports sparse-checkout' '
289 init_repos &&
290 git -C sparse-checkout status >full &&
wt-status.c
+9
@@ -651,6 +651,15 @@ static void wt_status_collect_changes_index(struct wt_status *s)
651 rev.diffopt.detect_rename = s->detect_rename >= 0 ? s->detect_rename : rev.diffopt.detect_rename;
652 rev.diffopt.rename_limit = s->rename_limit >= 0 ? s->rename_limit : rev.diffopt.rename_limit;
653 rev.diffopt.rename_score = s->rename_score >= 0 ? s->rename_score : rev.diffopt.rename_score;
654 +
655 + /*
656 + * The `recursive` option must be enabled to allow the diff to recurse
657 + * into subdirectories of sparse directory index entries. If it is not
658 + * enabled, a subdirectory containing file(s) with changes is reported
659 + * as "modified", rather than the modified files themselves.
660 + */
661 + rev.diffopt.flags.recursive = 1;
662 +
663 copy_pathspec(&rev.prune_data, &s->pathspec);
664 run_diff_index(&rev, 1);
665 object_array_clear(&rev.pending);