diff --git a/crates/ignore/src/walk.rs b/crates/ignore/src/walk.rs index 4856351..3e06fb3 100644 --- a/crates/ignore/src/walk.rs +++ b/crates/ignore/src/walk.rs @@ -484,6 +484,7 @@ pub struct WalkBuilder { paths: Vec, ig_builder: IgnoreBuilder, max_depth: Option, + min_depth: Option, max_filesize: Option, follow_links: bool, same_file_system: bool, @@ -508,6 +509,7 @@ impl std::fmt::Debug for WalkBuilder { .field("paths", &self.paths) .field("ig_builder", &self.ig_builder) .field("max_depth", &self.max_depth) + .field("min_depth", &self.min_depth) .field("max_filesize", &self.max_filesize) .field("follow_links", &self.follow_links) .field("threads", &self.threads) @@ -528,6 +530,7 @@ impl WalkBuilder { paths: vec![path.as_ref().to_path_buf()], ig_builder: IgnoreBuilder::new(), max_depth: None, + min_depth: None, max_filesize: None, follow_links: false, same_file_system: false, @@ -542,6 +545,7 @@ impl WalkBuilder { pub fn build(&self) -> Walk { let follow_links = self.follow_links; let max_depth = self.max_depth; + let min_depth = self.min_depth; let sorter = self.sorter.clone(); let its = self .paths @@ -556,6 +560,9 @@ impl WalkBuilder { if let Some(max_depth) = max_depth { wd = wd.max_depth(max_depth); } + if let Some(min_depth) = min_depth { + wd = wd.min_depth(min_depth); + } if let Some(ref sorter) = sorter { match sorter.clone() { Sorter::ByName(cmp) => { @@ -597,6 +604,7 @@ impl WalkBuilder { paths: self.paths.clone().into_iter(), ig_root: self.ig_builder.build(), max_depth: self.max_depth, + min_depth: self.min_depth, max_filesize: self.max_filesize, follow_links: self.follow_links, same_file_system: self.same_file_system, @@ -621,6 +629,26 @@ impl WalkBuilder { /// The default, `None`, imposes no depth restriction. pub fn max_depth(&mut self, depth: Option) -> &mut WalkBuilder { self.max_depth = depth; + if self.min_depth.is_some() + && self.max_depth.is_some() + && self.max_depth < self.min_depth + { + self.max_depth = self.min_depth; + } + self + } + + /// The minimum depth to recurse. + /// + /// The default, `None`, imposes no minimum depth restriction. + pub fn min_depth(&mut self, depth: Option) -> &mut WalkBuilder { + self.min_depth = depth; + if self.max_depth.is_some() + && self.min_depth.is_some() + && self.min_depth > self.max_depth + { + self.min_depth = self.max_depth; + } self } @@ -1199,6 +1227,7 @@ pub struct WalkParallel { ig_root: Ignore, max_filesize: Option, max_depth: Option, + min_depth: Option, follow_links: bool, same_file_system: bool, threads: usize, @@ -1298,6 +1327,7 @@ impl WalkParallel { quit_now: quit_now.clone(), active_workers: active_workers.clone(), max_depth: self.max_depth, + min_depth: self.min_depth, max_filesize: self.max_filesize, follow_links: self.follow_links, skip: self.skip.clone(), @@ -1487,6 +1517,8 @@ struct Worker<'s> { /// The maximum depth of directories to descend. A value of `0` means no /// descension at all. max_depth: Option, + /// The minimum depth of directories to descend. + min_depth: Option, /// The maximum size a searched file can be (in bytes). If a file exceeds /// this size it will be skipped. max_filesize: Option, @@ -1515,10 +1547,19 @@ impl<'s> Worker<'s> { } fn run_one(&mut self, mut work: Work) -> WalkState { + let should_visit = self + .min_depth + .map(|min_depth| work.dent.depth() >= min_depth) + .unwrap_or(true); + // If the work is not a directory, then we can just execute the // caller's callback immediately and move on. if work.is_symlink() || !work.is_dir() { - return self.visitor.visit(Ok(work.dent)); + return if should_visit { + self.visitor.visit(Ok(work.dent)) + } else { + WalkState::Continue + }; } if let Some(err) = work.add_parents() { let state = self.visitor.visit(Err(err)); @@ -1551,9 +1592,11 @@ impl<'s> Worker<'s> { // entry before passing the error value. let readdir = work.read_dir(); let depth = work.dent.depth(); - let state = self.visitor.visit(Ok(work.dent)); - if !state.is_continue() { - return state; + if should_visit { + let state = self.visitor.visit(Ok(work.dent)); + if !state.is_continue() { + return state; + } } if !descend { return WalkState::Skip; @@ -2156,6 +2199,51 @@ mod tests { ); } + #[test] + fn min_depth() { + let td = tmpdir(); + mkdirp(td.path().join("a/b/c")); + wfile(td.path().join("foo"), ""); + wfile(td.path().join("a/foo"), ""); + wfile(td.path().join("a/b/foo"), ""); + wfile(td.path().join("a/b/c/foo"), ""); + + let builder = WalkBuilder::new(td.path()); + assert_paths( + td.path(), + &builder, + &["a", "a/b", "a/b/c", "foo", "a/foo", "a/b/foo", "a/b/c/foo"], + ); + let mut builder = WalkBuilder::new(td.path()); + assert_paths( + td.path(), + &builder.min_depth(Some(0)), + &["a", "a/b", "a/b/c", "foo", "a/foo", "a/b/foo", "a/b/c/foo"], + ); + assert_paths( + td.path(), + &builder.min_depth(Some(1)), + &["a", "a/b", "a/b/c", "foo", "a/foo", "a/b/foo", "a/b/c/foo"], + ); + assert_paths( + td.path(), + builder.min_depth(Some(2)), + &["a/b", "a/b/c", "a/b/c/foo", "a/b/foo", "a/foo"], + ); + assert_paths( + td.path(), + builder.min_depth(Some(3)), + &["a/b/c", "a/b/c/foo", "a/b/foo"], + ); + assert_paths(td.path(), builder.min_depth(Some(10)), &[]); + + assert_paths( + td.path(), + builder.min_depth(Some(2)).max_depth(Some(1)), + &["a/b", "a/foo"], + ); + } + #[test] fn max_filesize() { let td = tmpdir();