Skip to content

Replace the Rust gitignore handling with a clean-room implementation - #2705

Merged
somewhatabstract merged 9 commits into
claude/jolly-galileo-72zesp-cleanroom-globfrom
claude/jolly-galileo-72zesp-cleanroom-gitignore
Nov 12, 2026
Merged

somewhatabstract merged 9 commits into
claude/jolly-galileo-72zesp-cleanroom-globfrom
claude/jolly-galileo-72zesp-cleanroom-gitignore

Conversation

@somewhatabstract

@somewhatabstract somewhatabstract commented Nov 12, 2026 •

Copy link
Copy Markdown
Owner

Stacked on #2704. This finishes removing the Rust code that was ported from git. read_gitignore_patterns.rs, parse_gitignore_pattern.rs, trim_gitignore_trailing_spaces.rs, gitignore_pattern_matches.rs and the matching logic in IgnoreFile were line-for-line ports of functions from git's dir.c, which is GPL v2.

The same clean-room agent that wrote the glob matcher wrote the replacement behind IgnoreFile's unchanged public API. It worked only from the documented gitignore behaviour, our own code and tests, and git 2.56 run as a black box.

  • IgnoreFile: gitignore_parse and gitignore_parse_line read the file. They handle comments, escapes, trailing spaces, CRLF, a BOM, NUL bytes, negation, and folder-only and anchored patterns. gitignore_decide_path decides as git does: a file inside an ignored folder stays ignored, and otherwise the last matching pattern decides.

  • Speed: each pattern is compiled once with compile_glob and stored. Matching then:

    • stops as soon as no match is possible;
    • updates one list in place instead of allocating one for each token;
    • works out a path's last name once rather than once per pattern.

    These address the review of Replace the Rust glob matcher with a clean-room implementation #2704. glob_matches is gone, since nothing outside the tests called it.

  • Pinned to git: __conformance__/glob-vectors.json holds 756 cases with git's answers. A test inside the crate checks the compiled matcher against every case. Nothing new is public.

Part of #2615.

Test plan

  • The clean-room agent's comparisons with git 2.56: 378,114 path decisions and 600,000 glob cases, after the review fixes. The random ignore files now often put NUL bytes next to carriage returns, which is the case the first review caught.
  • My own comparisons: 150,000 path decisions through the real IgnoreFile API, with files on disk, and 500,000 glob cases. Every one agreed.
  • Existing tests: ignore-vectors.json and the IgnoreFile and is_allowed_by_ignore_files tests still pass.

🤖 Generated with Claude Code

https://claude.ai/code/session_01QjLhVH1NnUc7MzCSYLd8Yn

Read .gitignore-style files (comments, blank lines, escapes, trailing
spaces, CRLF, a byte order mark, negation, folder-only and anchored
patterns) and decide about paths as git does: a file in an ignored
folder stays ignored, and otherwise the last matching pattern decides.
Paths are matched by their raw bytes on Unix, ignoring ASCII case.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QjLhVH1NnUc7MzCSYLd8Yn

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QjLhVH1NnUc7MzCSYLd8Yn
Split glob matching into compile_glob, which parses a pattern once, and
compiled_glob_matches, which matches a compiled pattern against a text.
IgnoreFile now stores compiled patterns, dropping any that can never
match. Matching stops as soon as no text position is reachable, and
updates one position list in place instead of allocating one per token.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QjLhVH1NnUc7MzCSYLd8Yn

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QjLhVH1NnUc7MzCSYLd8Yn
Add glob-vectors.json, 756 cases recorded from git's own glob matcher
covering each feature and the surprising corners (class detection,
unknown classes, backwards ranges with case folding, ** around escaped
slashes, [:space:], malformed brackets), and an integration test that
checks glob_matches against them. glob_matches and GlobOptions become
public so the test can reach them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QjLhVH1NnUc7MzCSYLd8Yn

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QjLhVH1NnUc7MzCSYLd8Yn
@changeset-bot

changeset-bot Bot commented Nov 12, 2026 •

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: b0439e4

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 0 packages

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@codecov

codecov Bot commented Nov 12, 2026

Copy link
Copy Markdown

Bundle Report

Bundle size has no change ✅

@codecov

codecov Bot commented Nov 12, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 100.00%. Comparing base (24f0939) to head (b0439e4).

Additional details and impacted files

Impacted file tree graph

@@                              Coverage Diff                              @@
##           claude/jolly-galileo-72zesp-cleanroom-glob     #2705    +/-   ##
=============================================================================
  Coverage                                      100.00%   100.00%            
=============================================================================
  Files                                             106       111     +5     
  Lines                                            3633      3830   +197     
  Branches                                         2840      3038   +198     
=============================================================================
+ Hits                                             3633      3830   +197     
Flag Coverage Δ
rust 100.00% <100.00%> (ø)
typescript 100.00% <ø> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
...t/crates/checksync-core/src/advance_any_folders.rs 100.00% <100.00%> (ø)
...rates/checksync-core/src/advance_glob_positions.rs 100.00% <100.00%> (ø)
rust/crates/checksync-core/src/advance_one_byte.rs 100.00% <100.00%> (ø)
rust/crates/checksync-core/src/advance_star.rs 100.00% <100.00%> (ø)
rust/crates/checksync-core/src/bytes_before_nul.rs 100.00% <100.00%> (ø)
rust/crates/checksync-core/src/compile_glob.rs 100.00% <100.00%> (ø)
...crates/checksync-core/src/compiled_glob_matches.rs 100.00% <100.00%> (ø)
...crates/checksync-core/src/gitignore_decide_path.rs 100.00% <100.00%> (ø)
.../crates/checksync-core/src/gitignore_last_match.rs 100.00% <100.00%> (ø)
rust/crates/checksync-core/src/gitignore_parse.rs 100.00% <100.00%> (ø)
... and 6 more

Continue to review full report in Codecov by Harness.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 24f0939...b0439e4. Read the comment docs.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

git drops only a carriage return directly before the line feed, so for
a line like `a\r\0x` the pattern stays `a\r`. Drop the carriage return
first and then cut at the NUL, and pin the order with tests. Also pin
how a leading slash combines with `**`.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QjLhVH1NnUc7MzCSYLd8Yn

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QjLhVH1NnUc7MzCSYLd8Yn
Nothing outside the crate uses the glob matcher, so check the vectors
from a unit test of compiled_glob_matches instead of an integration
test, make GlobOptions crate-private again, and remove glob_matches,
which only that integration test used.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QjLhVH1NnUc7MzCSYLd8Yn

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QjLhVH1NnUc7MzCSYLd8Yn
Collect the mismatching cases with a filter that runs on every case,
rather than formatting them in a closure that only runs on a mismatch.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QjLhVH1NnUc7MzCSYLd8Yn

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QjLhVH1NnUc7MzCSYLd8Yn
gitignore_last_match finds the last name in a path once and passes it to
gitignore_pattern_matches, instead of every unanchored pattern finding it
again.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QjLhVH1NnUc7MzCSYLd8Yn

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QjLhVH1NnUc7MzCSYLd8Yn
Show each mismatching glob vector's expected result, share one vector
table loader, wrap an over-long doc comment, and check in debug builds
that gitignore_pattern_matches gets the path's real last name.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QjLhVH1NnUc7MzCSYLd8Yn

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QjLhVH1NnUc7MzCSYLd8Yn

@somewhatabstract somewhatabstract left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

LGTM

@somewhatabstract
somewhatabstract merged commit 2131c61 into main Nov 12, 2026
20 checks passed
@somewhatabstract
somewhatabstract deleted the claude/jolly-galileo-72zesp-cleanroom-gitignore branch November 12, 2026 19:56
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