From 3c47deab868e2bcbc57b29e9ace5a2b90913b9f5 Mon Sep 17 00:00:00 2001 From: thekevinbot <248217931+thekevinbot@users.noreply.github.com> Date: Sun, 11 Oct 2026 21:42:23 -0400 Subject: [PATCH 1/5] test: a globstar-led glob matches on its last component; slashed borrows --- packages/rust/src/matcher.rs | 69 +++++++++++++++++++++++++++++++----- packages/rust/src/scanner.rs | 14 ++++++++ 2 files changed, 74 insertions(+), 9 deletions(-) diff --git a/packages/rust/src/matcher.rs b/packages/rust/src/matcher.rs index a6a998ccb..fc9c5d8b9 100644 --- a/packages/rust/src/matcher.rs +++ b/packages/rust/src/matcher.rs @@ -17,23 +17,36 @@ pub enum GlobError { /// byte-oriented, so it is recompiled in Unicode mode over the `/`-separated /// path text. #[derive(Debug, Clone)] -pub(crate) struct Pattern(Regex); +pub(crate) struct Pattern { + path: Regex, + last_component: Option, +} impl Pattern { /// `*` and `?` stop at `/`; `**` crosses it. pub(crate) fn new(glob: &str) -> Result { - let glob = GlobBuilder::new(&expand_posix_classes(glob)) - .literal_separator(true) - .build()?; - let regex = RegexBuilder::new(&unicode_regex(glob.regex())) - .dot_matches_new_line(true) - .build()?; - Ok(Self(regex)) + Ok(Self { + path: compile(glob)?, + last_component: None, + }) } pub(crate) fn is_match(&self, path: &Path) -> bool { - self.0.is_match(&crate::scanner::to_slash(path)) + self.path.is_match(&crate::scanner::slashed(path)) } + + pub(crate) fn matches_last_component_only(&self) -> bool { + self.last_component.is_some() + } +} + +fn compile(glob: &str) -> Result { + let glob = GlobBuilder::new(&expand_posix_classes(glob)) + .literal_separator(true) + .build()?; + Ok(RegexBuilder::new(&unicode_regex(glob.regex())) + .dot_matches_new_line(true) + .build()?) } /// globset spells each non-ASCII character as the `\xNN` escapes of its UTF-8 @@ -193,6 +206,44 @@ mod tests { .collect() } + #[test] + fn a_globstar_led_single_component_glob_matches_on_the_last_component_only() { + assert!( + Pattern::new("**/*.md") + .unwrap() + .matches_last_component_only() + ); + assert!( + Pattern::new("**/{a,b}*.md") + .unwrap() + .matches_last_component_only() + ); + } + + #[test] + fn a_glob_with_a_directory_part_matches_the_whole_path() { + assert!( + !Pattern::new("a/**/*.md") + .unwrap() + .matches_last_component_only() + ); + assert!( + !Pattern::new("**/a/*.md") + .unwrap() + .matches_last_component_only() + ); + assert!(!Pattern::new("*.md").unwrap().matches_last_component_only()); + } + + #[test] + fn a_last_component_pattern_matches_at_any_depth_including_the_top() { + let pattern = Pattern::new("**/[[:digit:]]*.md").unwrap(); + assert!(pattern.is_match(Path::new("1a.md"))); + assert!(pattern.is_match(Path::new("x/.y/2b.md"))); + assert!(!pattern.is_match(Path::new("1/b.md"))); + assert!(!pattern.is_match(Path::new("1a.md/b.txt"))); + } + #[test] fn match_all_returns_table_for_matching_glob() { let matcher = TableMatcher::new(&[("*.csv", "data")], &[]).unwrap(); diff --git a/packages/rust/src/scanner.rs b/packages/rust/src/scanner.rs index d2f377cbf..58d015cc0 100644 --- a/packages/rust/src/scanner.rs +++ b/packages/rust/src/scanner.rs @@ -3,6 +3,7 @@ use crate::matcher::{GlobError, Pattern, TableMatcher}; use crate::tree_walk::{Step, walk_in_order}; use ignore::Match; use ignore::gitignore::{Gitignore, GitignoreBuilder}; +use std::borrow::Cow; use std::ffi::OsStr; use std::fs; use std::path::{Path, PathBuf}; @@ -15,6 +16,10 @@ pub(crate) fn to_slash(path: &Path) -> String { with_slashes(&path.to_string_lossy(), std::path::MAIN_SEPARATOR) } +pub(crate) fn slashed(path: &Path) -> Cow<'_, str> { + Cow::Owned(to_slash(path)) +} + fn with_slashes(path: &str, native: char) -> String { if native == '/' { return path.to_owned(); @@ -1222,6 +1227,15 @@ mod tests { assert_eq!(with_slashes(r"dir/a\b.txt", '/'), r"dir/a\b.txt"); } + #[cfg(unix)] + #[test] + fn slashed_borrows_a_native_unix_path() { + assert!(matches!( + slashed(Path::new("docs/nested/a.md")), + Cow::Borrowed("docs/nested/a.md") + )); + } + #[test] fn to_slash_keeps_a_native_unix_path() { assert_eq!(to_slash(Path::new("docs/nested/a.md")), "docs/nested/a.md"); From 6a704d60f7963656097b921100e1f3aaefba6bbd Mon Sep 17 00:00:00 2001 From: thekevinbot <248217931+thekevinbot@users.noreply.github.com> Date: Sun, 11 Oct 2026 21:47:36 -0400 Subject: [PATCH 2/5] perf: match a globstar-led glob on its last component, without a path copy --- .../2026-10-11-faster-glob-matching.md | 1 + .../2026-10-11-faster-glob-matching.md | 21 +++++++++++++++++++ packages/rust/src/matcher.rs | 11 +++++++++- packages/rust/src/scanner.rs | 6 +++++- 4 files changed, 37 insertions(+), 2 deletions(-) create mode 100644 packages/rust/changelog.d/2026-10-11-faster-glob-matching.md create mode 100644 packages/rust/migrations.d/2026-10-11-faster-glob-matching.md diff --git a/packages/rust/changelog.d/2026-10-11-faster-glob-matching.md b/packages/rust/changelog.d/2026-10-11-faster-glob-matching.md new file mode 100644 index 000000000..26ecc04f4 --- /dev/null +++ b/packages/rust/changelog.d/2026-10-11-faster-glob-matching.md @@ -0,0 +1 @@ +**Changed** Matcher globs under `**` list sooner: a `**/`-led pattern is tested against the file name alone, and a path is no longer copied to be matched. diff --git a/packages/rust/migrations.d/2026-10-11-faster-glob-matching.md b/packages/rust/migrations.d/2026-10-11-faster-glob-matching.md new file mode 100644 index 000000000..11438480f --- /dev/null +++ b/packages/rust/migrations.d/2026-10-11-faster-glob-matching.md @@ -0,0 +1,21 @@ +### Faster glob matching + +#### Summary + +Glob matching under `**` copies fewer paths. Results and ordering are unchanged; nothing breaks. + +#### Required changes + +_None._ + +#### Deprecations removed + +_None._ + +#### Behavior changes without code changes + +_None._ + +#### Verification + +Run `dirsql --help`; the CLI starts as before. diff --git a/packages/rust/src/matcher.rs b/packages/rust/src/matcher.rs index fc9c5d8b9..bd5e03118 100644 --- a/packages/rust/src/matcher.rs +++ b/packages/rust/src/matcher.rs @@ -19,19 +19,28 @@ pub enum GlobError { #[derive(Debug, Clone)] pub(crate) struct Pattern { path: Regex, + /// For a `**/`-led glob whose remainder is one component: that remainder, + /// which a path matches exactly when its last component does. last_component: Option, } impl Pattern { /// `*` and `?` stop at `/`; `**` crosses it. pub(crate) fn new(glob: &str) -> Result { + let last_component = match glob.strip_prefix("**/") { + Some(rest) if !rest.is_empty() && !rest.contains('/') => Some(compile(rest)?), + _ => None, + }; Ok(Self { path: compile(glob)?, - last_component: None, + last_component, }) } pub(crate) fn is_match(&self, path: &Path) -> bool { + if let Some((regex, name)) = self.last_component.as_ref().zip(path.file_name()) { + return regex.is_match(&name.to_string_lossy()); + } self.path.is_match(&crate::scanner::slashed(path)) } diff --git a/packages/rust/src/scanner.rs b/packages/rust/src/scanner.rs index 58d015cc0..56d697663 100644 --- a/packages/rust/src/scanner.rs +++ b/packages/rust/src/scanner.rs @@ -17,7 +17,11 @@ pub(crate) fn to_slash(path: &Path) -> String { } pub(crate) fn slashed(path: &Path) -> Cow<'_, str> { - Cow::Owned(to_slash(path)) + let text = path.to_string_lossy(); + if std::path::MAIN_SEPARATOR == '/' { + return text; + } + Cow::Owned(with_slashes(&text, std::path::MAIN_SEPARATOR)) } fn with_slashes(path: &str, native: char) -> String { From d8c9441c314abcf613ba761c0d84ffa5b953d6d6 Mon Sep 17 00:00:00 2001 From: thekevinbot <248217931+thekevinbot@users.noreply.github.com> Date: Sun, 11 Oct 2026 22:23:22 -0400 Subject: [PATCH 3/5] e2e attestation for bot/speed-walk-components-1011 --- .../e2e-attestations/bot-speed-walk-components-1011.json | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 packages/python/e2e-attestations/bot-speed-walk-components-1011.json diff --git a/packages/python/e2e-attestations/bot-speed-walk-components-1011.json b/packages/python/e2e-attestations/bot-speed-walk-components-1011.json new file mode 100644 index 000000000..f317f1f6a --- /dev/null +++ b/packages/python/e2e-attestations/bot-speed-walk-components-1011.json @@ -0,0 +1,7 @@ +{ + "command": "uv run python -m pytest tests/e2e/glob_speed_depth_bound_test.py tests/e2e/glob_speed_one_level_test.py tests/e2e/glob_speed_recursive_test.py tests/e2e/glob_speed_symlink_test.py tests/e2e/glob_speed_dotfiles_test.py tests/e2e/glob_speed_node_modules_test.py tests/e2e/glob_speed_matchers_test.py tests/e2e/glob_speed_gitignore_test.py tests/e2e/stat_speed_test.py tests/e2e/query_contract_test.py -x -q", + "ran_at": 1791771802, + "exit_code": 0, + "commit": "6a704d60f7963656097b921100e1f3aaefba6bbd", + "branch": "bot/speed-walk-components-1011" +} From b6b40ce73b0b28735857aa62bb640e9ce4377eda Mon Sep 17 00:00:00 2001 From: thekevinbot <248217931+thekevinbot@users.noreply.github.com> Date: Sun, 11 Oct 2026 22:24:30 -0400 Subject: [PATCH 4/5] e2e attestation for bot/speed-walk-components-1011 --- .../e2e-attestations/bot-speed-walk-components-1011.json | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 packages/ts/e2e-attestations/bot-speed-walk-components-1011.json diff --git a/packages/ts/e2e-attestations/bot-speed-walk-components-1011.json b/packages/ts/e2e-attestations/bot-speed-walk-components-1011.json new file mode 100644 index 000000000..11fb07e6d --- /dev/null +++ b/packages/ts/e2e-attestations/bot-speed-walk-components-1011.json @@ -0,0 +1,7 @@ +{ + "command": "pnpm test:e2e", + "ran_at": 1791771870, + "exit_code": 0, + "commit": "d8c9441c314abcf613ba761c0d84ffa5b953d6d6", + "branch": "bot/speed-walk-components-1011" +} From 94610d17a61cac3a048a17e114cb6dc01143b79b Mon Sep 17 00:00:00 2001 From: thekevinbot <248217931+thekevinbot@users.noreply.github.com> Date: Sun, 11 Oct 2026 22:37:41 -0400 Subject: [PATCH 5/5] perf: keep the last-component probe test-only --- packages/rust/src/matcher.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/rust/src/matcher.rs b/packages/rust/src/matcher.rs index bd5e03118..28bf17389 100644 --- a/packages/rust/src/matcher.rs +++ b/packages/rust/src/matcher.rs @@ -44,6 +44,7 @@ impl Pattern { self.path.is_match(&crate::scanner::slashed(path)) } + #[cfg(test)] pub(crate) fn matches_last_component_only(&self) -> bool { self.last_component.is_some() }