feat(gitignore): remove libc fnmatch dependency and implement pure Zig glob#59
feat(gitignore): remove libc fnmatch dependency and implement pure Zig glob#59jiacai2050 merged 3 commits intomainfrom
Conversation
…g glob This change eliminates the reliance on libc's fnmatch by implementing our own .gitignore-compatible glob and character class matcher in Zig. Now, gitignore rules work consistently across all platforms, including Windows, and support character classes, wildcards, escaped spaces, and advanced edge cases found in real-world .gitignore files. Related test coverage is greatly expanded for compliance with the .gitignore specification. Previously, the 'gitignore' module failed to build or work properly on Windows due to the absence of fnmatch.h, and its pattern parsing lacked precise escapes and special-case handling. Now, pattern initialization, matching logic, and edge cases (like trailing spaces and escapes) strictly follow gitignore semantics. This increases cross-platform compatibility and future maintainability.
There was a problem hiding this comment.
Pull request overview
This PR replaces the previous libc fnmatch-based .gitignore matching with a pure Zig glob implementation to improve cross-platform behavior (notably Windows) and expands test coverage for .gitignore semantics.
Changes:
- Removed the libc
fnmatchdependency and implementedglobMatch+ character class matching in Zig. - Updated
.gitignorepattern parsing (whitespace/escape handling, negation, anchoring) and expanded tests for edge cases. - Updated build/CI wiring to stop linking libc for the
gitignoremodule and run its tests on Windows.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
src/mod/gitignore.zig |
Implements pure Zig glob + character classes and updates .gitignore parsing/matching logic with expanded tests. |
build.zig |
Removes link_libc for gitignore and no longer skips its tests on Windows. |
CLAUDE.md |
Adds repository guidance documentation (build commands, architecture, dependencies). |
.github/workflows/CI.yml |
Simplifies fmt/test steps across OS matrix and pins Zig 0.15.1 for cross-compilation job. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
src/mod/gitignore.zig
Outdated
| // Pass untrimmed line to Pattern.init for proper escape handling | ||
| // Actually, pass trimmed but Pattern.init re-trims with escape awareness | ||
| const pattern = Pattern.init(allocator, line) catch |e| switch (e) { |
There was a problem hiding this comment.
The inline comment here is self-contradictory: it says to pass the untrimmed line to Pattern.init, then immediately says “Actually, pass trimmed…”, but the code passes line. Please remove the contradictory sentence or rewrite the comment so it matches the actual behavior (passing the original line so Pattern.init can handle escaped whitespace/comments correctly).
There was a problem hiding this comment.
@copilot open a new pull request to apply changes based on this feedback
| var path_it_fork = path_it.*; | ||
| while (true) { | ||
| // Try to match pattern after "**" against the current sub-path. | ||
| var pattern_it_copy = pattern_it_after_glob; | ||
| if (try matchSegmentsRecursive(&pattern_it_copy, &path_it_fork, allocator)) { | ||
| if (matchSegmentsRecursive(&pattern_it_copy, &path_it_fork)) { | ||
| return true; | ||
| } | ||
| // If that failed, "**" needs to consume a segment. | ||
| // If there are no more segments to consume, we've failed. | ||
| if (path_it_fork.next() == null) { | ||
| return false; | ||
| } | ||
| } |
There was a problem hiding this comment.
In the ** branch, matchSegmentsRecursive is called with &path_it_fork, so the recursive attempt mutates path_it_fork. When that attempt fails after consuming some path segments (e.g. pattern **/a/b vs path a/a/b), the outer loop then calls path_it_fork.next() again, effectively skipping candidate start positions and producing false negatives. To avoid this, pass a copy of path_it_fork into the recursive call each iteration (so failed attempts don’t advance the iterator used by the outer loop), and only advance path_it_fork in the outer loop to represent ** consuming one more segment.
|
@jiacai2050 I've opened a new pull request, #60, to work on those changes. Once the pull request is ready, I'll request review from you. |
Summary of ChangesHello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! This pull request significantly improves the Highlights
🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console. Changelog
Ignored Files
Activity
Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here. You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension. Footnotes
|
There was a problem hiding this comment.
Code Review
This pull request significantly improves the gitignore module by removing the libc dependency and implementing a pure Zig glob matcher, which enhances cross-platform compatibility, especially for Windows. The new implementation handles many .gitignore specification edge cases correctly, and a comprehensive test suite has been added. There is one minor suggestion to improve code clarity.
src/mod/gitignore.zig
Outdated
| // Pass untrimmed line to Pattern.init for proper escape handling | ||
| // Actually, pass trimmed but Pattern.init re-trims with escape awareness |
There was a problem hiding this comment.
The comment on line 327 is confusing and contradicts both the preceding comment and the code. The code correctly passes the untrimmed line to Pattern.init to handle special whitespace cases, but this comment suggests trimmed is used. To improve clarity, this line should be removed.
// Pass untrimmed line to Pattern.init for proper escape handling
This change eliminates the reliance on libc's fnmatch by implementing our own .gitignore-compatible glob and character class matcher in Zig. Now, gitignore rules work consistently across all platforms, including Windows, and support character classes, wildcards, escaped spaces, and advanced edge cases found in real-world .gitignore files. Related test coverage is greatly expanded for compliance with the .gitignore specification.
Previously, the 'gitignore' module failed to build or work properly on Windows due to the absence of fnmatch.h, and its pattern parsing lacked precise escapes and special-case handling. Now, pattern initialization, matching logic, and edge cases (like trailing spaces and escapes) strictly follow gitignore semantics. This increases cross-platform compatibility and future maintainability.