Skip to content

Scan git-ignored directories inside explicit glob sources - #20405

Closed
61021 wants to merge 2 commits into
tailwindlabs:mainfrom
61021:fix/source-globs-through-gitignored-dirs
Closed

Scan git-ignored directories inside explicit glob sources#20405
61021 wants to merge 2 commits into
tailwindlabs:mainfrom
61021:fix/source-globs-through-gitignored-dirs

Conversation

@61021

@61021 61021 commented Aug 10, 2026

Copy link
Copy Markdown

Fixes #18870

Summary

@source "../storage/app/private/cms_content/**/*.html" finds nothing when the target directory is ignored from the inside, i.e. a .gitignore containing * + !.gitignore, the way Laravel ships every storage/ directory. The reporter's testcase also shows the two shapes that already work: pointing @source at the exact file, and the same glob once the nested .gitignore is deleted.

The asymmetry is in how the walker treats files vs. directories. Glob sources emit an !*.html rule, and manually registered rules rank above on-disk .gitignore files, so a git-ignored file matched by an explicit glob is scanned today (this PR adds a test pinning that). That rule can't save anything inside a git-ignored directory though: * matches the child directories themselves, the walker prunes them, and no file rule ever runs. An ignore in an ancestor .gitignore (say /storage in the project root) doesn't trigger the bug, because the normalized source base starts the walk inside the ignored tree. That's what makes the failure look random unless you know where the ignore line lives.

Glob sources now also register a directory re-include (!*/) for their base, ordered so that everything that should still win, does:

  • registered before the auto source detection rules, so node_modules etc. inside a glob source stay pruned unless targeted explicitly (the concern raised in the issue about accidentally walking huge trees)
  • registered before the @source rules, so @source not still excludes directories inside a re-opened source
  • on-disk .gitignore files rank below manually registered rules, which is what lets the walker descend

Bare directory sources (@source "../storage/app/private/cms_content") are unchanged: they emit no file rules, so .gitignore semantics inside them still apply, as pinned by it_respects_gitignore_in_workspace_root.

Test plan

  • Five new tests in crates/oxide/tests/scanner.rs: the issue's Laravel shape, a glob through an ancestor-ignored directory, git-ignored files matched by a glob (pins existing behavior), node_modules staying pruned inside a re-opened source, and @source not inside one.
  • cargo test --workspace passes.
  • Reproduced against the reporter's testcase layout (CSS entry in src/, relative ../storage/... sources, source(none)): before this change the scanner returns no files from cms_content; with it, the pre-rendered HTML is scanned and its candidates extracted.

[ci-all]

The extension rule emitted for glob sources already whitelists matching
files against .gitignore, but it cannot stop the walker from pruning
git-ignored directories on the way to those files. Directories that
ignore themselves with '*' (like Laravel's storage/ directories) were
never entered, so explicit sources like
'@source "../storage/app/private/cms_content/**/*.html"' found nothing.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@61021
61021 requested a review from a team as a code owner August 10, 2026 13:45
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: b94091c2-3c0f-4b4c-bc0d-fe20a66e4638

📥 Commits

Reviewing files that changed from the base of the PR and between 46df7ee and ea26e6c.

📒 Files selected for processing (3)
  • CHANGELOG.md
  • crates/oxide/src/scanner/mod.rs
  • crates/oxide/tests/scanner.rs

Walkthrough

The scanner now tracks extension-based glob-source bases and reopens ignored directories beneath those bases during traversal. Automatic directory ignores and @source not exclusions remain effective. New tests cover ignored files, nested ignored directories, self-ignoring directories, permanently auto-ignored directories, and explicit exclusions. The changelog documents the fix.

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely describes the main fix: scanning git-ignored directories within explicit glob sources.
Description check ✅ Passed The description directly explains the bug, implementation, scope, tests, and linked issue.
Linked Issues check ✅ Passed The changes address issue #18870 by scanning matching files inside git-ignored directories while preserving relevant exclusion behavior.
Out of Scope Changes check ✅ Passed The changes are limited to the scanner fix, focused tests, and a related changelog entry.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@greptile-apps

greptile-apps Bot commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

Confidence Score: 5/5

The PR appears safe to merge, with no concrete correctness or security defects identified.

The new directory whitelist has the intended precedence over on-disk gitignore rules, while later built-in and explicit source exclusions continue to prune protected directories.

Reviews (1): Last reviewed commit: "Add changelog entry" | Re-trigger Greptile

Comment on lines +848 to +857
// Re-open directories under glob sources. These rules are registered before
// the auto source detection rules and the `@source` rules so both still take
// precedence, e.g. `node_modules` inside a glob source stays pruned unless
// targeted explicitly.
for base in reopened_bases {
let mut ignore_builder = GitignoreBuilder::new(base);
ignore_builder.add_line(None, "!*/").unwrap();
let ignore = ignore_builder.build().unwrap();
builder.add_gitignore(ignore);
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This part opens a new can of worms that I only want to deal with if we can prove that there are issues that can't be solved by simpler glob patterns. More info: #18870 (comment)

In this case, let's say you have a setup like this:

# .gitignore
dist/

Then you have your CSS file that contains:

@source './src';
@source './src/**/*.html';

If you then, for any reason, have a file like ./src/dist/index.js then this file will be scanned because the src/ folder was re-opened. It was re-opened for the .html use case, not the .js use case.

@RobinMalfait

Copy link
Copy Markdown
Member

Thanks for the PR!

Were you trying to fix #18870, or did you run into an issue yourself with this kind of setup? If you ran into this issue yourself, can you open an issue with a minimal reproduction repo attached so we can take a look?

This PR does improve some of the bugs involved, but it introduces new bugs as well. I just want to make sure that if there is a bug that can't be solved in user land with a different @source pattern, that we solve it properly without introducing new bugs or a lot of new complexity.

Thanks again!

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.

Include glob path fails when path is also gitignored

2 participants