Skip to content

Match directory entries with directory-only patterns - #147

Merged
cpburnz merged 1 commit into
cpburnz:masterfrom
9Kun:fix/directory-entry-matching
Sep 28, 2026
Merged

cpburnz merged 1 commit into
cpburnz:masterfrom
9Kun:fix/directory-entry-matching

Conversation

@9Kun

@9Kun 9Kun commented Sep 22, 2026

Copy link
Copy Markdown
Contributor

match_entries() passes TreeEntry.path to the matcher without a trailing separator, even when the entry is a directory. A build/ pattern therefore misses the build entry itself. An empty build directory is omitted entirely by match_tree_entries().

Reproduced on master at 811bde1c624e85accee683b9cbf07c01538afacb:

from pathlib import Path
from tempfile import TemporaryDirectory
from pathspec import PathSpec

with TemporaryDirectory() as root:
    Path(root, "build").mkdir()
    spec = PathSpec.from_lines("gitignore", ["build/"], backend="simple")
    print(spec.match_file("build/"))  # True
    print([entry.path for entry in spec.match_tree_entries(root)])
    # Before: []; after: ['build']

This uses the entry's cached is_dir() result to append a separator to the normalized matching path, following the convention already used by append_dir_sep(). It leaves the returned entries and their paths unchanged, avoids adding a second separator, and does not change the string-based matching APIs or traversal logic. The inherited GitIgnoreSpec methods receive the same fix.

The regression tests reuse the existing backend matrix and filesystem helpers. They cover directory-only rules, empty directories, an ordinary file with the same name, exclusions, negate=True, tree entry matching, unchanged file-only matching, returned object identity, and custom/already-present separators.

Validation

Run locally on Windows with CPython 3.14.0 in a virtual environment. Both optional native backends were installed: google-re2 1.1.20251105 and hyperscan 0.8.2.

  • Added tests against the unchanged source: 17 failing subtests, as expected.
  • python -m unittest discover -t . -s tests/ -v: 223 tests, OK (14 skipped). All simple/re2/hyperscan configurations ran. The 14 skips are the existing Windows symlink tests; the unchanged baseline also skips them.
  • python -m mypy pathspec tests/check_usage.py: passed (32 files).
  • python -m sphinx -aWEnqb html doc/source doc/build: passed.
  • python -m compileall -q pathspec tests, python -m tabnanny pathspec/pathspec.py tests/test_05_pathspec.py, python -m pip check, and git diff --check: passed.
  • python -m pyright pathspec tests/check_usage.py: not passing; six native-backend attribute-resolution errors. A clean worktree at the baseline produces the same six errors.
  • Additional ruff check --select F pathspec/pathspec.py tests/test_05_pathspec.py: not passing; seven existing test-file findings (one unused import and six placeholder-free f-strings), identical on the baseline. No unrelated cleanup is included.

Other Python/OS combinations and the skipped symlink cases were not tested locally. No matching issue or PR was found, so the reproduction is included here without a separate issue.

AI assistance

An OpenAI Codex agent prepared this patch, reviewed the diff, and ran the commands above at the account owner's request. No human review or manual validation is claimed.

@cpburnz

cpburnz commented Sep 28, 2026

Copy link
Copy Markdown
Owner

Thanks for finding and fixing this!

@cpburnz
cpburnz merged commit 45bd0ab into cpburnz:master Sep 28, 2026
7 checks passed
cpburnz added a commit that referenced this pull request Sep 28, 2026
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.

2 participants