Make broken symlink depth robust under absolute paths

The depth for a broken symlink was derived by stripping the search root
from its path and counting the remaining components. Compare the path
and the roots in absolute form before stripping, so the match holds
whether or not --absolute-path has already made the roots absolute, and
pick the deepest matching root. This avoids an absolute path failing to
match a relative root and falling back to an inflated depth.

Add a regression test that exercises the broken symlink case together
with --absolute-path.
This commit is contained in:
hexbinoct
2026-08-10 14:30:46 +05:00
parent 350b33acda
commit 4a89affbb4
2 changed files with 65 additions and 11 deletions
+25 -11
View File
@@ -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>(
+40
View File
@@ -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() {