From ae45aca2958ce60199e045d63881acf1594e7eeb Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Wed, 12 Aug 2026 15:43:11 +0200 Subject: [PATCH] always include concrete file sources, even default-ignored ones MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `@source ".env"` scanned nothing: the pattern walker only bypasses the default file rules when the glob pins an extension, and Rust's `Path::extension()` returns `None` for leading-dot names like `.env`, so the file fell through to the default rules — which ignore `.env` — and was dropped. A pattern without any wildcards names a concrete file and is the most explicit source there is, so it now always bypasses the default file rules, extension or not. Extension-pinning globs behave as before, and globs that pin nothing (e.g. `blog/*/**/*`) still apply the default rules. --- crates/oxide/src/scanner/mod.rs | 36 ++++++++++++++++++++++----------- 1 file changed, 24 insertions(+), 12 deletions(-) diff --git a/crates/oxide/src/scanner/mod.rs b/crates/oxide/src/scanner/mod.rs index 2d9f58f77..3f87bc0a1 100644 --- a/crates/oxide/src/scanner/mod.rs +++ b/crates/oxide/src/scanner/mod.rs @@ -46,10 +46,10 @@ use tracing::event; // or a git ignored `dist/`; `@source "./dist/**/*.html"` does descend into `dist/`). // Individual *files* matching the glob are always included, even when git ignored — you were // explicit about wanting files of that shape (`@source "git-ignored.html"` and -// `@source "*.styl"` work). Extensions that are ignored by default are only included when the -// glob pins an extension (`@source "logo.{jpg,png}"`, `@source "do-include-me.bin"`), an -// extension-less glob like `@source "some/folder/**/*"`… is `Auto`, and e.g. -// `@source "blog/*/post/**/*"` still applies the default extension rules. +// `@source "*.styl"` work). Files that are ignored by default are only included when the +// pattern is explicit about them: it names a concrete file (`@source "do-include-me.bin"`, +// `@source ".env"`) or pins an extension (`@source "logo.{jpg,png}"`). A glob that does +// neither (e.g. `@source "blog/*/post/**/*"`) still applies the default file rules. // // - `Ignored`: `@source not "…"` — excludes matching files/folders, even when a `.gitignore` // or another `@source` allows them. @@ -902,9 +902,11 @@ struct PatternRule { /// The glob pattern, relative to the walker's base, e.g. `/ba*/*.html` pattern: String, - /// Whether the pattern pins a specific extension (e.g. `*.html` or `logo.png`). Patterns - /// that don't (e.g. `blog/*/**/*`) re-apply the default extension rules. - pins_extension: bool, + /// Whether the pattern is explicit enough to bypass the default file rules: it names a + /// concrete file (e.g. `.env` or `do-include-me.bin`) or pins a specific extension (e.g. + /// `*.html` or `logo.png`). Patterns that don't (e.g. `blog/*/**/*`) re-apply the default + /// file rules. + bypasses_default_file_rules: bool, } /// An `@source not` directive, together with its position. @@ -1027,7 +1029,7 @@ fn create_pattern_walkers(sources: &Sources) -> Vec { .push(PatternRule { idx, pattern: pattern.clone(), - pins_extension: pattern_pins_extension(pattern), + bypasses_default_file_rules: pattern_bypasses_default_file_rules(pattern), }); } @@ -1112,7 +1114,7 @@ fn create_pattern_walkers(sources: &Sources) -> Vec { && !filter_nots .iter() .any(|not| not.idx > p.idx && not.matches(path)) - && (p.pins_extension || !is_ignored_by_default_file_rules(path)) + && (p.bypasses_default_file_rules || !is_ignored_by_default_file_rules(path)) }) }); @@ -1121,9 +1123,19 @@ fn create_pattern_walkers(sources: &Sources) -> Vec { .collect() } -/// Whether a pattern pins a specific extension, e.g. `/*.html` or `/logo.png`. Patterns that -/// don't (e.g. `/blog/*/**/*`) keep the default extension rules applied. -fn pattern_pins_extension(pattern: &str) -> bool { +/// Whether a pattern is explicit enough to bypass the default file rules. +/// +/// A pattern without any wildcards names a concrete file, e.g. `/.env` or +/// `/do-include-me.bin` — you asked for exactly this file, so the default rules never apply. +/// A pattern that pins a specific extension, e.g. `/*.html` or `/**/*.bin`, bypasses them as +/// well. Patterns that do neither (e.g. `/blog/*/**/*`) keep the default file rules applied. +fn pattern_bypasses_default_file_rules(pattern: &str) -> bool { + // Concrete file, no wildcards (braces have already been expanded away) + if !pattern.contains(['*', '?', '[']) { + return true; + } + + // Pinned extension match Path::new(pattern).extension().and_then(|ext| ext.to_str()) { Some(ext) => !ext.contains(['*', '?', '[']), None => false,