-
-
Notifications
You must be signed in to change notification settings - Fork 20
feat: add gitignore module #53
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
Summary of ChangesHello @jiacai2050, 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 streamlines the project's documentation by replacing a symlinked README with a dedicated, informative file. Concurrently, it updates the project's version and dependency manifest, ensuring compatibility with a specified minimum Zig version and leveraging the latest version of the zig-curl library for improved stability or features. Highlights
Ignored Files
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.
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 correctly replaces the symlinked README.org with a proper file, which improves repository compatibility. The changes also include updating the build.zig.zon with a new version, a minimum Zig version requirement, and a dependency update. My review includes one suggestion to enhance the new README.org by re-adding the project badges that were present in the previous version, but with corrected links and updated information.
This commit fixes a critical logic error in the parser to ensure that patterns matching a directory also match all paths inside that directory, aligning with official Git behavior. For example, a pattern should ignore the path . The core change is in the function. The base case that handles an exhausted pattern now correctly returns if the path has remaining segments, signifying that the pattern is a valid prefix of the path. Key changes: - Modified to return when the pattern is exhausted but the path is not, correctly implementing prefix matching. - Added a new test case to verify that a pattern like correctly matches a path like . - The implementation continues to use for robust and standard-compliant glob matching of individual path segments. This fix ensures the parser is more compliant with the behavior demonstrated by .
3d27f45 to
f6f4cc2
Compare
|
/gemini review |
There was a problem hiding this 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 primarily introduces a new gitignore parsing module, alongside dependency updates and clean-up of the README file. The changes to build.zig and build.zig.zon to incorporate the new module and update versions are correct.
My review focuses on the new src/mod/gitignore.zig file. While it's a solid implementation of gitignore parsing, I've identified a few areas for improvement:
- A logic bug in pattern initialization that causes incorrect matching for directory-specific patterns (e.g.,
build/). - A memory management issue in the path matching loop due to how
deferis used. - A couple of smaller issues related to API design (
deinit) and test completeness.
I've provided detailed comments and suggestions for each of these points. Addressing them will make the new module more robust and correct.
| var path_parts = std.mem.splitScalar(u8, path, '/'); | ||
| while (path_parts.next()) |part| { | ||
| const c_pattern = try std.fmt.allocPrintSentinel(self.allocator, "{s}", .{pattern_to_match}, 0); | ||
| defer self.allocator.free(c_pattern); | ||
| const c_path = try std.fmt.allocPrintSentinel(self.allocator, "{s}", .{part}, 0); | ||
| defer self.allocator.free(c_path); | ||
| if (c.fnmatch(c_pattern.ptr, c_path.ptr, c.FNM_PATHNAME) == 0) { | ||
| return true; | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There are two issues in this loop:
- Inefficiency:
c_patternis allocated on every iteration, even though its value doesn't change. It should be allocated once before the loop. - Memory Management: The
defer self.allocator.free(c_path)statement is inside awhileloop. In Zig,deferstatements are executed at the end of the function, not the current block or loop iteration. This means that memory forc_pathwill be allocated in each iteration but only freed when thematchesfunction returns, which can lead to high memory usage for paths with many components. You should freec_pathmanually at the end of each iteration or use a block with adeferinside it.
const c_pattern = try std.fmt.allocPrintSentinel(self.allocator, "{s}", .{pattern_to_match}, 0);
defer self.allocator.free(c_pattern);
var path_parts = std.mem.splitScalar(u8, path, '/');
while (path_parts.next()) |part| {
if (try blk: {
const c_path = try std.fmt.allocPrintSentinel(self.allocator, "{s}", .{part}, 0);
defer self.allocator.free(c_path);
break :blk c.fnmatch(c_pattern.ptr, c_path.ptr, c.FNM_PATHNAME) == 0;
}) {
return true;
}
}
src/mod/gitignore.zig
Outdated
| fn deinit(self: *Pattern, allocator: Allocator) void { | ||
| allocator.free(self.pattern); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The deinit function takes an allocator argument, but the Pattern struct already stores its own allocator (self.allocator). The convention in Zig is for deinit to use the allocator stored within the struct, so its signature should be fn deinit(self: *Pattern) void. This avoids redundancy and potential bugs if a different allocator is accidentally passed.
fn deinit(self: *Pattern) void {
self.allocator.free(self.pattern);
}
src/mod/gitignore.zig
Outdated
| // Test build/ | ||
| try testing.expect(try gitignore.shouldIgnore("build", true)); | ||
| try testing.expect(!(try gitignore.shouldIgnore("build", false))); | ||
| // This is incorrect according to gitignore spec. "build/" should only match "build" at the root. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This comment correctly points out a bug in the implementation regarding how patterns ending in / are handled. It's great that you've identified this! To make the tests more robust and prevent regressions, consider adding an assertion that would fail with the current buggy implementation but pass once it's fixed. For example, a pattern like build/ should not match a path like foo/build.
You could add a test case like this:
// This is incorrect according to gitignore spec. "build/" should only match "build" at the root.
try testing.expect(!(try gitignore.shouldIgnore("foo/build", true)));There was a problem hiding this 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 adds a gitignore module to the zigcli toolkit, updates project dependencies and tooling to Zig 0.15.2, and replaces the symlinked README with actual content.
- Implements a new
gitignoremodule with pattern matching support for .gitignore file syntax - Updates Zig version to 0.15.2 and curl dependency to v0.3.2
- Converts README.org from a symlink to a standalone file with actual content
Reviewed changes
Copilot reviewed 9 out of 10 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| src/mod/gitignore.zig | New module implementing .gitignore pattern parsing and path matching with fnmatch support |
| build.zig | Adds gitignore module with libc linking, refactors test step to reuse existing modules |
| build.zig.zon | Version bump to 0.3.1, adds minimum Zig version 0.15.2, updates curl dependency to v0.3.2, removes README.org from paths |
| README.org | Replaces symlink with actual README content including project introduction and links |
| GEMINI.md | Adds comprehensive project documentation for AI assistants covering architecture, build commands, and conventions |
| .tool-versions | Updates Zig version from 0.15.1 to 0.15.2 |
| .github/workflows/*.yml | Removes hardcoded Zig version 0.15.1 to use default from setup-zig action |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
src/mod/gitignore.zig
Outdated
| fn deinit(self: *Pattern, allocator: Allocator) void { | ||
| allocator.free(self.pattern); |
Copilot
AI
Nov 30, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The deinit method should use self.allocator instead of taking an allocator parameter, since the allocator is already stored in the struct during init. This would make the API more consistent and prevent potential mismatches between the allocator used for allocation and deallocation.
| fn deinit(self: *Pattern, allocator: Allocator) void { | |
| allocator.free(self.pattern); | |
| fn deinit(self: *Pattern) void { | |
| self.allocator.free(self.pattern); |
|
|
||
| The project is structured into two main parts: | ||
|
|
||
| * **`src/mod`:** Contains reusable Zig modules (`pretty-table`, `simargs`). |
Copilot
AI
Nov 30, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The module list is incomplete. The PR adds a gitignore module (as seen in build.zig line 51), but the documentation only mentions pretty-table and simargs. Update this to include gitignore in the list of reusable Zig modules.
| * **`src/mod`:** Contains reusable Zig modules (`pretty-table`, `simargs`). | |
| * **`src/mod`:** Contains reusable Zig modules (`pretty-table`, `simargs`, `gitignore`). |
src/mod/gitignore.zig
Outdated
| // Test build/ | ||
| try testing.expect(try gitignore.shouldIgnore("build", true)); | ||
| try testing.expect(!(try gitignore.shouldIgnore("build", false))); | ||
| // This is incorrect according to gitignore spec. "build/" should only match "build" at the root. |
Copilot
AI
Nov 30, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The comment acknowledges that the implementation is incorrect according to the gitignore specification. The pattern "build/" should only match the "build" directory at the repository root, but the current implementation will match "build" anywhere in the tree. This is a known deviation from the gitignore spec that should be addressed or at least clearly documented as a limitation.
This commit introduces .gitignore support to the loc command and ensures
that symbolic links are ignored during directory traversal.
The core of this feature is a new, compliant .gitignore parser implemented
in src/mod/gitignore.zig. This parser has been refactored to use libc.fnmatch
for robust glob pattern matching.
Key changes:
- New 'gitignore' Module in 'src/mod/gitignore.zig'.
- The parser handles negation, directory-only patterns, anchored patterns,
internal slashes, and wildcards ('*', '?', '**').
- Replaced custom glob matcher with 'libc.fnmatch'.
- The 'walk' function in 'loc.zig' now ignores symbolic links.
- Updated 'build.zig' for the new module and its tests.
8c8a56b to
1412411
Compare
|
/gemini review |
There was a problem hiding this 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 primarily focuses on removing a symlinked README.org, adding a new README.org and GEMINI.md for documentation, and introducing a new gitignore parsing module. The changes also include updating the Zig version and dependencies, and modifying the build script to accommodate the new module. While the new gitignore module is a great addition, I've found a critical compilation error in its implementation. Additionally, there are a couple of minor issues: one in the new GEMINI.md documentation file and a typo in a test name within the gitignore module. Addressing these points will improve the quality and correctness of the pull request.
| if (trimmed.len == 0 or trimmed[0] == '#') { | ||
| continue; | ||
| } | ||
| try self.patterns.append(allocator, try Pattern.init(allocator, trimmed)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
|
|
||
| The project is structured into two main parts: | ||
|
|
||
| * **`src/mod`:** Contains reusable Zig modules (`pretty-table`, `simargs`). |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The list of reusable modules under the Architecture section is incomplete. It's missing the newly added gitignore module. Keeping this documentation updated is important for project clarity.
| * **`src/mod`:** Contains reusable Zig modules (`pretty-table`, `simargs`). | |
| * **`src/mod`:** Contains reusable Zig modules (`pretty-table`, `simargs`, `gitignore`). |
src/mod/gitignore.zig
Outdated
| try testing.expect(try gitignore.shouldIgnore("foo/bar/123", false)); | ||
| } | ||
|
|
||
| test "specal case, rule /" { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
Copilot reviewed 9 out of 10 changed files in this pull request and generated 5 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| const path = comptime source.path(); | ||
| const module = b.modules.get(name) orelse b.createModule(.{ | ||
| .root_source_file = b.path(path ++ "/" ++ name ++ ".zig"), | ||
| .target = target, | ||
| }); |
Copilot
AI
Nov 30, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The gitignore module requires linking libc (as specified in line 54), but when building tests through buildTestStep, if the module is not found in b.modules.get(name), it falls back to creating a new module without the link_libc flag. This will cause the gitignore tests to fail since they depend on fnmatch.h. The fallback should either copy the module's properties or always use the module from b.modules for modules that require special configuration.
| const path = comptime source.path(); | |
| const module = b.modules.get(name) orelse b.createModule(.{ | |
| .root_source_file = b.path(path ++ "/" ++ name ++ ".zig"), | |
| .target = target, | |
| }); | |
| const module = b.modules.get(name) orelse @panic("Module '" ++ name ++ "' not found in b.modules. Please ensure it is added with all required configuration before building tests."); |
|
|
||
| The project is structured into two main parts: | ||
|
|
||
| * **`src/mod`:** Contains reusable Zig modules (`pretty-table`, `simargs`). |
Copilot
AI
Nov 30, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The documentation lists only pretty-table and simargs as modules, but the new gitignore module has been added in this PR and should be included in this list.
| * **`src/mod`:** Contains reusable Zig modules (`pretty-table`, `simargs`). | |
| * **`src/mod`:** Contains reusable Zig modules (`pretty-table`, `simargs`, `gitignore`). |
| .negation = negation, | ||
| .is_dir = is_dir, | ||
| .anchored_to_root = anchored_to_root, | ||
| .contains_slash = std.mem.indexOf(u8, p, "/") != null or std.mem.indexOf(u8, p, "**") != null, |
Copilot
AI
Nov 30, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The condition std.mem.indexOf(u8, p, "**") != null in determining contains_slash seems incorrect. The ** wildcard is a gitignore feature, but it's not the same as containing a slash. A pattern like **foo without any slashes should probably not have contains_slash set to true. According to gitignore rules, contains_slash should determine whether the pattern is matched against the full path or just the basename. Consider whether this logic correctly implements the gitignore specification.
| .contains_slash = std.mem.indexOf(u8, p, "/") != null or std.mem.indexOf(u8, p, "**") != null, | |
| .contains_slash = std.mem.indexOf(u8, p, "/") != null, |
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
No description provided.