@ 261027.5

261027.1 · 9d07a922605313dc47b78b802c7b0616fd76d409

ignore: reuse incremental matchers for directories without rules

When a directory adds no ignore rules or repository boundary, we now reuse the parent matcher. Incremental matching already tracks visited directories separately, so adding an ignore file later preserves the existing cached snapshot.

On Linux x86-64 against 3fce3b5, warm incremental replay over Ruff and uv path sets improves 4.6–6.6% and 3.2–6.2%, respectively, across default and fat-LTO builds. Dense-rule controls stay within 0.6%; the synthetic warm control is flat with fat LTO.

PR #3560

reading declarations

Changes to crates/ignore/src/incremental.rs. All files

crates/ignore/src/incremental.rs

@@ -423,7 +423,7 @@ impl IncrementalIgnore {                     .insert(dir.as_os_str().to_owned(), CachedDir::Ignored);                 return Some(IncrementalMatch::ignore());             }-            let (child, err) = ignore.add_child(&path);+            let (child, err) = ignore.add_child_with_possible_reuse(&path);             errs.maybe_push(err);             self.dirs.insert(                 dir.as_os_str().to_owned(),@@ -1340,4 +1340,24 @@ mod tests {         assert!(dir.is_ignore());         assert!(!dir.is_within_depth());     }+    #[test]+    fn empty_directories_preserve_repository_boundaries() {+        let td = tmpdir();+        mkdirp(td.path().join(".git"));+        mkdirp(td.path().join("a/b/.git"));+        mkdirp(td.path().join("a/b/c"));+        wfile(td.path().join(".gitignore"), "git-blocked\n");+        wfile(td.path().join(".ignore"), "tool-blocked\n!allowed\n");++        let mut m = one_matcher(&builder(td.path()));+        assert!(matchedf(&mut m, "a/git-blocked").is_ignore());+        assert!(matchedf(&mut m, "a/b/c/git-blocked").is_none());+        assert!(matchedf(&mut m, "a/b/c/tool-blocked").is_ignore());+        assert!(matchedf(&mut m, "a/b/c/allowed").is_whitelist());++        wfile(td.path().join("a/b/c/.ignore"), "git-blocked\n");+        assert!(matchedf(&mut m, "a/b/c/git-blocked").is_none());+        let mut rebuilt = one_matcher(&builder(td.path()));+        assert!(matchedf(&mut rebuilt, "a/b/c/git-blocked").is_ignore());+    } }