From 2784f08fa5ff09fb0a72df102e4bae0b6e8e0b20 Mon Sep 17 00:00:00 2001 From: wuyangfan <1102042793@qq.com> Date: Sun, 17 May 2026 05:17:11 +0800 Subject: [PATCH 1/2] fix: normalize --full-path matches for parent search paths When searching from a subdirectory with a relative root like `..`, --full-path compared unnormalized paths (e.g. cwd/../foo) against absolute patterns. Normalize paths that contain `.` or `..` components. Fixes #1513. Co-authored-by: Cursor --- CHANGELOG.md | 1 + src/walk.rs | 16 ++++++++++++++-- tests/tests.rs | 18 ++++++++++++++++++ 3 files changed, 33 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 08e0ed1e8..bd0cd7790 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ ## Bugfixes - Handle invalid working directories gracefully when using `--full-path`, see #1900 (@Xavrir). +- Normalize `--full-path` matching for relative search paths such as `..`, see #1513 (@leno23). - Fire the "search pattern contains a path separator" diagnostic for any pattern containing `/`, not just patterns that happen to name an existing directory. Preserves the legacy Windows behaviour that also flags native `\` separators when the pattern resolves to a real directory. See #1873. # 10.4.2 diff --git a/src/walk.rs b/src/walk.rs index 018ad2f70..34db3926f 100644 --- a/src/walk.rs +++ b/src/walk.rs @@ -2,7 +2,7 @@ use std::borrow::Cow; use std::ffi::OsStr; use std::io::{self, Write}; use std::mem; -use std::path::PathBuf; +use std::path::{Component, PathBuf}; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::{Arc, Mutex, MutexGuard}; use std::thread; @@ -13,6 +13,7 @@ use crossbeam_channel::{Receiver, RecvTimeoutError, SendError, Sender, bounded}; use etcetera::BaseStrategy; use ignore::overrides::{Override, OverrideBuilder}; use ignore::{WalkBuilder, WalkParallel, WalkState}; +use normpath::PathExt; use regex::bytes::Regex; use crate::config::Config; @@ -674,7 +675,18 @@ fn search_str_for_entry<'a>( return Cow::Borrowed(entry_path.as_os_str()); } let path = entry_path.strip_prefix(".").unwrap_or(entry_path); - Cow::Owned(cwd.join(path).into()) + let absolute_path = cwd.join(path); + if path + .components() + .any(|component| matches!(component, Component::CurDir | Component::ParentDir)) + { + match absolute_path.normalize() { + Ok(normalized_path) => Cow::Owned(normalized_path.into()), + Err(_) => Cow::Owned(absolute_path.into()), + } + } else { + Cow::Owned(absolute_path.into()) + } } else { match entry_path.file_name() { Some(filename) => Cow::Borrowed(filename), diff --git a/tests/tests.rs b/tests/tests.rs index aa0919ca0..cc4cd651c 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -1299,6 +1299,24 @@ fn test_normalized_absolute_path() { ); } +/// Full path matching should normalize relative search paths as well. +#[test] +fn test_full_path_normalizes_relative_search_path() { + let (te, abs_path) = get_test_env_with_abs_path(DEFAULT_DIRS, DEFAULT_FILES); + + te.assert_output_subdirectory( + "one/two", + &[ + "--full-path", + "-t", + "f", + &format!("{abs_path}/one/b.foo"), + "..", + ], + "../b.foo", + ); +} + /// File type filter (--type) #[test] fn test_type() { From 55d6b8b5ce9115608bbe660ba1e80a1f69d1bfb3 Mon Sep 17 00:00:00 2001 From: wuyangfan <1102042793@qq.com> Date: Sun, 17 May 2026 05:22:42 +0800 Subject: [PATCH 2/2] test: escape absolute path in full-path Windows regex Windows CI failed because backslashes in the temp-dir path were interpreted as regex escapes. Build the pattern with regex::escape. Co-authored-by: Cursor --- tests/tests.rs | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/tests/tests.rs b/tests/tests.rs index cc4cd651c..ebbdda80b 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -4,7 +4,7 @@ mod testenv; use nix::unistd::{Gid, Group, Uid, User}; use std::fs; use std::io::Write; -use std::path::Path; +use std::path::{Path, PathBuf}; use std::time::{Duration, SystemTime}; use test_case::test_case; @@ -1304,15 +1304,12 @@ fn test_normalized_absolute_path() { fn test_full_path_normalizes_relative_search_path() { let (te, abs_path) = get_test_env_with_abs_path(DEFAULT_DIRS, DEFAULT_FILES); + let target = PathBuf::from(&abs_path).join("one").join("b.foo"); + let pattern = escape(target.to_string_lossy().as_ref()); + te.assert_output_subdirectory( "one/two", - &[ - "--full-path", - "-t", - "f", - &format!("{abs_path}/one/b.foo"), - "..", - ], + &["--full-path", "-t", "f", &pattern, ".."], "../b.foo", ); }