Skip to content

Support POSIX character classes in bracket expressions - #130

Open
dngr2 wants to merge 2 commits into
cpburnz:masterfrom
dngr2:support-posix-character-classes
Open

Support POSIX character classes in bracket expressions#130
dngr2 wants to merge 2 commits into
cpburnz:masterfrom
dngr2:support-posix-character-classes

Conversation

@dngr2

@dngr2 dngr2 commented Aug 16, 2026

Copy link
Copy Markdown

The bug

Git's wildmatch supports POSIX character classes inside bracket expressions — [[:digit:]], [[:alpha:]], [[:space:]], [[:upper:]], etc. pathspec does not, for two reasons:

  1. The closing-bracket scan stops too early. When scanning [[:digit:]] for the ] that ends the expression, it stops at the ] that closes the inner [:digit:], so the expression is mis-delimited.
  2. Python re has no POSIX class syntax. Even delimited, [[:digit:]] is handed to re unchanged, which reads it as a set of the literal characters [ : d i g t (raising a FutureWarning: Possible nested set) — matching nothing sensible.

Result: every POSIX class is broken. Against git check-ignore (the reference), all 12 classes diverge:

>>> import pathspec
>>> s = pathspec.PathSpec.from_lines("gitwildmatch", ["[[:digit:]].txt"])
>>> s.match_file("1.txt")
False          # git ignores it; pathspec does not

A .gitignore rule like [[:upper:]]*.tmp therefore matches files in Git but not in pathspec, so tools built on pathspec (Black, pre-commit, pip, GitPython via GitIgnoreSpec) silently disagree with Git about which files are ignored. This affects both the gitwildmatch and gitignore factories (they share the translation).

The fix

In _translate_segment_glob:

  1. Skip [:...:] as a unit when finding the closing bracket, so the inner ] isn't mistaken for the end.
  2. Expand each POSIX class to the equivalent ASCII range, matching Git's wildmatch in the C locale ([:digit:]0-9, [:alpha:]A-Za-z, [:punct:] → the punctuation ranges, etc.).

Negated ([![:digit:]], [^[:digit:]]) and mixed ([[:alpha:]0-9], [[:alnum:]_]) forms are handled. Non-class brackets ([a-z], [!a-z], []a]) are unchanged.

Verification

  • New test test_15_posix_character_class asserts both the translated regex and match behavior; it fails on the current code and passes with the fix.
  • Full suite green: 198 passed, 276 skipped.
  • Differentially checked against git check-ignore over all 12 POSIX classes (plus negated/mixed forms) and a 1,500-case fuzz of random bracket/wildcard patterns — 0 POSIX-class divergences remain.

dngr2 added 2 commits August 17, 2026 02:16
Git's wildmatch supports POSIX character classes inside bracket expressions
-- '[[:digit:]]', '[[:alpha:]]', '[[:space:]]', etc. pathspec did not: the
inner '[:...:]' confused the closing-bracket scan (the ']' that ends the
class was taken as the end of the whole expression), and even when delimited,
Python's 're' has no POSIX class syntax, so '[[:digit:]]' compiled to a set of
the literal characters '[', ':', 'd', 'i', 'g', 't' (with a 'Possible nested
set' warning) and matched nothing sensible.

The result: a .gitignore rule like '[[:upper:]]*.tmp' matched files in Git but
not in pathspec, so tools built on pathspec (e.g. via GitIgnoreSpec) silently
disagreed with Git about which files are ignored.

Skip '[:...:]' as a unit when finding the closing bracket, and expand each
POSIX class to the equivalent ASCII range (matching Git's wildmatch in the C
locale). Negated ('[![:digit:]]') and mixed ('[[:alpha:]0-9]') forms work too.
Adds tests covering the translation and matching.
@cpburnz

cpburnz commented Aug 18, 2026

Copy link
Copy Markdown
Owner

Thanks for the pull request. I'll review this along with the similar #128.

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