diff --git a/src/walk.rs b/src/walk.rs index f35d7bd..1c0623a 100644 --- a/src/walk.rs +++ b/src/walk.rs @@ -658,19 +658,33 @@ impl WorkerState { /// /// `ignore::DirEntry::depth()` provides this for normal entries, but broken /// symlinks are surfaced as errors that carry no depth, so we derive it from the -/// path components instead (see issue #1017). The matching root is the longest -/// search root (by component count) that `path` starts with; if none matches we -/// fall back to the full component count, which keeps the entry visible rather -/// than silently dropping it. +/// path instead (see issue #1017). The walker reports every entry's path +/// prefixed by the search root it was found under, so we strip the matching root +/// and count the remaining components. Both the path and the roots are put into +/// absolute form first, so the comparison is correct regardless of whether +/// `--absolute-path` has already made the roots absolute (otherwise an absolute +/// path would fail to match a relative root and the depth would be wrong). When +/// more than one root is a prefix, the deepest (most specific) one wins. If none +/// matches, which should not happen, we fall back to the full component count, +/// keeping the entry visible rather than silently dropping it. fn depth_relative_to_roots(path: &Path, roots: &[PathBuf]) -> usize { - let components = path.components().count(); - let root_components = roots + // `Path::join` ignores the base when its argument is already absolute, so + // this leaves absolute inputs untouched and anchors relative ones at the cwd. + let cwd = std::env::current_dir().ok(); + let absolute = |p: &Path| -> PathBuf { + match &cwd { + Some(cwd) => cwd.join(p), + None => p.to_path_buf(), + } + }; + + let absolute_path = absolute(path); + roots .iter() - .filter(|root| path.starts_with(root)) - .map(|root| root.components().count()) - .max() - .unwrap_or(0); - components.saturating_sub(root_components) + .filter_map(|root| absolute_path.strip_prefix(absolute(root)).ok()) + .map(|relative| relative.components().count()) + .min() + .unwrap_or_else(|| path.components().count()) } fn search_str_for_entry<'a>( diff --git a/tests/tests.rs b/tests/tests.rs index 16b3335..0319878 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -1245,6 +1245,46 @@ fn test_min_depth_broken_symlink() { ); } +/// Minimum depth with a broken symlink combined with --absolute-path (#1017) +/// +/// With --absolute-path the search root is made absolute before walking, so the +/// broken symlink's depth must still be computed relative to that root rather +/// than from the absolute path's full component count. +#[test] +fn test_min_depth_broken_symlink_absolute_path() { + let (mut te, abs_path) = get_test_env_with_abs_path(DEFAULT_DIRS, DEFAULT_FILES); + te.create_broken_symlink("one/two/broken_symlink") + .expect("Failed to create broken symlink."); + + // The broken symlink sits at depth 3 relative to the (absolute) root. + te.assert_output( + &[ + "--follow", + "--absolute-path", + "--type", + "symlink", + "--min-depth", + "3", + "broken_symlink", + ], + &format!("{abs_path}/one/two/broken_symlink"), + ); + + // A --min-depth beyond its actual depth must exclude it. + te.assert_output( + &[ + "--follow", + "--absolute-path", + "--type", + "symlink", + "--min-depth", + "4", + "broken_symlink", + ], + "", + ); +} + /// Exact depth (--exact-depth) #[test] fn test_exact_depth() {