Skip to content

fix(find): report a missing path like find does, and disclose hidden/gitignored matches - #3854

Merged
aeppling merged 2 commits into
developfrom
fix/find-exit-code-and-hiddens
Sep 3, 2026
Merged

aeppling merged 2 commits into
developfrom
fix/find-exit-code-and-hiddens

Conversation

@aeppling

@aeppling aeppling commented Sep 3, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #3851

Summary

rtk find <missing-path> exited 0 with no output: a nonexistent path was read as RTK name-pattern syntax and searched from .. Hidden and gitignored matches were also dropped silently, so an empty result looked like "not on disk".

Changes

  • A first token with a path separator is always a path; find's own status (No such file or directory, exit 1) now surfaces. Bare names keep RTK pattern syntax.
  • find_cmd returns the exit status instead of calling process::exit; the Find arm in main.rs yields it.
  • Output ends with ... (N filtered: hidden or gitignored; …) plus a tee file, derived from the directories the walk already visited — skipped files named, skipped directories reported once as dir/, never descended.

Test

  • 7 new tests (TDD): separator-path dispatch, exit status on missing path, disclosure on a git fixture, note surviving the output guard.
  • cargo test 3135 passed; fmt and clippy clean.
  • Manual vs real find: missing path → 1 + stderr on every route; gitignored existence check → disclosure note; 12 ordinary usages identical to develop apart from the note.
  • Perf on ext4: 12–14 ms vs 14–21 ms before.

…gitignored matches

`rtk find /tmp/nope` exited 0 with no output where `find` prints
`find: '/tmp/nope': No such file or directory` and exits 1. The dispatcher
read a nonexistent path as RTK name-pattern syntax (`!is_dir()` is true for
both), rewrote it to `-name /tmp/nope`, defaulted the search root to `.`, and
the existence guard then checked `.` instead of the argument. A token with a
path separator can never be a `-name` pattern (`-name` matches basenames), so
treat it as a path whether or not it exists; find's own status then surfaces.
`run`, `run_compress`, `run_verbatim` and `run_from_args` return the status
instead of calling `process::exit`, and the `Find` arm in main.rs yields it.

The native walk skips hidden and gitignored entries, so `rtk find . -name
secret.txt` printed nothing for a gitignored file and exited 0 — an existence
check reads that as "not on disk". The output now ends with
`... (N filtered: hidden or gitignored; dirs listed with / were not
searched)` plus a tee file listing them, the shape `rtk ls` already uses, and
says so even when nothing else matched. The note rides on both sides of the
never-worse guard, since the raw listing never holds filtered entries and
would otherwise win on size and drop it.

The disclosure is derived from the walk that already happened: for each
directory the filtered walk visited, its raw children are listed and any
child the walk did not visit was hidden or ignored. Skipped files are named
when they match; a skipped directory is reported once as `dir/` and never
descended. Cost is one read_dir per visited directory: on ext4, a 116-directory
checkout with 3000 gitignored files measures 12-14 ms against 14-21 ms
before. (Re-walking with the filters off to count matches inside ignored
trees took 7.5 s against 350 ms on the same checkout, which is why it is not
done.) On a Windows mount under WSL each read_dir is a 9P round-trip, so the
disclosure adds roughly 2 ms per visited directory there.

Bare names without a separator keep RTK's pattern semantics (`rtk find
Cargo.toml`); a pattern starting with `.` still walks hidden entries.

Fixes #3851
@aeppling
aeppling force-pushed the fix/find-exit-code-and-hiddens branch from b6b93ce to 765b270 Compare September 3, 2026 17:22
… root, align the note with ls

Review follow-ups on the disclosure added for #3851.

- The walk kept every visited path in a set for the whole run, so on a
  108k-entry tree RSS went from 7 MB to 18 MB and the extra read_dir pass
  doubled the time. Disclosure now switches off once 20 000 entries have been
  visited and the bookkeeping is dropped; below that nothing changes. Measured
  on /usr: 3.9-4.0 s and 9.6 MB against develop's 3.9-7.0 s and 6.9 MB.
- A symlinked search root reported its file type as a symlink, so it never
  counted as a visited directory and its filtered children went unreported.
  The root's type is now read through the link.
- The note is `... (N filtered)`, the same shape rtk ls emits; the trailing
  `/` on a reported directory already says it was not searched.
- `.git` comes from a named constant instead of an inline literal.
- The fixture test disables global gitignore rules so a developer's own
  excludes cannot change its result; two tests point RTK_TEE_DIR at a tempdir
  instead of writing under ~/.local/share/rtk.
- find_cmd.rs is included through automod, which cargo fmt does not see; the
  file is now rustfmt-clean when checked directly.
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.

find: exits 0 on a nonexistent path, and silently omits gitignored/hidden files

2 participants