Re: [PATCH] dir: skip excluded directory with nested repo on prefix match
- From
Junio C Hamano <gitster@pobox.com>
- Date
- Sep 22, 2026, 04:57 UTC
- Message-ID
- <xmqqjyodj320.fsf@gitster.g>
- In-Reply-To
- <be53c379-b0e1-4242-8504-e96c2c49d294@web.de>
René Scharfe <l.s.r@web.de> writes:
Show 9 quoted lines
> +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);
> +}OK, this is a good helper to extract and reuse.
Show 13 quoted lines
> @@ -1996,11 +2006,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;
> }So, this hunk shows that the treat_directory() function is prepared to be passed a NULL in pathspec. We make sure we do not trigger match_pathspec_with_flags() when pathspec is NULL, and this should extends to dir_match() now. This is a very sensible conditional, as match_pathspec_with_flags() calls do_match_pathspec() as the first thing, which begins with GUARD_PATHSPEC() macro that unconditionally dereferences the pathspec.
Show 14 quoted lines
> @@ -2034,8 +2040,13 @@ 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 (!matches_how)
> + matches_how = dir_match(istate, pathspec,
> + dirname, len);
> + if (!matches_how ||
> + matches_how == MATCHED_RECURSIVELY_LEADING_PATHSPEC)
> return path_none;Here, we do not know if we have pathspec==NULL. Shouldn't "make sure we have a result from dir_match() and return path_none as appropriate" be done only when pathspec != NULL or something like that, since dir_match() will crash if pathspec is NULL?
Taking all together, something along the following line squashed into your patch, perhaps? The newly added test is not essential; it merely is to demonstrate why an extra conditional I added below would help avoid segfaulting.
dir.c | 15 +++++++++------ t/t7061-wtstatus-ignore.sh | 25 +++++++++++++++++++++++++ 2 files changed, 34 insertions(+), 6 deletions(-)
diff --git c/dir.c w/dir.c index 6bda650891..8e858c26d1 100644 --- c/dir.c +++ w/dir.c @@ -2042,12 +2042,15 @@ 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) - matches_how = dir_match(istate, pathspec, - dirname, len); - if (!matches_how || - matches_how == MATCHED_RECURSIVELY_LEADING_PATHSPEC) - return path_none; + + if (pathspec) { + if (!matches_how) + matches_how = dir_match(istate, pathspec, + dirname, len); + if (!matches_how || + matches_how == MATCHED_RECURSIVELY_LEADING_PATHSPEC) + return path_none; + } return excluded ? path_excluded : path_untracked; } } diff --git c/t/t7061-wtstatus-ignore.sh w/t/t7061-wtstatus-ignore.sh index 182933471f..6bc680312e 100755 --- c/t/t7061-wtstatus-ignore.sh +++ w/t/t7061-wtstatus-ignore.sh @@ -340,6 +340,31 @@ test_expect_success 'status ignores submodule in excluded directory' ' test_cmp expected actual ' +test_expect_success 'replace directory with untracked nested repo' ' + test_create_repo checkout-nested && + ( + cd checkout-nested && + test_commit base && + + # Branch with tracked file "dir" + git checkout -b branch-file && + echo "tracked file" >dir && + git add dir && + git commit -m "add file dir" && + + # Switch back to base + git checkout -b branch-nested base && + + # Create an untracked directory containing a nested git repo + mkdir -p dir/nested && + git init dir/nested && + + # Attempting checkout invokes verify_clean_subdirectory() + # with pathspec=NULL + test_must_fail git checkout branch-file + ) +' + test_expect_success 'status omits excluded directory with submodule on prefix match' ' git status --porcelain --ignored -u tracke >actual && test_must_be_empty actual