Skip to content

Fix nested .gitignore directory patterns not excluding under --exclude-gitignore#21752

Open
Osamaali313 wants to merge 1 commit into
python:masterfrom
Osamaali313:fix/gitignore-nested-dir-exclude
Open

Fix nested .gitignore directory patterns not excluding under --exclude-gitignore#21752
Osamaali313 wants to merge 1 commit into
python:masterfrom
Osamaali313:fix/gitignore-nested-dir-exclude

Conversation

@Osamaali313

Copy link
Copy Markdown

Problem

matches_gitignore (mypy/modulefinder.py) decides whether to append a trailing / (needed for directory-only gitignore patterns like build/) using the wrong path:

def matches_gitignore(subpath: str, fscache: FileSystemCache, verbose: bool) -> bool:
    dir, _ = os.path.split(subpath)
    for gi_path, gi_spec in find_gitignores(dir):
        relative_path = os.path.relpath(subpath, gi_path)
        if fscache.isdir(relative_path):        # relative_path is relative to gi_path, not cwd
            relative_path = relative_path + "/"
        if gi_spec.match_file(relative_path):
            ...

relative_path is computed relative to the .gitignore's directory (gi_path) so it can be matched by gi_spec.match_file(...) — that part is correct (gitignore patterns are relative to the gitignore's location). But that same gi_path-relative string is then passed to fscache.isdir(...), which resolves paths relative to the process cwd. When the matched .gitignore lives in a scanned subdirectory (gi_path != cwd), the string doesn't resolve on disk, isdir returns False, the trailing / is never added, and directory-only patterns in nested .gitignores silently fail to exclude the directory.

Evidence (sibling proves intent)

The sibling matches_exclude in the same file does this correctly — it builds the match string via os.path.relpath(subpath) but calls isdir on the original path:

subpath_str = os.path.relpath(subpath).replace(os.sep, "/")
if fscache.isdir(subpath):        # original subpath, not the relativized string
    subpath_str += "/"

Fix

Stat the original subpath (keep relative_path only for the gitignore-spec match):

if fscache.isdir(subpath):
    relative_path = relative_path + "/"

Reproduction

subpath = "mypkg/sub" (a real directory), matched against a nested .gitignore in mypkg/ containing sub/:

trailing / added? directory excluded?
before (isdir(relative_path)) no (isdir("sub") is False from cwd) no
after (isdir(subpath)) yes yes ✅

Reachable from find_sources.py (and modulefinder) whenever --exclude-gitignore is enabled and a nested .gitignore uses a directory-only pattern.

…e-gitignore

`matches_gitignore` computes `relative_path = os.path.relpath(subpath, gi_path)`
so it can be matched against the gitignore spec (gitignore patterns are
relative to the .gitignore's directory). But it then passes that same
gi_path-relative string to `fscache.isdir(...)`, which resolves paths
relative to the process cwd. When the matched `.gitignore` lives in a scanned
subdirectory (`gi_path != cwd`), `relative_path` does not resolve on the real
filesystem, so `isdir` returns False, the trailing "/" is never appended, and
directory-only patterns (e.g. `build/`, `node_modules/`) fail to exclude the
directory.

The sibling `matches_exclude` in the same file does this correctly: it builds
the match string with `os.path.relpath(subpath)` but calls
`fscache.isdir(subpath)` on the original path. Match `matches_gitignore` to
that: stat the original `subpath`, keep using `relative_path` only for the
gitignore-spec match.
Copilot AI review requested due to automatic review settings July 19, 2026 20:59

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@github-actions

Copy link
Copy Markdown
Contributor

According to mypy_primer, this change doesn't affect type check results on a corpus of open source code. ✅

@A5rocks

A5rocks commented Jul 20, 2026

Copy link
Copy Markdown
Collaborator

Please make an issue first.

@Osamaali313

Copy link
Copy Markdown
Author

Thanks — opened #21760 describing the bug (directory-only patterns in a nested .gitignore not excluded under --exclude-gitignore, because the trailing-slash isdir check stats a gitignore-relative path against cwd). This PR fixes it by statting the original subpath, matching the sibling matches_exclude.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants