Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
- Also fire the "search pattern contains a path separator" diagnostic for `--and` patterns, not only the primary positional pattern. `--and` patterns are matched against the file name just like the primary pattern, so a path separator in them silently returned zero results. See #1873.
- Fix bug where passing "-" as a directory argument didn't actually search that directory, see #849 (@Sean-Kenneth-Doherty).
- Fix panic when `--changed-before`/`--changed-within` is given an out-of-range `@` Unix timestamp; the value is now rejected gracefully, see #2081 (@nikolauspschuetz).
- Fix dot-prefixed hidden files being listed when a `.gitignore` pattern negates them, even without `--hidden`, see #1266 (@SomSamantray).

# 10.4.2

Expand Down

@tmccombs tmccombs Aug 16, 2026

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.

This file should not be checked in

Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# Residual Review Findings

Run context: LFG pipeline for sharkdp/fd issue #1266 (hidden files wrongly listed when negated in `.gitignore`). Plan: `docs/plans/2026-08-15-001-fix-hidden-gitignore-negation-plan.md`. Review run: `ce-code-review mode:agent`, run id `20260815-212237-5e098adb`, artifact at `/tmp/compound-engineering-501/ce-code-review/20260815-212237-5e098adb/`.

## Residual Review Findings

- P3 — `src/walk.rs:515` — Hidden check only covers dot-prefix names, so attribute-hidden entries with non-dot names still leak on Windows when un-ignored by a gitignore negation — tracker ticket: https://github.com/sharkdp/fd/issues/2096

## Not applied in step 5

- #1 (above): fix is a behavior change (per-entry attribute metadata checks on Windows) conflicting with the plan's documented name-based, no-syscall decision (KTD1); documented as a limitation in the plan's Assumptions and in the PR description. Filed as issue #2096.
- Broken-symlink dot-prefix coverage gap (advisory, anchor 50): routed to `testing_gaps`; not eligible for apply per the review-followup bar.
17 changes: 14 additions & 3 deletions src/walk.rs
Original file line number Diff line number Diff line change
Expand Up @@ -505,15 +505,26 @@ impl WorkerState {
}
};

// Never show dot-prefixed hidden entries unless --hidden is set. This is
// enforced here instead of relying on the ignore crate's hidden filter,
// because a negated ignore pattern overrides that filter (see #1266).
// Skipping prunes hidden directories as well, so their contents are not
// searched.
let entry_path = entry.path();
let file_name = entry_path.file_name();
if config.ignore_hidden
&& file_name.is_some_and(|name| name.as_encoded_bytes().first() == Some(&b'.'))
{
return WalkState::Skip;
}

if let Some(min_depth) = config.min_depth
&& entry.depth().is_none_or(|d| d < min_depth)
{
return WalkState::Continue;
}

// Check the name first, since it doesn't require metadata
let entry_path = entry.path();

let search_str = search_str_for_entry(entry_path, config.full_path_base.as_deref());

if !patterns
Expand All @@ -525,7 +536,7 @@ impl WorkerState {

// Filter out unwanted extensions.
if let Some(ref exts_regex) = config.extensions {
if let Some(path_str) = entry_path.file_name() {
if let Some(path_str) = file_name {
if !exts_regex.is_match(&filesystem::osstr_to_bytes(path_str)) {
return WalkState::Continue;
}
Expand Down
26 changes: 26 additions & 0 deletions tests/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -694,6 +694,32 @@ fn test_hidden() {
);
}

/// Hidden entries un-ignored by a negated .gitignore pattern must stay hidden without --hidden
#[test]
fn test_hidden_negated_gitignore() {
let te = TestEnv::new(DEFAULT_DIRS, DEFAULT_FILES);

fs::File::create(te.test_root().join(".gitignore"))
.unwrap()
.write_all(b"!.gitignore\n!.hdir/")
.unwrap();
fs::create_dir(te.test_root().join(".hdir")).unwrap();
fs::File::create(te.test_root().join(".hdir").join("x.txt")).unwrap();

// The negation un-ignores .gitignore, but it is a hidden file and must not be
// listed without --hidden.
te.assert_output(&[".gitignore"], "");
te.assert_output(&["--hidden", ".gitignore"], ".gitignore");

// A negated hidden directory is not searched without --hidden.
te.assert_output(&["x.txt"], "");
te.assert_output(&["--hidden", "x.txt"], ".hdir/x.txt");

// The hidden check applies regardless of --min-depth.
te.assert_output(&["--min-depth", "2", "x.txt"], "");
te.assert_output(&["--hidden", "--min-depth", "2", "x.txt"], ".hdir/x.txt");
}

/// Hidden file attribute on Windows
#[cfg(windows)]
#[test]
Expand Down