Skip to content

Add brace expansion and a dotfile option to the Rust glob module - #2708

Open
somewhatabstract wants to merge 14 commits into
claude/jolly-galileo-72zesp-glob-gitignore-modulesfrom
claude/jolly-galileo-72zesp-glob-braces-dot
Open

somewhatabstract wants to merge 14 commits into
claude/jolly-galileo-72zesp-glob-gitignore-modulesfrom
claude/jolly-galileo-72zesp-glob-braces-dot

Conversation

@somewhatabstract

Copy link
Copy Markdown
Owner

Stacked on #2707. These are the two pieces of fast-glob's matching that get_files needs in Rust. Both were written by the clean-room agent and checked against the micromatch build fast-glob 3.3.3 uses, run as a black box.

  • Brace expansion (expand_braces): src/*.{js,ts} becomes src/*.js and src/*.ts. It handles comma lists, nesting and escapes, and braces that don't form a group stay literal.
    • Comparison: across 140,000 random patterns, the expansions match micromatch's in number and order. The text differs only where micromatch removes escape backslashes; we keep them, because each result is still a glob pattern.
    • Ranges: {1..3} is deliberately not expanded and stays literal.
  • Dotfile option (GlobOptions::dot): with dot: false, no wildcard (*, ?, a set, **) matches a . at the start of a name, but a literal . in the pattern does. gitignore keeps dot: true, so its behaviour doesn't change.
    • Agreement with micromatch on 160,000 random cases: 97.8%.
    • Differences a file walk can't hit: micromatch lets x/** match the folder x itself, skips a leading ./ in a pattern, and treats ., .. and empty names specially. The walker and pattern-splitting steps cover the cases that matter.
    • Differences recorded for 12.0.0: micromatch's *.* doesn't match a., and its [!a] doesn't act as a negated set.

__conformance__/fastglob-vectors.json records 172 cases with micromatch's answers, plus a rust reason for each deliberate difference. Tests check both features against it.

expand_braces has no caller yet, so it carries a test-only dead_code expectation. The next PR, which splits include patterns, calls it and removes the expectation.

Part of #2615.

Test plan

  • With dot: true, the glob matcher and IgnoreFile still agree with git on the earlier 500,000 glob cases and 150,000 path decisions. The clean-room agent's own runs: 100,000 glob cases and 110,182 decisions.

Generated by Claude Code

expand_braces expands `{a,b}` groups, including nested and repeated
groups, as fast-glob's micromatch does, keeping braces that form no
group literal and leaving ranges unexpanded. Escapes are kept in the
results so each stays a glob pattern. fastglob-vectors.json records
micromatch's expansions, with the deliberate differences explained.

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
GlobOptions gains `dot`. With it off, in pathname mode, as in
fast-glob, wildcards don't match a `.` that starts a name: only a
literal `.` or a bracket set that isn't negated does, and `**` doesn't
enter hidden folders. With it on, matching is unchanged, and the
gitignore handling turns it on. fastglob-vectors.json gains match cases
from micromatch, with the deliberate differences explained.

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: 3f070b3

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 (69cbdb4) to head (3f070b3).

Additional details and impacted files

Impacted file tree graph

@@                                  Coverage Diff                                   @@
##           claude/jolly-galileo-72zesp-glob-gitignore-modules     #2708     +/-   ##
======================================================================================
  Coverage                                              100.00%   100.00%             
======================================================================================
  Files                                                     112       126     +14     
  Lines                                                    3843      5085   +1242     
  Branches                                                 3051      4293   +1242     
======================================================================================
+ Hits                                                     3843      5085   +1242     
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 Δ
...ates/checksync-core/src/gitignore/relative_path.rs 100.00% <ø> (ø)
...tes/checksync-core/src/glob/advance_any_folders.rs 100.00% <100.00%> (ø)
.../checksync-core/src/glob/advance_glob_positions.rs 100.00% <100.00%> (ø)
...crates/checksync-core/src/glob/advance_one_byte.rs 100.00% <100.00%> (ø)
...ust/crates/checksync-core/src/glob/advance_star.rs 100.00% <100.00%> (ø)
...rates/checksync-core/src/glob/brace_bracket_end.rs 100.00% <100.00%> (ø)
...rates/checksync-core/src/glob/brace_results_fit.rs 100.00% <100.00%> (ø)
...es/checksync-core/src/glob/byte_matcher_matches.rs 100.00% <100.00%> (ø)
...c-core/src/glob/byte_matcher_matches_hidden_dot.rs 100.00% <100.00%> (ø)
...tes/checksync-core/src/glob/combine_brace_texts.rs 100.00% <100.00%> (ø)
... and 14 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 69cbdb4...3f070b3. Read the comment docs.

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

expand_braces now returns a Result. It refuses a pattern over 10,000
bytes, braces nested more than 64 deep, and an expansion of more than
100,000 patterns or 10,000,000 bytes, so no input can overflow the
stack or exhaust memory. A piece with a single text is appended to the
results in place, and runs of ordinary bytes are read as one piece.

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
Remove GlobOptions' Default, whose `dot: false` was easy to pick up by
accident, and work out once, when a pattern is compiled, whether its
wildcards stay off hidden names. Bring the docs that described only
slashes and case up to date.

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
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
Reword the escape divergences to say the results match fast-glob, which
keeps escapes, while micromatch's bare brace expansion drops them. Pin
the differences for parentheses, quotes and `${` in braces, for `[^a]`
next to a leading dot, and for micromatch's `[!.]` and `[[:punct:]]`
quirks.

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
Thread one output budget through the recursion, counting every partial
result and alternative held at once, so the 100,000-pattern and
10,000,000-byte caps hold for the whole call. Combining consumes the old
results as it builds the new ones.

Measure nesting by brace groups only, in one pass, so a pattern with no
group always expands to itself, whatever its braces or length; the
length and nesting limits apply only to patterns with groups.

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
Rewrite the doc summaries that wrapped onto a second line, and define
the plain and pathname test options once in the glob module, so adding
an option means one edit.

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

Copy link
Copy Markdown
Owner Author

Test (windows-latest) is failing on this PR, and the failure is this PR's. A deep-nesting brace expansion test overflows the stack on Windows debug builds (STATUS_STACK_OVERFLOW). Brace expansion recurses once per nesting level, and 64 levels of debug-sized frames don't fit in a Windows thread's stack. A fix is in progress: it replaces the recursion with a heap-allocated work stack, and adds a small-stack test so the problem also shows up on Linux.


Generated by Claude Code

Brace expansion recursed once per level of nested groups, which
overflowed the smaller stacks on Windows. It now keeps its work as
frames on a heap-allocated stack, so its use of the call stack doesn't
grow with nesting, and the 64-level nesting limit is gone. The shared
output budget and the 10,000-byte length limit are unchanged, and a
pattern with no group still expands to itself.

Tests now expand deeply nested groups on a thread with a 64 KB stack,
so a regression shows up on any platform.

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
Check the documented caps of 100,000 patterns and 10,000,000 bytes on
each set of results as it's built. Each such set ends up in the final
output, so this refuses exactly the expansions whose output is too
large, whatever order the groups come in. The shared budget now only
bounds what's held at once, at twice the caps, which an expansion within
the caps never reaches.

Debug builds now catch output released twice. Tests cover the exact cap,
groups before and after a long literal, results left alone on error, and
the budget counting what enclosing groups hold.

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
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
micromatch refuses `{a}` followed by 10,000 bytes, though it has no
brace group; here it's kept as it is.

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