From b538410c816858d1cb634681351a99c35f6542f3 Mon Sep 17 00:00:00 2001 From: Nik Everett Date: Thu, 6 Aug 2026 18:20:41 -0400 Subject: [PATCH] fix(rust): skip scanning dirs that can't match Modifies the `find_oaths` rust internals to skip scanning directories that can't possibly can't match. This speeds up my usage which wants to scan the repo root to pick up oaths from the README. --- rust/runner/src/discovery.rs | 122 ++++++++++++++++++++++++++++++++++- rust/runner/tests/runner.rs | 50 ++++++++++++++ 2 files changed, 169 insertions(+), 3 deletions(-) diff --git a/rust/runner/src/discovery.rs b/rust/runner/src/discovery.rs index e2d92b27..c405c9d7 100644 --- a/rust/runner/src/discovery.rs +++ b/rust/runner/src/discovery.rs @@ -60,14 +60,67 @@ fn rel_posix(path: &Path, root: &Path) -> String { .join("/") } -fn walk(dir: &Path, out: &mut Vec) { +/// Returns the literal directory prefix of a glob — the path segment up to the +/// last `/` before the first wildcard (`*` or `?`). Used to prune subtrees that +/// cannot possibly match. +/// +/// Examples: +/// - `"README.md"` → `""` (literal file at root; no dir prefix) +/// - `"docs/loop.md"` → `"docs"` +/// - `"src/**/*.md"` → `"src"` +/// - `"a/b/c/*.md"` → `"a/b/c"` +/// - `"**/*.md"` → `""` (starts with wildcard; no literal dir) +fn glob_literal_dir(glob: &str) -> &str { + let wild = glob.find(['*', '?']).unwrap_or(glob.len()); + match glob[..wild].rfind('/') { + Some(pos) => &glob[..pos], + None => "", + } +} + +/// Returns `true` if any file under directory `dir_rel` (relative to root) +/// could potentially match one of the `include` globs. +/// +/// A directory is prunable when every include glob is anchored to a different +/// part of the tree — e.g. `docs/loop.md` cannot match anything under `target/`. +/// Globs that start with a wildcard (`**/*.md`, `*.md`) are never prunable. +fn dir_could_match_include(dir_rel: &str, include: &[String]) -> bool { + include.iter().any(|g| { + let lit = glob_literal_dir(g); + if lit.is_empty() { + // Glob either starts with a wildcard (could match anywhere) or is a + // bare filename like "README.md" that only lives at the root. + // Prune subdirectories for bare root filenames; keep for wildcards. + g.starts_with(['*', '?']) + } else { + // lit is something like "docs" or "src/components". + // Keep if dir_rel is heading toward lit, is lit, or is already inside lit. + lit == dir_rel + || lit.starts_with(&format!("{dir_rel}/")) // target is deeper + || dir_rel.starts_with(&format!("{lit}/")) // we're inside target + } + }) +} + +/// Returns `true` if the directory at `dir_rel` is broadly excluded — i.e. +/// any probe file under it (`dir_rel/x`) matches an exclude glob. +fn dir_is_excluded(dir_rel: &str, exclude: &[String]) -> bool { + let probe = format!("{dir_rel}/x"); + exclude.iter().any(|g| glob_to_regex(g).is_match(&probe)) +} + +fn walk(dir: &Path, root: &Path, include: &[String], exclude: &[String], out: &mut Vec) { let Ok(entries) = std::fs::read_dir(dir) else { return; }; for entry in entries.flatten() { let path = entry.path(); if path.is_dir() { - walk(&path, out); + let child_rel = rel_posix(&path, root); + if dir_could_match_include(&child_rel, include) && !dir_is_excluded(&child_rel, exclude) + { + walk(&path, root, include, exclude, out); + } } else if path.is_file() { out.push(path); } @@ -83,8 +136,11 @@ pub fn match_oath(path: &Path, include: &[String], exclude: &[String], root: &Pa /// Files under `root` matching any `docs.include` glob and no `docs.exclude`, /// sorted. pub fn find_oaths(config: &Config, root: &Path) -> Vec { + if config.docs_include.is_empty() { + return Vec::new(); + } let mut files = Vec::new(); - walk(root, &mut files); + walk(root, root, &config.docs_include, &config.docs_exclude, &mut files); let mut kept: Vec = files .into_iter() .filter(|p| match_oath(p, &config.docs_include, &config.docs_exclude, root)) @@ -92,3 +148,63 @@ pub fn find_oaths(config: &Config, root: &Path) -> Vec { kept.sort(); kept } + +#[cfg(test)] +mod tests { + use super::*; + use std::path::PathBuf; + + fn tmp(suffix: &str) -> PathBuf { + let dir = + std::env::temp_dir().join(format!("varar-discovery-{}-{suffix}", std::process::id())); + let _ = std::fs::remove_dir_all(&dir); + std::fs::create_dir_all(&dir).unwrap(); + dir + } + + /// `walk` itself must not collect files from a directory that the literal + /// include prefix excludes. The integration-level `find_oaths` tests only + /// verify the final output; a post-walk filter would hide a missing prune + /// there. This test calls `walk` directly so there is no filter to rescue + /// a bad walk. + #[test] + fn walk_skips_dir_outside_literal_include_prefix() { + let root = tmp("walk-prune"); + std::fs::write(root.join("README.md"), "x").unwrap(); + std::fs::create_dir_all(root.join("docs")).unwrap(); + std::fs::write(root.join("docs/loop.md"), "x").unwrap(); + std::fs::create_dir_all(root.join("target/debug")).unwrap(); + std::fs::write(root.join("target/debug/not_a_doc.md"), "x").unwrap(); + + let include = vec!["README.md".to_string(), "docs/loop.md".to_string()]; + let exclude: Vec = vec![]; + let mut out = Vec::new(); + walk(&root, &root, &include, &exclude, &mut out); + + assert!( + !out.iter().any(|p| p.ends_with("not_a_doc.md")), + "walk should not have entered target/: {out:?}" + ); + assert_eq!(out.len(), 2, "expected README.md + docs/loop.md, got {out:?}"); + } + + /// Same guarantee for exclude-based pruning. + #[test] + fn walk_skips_excluded_dir() { + let root = tmp("walk-excl"); + std::fs::write(root.join("good.md"), "x").unwrap(); + std::fs::create_dir_all(root.join("skip/nested")).unwrap(); + std::fs::write(root.join("skip/nested/bad.md"), "x").unwrap(); + + let include = vec!["**/*.md".to_string()]; + let exclude = vec!["skip/**".to_string()]; + let mut out = Vec::new(); + walk(&root, &root, &include, &exclude, &mut out); + + assert!( + !out.iter().any(|p| p.ends_with("bad.md")), + "walk should not have entered skip/: {out:?}" + ); + assert_eq!(out.len(), 1, "expected only good.md, got {out:?}"); + } +} diff --git a/rust/runner/tests/runner.rs b/rust/runner/tests/runner.rs index 58cff1d5..e2e9b500 100644 --- a/rust/runner/tests/runner.rs +++ b/rust/runner/tests/runner.rs @@ -70,6 +70,56 @@ fn find_oaths_honours_include_and_exclude() { assert_eq!(find_oaths(&recursive, &root).len(), 2); // a.md + sub/b.md } +/// Simulates a project that wants to scan README.md an and a tiny `docs/` tree +/// but has a huge `target/`. +#[test] +fn find_oaths_prunes_dirs_outside_literal_include_prefix() { + let root = tmp("prune"); + std::fs::write(root.join("README.md"), "x").unwrap(); + std::fs::create_dir_all(root.join("docs")).unwrap(); + std::fs::write(root.join("docs/loop.md"), "x").unwrap(); + // A decoy directory that should be pruned: not reachable from either glob. + std::fs::create_dir_all(root.join("target/debug")).unwrap(); + std::fs::write(root.join("target/debug/not_a_doc.md"), "x").unwrap(); + + let config = Config { + docs_include: vec!["README.md".to_string(), "docs/loop.md".to_string()], + docs_exclude: vec![], + ..Default::default() + }; + let paths = find_oaths(&config, &root); + let names: Vec = paths + .iter() + .map(|p| { + p.strip_prefix(&root) + .unwrap() + .to_string_lossy() + .into_owned() + }) + .collect(); + // target/ is unreachable from both globs and must not appear. + assert_eq!(names, vec!["README.md", "docs/loop.md"]); +} + +#[test] +fn find_oaths_prunes_dirs_matching_exclude() { + let root = tmp("excl-prune"); + std::fs::write(root.join("good.md"), "x").unwrap(); + std::fs::create_dir_all(root.join("skip/nested")).unwrap(); + std::fs::write(root.join("skip/nested/bad.md"), "x").unwrap(); + + let config = Config { + docs_include: vec!["**/*.md".to_string()], + docs_exclude: vec!["skip/**".to_string()], + ..Default::default() + }; + let names: Vec = find_oaths(&config, &root) + .iter() + .map(|p| p.file_name().unwrap().to_string_lossy().into_owned()) + .collect(); + assert_eq!(names, vec!["good.md"]); +} + #[test] fn baseline_store_round_trips_and_reconcile_writes_lock() { let root = tmp("drift");