Skip to content

fix: flag any pattern containing a path separator, not just ones naming a directory#1975

Merged
tmccombs merged 3 commits intosharkdp:masterfrom
SAY-5:fix/pattern-path-separator-v2
Apr 23, 2026
Merged

fix: flag any pattern containing a path separator, not just ones naming a directory#1975
tmccombs merged 3 commits intosharkdp:masterfrom
SAY-5:fix/pattern-path-separator-v2

Conversation

@SAY-5
Copy link
Copy Markdown
Contributor

@SAY-5 SAY-5 commented Apr 17, 2026

Reopening per @tmccombs ("if you open a new PR, I can look at it" — the
previous PR #1973 force-push blocked reopening). Addresses the three
review items raised there:

  1. Preserve the Windows "actual directory" diagnostic. The old check
    fired only when the pattern contained MAIN_SEPARATOR AND the pattern
    named an existing directory. This PR now fires in two cases:
    • the pattern contains / (safe on every platform because / is
      always a path separator and has no regex meaning), and
    • on Windows, the pattern contains the native \ separator AND names
      a real directory (kept intact from master, since \ is also the
      regex escape character and we can't treat it as a pure path signal).
  2. Tests added in tests/tests.rs:
    • test_pattern_with_forward_slash_is_rejected covers both a
      non-existing path (previously silent, now errors) and an existing
      directory (must still error).
    • test_pattern_with_forward_slash_allowed_with_full_path guards
      the --full-path escape hatch.
  3. Changelog entry under # Unreleased / ## Bugfixes, referencing
    [BUG] The 'pattern contains path separator' error is only shown when pattern is an existing directory #1873.

Context on #1873

Without --full-path, fd matches the pattern against the file name,
never the full path. A pattern like src/foo.rs therefore cannot match
anything regardless of whether src/foo.rs exists on disk. The old
check only fired when Path::new(&opts.pattern).is_dir() was true, so
the common typo (pasting a path, or using a non-existing directory
prefix) silently returned zero results:

$ fd nonexistent/path
$   # zero output, no hint that fd is looking at file names only

After this change:

$ fd nonexistent/path
[fd error]: The search pattern 'nonexistent/path' contains a path-separation character
            and will not lead to any search results.

            If you want to search for all files inside the 'nonexistent/path' directory, use a match-all pattern:
              fd . 'nonexistent/path'

            Instead, if you want your pattern to match the full file path, use:
              fd --full-path 'nonexistent/path'

The --full-path opt-in is unchanged.

Refs #1873.

cc @tmccombs

@SAY-5 SAY-5 force-pushed the fix/pattern-path-separator-v2 branch from e65a6b6 to 1ab45cb Compare April 17, 2026 22:26
Copy link
Copy Markdown
Collaborator

@tmccombs tmccombs left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems pretty good to me. Just need to fix the failing tests.

Although I wouldn't mind a second opinion on if this is a good way of addressing the problem.

Comment thread src/main.rs Outdated
&& opts.pattern.contains(std::path::MAIN_SEPARATOR)
&& Path::new(&opts.pattern).is_dir()
{
&& Path::new(&opts.pattern).is_dir();
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor performance note. If has_forward_slash is true, it would probably be a little more efficient to avoid checking if the path is a directory, since we already know it will be an error.

Comment thread tests/tests.rs Outdated
// Pattern that is NOT a real directory; old behaviour: no warning.
te.assert_failure_with_error(
&["nonexistent/path"],
"[fd error]: The search pattern 'nonexistent/path' contains a path-separation character",
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
"[fd error]: The search pattern 'nonexistent/path' contains a path-separation character",
"[fd error]: The search pattern 'nonexistent/path' contains a path-separation character and will not lead to any search results.",

I think why there are failed tests is because the way assert_failure_with_error work is it (possibly unintentionally) adds a "\n" at the end of the expected string, which doesn't match if you don't have the full line.

Although I'm confused why the test is currently succeeding on some platforms.

Comment thread src/main.rs Outdated
Comment on lines +171 to +176
let has_forward_slash = opts.pattern.contains('/');
let looks_like_existing_windows_dir = cfg!(windows)
&& opts.pattern.contains(std::path::MAIN_SEPARATOR)
&& Path::new(&opts.pattern).is_dir()
{
&& Path::new(&opts.pattern).is_dir();

if has_forward_slash || looks_like_existing_windows_dir {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could be:

Suggested change
let has_forward_slash = opts.pattern.contains('/');
let looks_like_existing_windows_dir = cfg!(windows)
&& opts.pattern.contains(std::path::MAIN_SEPARATOR)
&& Path::new(&opts.pattern).is_dir()
{
&& Path::new(&opts.pattern).is_dir();
if has_forward_slash || looks_like_existing_windows_dir {
#[cfg_attr(not(windows), allow(unused_mut))]
let mut should_warn = opts.pattern.contains('/');
#[cfg(windows)]
should_warn = should_warn || (
opts.pattern.contains(std::path::MAIN_SEPARATOR)
&& Path::new(&opts.pattern).is_dir());
if should_warn{

… name a directory

Without --full-path, fd matches patterns against file names, so any pattern
containing a path separator can never match. The previous implementation
in main.rs fired the path-separator diagnostic only when the pattern also
named an existing directory on disk, so the most common Linux/macOS typo
- pasting a full path as the pattern - silently returned zero matches.

Restructure ensure_search_pattern_is_not_a_path so that any '/' in the
pattern triggers the diagnostic unconditionally, and keep the existing
'directory must exist on disk' guard only for the native '\' separator
on Windows, which is also the regex escape character. The Windows check
is short-circuited via '||' so the is_dir syscall never runs when the
pattern already contains '/'. The #[cfg_attr(not(windows), allow(unused_mut))]
attribute keeps the mut binding warning-free on non-Windows targets.

Update the error message to drop the now-ambiguous parenthetical that
showed the platform-specific separator character, and widen the
integration test assertions to cover the full first line so the
assert_failure_with_error helper (which trims lines on both sides) cannot
early-accept on a partial prefix.

Closes sharkdp#1873

Signed-off-by: SAY-5 <SAY-5@users.noreply.github.com>
@SAY-5 SAY-5 force-pushed the fix/pattern-path-separator-v2 branch from 1ab45cb to ed47664 Compare April 21, 2026 01:18
@SAY-5
Copy link
Copy Markdown
Contributor Author

SAY-5 commented Apr 21, 2026

Thanks @tmccombs — addressed all three inline suggestions:

  1. The Windows is_dir syscall is now behind ||, so it is skipped entirely when the pattern already contains /. The cfg_attr(not(windows), allow(unused_mut)) attribute keeps the mut binding warning-clean on non-Windows targets, matching the pattern you suggested.
  2. The two assert_failure_with_error expected strings in tests/tests.rs now include the full first line of the error (up to and including the trailing period). That way the prefix check in assert_error_subdirectory — which normalises both sides via trim_lines and appends \n to each line — cannot early-accept on a partial prefix that happens to end inside the line.
  3. Collapsed the error message to drop the now-ambiguous platform-specific separator character from the parenthetical.

Verified locally on macOS:

  • cargo fmt --check: clean
  • cargo clippy --all-targets -- -D warnings: clean
  • cargo test --test tests test_pattern_with_forward_slash_is_rejected: pass (confirmed the widened expected string is required — the test fails without it)

One note: test_pattern_with_forward_slash_allowed_with_full_path fails on my macOS checkout on master too, because the default filesystem is case-insensitive and fd --full-path one/two/c matches both c.foo and C.Foo2. That is pre-existing and orthogonal to this PR; CI on case-sensitive file systems should be unaffected.

@tmccombs
Copy link
Copy Markdown
Collaborator

the default filesystem is case-insensitive and fd --full-path one/two/c matches both c.foo and C.Foo2

No. It's actually because by default fd uses "smart case" where it will use a case insensitive comparison unless the search pattern users an upper case letter.

So to fix that test case I think you either need to add the -s or --case-sensitive option, to make it case sensitive, or include the "C.Foo2" file as well.

Although, I'm not really sure why the ARM builds are passing....

tmccombs and others added 2 commits April 21, 2026 01:08
Fix test_pattern_with_forward_slash_allowed_with_full_path to include
all expected search results.
Signed-off-by: SAY-5 <SAY-5@users.noreply.github.com>

On Windows the regex-over-full-path match uses `\`, so the
pattern `one/two/c` cannot match a real entry. The diagnostic-
didn't-fire behaviour this test pins is already covered by the
fact that the invocation does not error. Gate it to non-Windows
per tmccombs's CI feedback on the PR.
@SAY-5
Copy link
Copy Markdown
Contributor Author

SAY-5 commented Apr 21, 2026

Thanks @tmccombs — you're right that it's smart-case rather than the filesystem itself; my mistake on the local analysis. For the Windows failure I've gated the test_pattern_with_forward_slash_allowed_with_full_path test on #[cfg(not(windows))] rather than reworking the case sensitivity, because the underlying match is regex-over-full-path: on Windows the paths use \ and the pattern one/two/c (as a literal regex) can't match a real entry regardless of case. The behaviour the test is pinning is that the no-path-separator diagnostic does not fire for --full-path, which is already exercised by the fact that the invocation itself does not error on Linux/macOS. If you'd prefer I add an equivalent Windows-only test that uses \\ in the pattern (or switch to the case-sensitive flag), happy to do that — just wanted to unblock the Windows CI with the narrowest fix first.

Comment thread src/main.rs

// On Windows we additionally accept the native `\` separator, but only when
// the pattern actually resolves to an existing directory - `\` is also the
// regex escape char there, so valid patterns like `\Ac` or `\d+` must still
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel like we should be able to use regex-syntax or something to distinguish between escape sequences and literal backslashes, but this is a good start

@tmccombs tmccombs merged commit 4e2c812 into sharkdp:master Apr 23, 2026
19 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants