Skip to content

feat(gitignore): remove libc fnmatch dependency and implement pure Zig glob#59

Merged
jiacai2050 merged 3 commits intomainfrom
feat-ignore
Mar 10, 2026
Merged

feat(gitignore): remove libc fnmatch dependency and implement pure Zig glob#59
jiacai2050 merged 3 commits intomainfrom
feat-ignore

Conversation

@jiacai2050
Copy link
Owner

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.

…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.
Copilot AI review requested due to automatic review settings March 10, 2026 12:14
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

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 fnmatch dependency and implemented globMatch + character class matching in Zig.
  • Updated .gitignore pattern parsing (whitespace/escape handling, negation, anchoring) and expanded tests for edge cases.
  • Updated build/CI wiring to stop linking libc for the gitignore module 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.

Comment on lines +326 to +328
// 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) {
Copy link

Copilot AI Mar 10, 2026

Choose a reason for hiding this comment

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

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).

Copilot uses AI. Check for mistakes.
Copy link
Owner Author

Choose a reason for hiding this comment

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

@copilot open a new pull request to apply changes based on this feedback

Comment on lines 286 to 295
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;
}
}
Copy link

Copilot AI Mar 10, 2026

Choose a reason for hiding this comment

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

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.

Copilot uses AI. Check for mistakes.
Copy link
Contributor

Copilot AI commented Mar 10, 2026

@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.

@gemini-code-assist
Copy link

Summary of Changes

Hello, 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 gitignore module by removing its dependency on libc's fnmatch and introducing a pure Zig implementation for glob and character class matching. This change resolves cross-platform compatibility issues, particularly on Windows, and ensures more accurate and consistent adherence to the .gitignore specification, including advanced pattern semantics. The refactored logic and expanded test coverage contribute to a more reliable and maintainable module.

Highlights

  • Native Zig Glob Implementation: Replaced the libc fnmatch dependency with a pure Zig implementation for glob and character class matching, enhancing cross-platform compatibility and consistency.
  • Improved .gitignore Semantics: Enhanced pattern parsing to strictly follow .gitignore specification, including correct handling of character classes, wildcards, escaped characters (e.g., \ , \!, \#), and trailing spaces.
  • Expanded Test Coverage: Significantly expanded the test suite to cover a wider range of .gitignore specification edge cases and behaviors, ensuring robust and accurate pattern matching.
  • Windows Compatibility: Resolved build and functionality issues on Windows by removing the reliance on fnmatch.h, making .gitignore rules work consistently across all platforms.

🧠 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
  • CLAUDE.md
    • Added a new documentation file providing an overview of the Zigcli project, build commands, architecture, and dependencies for AI assistants.
  • build.zig
    • Removed libc linking for the gitignore module.
    • Removed Windows-specific build exclusion for the gitignore module.
  • src/mod/gitignore.zig
    • Removed the fnmatch.h C import.
    • Introduced globMatch function for native Zig glob pattern matching.
    • Introduced matchCharClass function for character class matching within glob patterns.
    • Updated Pattern.init to implement precise .gitignore parsing rules for leading/trailing whitespace, escaped characters (\ , \!, \#), and negation.
    • Refactored Pattern.matches to utilize the new globMatch function for segment matching, removing fnmatch calls.
    • Modified matchPathSegments and matchSegmentsRecursive to use the new globMatch and simplify error handling by returning bool instead of !bool.
    • Adjusted Gitignore.init to correctly process raw pattern lines, preserving escaped whitespace for Pattern.init.
    • Updated Gitignore.shouldIgnore to reflect the non-error-returning matches function.
    • Added comprehensive new test cases covering character classes, escape sequences, ? wildcard, ** edge cases, and basic globMatch functionality.
Ignored Files
  • Ignored by pattern: .github/workflows/** (1)
    • .github/workflows/CI.yml
Activity
  • No specific human activity (comments, reviews) was provided in the context.
Using Gemini Code Assist

The 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 /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

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 .gemini/ folder in the base of the repository. Detailed instructions can be found here.

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

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

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.

Comment on lines +326 to +327
// Pass untrimmed line to Pattern.init for proper escape handling
// Actually, pass trimmed but Pattern.init re-trims with escape awareness

Choose a reason for hiding this comment

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

medium

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

@jiacai2050 jiacai2050 merged commit c0e02ae into main Mar 10, 2026
8 checks passed
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