[PATCH v2] dir: skip excluded directory with nested repo on prefix match
- From
René Scharfe <l.s.r@web.de>
- Date
- Sep 26, 2026, 10:48 UTC
- Message-ID
- <1c6ac703-a5a5-421b-bf0b-640bc1a0932f@web.de>
- In-Reply-To
- <be53c379-b0e1-4242-8504-e96c2c49d294@web.de>
95c11ecc73 (Fix error-prone fill_directory() API; make it only return matches, 2020-04-01) optimized away pathspec matching too eagerly and cada7308ad (dir: check pathspecs before returning `path_excluded`, 2020-07-20) fixed that for files and symlinks.
A corner case remained unaddressed: Ignored directories that contain a nested repository. Make sure to run match_pathspec_with_flags() for it if necessary.
Add a status test to check whether the pathspec is applied correctly as well as a checkout test to exercise the changed code without a pathspec.
Reported-by: Sean Whitton <spwhitton@spwhitton.name> Reported-by: Lester Longley <lester@ieee.org> Helped-by: Junio C Hamano <gitster@pobox.com> Signed-off-by: René Scharfe <l.s.r@web.de> --- dir.c | 27 ++++++++++++++++++++------- t/t2021-checkout-overwrite.sh | 7 +++++++ t/t7061-wtstatus-ignore.sh | 5 +++++ 3 files changed, 32 insertions(+), 7 deletions(-)
diff --git a/dir.c b/dir.c index d896e7be4b..ac246ced1d 100644 --- a/dir.c +++ b/dir.c @@ -1935,6 +1935,16 @@ static enum exist_status directory_exists_in_index(struct index_state *istate, return index_nonexistent; } +static int dir_match(struct index_state *istate, + const struct pathspec *pathspec, + const char *dirname, int len) +{ + return match_pathspec_with_flags(istate, pathspec, dirname, len, + 0 /* prefix */, + NULL /* seen */, + DO_MATCH_LEADING_PATHSPEC); +} + /* * When we find a directory when traversing the filesystem, we * have three distinct cases: @@ -2001,11 +2011,7 @@ static enum path_treatment treat_directory(struct dir_struct *dir, * for matching patterns. */ if (pathspec && !excluded) { - matches_how = match_pathspec_with_flags(istate, pathspec, - dirname, len, - 0 /* prefix */, - NULL /* seen */, - DO_MATCH_LEADING_PATHSPEC); + matches_how = dir_match(istate, pathspec, dirname, len); if (!matches_how) return path_none; } @@ -2039,8 +2045,15 @@ static enum path_treatment treat_directory(struct dir_struct *dir, strbuf_release(&sb); if (nested_repo) { - if ((dir->flags & DIR_SKIP_NESTED_GIT) || - (matches_how == MATCHED_RECURSIVELY_LEADING_PATHSPEC)) + if (dir->flags & DIR_SKIP_NESTED_GIT) + return path_none; + if (pathspec && !matches_how) { + matches_how = dir_match(istate, pathspec, + dirname, len); + if (!matches_how) + return path_none; + } + if (matches_how == MATCHED_RECURSIVELY_LEADING_PATHSPEC) return path_none; return excluded ? path_excluded : path_untracked; } diff --git a/t/t2021-checkout-overwrite.sh b/t/t2021-checkout-overwrite.sh index 38c41ae373..2b7fff5159 100755 --- a/t/t2021-checkout-overwrite.sh +++ b/t/t2021-checkout-overwrite.sh @@ -79,4 +79,11 @@ test_expect_success 'checkout --overwrite-ignore should succeed if only ignored test_path_is_file some_dir ' +test_expect_success 'checkout must not overwrite untracked nested repo' ' + git checkout -f start && + rm -rf some_dir && + git init some_dir && + test_must_fail git checkout df_conflict +' + test_done diff --git a/t/t7061-wtstatus-ignore.sh b/t/t7061-wtstatus-ignore.sh index 14ddaba2f3..182933471f 100755 --- a/t/t7061-wtstatus-ignore.sh +++ b/t/t7061-wtstatus-ignore.sh @@ -340,4 +340,9 @@ test_expect_success 'status ignores submodule in excluded directory' ' test_cmp expected actual ' +test_expect_success 'status omits excluded directory with submodule on prefix match' ' + git status --porcelain --ignored -u tracke >actual && + test_must_be_empty actual +' + test_done Interdiff against v1: diff --git a/dir.c b/dir.c index aa457f0f4e..ac246ced1d 100644 --- a/dir.c +++ b/dir.c @@ -2047,11 +2047,13 @@ static enum path_treatment treat_directory(struct dir_struct *dir, if (nested_repo) { if (dir->flags & DIR_SKIP_NESTED_GIT) return path_none; - if (!matches_how) + if (pathspec && !matches_how) { matches_how = dir_match(istate, pathspec, dirname, len); - if (!matches_how || - matches_how == MATCHED_RECURSIVELY_LEADING_PATHSPEC) + if (!matches_how) + return path_none; + } + if (matches_how == MATCHED_RECURSIVELY_LEADING_PATHSPEC) return path_none; return excluded ? path_excluded : path_untracked; } diff --git a/t/t2021-checkout-overwrite.sh b/t/t2021-checkout-overwrite.sh index 38c41ae373..2b7fff5159 100755 --- a/t/t2021-checkout-overwrite.sh +++ b/t/t2021-checkout-overwrite.sh @@ -79,4 +79,11 @@ test_expect_success 'checkout --overwrite-ignore should succeed if only ignored test_path_is_file some_dir ' +test_expect_success 'checkout must not overwrite untracked nested repo' ' + git checkout -f start && + rm -rf some_dir && + git init some_dir && + test_must_fail git checkout df_conflict +' + test_done
-- 2.55.0