Compare commits

...

260 Commits

Author SHA1 Message Date
David Peter 56c405d8e6 Updates for fd 8.5.2 2022-11-03 09:13:15 +01:00
David Peter 99d1db8cb3 Merge pull request #1164 from tmccombs/owner-without-panic
Fix panic when using --owner
2022-11-03 09:08:09 +01:00
David Peter fbef976b92 Merge pull request #1159 from tmccombs/silence-large-variant
Silence warning about large enum variant
2022-11-03 08:54:52 +01:00
Thayne McCombs 55aae3a8c0 Silence warning about large enum variant
Because boxing the large variant is probably worse.
2022-11-03 00:56:56 -06:00
Thayne McCombs b04cae2ca0 Only run owner root test on linux
Because macos doesn't have a "root" user
2022-11-02 23:55:31 -06:00
Thayne McCombs c159ea2042 Fix panic when using --owner
Unfortunately, clap_derive can't combine a value_parser of Option<T>
with an optional argument to get a merged Option<T> so we need to do the
check for the nop outside of the value parser.

Also adds some tests for --owner

Fixes: #1163
2022-11-02 23:26:02 -06:00
David Peter 567ce2640f Update CHANGELOG 2022-11-02 13:48:18 +01:00
David Peter 527fe9e262 Updates for fd 8.5.1 2022-11-02 13:46:20 +01:00
David Peter 0a7b51ad42 Merge pull request #1162 from sharkdp/fix-threads-option
Fix --threads/-j option value parsing
2022-11-02 13:45:45 +01:00
David Peter f15be89bff Fix --threads/-j option value parsing 2022-11-02 13:32:22 +01:00
David Peter cbd11d8a45 Try to fix bug issue template 2022-11-02 13:15:55 +01:00
Thayne McCombs 6b7f8da381 Merge pull request #1157 from miles170/ci-migrate-set-output
CI: migrate deprecating set-output commands
2022-11-02 00:50:46 -06:00
Miles Liu 8d1c3631cf CI: migrate deprecating set-output commands 2022-11-02 12:06:43 +08:00
David Peter c85929045e Update CHANGELOG 2022-11-01 21:36:56 +01:00
David Peter 715f0a9e44 Update release checklist 2022-11-01 21:36:30 +01:00
David Peter bab77275ec Update Usage in Readme 2022-11-01 21:18:11 +01:00
David Peter 12165c73f8 Reorder command line options 2022-11-01 21:17:48 +01:00
David Peter 70c4865191 Set max. term width to make help texts easier to read 2022-11-01 21:14:16 +01:00
David Peter 7f5bc9884f Add simple 'about' text 2022-11-01 21:02:37 +01:00
David Peter ac35b11dc8 Change clap CLI binary name to 'fd' 2022-11-01 21:00:32 +01:00
David Peter f8e832f212 Preparation for v8.5 2022-11-01 20:55:25 +01:00
David Peter bc94fcc90f Update CHANGELOG 2022-11-01 20:45:16 +01:00
David Peter b57ed11f65 Merge pull request #1137 from amesgen/batch-exit-code
Respect exit codes with `--exec-batch`
2022-11-01 20:42:28 +01:00
David Peter 831fe666fa Remove outdated TODO comment 2022-11-01 20:34:42 +01:00
David Peter 13a47c3a2c Merge pull request #1067 from tmccombs/clap-derive
Clap derive
2022-11-01 20:34:08 +01:00
David Peter 5771e74b95 Add newlines between options 2022-11-01 20:18:17 +01:00
David Peter 55029e889e Minor: add missing full stop 2022-11-01 20:13:32 +01:00
David Peter ebd48d406e Fix unit tests 2022-11-01 20:12:56 +01:00
David Peter 3f72ef4cdd Remove outdated TODO comment 2022-11-01 20:07:09 +01:00
David Peter 169d22fde2 Merge remote-tracking branch 'origin/master' into clap-derive 2022-11-01 20:00:16 +01:00
David Peter c6f9595a02 Update CHANGELOG 2022-11-01 19:57:11 +01:00
Tavian Barnes 5278405263 walk: Use a bounded queue.
Fixes #918.
2022-11-01 19:48:39 +01:00
Tavian Barnes 5bb7a52704 walk: Switch back to crossbeam-channel
Fixes #933.  Fixes #1060.  Fixes #1113.
2022-11-01 19:48:39 +01:00
Tavian Barnes 93e5488420 walk: Simplify the code in scan() a bit 2022-11-01 15:36:48 +01:00
Thayne McCombs 36ee44a3f6 Make help output more like what it was before use clap_derive
In particular, we specifically use `long_help` instead of doc comments
because using  doc comments will always trim the "." off the end of the
first paragraph, and will include the short help as the first paragraph
of the full help.
2022-11-01 02:30:22 -06:00
Thayne McCombs 8500c3193d Merge remote-tracking branch 'upstream/master' into clap-derive 2022-11-01 01:12:41 -06:00
Tavian Barnes cd8ec44abf Address review comments 2022-10-31 22:23:30 +01:00
Tavian Barnes 17d849df6c Compute colors in parallel 2022-10-31 22:23:30 +01:00
Tavian Barnes b2c8888a50 dir_entry: Implement file_name() for broken symlinks 2022-10-31 22:23:30 +01:00
Tavian Barnes f0c50befce Add a changelog entry 2022-10-31 22:23:30 +01:00
Tavian Barnes 7917c00887 dir_entry: Implement lscolors::Colorable 2022-10-31 22:23:30 +01:00
Vlad Kooklev c9f4dec2ed chore: Update outdated LICENSE year 2022-10-31 21:19:34 +01:00
Frieder Bluemle e9121ee515 Remove trailing spaces from README.md 2022-10-31 21:19:07 +01:00
Thayne McCombs 3782278f02 Improve help output 2022-10-20 00:34:52 -06:00
Thayne McCombs a3622ba294 Remove TODO comment
In favor of tracking scoped threads in #1141
2022-10-19 23:43:45 -06:00
Thayne McCombs aec125637b Fix format 2022-10-14 23:14:03 -06:00
amesgen cb95f1dcd5 Respect exit codes with --exec-batch 2022-10-13 23:33:54 +02:00
David Peter 4257034209 Add release checklist for maintainers 2022-10-13 22:52:09 +02:00
Thayne McCombs b6f0088b68 Change thread option to take u32 instead of usize
To simplify the option parsing.
2022-10-12 00:33:49 -06:00
Thayne McCombs c2115884de Fix deprecations from clap 4.0 2022-10-11 22:27:57 -06:00
Thayne McCombs c0b14705cd Update msrv to 1.60
Because that is what clap requires
2022-10-11 00:39:57 -06:00
Thayne McCombs bf1a6f6680 Update clap depenency version 2022-10-11 00:35:21 -06:00
Thayne McCombs 10ecb64ff3 Fix typo in test 2022-10-10 23:58:33 -06:00
Thayne McCombs aca64c09f8 Actually test if exec or exec-batch is used
By the time we check if we should strip the cwd, we've already moved the
command out of the options, so store if we got that out earlier.
2022-10-09 01:25:03 -06:00
Thayne McCombs 86c33492a7 Fix name of powershell in guess_shell 2022-10-09 01:05:27 -06:00
Thayne McCombs c41d61eeda Merge remote-tracking branch 'upstream/master' into clap-derive 2022-10-09 01:00:48 -06:00
Thayne McCombs 5b9e302e9b Support powershell when guessing shell for completions 2022-10-09 00:02:28 -06:00
Thayne McCombs f4c34b81c7 Upgrade to clap 4.0 2022-10-08 23:51:45 -06:00
Thayne McCombs a50e417c67 Add action clap attribute to fixed_strings 2022-10-08 00:42:11 -06:00
Thayne McCombs ff7336b202 clap derive suggestions from epage 2022-10-08 00:42:11 -06:00
Thayne McCombs e6f4805bae Fix clippy warnings 2022-10-08 00:42:11 -06:00
Thayne McCombs 066ce41299 Fix case of color options to ls 2022-10-08 00:42:11 -06:00
Thayne McCombs b7f5f4ac7d Improve help output after switch to clap-derive
Make it more like it used to be.
2022-10-08 00:42:11 -06:00
Thayne McCombs 4e7b403c1f Use clap-derive for option parsing
This makes the definition of arguments to fd a little more ergonomic,
and makes it easier to insure the types for the arguments are consitent.
2022-10-08 00:41:33 -06:00
Tavian Barnes 0984ed91ea Update CHANGELOG for -x/-X leading ./ change 2022-10-03 21:35:39 +02:00
Tavian Barnes 4ffc34956f Make --strip-cwd-prefix apply to -x/-X
Fixes #898.
2022-10-03 21:35:39 +02:00
Tavian Barnes 5039d2db99 Merge pull request #1123 from sharkdp/dependabot/cargo/once_cell-1.15.0
Bump once_cell from 1.14.0 to 1.15.0
2022-10-02 10:03:00 -04:00
Thayne McCombs ee2396b57a Merge pull request #1122 from sharkdp/dependabot/cargo/libc-0.2.134
Bump libc from 0.2.132 to 0.2.134
2022-10-01 14:04:22 -06:00
Thayne McCombs 5376676d07 Merge pull request #1125 from sharkdp/dependabot/cargo/anyhow-1.0.65
Bump anyhow from 1.0.64 to 1.0.65
2022-10-01 14:04:08 -06:00
dependabot[bot] 3f33b15e52 Bump anyhow from 1.0.64 to 1.0.65
Bumps [anyhow](https://github.com/dtolnay/anyhow) from 1.0.64 to 1.0.65.
- [Release notes](https://github.com/dtolnay/anyhow/releases)
- [Commits](https://github.com/dtolnay/anyhow/compare/1.0.64...1.0.65)

---
updated-dependencies:
- dependency-name: anyhow
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-10-01 17:02:33 +00:00
dependabot[bot] b1ee6e3046 Bump once_cell from 1.14.0 to 1.15.0
Bumps [once_cell](https://github.com/matklad/once_cell) from 1.14.0 to 1.15.0.
- [Release notes](https://github.com/matklad/once_cell/releases)
- [Changelog](https://github.com/matklad/once_cell/blob/master/CHANGELOG.md)
- [Commits](https://github.com/matklad/once_cell/compare/v1.14.0...v1.15.0)

---
updated-dependencies:
- dependency-name: once_cell
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-10-01 17:02:24 +00:00
dependabot[bot] 46c38a827e Bump libc from 0.2.132 to 0.2.134
Bumps [libc](https://github.com/rust-lang/libc) from 0.2.132 to 0.2.134.
- [Release notes](https://github.com/rust-lang/libc/releases)
- [Commits](https://github.com/rust-lang/libc/compare/0.2.132...0.2.134)

---
updated-dependencies:
- dependency-name: libc
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-10-01 17:02:17 +00:00
David Peter 4f1bcfedf3 Update CHANGELOG 2022-09-27 21:25:10 +02:00
David Peter e3e6057434 Minor adaptation to help text 2022-09-27 21:00:29 +02:00
Scott Baker 12142008df Add a link back to Github to --help (but not -h) 2022-09-27 21:00:29 +02:00
Kamil Aronowski 8ccf21437b Mention EuroLinux 2022-09-27 20:59:31 +02:00
Tavian Barnes 4419401b6c Enable --strip-cwd-prefix by default except with -0
Fixes #1046.
2022-09-27 20:56:24 +02:00
Tavian Barnes e46d402ed1 Enable jemallocator on 64-bit musl builds
Partial fix for #710 and #980.
2022-09-19 21:10:05 +02:00
David Peter 9ea882d7cc Fix clippy suggestions 2022-09-11 21:29:43 +02:00
Thayne McCombs f63c463471 update dependencies 2022-09-11 21:27:42 +02:00
David Peter bd44c82404 Add CHANGELOG entry 2022-09-11 21:05:06 +02:00
David Peter 2569317ee8 Revert changes in help text, add /issues in manpage 2022-09-11 21:05:06 +02:00
Scott Baker cb91a5c848 Simplify some whitespace on lines 2022-09-11 21:05:06 +02:00
Scott Baker fcf8099587 Add Github URL to --help also 2022-09-11 21:05:06 +02:00
Scott Baker 97dfd8b428 Simplify some wording 2022-09-11 21:05:06 +02:00
Scott Baker c127dc89a5 Add a link back the Github page to address #1052 2022-09-11 21:05:06 +02:00
David Peter c48586de08 Add CHANGELOG entry 2022-09-11 20:58:50 +02:00
Marie Katrine Ekeberg d6b1d16bfc Use to_string_lossy to avoid relying on Debug-outputs 2022-09-11 20:58:50 +02:00
Marie Katrine Ekeberg 90e860c7a5 Update msrv 2022-09-11 20:58:50 +02:00
Marie Katrine Ekeberg c7370ca82c Simplistic "Command not found" error message 2022-09-11 20:58:50 +02:00
Tavian Barnes cbc6ddeefc Implement --type executable on Windows
Fixes #1051.
2022-09-11 20:46:19 +02:00
Thayne McCombs 45d6f55d3a Bump MSRV to 1.56.1
Because that is what is needed by clap 2
2022-09-09 00:19:44 -06:00
Thayne McCombs 6655356ce3 Upgrade clap to 3.2 2022-09-09 00:18:57 -06:00
Tom Eichlersmith 3e68733c94 add file-type requirement and redirect
as suggested by @sharkdp

Co-authored-by: David Peter <sharkdp@users.noreply.github.com>
2022-09-03 00:09:53 +02:00
Tom Eichlersmith c9db74bfd4 add md5sum example to README
this includes a statement that `fd` will not interlace or garble the terminal output from its multiple threads
2022-09-03 00:09:53 +02:00
David Peter ff77a2e29e Revert "Bump nix from 0.24.2 to 0.25.0"
This reverts commit 448ed6dfe5.
2022-09-02 10:56:48 +02:00
David Peter 19f5de51af Revert "Bump anyhow from 1.0.59 to 1.0.63"
This reverts commit 1550ec3ffc.
2022-09-02 10:56:47 +02:00
David Peter 9dc1b03905 Revert "Bump chrono from 0.4.19 to 0.4.22"
This reverts commit b1296d8557.
2022-09-02 10:56:46 +02:00
dependabot[bot] b1296d8557 Bump chrono from 0.4.19 to 0.4.22
Bumps [chrono](https://github.com/chronotope/chrono) from 0.4.19 to 0.4.22.
- [Release notes](https://github.com/chronotope/chrono/releases)
- [Changelog](https://github.com/chronotope/chrono/blob/v0.4.22/CHANGELOG.md)
- [Commits](https://github.com/chronotope/chrono/compare/v0.4.19...v0.4.22)

---
updated-dependencies:
- dependency-name: chrono
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-09-02 09:13:03 +02:00
dependabot[bot] 1550ec3ffc Bump anyhow from 1.0.59 to 1.0.63
Bumps [anyhow](https://github.com/dtolnay/anyhow) from 1.0.59 to 1.0.63.
- [Release notes](https://github.com/dtolnay/anyhow/releases)
- [Commits](https://github.com/dtolnay/anyhow/compare/1.0.59...1.0.63)

---
updated-dependencies:
- dependency-name: anyhow
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-09-02 09:11:56 +02:00
dependabot[bot] 448ed6dfe5 Bump nix from 0.24.2 to 0.25.0
Bumps [nix](https://github.com/nix-rust/nix) from 0.24.2 to 0.25.0.
- [Release notes](https://github.com/nix-rust/nix/releases)
- [Changelog](https://github.com/nix-rust/nix/blob/master/CHANGELOG.md)
- [Commits](https://github.com/nix-rust/nix/compare/v0.24.2...v0.25.0)

---
updated-dependencies:
- dependency-name: nix
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-09-02 09:11:49 +02:00
dependabot[bot] a552b57d36 Bump lscolors from 0.11.0 to 0.12.0
Bumps [lscolors](https://github.com/sharkdp/lscolors) from 0.11.0 to 0.12.0.
- [Release notes](https://github.com/sharkdp/lscolors/releases)
- [Commits](https://github.com/sharkdp/lscolors/compare/v0.11.0...v0.12.0)

---
updated-dependencies:
- dependency-name: lscolors
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-09-02 09:11:31 +02:00
Thayne McCombs e17c592dec Merge pull request #1096 from sharkdp/dependabot/cargo/once_cell-1.13.1
Bump once_cell from 1.13.0 to 1.13.1
2022-09-01 22:12:23 -06:00
Thayne McCombs 441c726d47 Merge pull request #1095 from sharkdp/dependabot/cargo/ctrlc-3.2.3
Bump ctrlc from 3.2.2 to 3.2.3
2022-09-01 22:11:46 -06:00
Thayne McCombs 9ced5675d2 Merge pull request #1094 from sharkdp/dependabot/cargo/libc-0.2.132
Bump libc from 0.2.126 to 0.2.132
2022-09-01 22:10:55 -06:00
dependabot[bot] b984596e3c Bump once_cell from 1.13.0 to 1.13.1
Bumps [once_cell](https://github.com/matklad/once_cell) from 1.13.0 to 1.13.1.
- [Release notes](https://github.com/matklad/once_cell/releases)
- [Changelog](https://github.com/matklad/once_cell/blob/master/CHANGELOG.md)
- [Commits](https://github.com/matklad/once_cell/compare/v1.13.0...v1.13.1)

---
updated-dependencies:
- dependency-name: once_cell
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-09-01 17:05:16 +00:00
dependabot[bot] 9c1b025695 Bump ctrlc from 3.2.2 to 3.2.3
Bumps [ctrlc](https://github.com/Detegr/rust-ctrlc) from 3.2.2 to 3.2.3.
- [Release notes](https://github.com/Detegr/rust-ctrlc/releases)
- [Commits](https://github.com/Detegr/rust-ctrlc/compare/3.2.2...3.2.3)

---
updated-dependencies:
- dependency-name: ctrlc
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-09-01 17:05:13 +00:00
dependabot[bot] 65837a573e Bump libc from 0.2.126 to 0.2.132
Bumps [libc](https://github.com/rust-lang/libc) from 0.2.126 to 0.2.132.
- [Release notes](https://github.com/rust-lang/libc/releases)
- [Commits](https://github.com/rust-lang/libc/compare/0.2.126...0.2.132)

---
updated-dependencies:
- dependency-name: libc
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-09-01 17:05:09 +00:00
Sijmen a0062b9a1b Disable invalid CWD test on Windows 2022-08-10 20:01:21 +02:00
Sijmen 922f127aca Add test when running from non-existent directory
When fd is run from a non-existent directory, but a search path is
passed in the arguments, it should still run. This tests for that.
2022-08-10 20:01:21 +02:00
Sijmen cb6295d025 Move current_directory variable definition closer to usage 2022-08-10 20:01:21 +02:00
Sijmen a5f69579cf Update CHANGELOG 2022-08-10 20:01:21 +02:00
Sijmen fdcbb2f008 Allow working directory to be nonexistent if search path is given
Fixes #1072
2022-08-10 20:01:21 +02:00
Tavian Barnes ac934bd703 Merge pull request #1081 from sharkdp/dependabot/cargo/anyhow-1.0.59
Bump anyhow from 1.0.58 to 1.0.59
2022-08-02 12:45:47 -04:00
Tavian Barnes 3a4bb4f344 Merge pull request #1076 from sharkdp/dependabot/cargo/regex-1.6.0
Bump regex from 1.5.6 to 1.6.0
2022-08-02 12:45:08 -04:00
Tavian Barnes aed3bac71b Merge pull request #1073 from sharkdp/dependabot/cargo/test-case-2.2.1
Bump test-case from 2.1.0 to 2.2.1
2022-08-02 12:44:44 -04:00
dependabot[bot] e76402374f Bump anyhow from 1.0.58 to 1.0.59
Bumps [anyhow](https://github.com/dtolnay/anyhow) from 1.0.58 to 1.0.59.
- [Release notes](https://github.com/dtolnay/anyhow/releases)
- [Commits](https://github.com/dtolnay/anyhow/compare/1.0.58...1.0.59)

---
updated-dependencies:
- dependency-name: anyhow
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-08-02 07:19:43 +00:00
dependabot[bot] 41237e73a5 Bump once_cell from 1.12.0 to 1.13.0
Bumps [once_cell](https://github.com/matklad/once_cell) from 1.12.0 to 1.13.0.
- [Release notes](https://github.com/matklad/once_cell/releases)
- [Changelog](https://github.com/matklad/once_cell/blob/master/CHANGELOG.md)
- [Commits](https://github.com/matklad/once_cell/compare/v1.12.0...v1.13.0)

---
updated-dependencies:
- dependency-name: once_cell
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-08-02 09:18:53 +02:00
dependabot[bot] bfba33a230 Bump filetime from 0.2.16 to 0.2.17
Bumps [filetime](https://github.com/alexcrichton/filetime) from 0.2.16 to 0.2.17.
- [Release notes](https://github.com/alexcrichton/filetime/releases)
- [Commits](https://github.com/alexcrichton/filetime/commits/0.2.17)

---
updated-dependencies:
- dependency-name: filetime
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-08-02 09:18:47 +02:00
Thayne McCombs e8615b88f9 Merge pull request #1078 from sharkdp/dependabot/cargo/nix-0.24.2
Bump nix from 0.24.1 to 0.24.2
2022-08-01 18:57:57 -06:00
dependabot[bot] b1763ba7db Bump regex from 1.5.6 to 1.6.0
Bumps [regex](https://github.com/rust-lang/regex) from 1.5.6 to 1.6.0.
- [Release notes](https://github.com/rust-lang/regex/releases)
- [Changelog](https://github.com/rust-lang/regex/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/regex/compare/1.5.6...1.6.0)

---
updated-dependencies:
- dependency-name: regex
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-08-01 20:11:11 +00:00
dependabot[bot] 6219aa3b26 Bump nix from 0.24.1 to 0.24.2
Bumps [nix](https://github.com/nix-rust/nix) from 0.24.1 to 0.24.2.
- [Release notes](https://github.com/nix-rust/nix/releases)
- [Changelog](https://github.com/nix-rust/nix/blob/master/CHANGELOG.md)
- [Commits](https://github.com/nix-rust/nix/compare/v0.24.1...v0.24.2)

---
updated-dependencies:
- dependency-name: nix
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-08-01 20:10:50 +00:00
Thayne McCombs 218d475cb2 Merge pull request #1077 from sharkdp/dependabot/cargo/lscolors-0.11.0
Bump lscolors from 0.10.0 to 0.11.0
2022-08-01 14:09:58 -06:00
dependabot[bot] 00a1abbf43 Bump lscolors from 0.10.0 to 0.11.0
Bumps [lscolors](https://github.com/sharkdp/lscolors) from 0.10.0 to 0.11.0.
- [Release notes](https://github.com/sharkdp/lscolors/releases)
- [Commits](https://github.com/sharkdp/lscolors/compare/v0.10.0...v0.11.0)

---
updated-dependencies:
- dependency-name: lscolors
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-08-01 17:03:52 +00:00
dependabot[bot] f189d99b98 Bump test-case from 2.1.0 to 2.2.1
Bumps [test-case](https://github.com/frondeus/test-case) from 2.1.0 to 2.2.1.
- [Release notes](https://github.com/frondeus/test-case/releases)
- [Changelog](https://github.com/frondeus/test-case/blob/master/CHANGELOG.md)
- [Commits](https://github.com/frondeus/test-case/compare/v2.1.0...v2.2.1)

---
updated-dependencies:
- dependency-name: test-case
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-08-01 17:03:36 +00:00
SukkaW dc1b4ea720 CI: update macos runner version 2022-07-25 21:31:47 +02:00
Tavian Barnes 1b71425419 Merge pull request #1057 from sharkdp/dependabot/cargo/diff-0.1.13
Bump diff from 0.1.12 to 0.1.13
2022-07-01 14:40:37 -04:00
Tavian Barnes 791a66893e Merge pull request #1055 from sharkdp/dependabot/cargo/globset-0.4.9
Bump globset from 0.4.8 to 0.4.9
2022-07-01 14:40:20 -04:00
Tavian Barnes d298ce15cc Merge pull request #1054 from sharkdp/dependabot/cargo/anyhow-1.0.58
Bump anyhow from 1.0.57 to 1.0.58
2022-07-01 14:39:35 -04:00
dependabot[bot] 7c32932ada Bump diff from 0.1.12 to 0.1.13
Bumps [diff](https://github.com/utkarshkukreti/diff.rs) from 0.1.12 to 0.1.13.
- [Release notes](https://github.com/utkarshkukreti/diff.rs/releases)
- [Commits](https://github.com/utkarshkukreti/diff.rs/compare/0.1.12...0.1.13)

---
updated-dependencies:
- dependency-name: diff
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-07-01 17:04:05 +00:00
dependabot[bot] 777b8a031b Bump globset from 0.4.8 to 0.4.9
Bumps [globset](https://github.com/BurntSushi/ripgrep) from 0.4.8 to 0.4.9.
- [Release notes](https://github.com/BurntSushi/ripgrep/releases)
- [Changelog](https://github.com/BurntSushi/ripgrep/blob/master/CHANGELOG.md)
- [Commits](https://github.com/BurntSushi/ripgrep/compare/globset-0.4.8...globset-0.4.9)

---
updated-dependencies:
- dependency-name: globset
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-07-01 17:03:56 +00:00
dependabot[bot] 4af4c8df44 Bump anyhow from 1.0.57 to 1.0.58
Bumps [anyhow](https://github.com/dtolnay/anyhow) from 1.0.57 to 1.0.58.
- [Release notes](https://github.com/dtolnay/anyhow/releases)
- [Commits](https://github.com/dtolnay/anyhow/compare/1.0.57...1.0.58)

---
updated-dependencies:
- dependency-name: anyhow
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-07-01 17:03:51 +00:00
Thayne McCombs d6e5ee2f63 Merge pull request #1050 from AllanDowney/patch-1
fix(_fd): fix zsh completion file `-V, --version`
2022-06-25 16:44:16 -06:00
allandowney a263b2e58e fix(_fd): fix zsh completion file -V, --version 2022-06-25 22:31:23 +08:00
Jonathan Goren f227bb291a apply workaround for #1003 2022-06-12 08:54:48 +02:00
Jonathan Goren b82e48f6da Update screencast 2022-06-12 08:54:48 +02:00
David Peter c47501ef7c Update jemalloc to 5.3 (jemallocator 0.5)
Benchmarks show that this is a tiny bit faster than what we had
previously.
2022-05-29 11:21:45 +02:00
David Peter 1d2d06ea25 Update dependencies 2022-05-29 11:21:45 +02:00
David Peter 33beb7fc64 Bump version 2022-05-29 11:21:45 +02:00
David Peter 8b96a1e99c Update documentation of --batch-size feature 2022-05-28 22:59:46 +02:00
David Peter f52dec11a4 Update CHANGELOG 2022-05-28 22:50:04 +02:00
David Peter 941f712975 Fix (pointless) clippy suggestion 2022-05-28 22:40:07 +02:00
David Peter 74d6990b4e Use cross to build x86_64-unknown-linux-gnu binaries to relax glibc requirements 2022-05-28 22:39:33 +02:00
David Peter 42e7e9ec64 Use minimal toolchain for clippy runs 2022-05-28 22:39:33 +02:00
Tavian Barnes 40b368e761 exec: Execute batches before they get too long
Fixes #410.
2022-05-28 22:19:47 +02:00
Amir Zolfaghari 16acdeb6ce remove description of --batch-size flag from EXAMPLES subsection
This also removes the indent before "SEE ALSO" to make it a new
subsection
2022-05-21 19:32:41 +02:00
Tavian Barnes 6782c21d52 Merge pull request #1019 from rtzoeller/nix_limit_features
Limit nix features
2022-05-18 10:22:37 -04:00
Ryan Zoeller d00c8ba0d8 Limit nix features
This removes memoffset as an indirect dependency and reduces clean build times slightly.
2022-05-17 22:00:44 -05:00
David Peter de27835264 Merge pull request #812 from yyogo/master
Append trailing path separators to directories
2022-05-15 16:53:20 +02:00
David Peter 41affe18c4 Merge remote-tracking branch 'origin/master' into yyogo/master 2022-05-15 16:31:06 +02:00
David Peter dcde6d358f Add CHANGELOG entry for multi exec 2022-05-15 16:26:44 +02:00
David Peter f57206a3a1 Update help text and man page 2022-05-15 16:22:24 +02:00
Jackson Theel f823eac672 Update CHANGELOG.md 2022-05-15 16:22:24 +02:00
Jackson Theel cbf3f11cf8 Make -u idempotent 2022-05-15 16:22:24 +02:00
Thayne McCombs 306cd99273 Merge pull request #1012 from sharkdp/dependabot/cargo/nix-0.24.1
Bump nix from 0.23.1 to 0.24.1
2022-05-02 09:57:52 -06:00
Thayne McCombs 6fed29eda2 Merge pull request #1013 from sharkdp/dependabot/cargo/libc-0.2.125
Bump libc from 0.2.121 to 0.2.125
2022-05-02 09:57:33 -06:00
dependabot[bot] 4fecbe4ede Bump libc from 0.2.121 to 0.2.125
Bumps [libc](https://github.com/rust-lang/libc) from 0.2.121 to 0.2.125.
- [Release notes](https://github.com/rust-lang/libc/releases)
- [Commits](https://github.com/rust-lang/libc/compare/0.2.121...0.2.125)

---
updated-dependencies:
- dependency-name: libc
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-05-02 04:49:25 +00:00
dependabot[bot] 3ad15721e0 Bump nix from 0.23.1 to 0.24.1
Bumps [nix](https://github.com/nix-rust/nix) from 0.23.1 to 0.24.1.
- [Release notes](https://github.com/nix-rust/nix/releases)
- [Changelog](https://github.com/nix-rust/nix/blob/master/CHANGELOG.md)
- [Commits](https://github.com/nix-rust/nix/compare/v0.23.1...v0.24.1)

---
updated-dependencies:
- dependency-name: nix
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-05-02 04:49:20 +00:00
Thayne McCombs 7ec7c56a01 Merge pull request #1015 from sharkdp/dependabot/cargo/anyhow-1.0.57
Bump anyhow from 1.0.56 to 1.0.57
2022-05-01 22:48:15 -06:00
Thayne McCombs d3dc18ba65 Merge pull request #1014 from sharkdp/dependabot/cargo/regex-1.5.5
Bump regex from 1.5.4 to 1.5.5
2022-05-01 22:47:30 -06:00
Thayne McCombs 8ab6dd3e45 Merge pull request #1016 from sharkdp/dependabot/cargo/ctrlc-3.2.2
Bump ctrlc from 3.2.1 to 3.2.2
2022-05-01 22:46:45 -06:00
dependabot[bot] b311b01336 Bump ctrlc from 3.2.1 to 3.2.2
Bumps [ctrlc](https://github.com/Detegr/rust-ctrlc) from 3.2.1 to 3.2.2.
- [Release notes](https://github.com/Detegr/rust-ctrlc/releases)
- [Commits](https://github.com/Detegr/rust-ctrlc/compare/3.2.1...3.2.2)

---
updated-dependencies:
- dependency-name: ctrlc
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-05-01 23:50:02 +00:00
Thayne McCombs ecfcfe448d Merge pull request #1011 from sharkdp/dependabot/cargo/clap_complete-3.1.3
Bump clap_complete from 3.1.1 to 3.1.3
2022-05-01 17:48:56 -06:00
dependabot[bot] aa5c03834f Bump anyhow from 1.0.56 to 1.0.57
Bumps [anyhow](https://github.com/dtolnay/anyhow) from 1.0.56 to 1.0.57.
- [Release notes](https://github.com/dtolnay/anyhow/releases)
- [Commits](https://github.com/dtolnay/anyhow/compare/1.0.56...1.0.57)

---
updated-dependencies:
- dependency-name: anyhow
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-05-01 17:04:02 +00:00
dependabot[bot] 4269217e28 Bump regex from 1.5.4 to 1.5.5
Bumps [regex](https://github.com/rust-lang/regex) from 1.5.4 to 1.5.5.
- [Release notes](https://github.com/rust-lang/regex/releases)
- [Changelog](https://github.com/rust-lang/regex/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/regex/compare/1.5.4...1.5.5)

---
updated-dependencies:
- dependency-name: regex
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-05-01 17:03:57 +00:00
dependabot[bot] 4e82809624 Bump clap_complete from 3.1.1 to 3.1.3
Bumps [clap_complete](https://github.com/clap-rs/clap) from 3.1.1 to 3.1.3.
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/clap_complete-v3.1.1...clap_complete-v3.1.3)

---
updated-dependencies:
- dependency-name: clap_complete
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-05-01 17:03:39 +00:00
Bost cf4964d0f8 Update README.md
GuixOS installation instructions.
2022-04-20 22:49:52 +02:00
Hang Qian 3f6ff2ca32 Match against reference instead of value 2022-04-02 20:56:38 +02:00
ethsol bbd8c78861 Added installation instructions for RHEL8 2022-04-02 20:55:08 +02:00
ethsol 9d93807512 modification skeleton 2022-04-02 20:55:08 +02:00
ethsol 8a17ca25d6 Added install instructions for RHEL8 and clones
added chown for fd.1.gz, too
2022-04-02 20:55:08 +02:00
ethsol 6faf9ff1e0 Added install instructions for RHEL8 and clones 2022-04-02 20:55:08 +02:00
Tavian Barnes 37697f4ad3 Merge pull request #992 from sharkdp/dependabot/cargo/once_cell-1.10.0
Bump once_cell from 1.9.0 to 1.10.0
2022-04-01 22:12:16 -04:00
Thayne McCombs 36361c39d2 Merge pull request #991 from sharkdp/dependabot/cargo/anyhow-1.0.56
Bump anyhow from 1.0.55 to 1.0.56
2022-04-01 19:27:11 -06:00
Thayne McCombs 4349bfe985 Merge pull request #995 from sharkdp/dependabot/cargo/libc-0.2.121
Bump libc from 0.2.119 to 0.2.121
2022-04-01 15:07:48 -06:00
Thayne McCombs 2f9a858b2f Merge pull request #993 from sharkdp/dependabot/cargo/test-case-2.0.2
Bump test-case from 2.0.0 to 2.0.2
2022-04-01 13:49:14 -06:00
Thayne McCombs d64fca0a32 Merge pull request #994 from sharkdp/dependabot/cargo/clap-3.1.8
Bump clap from 3.1.5 to 3.1.8
2022-04-01 13:45:48 -06:00
dependabot[bot] 803ea0a0b1 Bump libc from 0.2.119 to 0.2.121
Bumps [libc](https://github.com/rust-lang/libc) from 0.2.119 to 0.2.121.
- [Release notes](https://github.com/rust-lang/libc/releases)
- [Commits](https://github.com/rust-lang/libc/compare/0.2.119...0.2.121)

---
updated-dependencies:
- dependency-name: libc
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-04-01 17:03:53 +00:00
dependabot[bot] cfa768755f Bump clap from 3.1.5 to 3.1.8
Bumps [clap](https://github.com/clap-rs/clap) from 3.1.5 to 3.1.8.
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/v3.1.5...v3.1.8)

---
updated-dependencies:
- dependency-name: clap
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-04-01 17:03:48 +00:00
dependabot[bot] 9dcfa4a662 Bump test-case from 2.0.0 to 2.0.2
Bumps [test-case](https://github.com/frondeus/test-case) from 2.0.0 to 2.0.2.
- [Release notes](https://github.com/frondeus/test-case/releases)
- [Changelog](https://github.com/frondeus/test-case/blob/master/CHANGELOG.md)
- [Commits](https://github.com/frondeus/test-case/compare/v2.0.0...v2.0.2)

---
updated-dependencies:
- dependency-name: test-case
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-04-01 17:03:43 +00:00
dependabot[bot] 7ab29e17a3 Bump once_cell from 1.9.0 to 1.10.0
Bumps [once_cell](https://github.com/matklad/once_cell) from 1.9.0 to 1.10.0.
- [Release notes](https://github.com/matklad/once_cell/releases)
- [Changelog](https://github.com/matklad/once_cell/blob/master/CHANGELOG.md)
- [Commits](https://github.com/matklad/once_cell/compare/v1.9.0...v1.10.0)

---
updated-dependencies:
- dependency-name: once_cell
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-04-01 17:03:38 +00:00
dependabot[bot] 7d2740d6cd Bump anyhow from 1.0.55 to 1.0.56
Bumps [anyhow](https://github.com/dtolnay/anyhow) from 1.0.55 to 1.0.56.
- [Release notes](https://github.com/dtolnay/anyhow/releases)
- [Commits](https://github.com/dtolnay/anyhow/compare/1.0.55...1.0.56)

---
updated-dependencies:
- dependency-name: anyhow
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-04-01 17:03:35 +00:00
Jonathan Goren 60c14b1af2 make DirEntry Ord 2022-03-16 18:38:16 +02:00
Jonathan Goren 47e30d3d4a add actual path separator value to config 2022-03-16 18:13:05 +02:00
Thayne McCombs 38b84d08d7 Fix mistakes from resolving conflicts 2022-03-15 01:40:05 -06:00
Thayne McCombs 50c0fa812f Merge branch 'master' into master 2022-03-15 01:20:35 -06:00
Thayne McCombs 3e201de9b0 Merge pull request #983 from vegerot/patch-1
📝 Update README formatting
2022-03-14 12:25:40 -06:00
Max Coplan db2fd00c4a 📝 Update README formatting
Probably meant to use `code styling` instead of *italicizing*
2022-03-14 10:44:36 -07:00
Thayne McCombs c577b0838b Error out if no args provided to --exec or --exec-batch
Accepting multiple occurances means we need to check this ourselves. See
https://github.com/clap-rs/clap/issues/3542.
2022-03-08 10:15:48 +01:00
Thayne McCombs 5a12a5e421 Use full names in command.rs 2022-03-08 10:15:48 +01:00
Thayne McCombs 9f39f1d75b Add tests for multiple execs 2022-03-08 10:15:48 +01:00
Thayne McCombs 9fb0c5d372 Group together output from multi exec commands
So that if multiple `--exec` options are given, and the commands are run
in parallel, the buffered output for related commands will be
consecutive.
2022-03-08 10:15:48 +01:00
Thayne McCombs e54e352035 Add description of multiple --exec to man page 2022-03-08 10:15:48 +01:00
Thayne McCombs f27332ee8d Fix clippy lints 2022-03-08 10:15:48 +01:00
Thayne McCombs 0aee9b0fd9 Support multiple --exec instances
and `--exec-batch`.

Fixes: #406
2022-03-08 10:15:48 +01:00
Jacob Chapman 45f490a407 Looks like the COPR repo is no longer maintained
or only available on fedora rawhide
2022-03-08 07:41:46 +01:00
David Peter 0fd7ec5c2a Fix missing clap feature 2022-03-04 08:31:05 +01:00
dependabot[bot] ba473fc925 Bump lscolors from 0.8.1 to 0.9.0
Bumps [lscolors](https://github.com/sharkdp/lscolors) from 0.8.1 to 0.9.0.
- [Release notes](https://github.com/sharkdp/lscolors/releases)
- [Commits](https://github.com/sharkdp/lscolors/compare/v0.8.1...v0.9.0)

---
updated-dependencies:
- dependency-name: lscolors
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-03-04 08:31:05 +01:00
Thayne McCombs 6e5c8d9c20 Fix some clippy warnings 2022-03-04 08:30:41 +01:00
Thayne McCombs b29e87ec30 Merge pull request #976 from tmccombs/regex-doc
Regex doc
2022-03-03 23:23:01 -07:00
Tavian Barnes 7db25a7b23 Merge pull request #967 from tmccombs/clap-upgrade
Clap upgrade
2022-03-03 09:12:19 -05:00
Thayne McCombs bbdb8b9d9e Upgrade to clap 3.1
And fix deprecations
2022-03-02 23:32:04 -07:00
Thayne McCombs 6b8056ca86 More prominently document that fd uses regex by default
Fixes: #948
2022-03-02 00:50:41 -07:00
Thayne McCombs 5ec55eed96 Merge pull request #975 from sharkdp/dependabot/cargo/test-case-2.0.0
Bump test-case from 1.2.1 to 2.0.0
2022-03-02 00:28:07 -07:00
dependabot[bot] efedd9c4e0 Bump test-case from 1.2.1 to 2.0.0
Bumps [test-case](https://github.com/frondeus/test-case) from 1.2.1 to 2.0.0.
- [Release notes](https://github.com/frondeus/test-case/releases)
- [Changelog](https://github.com/frondeus/test-case/blob/master/CHANGELOG.md)
- [Commits](https://github.com/frondeus/test-case/compare/v1.2.1...v2.0.0)

---
updated-dependencies:
- dependency-name: test-case
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-03-01 20:29:59 +00:00
dependabot[bot] 4e0b193ab6 Bump anyhow from 1.0.53 to 1.0.55
Bumps [anyhow](https://github.com/dtolnay/anyhow) from 1.0.53 to 1.0.55.
- [Release notes](https://github.com/dtolnay/anyhow/releases)
- [Commits](https://github.com/dtolnay/anyhow/compare/1.0.53...1.0.55)

---
updated-dependencies:
- dependency-name: anyhow
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-03-01 21:28:23 +01:00
dependabot[bot] 1ac2c38b6b Bump libc from 0.2.116 to 0.2.119
Bumps [libc](https://github.com/rust-lang/libc) from 0.2.116 to 0.2.119.
- [Release notes](https://github.com/rust-lang/libc/releases)
- [Commits](https://github.com/rust-lang/libc/compare/0.2.116...0.2.119)

---
updated-dependencies:
- dependency-name: libc
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-03-01 21:27:50 +01:00
Thayne McCombs eb111aa835 Merge pull request #954 from sharkdp/dependabot/cargo/anyhow-1.0.53
Bump anyhow from 1.0.52 to 1.0.53
2022-02-01 12:25:48 -07:00
Thayne McCombs 25b6ac8b3e Merge pull request #953 from sharkdp/dependabot/cargo/clap_complete-3.0.5
Bump clap_complete from 3.0.2 to 3.0.5
2022-02-01 12:25:33 -07:00
Thayne McCombs cdd771a018 Merge pull request #952 from sharkdp/dependabot/cargo/clap-3.0.13
Bump clap from 3.0.5 to 3.0.13
2022-02-01 12:25:23 -07:00
Thayne McCombs ae861f7e4b Merge pull request #955 from sharkdp/dependabot/cargo/libc-0.2.116
Bump libc from 0.2.112 to 0.2.116
2022-02-01 12:25:10 -07:00
dependabot[bot] 43f683469b Bump libc from 0.2.112 to 0.2.116
Bumps [libc](https://github.com/rust-lang/libc) from 0.2.112 to 0.2.116.
- [Release notes](https://github.com/rust-lang/libc/releases)
- [Commits](https://github.com/rust-lang/libc/compare/0.2.112...0.2.116)

---
updated-dependencies:
- dependency-name: libc
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-02-01 17:03:47 +00:00
dependabot[bot] 2395e7cac5 Bump anyhow from 1.0.52 to 1.0.53
Bumps [anyhow](https://github.com/dtolnay/anyhow) from 1.0.52 to 1.0.53.
- [Release notes](https://github.com/dtolnay/anyhow/releases)
- [Commits](https://github.com/dtolnay/anyhow/compare/1.0.52...1.0.53)

---
updated-dependencies:
- dependency-name: anyhow
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-02-01 17:03:42 +00:00
dependabot[bot] abaed6686d Bump clap_complete from 3.0.2 to 3.0.5
Bumps [clap_complete](https://github.com/clap-rs/clap) from 3.0.2 to 3.0.5.
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/clap_complete-v3.0.2...clap_complete-v3.0.5)

---
updated-dependencies:
- dependency-name: clap_complete
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-02-01 17:03:39 +00:00
dependabot[bot] 1844ed6b8c Bump clap from 3.0.5 to 3.0.13
Bumps [clap](https://github.com/clap-rs/clap) from 3.0.5 to 3.0.13.
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/clap_complete-v3.0.5...v3.0.13)

---
updated-dependencies:
- dependency-name: clap
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-02-01 17:03:32 +00:00
David Peter 9f5ed8534e Add temporary version of logo 2022-01-29 16:53:13 +01:00
David Peter c48d02e0a9 Bump version to v8.3.2 2022-01-29 16:49:09 +01:00
David Peter b3399c239b Add clippy run in CI/CD 2022-01-29 13:01:46 +01:00
David Peter 4377954cf9 Add tavianator sponsoring link 2022-01-29 12:51:16 +01:00
Thayne McCombs 125cb81a5b Merge pull request #929 from tmccombs/clap-upgrade
Upgrade to clap 3.0
2022-01-08 00:24:43 -07:00
Thayne McCombs 2e9be3e3f5 Bump MSRV to 1.54
Because that is needed by clap 3.0
2022-01-07 23:56:03 -07:00
Thayne McCombs 43f276e073 Upgrade to clap 3.0
This wasn't backwards compatible so required some more substantial
changes.
2022-01-07 23:56:03 -07:00
David Peter ec38e23d58 Add CHANGELOG entry 2022-01-07 17:39:24 +01:00
Tavian Barnes 56060a5b6b Merge pull request #936 from gbarta/fix_fd_issue_931
Update normpath to 0.3.2 to fix issue #931
2022-01-07 10:14:08 -05:00
Tavian Barnes 03548a847f Merge pull request #937 from tmccombs/less-flaky-tests
Less flaky tests
2022-01-07 10:11:54 -05:00
Thayne McCombs 06cd391845 Change test_opposing to be less flaky.
Make it resilient to the different invocations returning results in
different orders.
2022-01-07 03:10:09 -07:00
Gabriel Barta ef4abfc1d2 Update normpath to 0.3.2 to fix issue #931 2022-01-07 10:53:03 +11:00
David Peter 72895675ef Bump version to v8.3.1 2022-01-06 14:20:02 +01:00
David Peter ccc6cc54b2 Merge pull request #916 from tavianator/revert-crossbeam
Revert "Switch from std::sync::mpsc to crossbeam-channel"
2022-01-06 13:59:16 +01:00
David Peter aa1c0250b7 Update CHANGELOG 2022-01-06 13:59:04 +01:00
Thayne McCombs d5f740c9d3 Add a couple of options to zsh completion
--no-ignore-parent and --strip-cwd-prefix
2022-01-06 13:52:49 +01:00
David Peter aa6933d9e3 Merge branch 'master' into revert-crossbeam 2022-01-06 13:49:58 +01:00
dependabot[bot] c800c79976 Bump anyhow from 1.0.51 to 1.0.52
Bumps [anyhow](https://github.com/dtolnay/anyhow) from 1.0.51 to 1.0.52.
- [Release notes](https://github.com/dtolnay/anyhow/releases)
- [Commits](https://github.com/dtolnay/anyhow/compare/1.0.51...1.0.52)

---
updated-dependencies:
- dependency-name: anyhow
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-01-03 21:45:55 +01:00
dependabot[bot] 075c1af7d8 Bump num_cpus from 1.13.0 to 1.13.1 (#927)
Bumps [num_cpus](https://github.com/seanmonstar/num_cpus) from 1.13.0 to 1.13.1.
- [Release notes](https://github.com/seanmonstar/num_cpus/releases)
- [Changelog](https://github.com/seanmonstar/num_cpus/blob/master/CHANGELOG.md)
- [Commits](https://github.com/seanmonstar/num_cpus/compare/v1.13.0...v1.13.1)

---
updated-dependencies:
- dependency-name: num_cpus
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-01-03 09:47:20 -07:00
dependabot[bot] fbebaa299d Bump version_check from 0.9.3 to 0.9.4 (#921)
Bumps [version_check](https://github.com/SergioBenitez/version_check) from 0.9.3 to 0.9.4.
- [Release notes](https://github.com/SergioBenitez/version_check/releases)
- [Commits](https://github.com/SergioBenitez/version_check/compare/v0.9.3...v0.9.4)

---
updated-dependencies:
- dependency-name: version_check
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-01-03 09:26:25 -07:00
dependabot[bot] f6bbeb35f5 Bump nix from 0.23.0 to 0.23.1 (#922)
Bumps [nix](https://github.com/nix-rust/nix) from 0.23.0 to 0.23.1.
- [Release notes](https://github.com/nix-rust/nix/releases)
- [Changelog](https://github.com/nix-rust/nix/blob/master/CHANGELOG.md)
- [Commits](https://github.com/nix-rust/nix/compare/v0.23.0...v0.23.1)

---
updated-dependencies:
- dependency-name: nix
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-01-03 09:26:00 -07:00
dependabot[bot] 0dc49b07a0 Bump libc from 0.2.108 to 0.2.112 (#924)
Bumps [libc](https://github.com/rust-lang/libc) from 0.2.108 to 0.2.112.
- [Release notes](https://github.com/rust-lang/libc/releases)
- [Commits](https://github.com/rust-lang/libc/compare/0.2.108...0.2.112)

---
updated-dependencies:
- dependency-name: libc
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-01-03 09:25:33 -07:00
dependabot[bot] c2e5075316 Bump once_cell from 1.8.0 to 1.9.0 (#925)
Bumps [once_cell](https://github.com/matklad/once_cell) from 1.8.0 to 1.9.0.
- [Release notes](https://github.com/matklad/once_cell/releases)
- [Changelog](https://github.com/matklad/once_cell/blob/master/CHANGELOG.md)
- [Commits](https://github.com/matklad/once_cell/compare/v1.8.0...v1.9.0)

---
updated-dependencies:
- dependency-name: once_cell
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-01-03 09:25:06 -07:00
Tavian Barnes e7f192f1c6 Revert "Switch from std::sync::mpsc to crossbeam-channel"
@sharkdp [noticed][1] a quite severe performance degredation due to this
change.  Switch back to std::sync::mpsc until we can fix the performance
regression.

This reverts commit a4bb734482.

[1]: https://github.com/sharkdp/fd/pull/895#issuecomment-1002099369
2021-12-28 12:11:32 -05:00
Yujia Qiao e5145ffb98 fix: disable jemalloc on risc-v target
`jemallocator` does not support risc-v and fails the compilation.
This fixes build for risc-v.
2021-12-28 17:38:28 +01:00
Ryan Lue 30add71233 Update manpage to match --help output
Three command line options were added in late 2021
that never made their way into the manpage:

* f8ae334 introduced --no-ignore-parent;
* bf9e6fd introduced --strip-prefix
  (later renamed strip-cwd-prefix); and
* 17dd2a6 introduced --batch-size.

This commit updates the manpage content to reflect
the auto-generated output of `fd --help`.
2021-12-28 14:21:10 +01:00
Thayne McCombs dade9736ac Add entry to changelog for no-ignore-vcs fix
I forgot to add it when I made the PR.

See commit 8bdbbd8e0cbe8ba010ce5c7f863e23af44386741
2021-12-28 14:20:25 +01:00
eatradish 639ed34791 Allow use to disable jemalloc
jemalloc will require special flags on devices where page size is not 4K. With default options fd will not work on a Mac Mini M1 where hardware only allow 16K paging size. This commit will allow the user to turn off jemalloc (using --no-default-features) under special circumstances where jemalloc is not usable.
2021-12-28 14:15:45 +01:00
David Peter da7ea79034 Use correct description for Debian package, closes #915 2021-12-28 14:00:36 +01:00
Thayne McCombs b7e077320d Fix logic for --no-ignore-parent (#908)
Make sure that using `--no-ignore-vcs` or `--no-ignore` don't also
enable `--no-ignore-parent`. So that if `--no-ignore-vcs` is enabled, it
continues to respect .fdignore and .ignore in the parent directories.

Fixes: #907
Fixes: #901
2021-12-22 23:38:00 -07:00
Jonathan Goren 22dbed0545 convert to path instead of cloning in batch exec
update changelog
2021-12-11 15:43:53 +02:00
Jonathan Goren 3dc61b5f28 rename module entry -> dir_entry 2021-12-11 15:43:52 +02:00
Jonathan Goren 1153e3e155 tests: fix for trailing slashes 2021-12-11 15:43:24 +02:00
Jonathan Goren a26bd3232c append trailing slash to folders
update changelog
2021-12-11 15:43:23 +02:00
Jonathan Goren 813a802b2c send DirEntries to output instead of just path 2021-12-11 15:42:07 +02:00
Tavian Barnes 81669f4c10 Merge pull request #902 from tavianator/quit-senders
Quit senders more aggressively
2021-12-10 13:56:28 -05:00
Thayne McCombs 5ea7cb7a05 A couple of minor recommendations from the Code Quality action (#903)
use derive for a Default impl, and use a function directly instead of
needlessly wrapping it in a closure
2021-12-07 10:04:17 -07:00
Tavian Barnes 4baefb2f5a walk: Split wants_to_quit into two separate flags
quit_flag is now used to quit the sender threads for any reason, either
due to an interrupt or because the receiver is done.

interrupt_flag is used specifically for ^C interrupts, and causes the
receiver to stop between printing paths, to avoid unfinished escape
sequences when colors are being used.

Fixes #867.
2021-12-05 12:06:49 -05:00
Tavian Barnes 97a8825b00 output: Remove wants_to_quit handling from print_entry()
Since we only check it once per path now, we might as well just check it
before printing anything.
2021-12-05 12:06:49 -05:00
Tavian Barnes 7fe4bfaacb Merge pull request #895 from tavianator/receiver-buffer
walk: Encapsulate the buffering behavior in a struct
2021-12-05 11:56:22 -05:00
dependabot[bot] 1a6638ba23 Bump anyhow from 1.0.48 to 1.0.51 (#899)
Bumps [anyhow](https://github.com/dtolnay/anyhow) from 1.0.48 to 1.0.51.
- [Release notes](https://github.com/dtolnay/anyhow/releases)
- [Commits](https://github.com/dtolnay/anyhow/compare/1.0.48...1.0.51)

---
updated-dependencies:
- dependency-name: anyhow
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-12-01 13:58:36 -07:00
dependabot[bot] 8772708aa7 Bump clap from 2.33.3 to 2.34.0 (#900)
Bumps [clap](https://github.com/clap-rs/clap) from 2.33.3 to 2.34.0.
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/commits)

---
updated-dependencies:
- dependency-name: clap
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-12-01 13:58:05 -07:00
Tavian Barnes a4bb734482 Switch from std::sync::mpsc to crossbeam-channel
This lets us avoid https://github.com/rust-lang/rust/issues/39364, which
could potentially be seen now that we're using recv_timeout().
2021-12-01 10:24:58 -05:00
Alexander Sieg 5fbfdfadb8 fix: disable jemalloc on FreeBSD
As jemalloc is the default system allocator on FreeBSD and the
jemalloc-sys crate failes to compile on FreeBSD, this fixes the build on
FreeBSD.
2021-11-28 17:42:15 +01:00
Tavian Barnes 16ae03c3b4 walk: Encapsulate the buffering behavior in a struct
The new ReceiverBuffer struct allows us to factor out the receiver
implementation into a number of helper methods.  The new implementation
uses rx.{recv,recv_timeout} instead of a for loop, which enables us to
switch to streaming mode at the right time without waiting for more
results.

Fixes #868.
2021-11-27 12:56:22 -05:00
40 changed files with 3613 additions and 2174 deletions
+1
View File
@@ -0,0 +1 @@
github: [sharkdp, tavianator]
+1
View File
@@ -9,6 +9,7 @@ body:
Please check out the [troubleshooting section](https://github.com/sharkdp/fd#troubleshooting) first.
- type: checkboxes
attributes:
label: Checks
options:
- label: I have read the troubleshooting section and still think this is a bug.
required: true
+33 -22
View File
@@ -1,7 +1,7 @@
name: CICD
env:
MIN_SUPPORTED_RUST_VERSION: "1.53.0"
MIN_SUPPORTED_RUST_VERSION: "1.60.0"
CICD_INTERMEDIATES_DIR: "_cicd-intermediates"
on:
@@ -46,12 +46,19 @@ jobs:
steps:
- name: Checkout source code
uses: actions/checkout@v2
- name: Install rust toolchain (v${{ env.MIN_SUPPORTED_RUST_VERSION }})
uses: actions-rs/toolchain@v1
with:
toolchain: ${{ env.MIN_SUPPORTED_RUST_VERSION }}
default: true
profile: minimal # minimal component installation (ie, no documentation)
components: clippy
profile: minimal
- name: Run clippy (on minimum supported rust version to prevent warnings we can't fix)
uses: actions-rs/cargo@v1
with:
command: clippy
args: --locked --all-targets --all-features
- name: Run tests
uses: actions-rs/cargo@v1
with:
@@ -70,9 +77,9 @@ jobs:
- { os: ubuntu-20.04, target: aarch64-unknown-linux-gnu , use-cross: true }
- { os: ubuntu-20.04, target: i686-unknown-linux-gnu , use-cross: true }
- { os: ubuntu-20.04, target: i686-unknown-linux-musl , use-cross: true }
- { os: ubuntu-20.04, target: x86_64-unknown-linux-gnu }
- { os: ubuntu-20.04, target: x86_64-unknown-linux-gnu , use-cross: true }
- { os: ubuntu-20.04, target: x86_64-unknown-linux-musl , use-cross: true }
- { os: macos-10.15 , target: x86_64-apple-darwin }
- { os: macos-latest , target: x86_64-apple-darwin }
# - { os: windows-2019, target: i686-pc-windows-gnu } ## disabled; error: linker `i686-w64-mingw32-gcc` not found
- { os: windows-2019, target: i686-pc-windows-msvc }
- { os: windows-2019, target: x86_64-pc-windows-gnu }
@@ -155,8 +162,8 @@ jobs:
fi
# Let subsequent steps know where to find the (stripped) bin
echo ::set-output name=BIN_PATH::${BIN_PATH}
echo ::set-output name=BIN_NAME::${BIN_NAME}
echo "BIN_PATH=${BIN_PATH}" >> $GITHUB_OUTPUT
echo "BIN_NAME=${BIN_NAME}" >> $GITHUB_OUTPUT
- name: Set testing options
id: test-options
@@ -165,7 +172,7 @@ jobs:
# test only library unit tests and binary for arm-type targets
unset CARGO_TEST_OPTIONS
unset CARGO_TEST_OPTIONS ; case ${{ matrix.job.target }} in arm-* | aarch64-*) CARGO_TEST_OPTIONS="--bin ${PROJECT_NAME}" ;; esac;
echo ::set-output name=CARGO_TEST_OPTIONS::${CARGO_TEST_OPTIONS}
echo "CARGO_TEST_OPTIONS=${CARGO_TEST_OPTIONS}" >> $GITHUB_OUTPUT
- name: Run tests
uses: actions-rs/cargo@v1
@@ -174,6 +181,11 @@ jobs:
command: test
args: --locked --target=${{ matrix.job.target }} ${{ steps.test-options.outputs.CARGO_TEST_OPTIONS}}
- name: Generate completions
id: completions
shell: bash
run: make completions
- name: Create tarball
id: package
shell: bash
@@ -181,12 +193,11 @@ jobs:
PKG_suffix=".tar.gz" ; case ${{ matrix.job.target }} in *-pc-windows-*) PKG_suffix=".zip" ;; esac;
PKG_BASENAME=${PROJECT_NAME}-v${PROJECT_VERSION}-${{ matrix.job.target }}
PKG_NAME=${PKG_BASENAME}${PKG_suffix}
echo ::set-output name=PKG_NAME::${PKG_NAME}
echo "PKG_NAME=${PKG_NAME}" >> $GITHUB_OUTPUT
PKG_STAGING="${{ env.CICD_INTERMEDIATES_DIR }}/package"
ARCHIVE_DIR="${PKG_STAGING}/${PKG_BASENAME}/"
mkdir -p "${ARCHIVE_DIR}"
mkdir -p "${ARCHIVE_DIR}/autocomplete"
# Binary
cp "${{ steps.strip.outputs.BIN_PATH }}" "$ARCHIVE_DIR"
@@ -198,10 +209,7 @@ jobs:
cp "README.md" "LICENSE-MIT" "LICENSE-APACHE" "CHANGELOG.md" "$ARCHIVE_DIR"
# Autocompletion files
cp 'target/${{ matrix.job.target }}/release/build/${{ env.PROJECT_NAME }}'*/out/'${{ env.PROJECT_NAME }}.bash' "$ARCHIVE_DIR/autocomplete/"
cp 'target/${{ matrix.job.target }}/release/build/${{ env.PROJECT_NAME }}'*/out/'${{ env.PROJECT_NAME }}.fish' "$ARCHIVE_DIR/autocomplete/"
cp 'target/${{ matrix.job.target }}/release/build/${{ env.PROJECT_NAME }}'*/out/'_${{ env.PROJECT_NAME }}.ps1' "$ARCHIVE_DIR/autocomplete/"
cp 'contrib/completion/_fd' "$ARCHIVE_DIR/autocomplete/"
cp -r autocomplete "${ARCHIVE_DIR}"
# base compressed package
pushd "${PKG_STAGING}/" >/dev/null
@@ -212,7 +220,7 @@ jobs:
popd >/dev/null
# Let subsequent steps know where to find the compressed package
echo ::set-output name=PKG_PATH::"${PKG_STAGING}/${PKG_NAME}"
echo "PKG_PATH=${PKG_STAGING}/${PKG_NAME}" >> $GITHUB_OUTPUT
- name: Create Debian package
id: debian-package
@@ -239,7 +247,7 @@ jobs:
esac;
DPKG_NAME="${DPKG_BASENAME}_${DPKG_VERSION}_${DPKG_ARCH}.deb"
echo ::set-output name=DPKG_NAME::${DPKG_NAME}
echo "DPKG_NAME=${DPKG_NAME}" >> $GITHUB_OUTPUT
# Binary
install -Dm755 "${{ steps.strip.outputs.BIN_PATH }}" "${DPKG_DIR}/usr/bin/${{ steps.strip.outputs.BIN_NAME }}"
@@ -249,9 +257,9 @@ jobs:
gzip -n --best "${DPKG_DIR}/usr/share/man/man1/${{ env.PROJECT_NAME }}.1"
# Autocompletion files
install -Dm644 'target/${{ matrix.job.target }}/release/build/${{ env.PROJECT_NAME }}'*/out/'${{ env.PROJECT_NAME }}.bash' "${DPKG_DIR}/usr/share/bash-completion/completions/${{ env.PROJECT_NAME }}"
install -Dm644 'target/${{ matrix.job.target }}/release/build/${{ env.PROJECT_NAME }}'*/out/'${{ env.PROJECT_NAME }}.fish' "${DPKG_DIR}/usr/share/fish/vendor_completions.d/${{ env.PROJECT_NAME }}.fish"
install -Dm644 'contrib/completion/_fd' "${DPKG_DIR}/usr/share/zsh/vendor-completions/_${{ env.PROJECT_NAME }}"
install -Dm644 'autocomplete/fd.bash' "${DPKG_DIR}/usr/share/bash-completion/completions/${{ env.PROJECT_NAME }}"
install -Dm644 'autocomplete/fd.fish' "${DPKG_DIR}/usr/share/fish/vendor_completions.d/${{ env.PROJECT_NAME }}.fish"
install -Dm644 'autocomplete/_fd' "${DPKG_DIR}/usr/share/zsh/vendor-completions/_${{ env.PROJECT_NAME }}"
# README and LICENSE
install -Dm644 "README.md" "${DPKG_DIR}/usr/share/doc/${DPKG_BASENAME}/README.md"
@@ -313,12 +321,15 @@ jobs:
Architecture: ${DPKG_ARCH}
Provides: ${{ env.PROJECT_NAME }}
Conflicts: ${DPKG_CONFLICTS}
Description: cat(1) clone with wings.
A cat(1) clone with syntax highlighting and Git integration.
Description: simple, fast and user-friendly alternative to find
fd is a program to find entries in your filesystem.
It is a simple, fast and user-friendly alternative to find.
While it does not aim to support all of finds powerful functionality, it provides
sensible (opinionated) defaults for a majority of use cases.
EOF
DPKG_PATH="${DPKG_STAGING}/${DPKG_NAME}"
echo ::set-output name=DPKG_PATH::${DPKG_PATH}
echo "DPKG_PATH=${DPKG_PATH}" >> $GITHUB_OUTPUT
# build dpkg
fakeroot dpkg-deb --build "${DPKG_DIR}" "${DPKG_PATH}"
@@ -341,7 +352,7 @@ jobs:
shell: bash
run: |
unset IS_RELEASE ; if [[ $GITHUB_REF =~ ^refs/tags/v[0-9].* ]]; then IS_RELEASE='true' ; fi
echo ::set-output name=IS_RELEASE::${IS_RELEASE}
echo "IS_RELEASE=${IS_RELEASE}" >> $GITHUB_OUTPUT
- name: Publish archives and packages
uses: softprops/action-gh-release@v1
+1
View File
@@ -1,2 +1,3 @@
target/
/autocomplete/
**/*.rs.bk
+83 -5
View File
@@ -1,15 +1,93 @@
# Upcoming release
## Performance improvements
## Features
# v8.5.2
## Bugfixes
- Fix --owner option value parsing, see #1163 and #1164 (@tmccombs)
# v8.5.1
## Bugfixes
- Fix --threads/-j option value parsing, see #1160 and #1162 (@sharkdp)
# v8.5.0
## Features
- `--type executable`/`-t` now works on Windows, see #1051 and #1061 (@tavianator)
## Bugfixes
- Fixed differences between piped / non-piped output. This changes `fd`s behavior back to what we
had before 8.3.0, i.e. there will be no leading `./` prefixes, unless `--exec`/`-x`,
`--exec-batch`/`-X`, or `--print0`/`-0` are used. `--strip-cwd-prefix` can be used to strip that
prefix in those cases. See #1046, #1115, and #1121 (@tavianator)
- `fd` could previously crash with a panic due to a race condition in Rusts standard library
(see https://github.com/rust-lang/rust/issues/39364). This has been fixed by switching to a different
message passing implementation, see #1060 and #1146 (@tavianator)
- `fd`s memory usage will not grow unboundedly on huge directory trees, see #1146 (@tavianator)
- fd returns an error when current working directory does not exist while a search path is
specified, see #1072 (@vijfhoek)
- Improved "command not found" error message, see #1083 and #1109 (@themkat)
- Preserve command exit codes when using `--exec-batch`, see #1136 and #1137 (@amesgen)
## Changes
- No leading `./` prefix for non-interactive results, see above.
- fd now colorizes paths in parallel, significantly improving performance, see #1148 (@tavianator)
- fd can now avoid `stat` syscalls even when colorizing paths, as long as the color scheme doesn't
require metadata, see #1148 (@tavianator)
- The statically linked `musl` versions of `fd` now use `jmalloc`, leading to a significant performance
improvement, see #1062 (@tavianator)
## Other
- Added link back to GitHub in man page and `--help` text, see #1086 (@scottchiefbaker)
- Major update in how `fd` handles command line options internally, see #1067 (@tmccombs)
# v8.4.0
## Features
- Support multiple `--exec <cmd>` instances, see #406 and #960 (@tmccombs)
## Bugfixes
- "Argument list too long" errors can not appear anymore when using `--exec-batch`/`-X`, as the command invocations are automatically batched at the maximum possible size, even if `--batch-size` is not given. See #410 and #1020 (@tavianator)
## Changes
- Directories are now printed with an additional path separator at the end: `foo/bar/`, see #436 and #812 (@yyogo)
- The `-u` flag was changed to be equivalent to `-HI` (previously, a single `-u` was only equivalent to `-I`). Additional `-u` flags are still allowed, but ignored. See #840 and #986 (@jacksontheel)
## Other
- Added installation instructions for RHEL8, see #989 (@ethsol)
# v8.3.2
## Bugfixes
- Invalid absolute path on windows when searching from the drive root, see #931 and #936 (@gbarta)
# v8.3.1
## Bugfixes
- Stop implying `--no-ignore-parent` when `--no-vcs-ignore` is supplied, see #907, #901, #908 (@tmccombs)
- fd no longer waits for the whole traversal if the only matches arrive within max_buffer_time, see #868 and #895 (@tavianator)
- `--max-results=1` now immediately quits after the first result, see #867
- `fd -h` does not panic anymore when stdout is closed, see #897
## Changes
- Disable jemalloc on FreeBSD, see #896 (@xanderio)
- Updated man page, see #912 (@rlue)
- Updated zsh completions, see #932 (@tmccombs)
# v8.3.0
Generated
+581 -131
View File
File diff suppressed because it is too large Load Diff
+25 -14
View File
@@ -16,8 +16,8 @@ license = "MIT/Apache-2.0"
name = "fd-find"
readme = "README.md"
repository = "https://github.com/sharkdp/fd"
version = "8.3.0"
edition= "2018"
version = "8.5.2"
edition= "2021"
[badges.appveyor]
repository = "sharkdp/fd"
@@ -30,49 +30,60 @@ name = "fd"
path = "src/main.rs"
[build-dependencies]
clap = "2.31.2"
version_check = "0.9"
[dependencies]
ansi_term = "0.12"
argmax = "0.3.1"
atty = "0.2"
ignore = "0.4.3"
num_cpus = "1.8"
regex = "1.5.4"
num_cpus = "1.13"
regex = "1.6.0"
regex-syntax = "0.6"
ctrlc = "3.2"
humantime = "2.1"
lscolors = "0.8"
lscolors = "0.12"
globset = "0.4"
anyhow = "1.0"
dirs-next = "2.0"
normpath = "0.3"
normpath = "0.3.2"
chrono = "0.4"
once_cell = "1.8.0"
once_cell = "1.15.0"
crossbeam-channel = "0.5.6"
clap_complete = {version = "4.0", optional = true}
[dependencies.clap]
version = "2.31.3"
features = ["suggestions", "color", "wrap_help"]
version = "4.0.12"
features = ["suggestions", "color", "wrap_help", "cargo", "unstable-grouped", "derive"]
[target.'cfg(unix)'.dependencies]
users = "0.11.0"
nix = "0.23.0"
nix = { version = "0.24.2", default-features = false, features = ["signal"] }
[target.'cfg(all(unix, not(target_os = "redox")))'.dependencies]
libc = "0.2"
[target.'cfg(windows)'.dependencies]
faccess = "0.2.4"
# FIXME: Re-enable jemalloc on macOS
# jemalloc is currently disabled on macOS due to a bug in jemalloc in combination with macOS
# Catalina. See https://github.com/sharkdp/fd/issues/498 for details.
[target.'cfg(all(not(windows), not(target_os = "android"), not(target_os = "macos"), not(target_env = "musl")))'.dependencies]
jemallocator = "0.3.0"
[target.'cfg(all(not(windows), not(target_os = "android"), not(target_os = "macos"), not(target_os = "freebsd"), not(all(target_env = "musl", target_pointer_width = "32")), not(target_arch = "riscv64")))'.dependencies]
jemallocator = {version = "0.5.0", optional = true}
[dev-dependencies]
diff = "0.1"
tempdir = "0.3"
filetime = "0.2"
test-case = "1.2"
test-case = "2.2"
[profile.release]
lto = true
codegen-units = 1
[features]
use-jemalloc = ["jemallocator"]
completions = ["clap_complete"]
base = ["use-jemalloc"]
default = ["use-jemalloc", "completions"]
+1 -1
View File
@@ -1,6 +1,6 @@
MIT License
Copyright (c) 2017-2021 The fd developers
Copyright (c) 2017-present The fd developers
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
+37
View File
@@ -0,0 +1,37 @@
PROFILE=release
EXE=target/$(PROFILE)/fd
prefix=/usr/local
bindir=$(prefix)/bin
datadir=$(prefix)/share
exe_name=fd
$(EXE): Cargo.toml src/**/*.rs
cargo build --profile $(PROFILE)
.PHONY: completions
completions: autocomplete/fd.bash autocomplete/fd.fish autocomplete/fd.ps1 autocomplete/_fd
comp_dir=@mkdir -p autocomplete
autocomplete/fd.bash: $(EXE)
$(comp_dir)
$(EXE) --gen-completions bash > $@
autocomplete/fd.fish: $(EXE)
$(comp_dir)
$(EXE) --gen-completions fish > $@
autocomplete/fd.ps1: $(EXE)
$(comp_dir)
$(EXE) --gen-completions powershell > $@
autocomplete/_fd: contrib/completion/_fd
$(comp_dir)
cp $< $@
install: $(EXE) completions
install -Dm755 $(EXE) $(DESTDIR)$(bindir)/fd
install -Dm644 autocomplete/fd.bash $(DESTDIR)/$(datadir)/bash-completion/completions/$(exe_name)
install -Dm644 autocomplete/fd.fish $(DESTDIR)/$(datadir)/fish/vendor_completions.d/$(exe_name).fish
install -Dm644 autocomplete/_fd $(DESTDIR)/$(datadir)/zsh/site-functions/_$(exe_name)
install -Dm644 doc/fd.1 $(DESTDIR)/$(datadir)/man/man1/$(exe_name).1
+62 -40
View File
@@ -20,7 +20,7 @@ Quick links:
* Intuitive syntax: `fd PATTERN` instead of `find -iname '*PATTERN*'`.
* Regular expression (default) and glob-based patterns.
* [Very fast](#benchmark) due to parallelized directory traversal.
* Uses colors to highlight different file types (same as *ls*).
* Uses colors to highlight different file types (same as `ls`).
* Supports [parallel command execution](#command-execution)
* Smart case: the search is case-insensitive by default. It switches to
case-sensitive if the pattern contains an uppercase
@@ -206,6 +206,13 @@ fd -e jpg -x convert {} {.}.png
Here, `{}` is a placeholder for the search result. `{.}` is the same, without the file extension.
See below for more details on the placeholder syntax.
The terminal output of commands run from parallel threads using `-x` will not be interlaced or garbled,
so `fd -x` can be used to rudimentarily parallelize a task run over many files.
An example of this is calculating the checksum of each individual file within a directory.
```
fd -tf -x md5sum > file_checksums.txt
```
#### Placeholder syntax
The `-x` and `-X` options take a *command template* as a series of arguments (instead of a single string).
@@ -288,39 +295,37 @@ This is the output of `fd -h`. To see the full set of command-line options, use
also includes a much more detailed help text.
```
USAGE:
fd [FLAGS/OPTIONS] [<pattern>] [<path>...]
Usage: fd [OPTIONS] [pattern] [path]...
FLAGS:
-H, --hidden Search hidden files and directories
-I, --no-ignore Do not respect .(git|fd)ignore files
-s, --case-sensitive Case-sensitive search (default: smart case)
-i, --ignore-case Case-insensitive search (default: smart case)
-g, --glob Glob-based search (default: regular expression)
-a, --absolute-path Show absolute instead of relative paths
-l, --list-details Use a long listing format with file metadata
-L, --follow Follow symbolic links
-p, --full-path Search full abs. path (default: filename only)
-h, --help Prints help information
-V, --version Prints version information
Arguments:
[pattern] the search pattern (a regular expression, unless '--glob' is used; optional)
[path]... the root directories for the filesystem search (optional)
OPTIONS:
-d, --max-depth <depth> Set maximum search depth (default: none)
-t, --type <filetype>... Filter by type: file (f), directory (d), symlink (l),
executable (x), empty (e), socket (s), pipe (p)
-e, --extension <ext>... Filter by file extension
-x, --exec <cmd> Execute a command for each search result
-X, --exec-batch <cmd> Execute a command with all search results at once
-E, --exclude <pattern>... Exclude entries that match the given glob pattern
-c, --color <when> When to use colors: never, *auto*, always
-S, --size <size>... Limit results based on the size of files
--changed-within <date|dur> Filter by file modification time (newer than)
--changed-before <date|dur> Filter by file modification time (older than)
-o, --owner <user:group> Filter by owning user and/or group
ARGS:
<pattern> the search pattern (a regular expression, unless '--glob' is used; optional)
<path>... the root directory for the filesystem search (optional)
Options:
-H, --hidden Search hidden files and directories
-I, --no-ignore Do not respect .(git|fd)ignore files
-s, --case-sensitive Case-sensitive search (default: smart case)
-i, --ignore-case Case-insensitive search (default: smart case)
-g, --glob Glob-based search (default: regular expression)
-a, --absolute-path Show absolute instead of relative paths
-l, --list-details Use a long listing format with file metadata
-L, --follow Follow symbolic links
-p, --full-path Search full abs. path (default: filename only)
-d, --max-depth <depth> Set maximum search depth (default: none)
-E, --exclude <pattern> Exclude entries that match the given glob pattern
-t, --type <filetype> Filter by type: file (f), directory (d), symlink (l),
executable (x), empty (e), socket (s), pipe (p)
-e, --extension <ext> Filter by file extension
-S, --size <size> Limit results based on the size of files
--changed-within <date|dur> Filter by file modification time (newer than)
--changed-before <date|dur> Filter by file modification time (older than)
-o, --owner <user:group> Filter by owning user and/or group
-x, --exec <cmd>... Execute a command for each search result
-X, --exec-batch <cmd>... Execute a command with all search results at once
-c, --color <when> When to use colors [default: auto] [possible values: auto,
always, never]
-h, --help Print help information (use `--help` for more detail)
-V, --version Print version information
```
## Benchmark
@@ -535,7 +540,7 @@ Make sure that `$HOME/.local/bin` is in your `$PATH`.
If you use an older version of Ubuntu, you can download the latest `.deb` package from the
[release page](https://github.com/sharkdp/fd/releases) and install it via:
``` bash
sudo dpkg -i fd_8.3.0_amd64.deb # adapt version number and architecture
sudo dpkg -i fd_8.5.2_amd64.deb # adapt version number and architecture
```
### On Debian
@@ -557,12 +562,6 @@ Starting with Fedora 28, you can install `fd` from the official package sources:
dnf install fd-find
```
For older versions, you can use this [Fedora copr](https://copr.fedorainfracloud.org/coprs/keefle/fd/) to install `fd`:
``` bash
dnf copr enable keefle/fd
dnf install fd
```
### On Alpine Linux
You can install [the fd package](https://pkgs.alpinelinux.org/packages?name=fd)
@@ -598,6 +597,22 @@ You can install `fd` via xbps-install:
xbps-install -S fd
```
### On RedHat Enterprise Linux 8 (RHEL8), Almalinux 8, EuroLinux 8 or Rocky Linux 8
Get the latest fd-v*-x86_64-unknown-linux-gnu.tar.gz file from [sharkdp on github](https://github.com/sharkdp/fd/releases)
```
tar xf fd-v*-x86_64-unknown-linux-gnu.tar.gz
chown -R root:root fd-v*-x86_64-unknown-linux-gnu
cd fd-v*-x86_64-unknown-linux-gnu
sudo cp fd /bin
gzip fd.1
chown root:root fd.1.gz
sudo cp fd.1.gz /usr/share/man/man1
sudo cp autocomplete/fd.bash /usr/share/bash-completion/completions/fd
source /usr/share/bash-completion/completions/fd
fd
```
### On macOS
You can install `fd` with [Homebrew](https://formulae.brew.sh/formula/fd):
@@ -624,6 +639,13 @@ Or via [Chocolatey](https://chocolatey.org):
choco install fd
```
### On GuixOS
You can install [the fd package](https://guix.gnu.org/en/packages/fd-8.1.1/) from the official repo:
```
guix install fd
```
### On NixOS / via Nix
You can use the [Nix package manager](https://nixos.org/nix/) to install `fd`:
@@ -652,7 +674,7 @@ With Rust's package manager [cargo](https://github.com/rust-lang/cargo), you can
```
cargo install fd-find
```
Note that rust version *1.53.0* or later is required.
Note that rust version *1.60.0* or later is required.
`make` is also needed for the build.
+1 -19
View File
@@ -1,11 +1,5 @@
use std::fs;
use clap::Shell;
include!("src/app.rs");
fn main() {
let min_version = "1.53";
let min_version = "1.60";
match version_check::is_min_version(min_version) {
Some(true) => {}
@@ -15,16 +9,4 @@ fn main() {
std::process::exit(1);
}
}
let var = std::env::var_os("SHELL_COMPLETIONS_DIR").or_else(|| std::env::var_os("OUT_DIR"));
let outdir = match var {
None => return,
Some(outdir) => outdir,
};
fs::create_dir_all(&outdir).unwrap();
let mut app = build_app();
app.gen_completions("fd", Shell::Bash, &outdir);
app.gen_completions("fd", Shell::Fish, &outdir);
app.gen_completions("fd", Shell::PowerShell, &outdir);
}
+1 -1
View File
@@ -1 +1 @@
msrv = "1.53.0"
msrv = "1.60.0"
+5 -1
View File
@@ -57,6 +57,7 @@ _fd() {
+ no-ignore-partial # some ignore files
"(no-ignore-full --no-ignore-vcs)--no-ignore-vcs[don't respect .gitignore files]"
"!(no-ignore-full --no-global-ignore-file)--no-global-ignore-file[don't respect the global ignore file]"
$no'(no-ignore-full --no-ignore-parent)--no-ignore-parent[]'
+ '(case)' # case-sensitivity
{-s,--case-sensitive}'[perform a case-sensitive search]'
@@ -145,7 +146,7 @@ _fd() {
+ '(about)' # about flags
'(: * -)'{-h,--help}'[display help message]'
'(: * -)'{-v,--version}'[display version information]'
'(: * -)'{-V,--version}'[display version information]'
+ path-sep # set path separator for output
$no'(--path-separator)--path-separator=[set the path separator to use when printing file paths]:path separator'
@@ -154,6 +155,9 @@ _fd() {
$no'(--base-directory)--base-directory=[change the current working directory to the given path]:directory:_files -/'
$no'(*)*--search-path=[set search path (instead of positional <path> arguments)]:directory:_files -/'
+ strip-cwd-prefix
$no'(strip-cwd-prefix exec-cmds)--strip-cwd-prefix[Strip ./ prefix when output is redirected]'
+ args # positional arguments
'1: :_guard "^-*" pattern'
'(--search-path)*:directory:_files -/'
Vendored
+28 -8
View File
@@ -24,6 +24,11 @@ fd \- find entries in the filesystem
.B fd
is a simple, fast and user-friendly alternative to
.BR find (1).
.P
By default
.B fd
uses regular expressions for the pattern. However, this can be changed to use simple glob patterns
with the '\-\-glob' option.
.SH OPTIONS
.TP
.B \-H, \-\-hidden
@@ -53,7 +58,7 @@ The global fd ignore file (usually
The flag can be overridden with '--ignore'.
.TP
.B \-u, \-\-unrestricted
Alias for '--no-ignore'. Can be repeated; '-uu' is an alias for '--no-ignore --hidden'.
Perform an unrestricted search, including ignored and hidden files. This is an alias for '--hidden --no-ignore'.
.TP
.B \-\-no\-ignore\-vcs
Show search results from files and directories that would otherwise be ignored by gitignore files
@@ -66,6 +71,10 @@ git setting, which defaults to
.IR $HOME/.config/git/ignore ).
The flag can be overridden with '--ignore-vcs'.
.TP
.B \-\-no\-ignore\-parent
Show search results from files and directories that would otherwise be ignored by gitignore files in
parent directories.
.TP
.B \-s, \-\-case\-sensitive
Perform a case-sensitive search. By default, fd uses case-insensitive searches, unless the
pattern contains an uppercase character (smart case).
@@ -126,6 +135,10 @@ can be used as an alias.
Enable the display of filesystem errors for situations such as insufficient
permissions or dead symlinks.
.TP
.B \-\-strip-cwd-prefix
By default, relative paths are prefixed with './' when the output goes to a non interactive terminal
(TTY). Use this flag to disable this behaviour.
.TP
.B \-\-one\-file\-system, \-\-mount, \-\-xdev
By default, fd will traverse the file system tree as far as other options dictate. With this flag, fd ensures that it does not descend into a different file system than the one it started in. Comparable to the -mount or -xdev filters of find(1).
.TP
@@ -328,6 +341,9 @@ Note that all subsequent positional arguments are considered to be arguments to
It is therefore recommended to place the \-x/\-\-exec option last. Alternatively, you can supply
a ';' argument to end the argument list and continue with more fd options.
Most shells require ';' to be escaped: '\\;'.
This option can be specified multiple times, in which case all commands are run for each
file found, in the order they are provided. In that case, you must supply a ';' argument for
all but the last commands.
The following placeholders are substituted before the command is executed:
.RS
@@ -381,6 +397,9 @@ basename without file extension
If no placeholder is present, an implicit "{}" at the end is assumed.
Like \-\-exec, this can be used multiple times, in which case each command will be run in
the order given.
Examples:
- Find all test_*.py files and open them in your favorite editor:
@@ -393,7 +412,12 @@ Examples:
fd -e rs -X wc -l
.RE
.TP
.BI "\-\-batch-size " size
Maximum number of arguments to pass to the command given with -X. If the number of results is
greater than the given size, the command given with -X is run again with remaining arguments. A
batch size of zero means there is no limit (default), but note that batching might still happen
due to OS restrictions on the maximum length of command lines.
.SH PATTERN SYNTAX
The regular expression syntax used by fd is documented here:
@@ -431,11 +455,7 @@ $ fd -e py
.TP
.RI "Open all search results with vim:"
$ fd pattern -X vim
.TP
.BI "\-\-batch\-size " size
Pass at most
.I size
arguments to each call to the command given with -X.
.TP
.SH BUGS
Bugs can be reported on GitHub: https://github.com/sharkdp/fd/issues
.SH SEE ALSO
.BR find (1)
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 9.9 KiB

+161
View File
@@ -0,0 +1,161 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="66mm"
height="66mm"
viewBox="0 0 66 66.000001"
version="1.1"
id="svg5"
inkscape:version="1.1 (c4e8f9ed74, 2021-05-24)"
sodipodi:docname="logo.svg"
inkscape:export-filename="/home/shark/Informatik/rust/fd/doc/logo.png"
inkscape:export-xdpi="192.42"
inkscape:export-ydpi="192.42"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<sodipodi:namedview
id="namedview7"
pagecolor="#ffffff"
bordercolor="#999999"
borderopacity="1"
inkscape:pageshadow="0"
inkscape:pageopacity="1"
inkscape:pagecheckerboard="0"
inkscape:document-units="mm"
showgrid="false"
inkscape:zoom="2.1795515"
inkscape:cx="114.47309"
inkscape:cy="176.18304"
inkscape:window-width="1920"
inkscape:window-height="1175"
inkscape:window-x="1920"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="layer1"
showguides="false"
inkscape:guide-bbox="true"
inkscape:snap-global="false"
fit-margin-top="2"
fit-margin-left="2"
fit-margin-right="2"
fit-margin-bottom="2"
lock-margins="true">
<sodipodi:guide
position="26.228232,26.126763"
orientation="0,-1"
id="guide47826" />
<sodipodi:guide
position="25.799494,2.3628924"
orientation="0,-1"
id="guide47828" />
</sodipodi:namedview>
<defs
id="defs2" />
<g
inkscape:label="Ebene 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(-21.358009,-148.28012)">
<g
id="g66267"
transform="matrix(0.84959471,0,0,0.84959471,7.9920783,43.351816)">
<text
xml:space="preserve"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:29.7126px;line-height:0;font-family:'Fira Sans Condensed';-inkscape-font-specification:'Fira Sans Condensed, ';white-space:pre;inline-size:37.3715;fill:#e5e5e5;fill-opacity:1;stroke-width:0.742816"
x="50.647034"
y="173.19841"
id="text50653"
transform="matrix(1.0604862,0,0,1.0604862,-3.3101428,-10.150043)"><tspan
x="50.647034"
y="173.19841"
id="tspan66635"><tspan
style="font-family:'Source Code Pro';-inkscape-font-specification:'Source Code Pro'"
id="tspan66633">fd</tspan></tspan></text>
<text
xml:space="preserve"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:29.7126px;line-height:0;font-family:'Fira Sans Condensed';-inkscape-font-specification:'Fira Sans Condensed, ';white-space:pre;inline-size:37.3715;fill:#00ccff;fill-opacity:0.996078;stroke-width:0.742816"
x="50.647034"
y="173.19841"
id="text1244"
transform="matrix(1.0604862,0,0,1.0604862,-2.8008599,-9.6407599)"><tspan
x="50.647034"
y="173.19841"
id="tspan66639"><tspan
style="font-family:'Source Code Pro';-inkscape-font-specification:'Source Code Pro'"
id="tspan66637">fd</tspan></tspan></text>
<g
id="g47824"
transform="translate(0.1724878,-0.35338542)">
<g
id="g42041">
<path
style="fill:none;stroke:#939dac;stroke-width:1.065;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 28.209616,155.51329 v 15.68758 H 40.83001"
id="path39763"
sodipodi:nodetypes="ccc" />
<path
style="fill:#b7bec8;stroke:#939dac;stroke-width:1.065;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="M 27.869464,161.83767 H 40.261291"
id="path39765" />
</g>
<g
id="g41945"
transform="translate(-1.0583333)">
<path
style="fill:#0088aa;fill-opacity:0.993797;stroke:none;stroke-width:0.265;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 42.164961,159.42939 -1.554274,-1.89462 -1.975227,-0.005 c -0.941841,0.014 -1.165466,0.27232 -1.14085,2.88812 z"
id="path40006"
sodipodi:nodetypes="ccccc" />
<rect
style="fill:#01ccff;fill-opacity:1;stroke:none;stroke-width:2.3;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0.80126;paint-order:stroke fill markers"
id="rect39949"
width="9.4925022"
height="6.2080379"
x="37.492516"
y="158.82776"
ry="0.90871465" />
</g>
<g
id="g41951"
transform="translate(-1.0583334,9.3665773)">
<path
style="fill:#373e48;fill-opacity:0.993797;stroke:none;stroke-width:0.265;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 42.164961,159.42939 -1.554274,-1.89462 -1.975227,-0.005 c -0.941841,0.014 -1.165466,0.27232 -1.14085,2.88812 z"
id="path41947"
sodipodi:nodetypes="ccccc" />
<rect
style="fill:#535d6c;fill-opacity:0.993797;stroke:none;stroke-width:2.3;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0.80126;paint-order:stroke fill markers"
id="rect41949"
width="9.4925022"
height="6.2080379"
x="37.492516"
y="158.82776"
ry="0.90871465" />
</g>
<g
id="g41957"
transform="translate(-14.306994,-6.8962642)">
<path
style="fill:#373e48;fill-opacity:0.993797;stroke:none;stroke-width:0.265;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 42.164961,159.42939 -1.554274,-1.89462 -1.975227,-0.005 c -0.941841,0.014 -1.165466,0.27232 -1.14085,2.88812 z"
id="path41953"
sodipodi:nodetypes="ccccc" />
<rect
style="fill:#535d6c;fill-opacity:0.993797;stroke:none;stroke-width:2.3;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0.80126;paint-order:stroke fill markers"
id="rect41955"
width="9.4925022"
height="6.2080379"
x="37.492516"
y="158.82776"
ry="0.90871465" />
</g>
</g>
<g
id="g65006"
transform="matrix(0.55302761,0,0,0.55302761,66.463548,117.45819)" />
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 6.8 KiB

+66
View File
@@ -0,0 +1,66 @@
# Release checklist
This file can be used as-is, or copied into the GitHub PR description which includes
necessary changes for the upcoming release.
## Version bump
- [ ] Create a new branch for the required changes for this release.
- [ ] Update version in `Cargo.toml`. Run `cargo build` to update `Cargo.lock`.
Make sure to `git add` the `Cargo.lock` changes as well.
- [ ] Find the current min. supported Rust version by running
`grep '^\s*MIN_SUPPORTED_RUST_VERSION' .github/workflows/CICD.yml`.
- [ ] Update the `fd` version and the min. supported Rust version in `README.md`.
- [ ] Update `CHANGELOG.md`. Change the heading of the *"Upcoming release"* section
to the version of this release.
## Pre-release checks and updates
- [ ] Install the latest version (`cargo install --locked -f --path .`) and make
sure that it is available on the `PATH` (`fd --version` should show the
new version).
- [ ] Review `-h`, `--help`, and the `man` page.
- [ ] Run `fd -h` and copy the output to the *"Command-line options"* section in
the README
- [ ] Push all changes and wait for CI to succeed (before continuing with the
next section).
- [ ] Optional: manually test the new features and command-line options described
in the `CHANGELOG.md`.
- [ ] Run `cargo publish --dry-run` to make sure that it will succeed later
(after creating the GitHub release).
## Release
- [ ] Merge your release branch (should be a fast-forward merge).
- [ ] Create a tag and push it: `git tag vX.Y.Z; git push origin tag vX.Y.Z`.
This will trigger the deployment via GitHub Actions.
REMINDER: If your `origin` is a fork, don't forget to push to e.g. `upstream`
instead.
- [ ] Go to https://github.com/sharkdp/fd/releases/new to create the new
release. Select the new tag and also use it as the release title. For the
release notes, copy the corresponding section from `CHANGELOG.md` and
possibly add additional remarks for package maintainers.
Publish the release.
- [ ] Check if the binary deployment works (archives and Debian packages should
appear when the CI run *for the Git tag* has finished).
- [ ] Publish to crates.io by running `cargo publish` in a *clean* repository.
One way to do this is to clone a fresh copy.
## Post-release
- [ ] Prepare a new *"Upcoming release"* section at the top of `CHANGELOG.md`.
Put this at the top:
# Upcoming release
## Features
## Bugfixes
## Changes
## Other
+6 -2
View File
@@ -1,6 +1,8 @@
#!/bin/bash
# Designed to be executed via svg-term from the fd root directory:
# svg-term --command="bash doc/screencast.sh" --out doc/screencast.svg --padding=10
# Then run this (workaround for #1003):
# sed -i '' 's/<text/<text font-size="1.67"/g' doc/screencast.svg
set -e
set -u
@@ -34,9 +36,11 @@ main() {
enter "fd app"
enter "fd sh"
enter "fd fi"
enter "fd sh --type f"
enter "fd fi --type f"
enter "fd --type d"
enter "fd -e md"
+1 -1
View File
File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 115 KiB

After

Width:  |  Height:  |  Size: 124 KiB

-760
View File
@@ -1,760 +0,0 @@
use clap::{crate_version, App, AppSettings, Arg};
pub fn build_app() -> App<'static, 'static> {
let clap_color_setting = if std::env::var_os("NO_COLOR").is_none() {
AppSettings::ColoredHelp
} else {
AppSettings::ColorNever
};
let mut app = App::new("fd")
.version(crate_version!())
.usage("fd [FLAGS/OPTIONS] [<pattern>] [<path>...]")
.setting(clap_color_setting)
.setting(AppSettings::DeriveDisplayOrder)
.after_help(
"Note: `fd -h` prints a short and concise overview while `fd --help` gives all \
details.",
)
.arg(
Arg::with_name("hidden")
.long("hidden")
.short("H")
.overrides_with("hidden")
.help("Search hidden files and directories")
.long_help(
"Include hidden directories and files in the search results (default: \
hidden files and directories are skipped). Files and directories are \
considered to be hidden if their name starts with a `.` sign (dot). \
The flag can be overridden with --no-hidden.",
),
)
.arg(
Arg::with_name("no-hidden")
.long("no-hidden")
.overrides_with("hidden")
.hidden(true)
.long_help(
"Overrides --hidden.",
),
)
.arg(
Arg::with_name("no-ignore")
.long("no-ignore")
.short("I")
.overrides_with("no-ignore")
.help("Do not respect .(git|fd)ignore files")
.long_help(
"Show search results from files and directories that would otherwise be \
ignored by '.gitignore', '.ignore', '.fdignore', or the global ignore file. \
The flag can be overridden with --ignore.",
),
)
.arg(
Arg::with_name("ignore")
.long("ignore")
.overrides_with("no-ignore")
.hidden(true)
.long_help(
"Overrides --no-ignore.",
),
)
.arg(
Arg::with_name("no-ignore-vcs")
.long("no-ignore-vcs")
.overrides_with("no-ignore-vcs")
.hidden_short_help(true)
.help("Do not respect .gitignore files")
.long_help(
"Show search results from files and directories that would otherwise be \
ignored by '.gitignore' files. The flag can be overridden with --ignore-vcs.",
),
)
.arg(
Arg::with_name("ignore-vcs")
.long("ignore-vcs")
.overrides_with("no-ignore-vcs")
.hidden(true)
.long_help(
"Overrides --no-ignore-vcs.",
),
)
.arg(
Arg::with_name("no-ignore-parent")
.long("no-ignore-parent")
.overrides_with("no-ignore-parent")
.hidden_short_help(true)
.help("Do not respect .(git|fd)ignore files in parent directories")
.long_help(
"Show search results from files and directories that would otherwise be \
ignored by '.gitignore', '.ignore', or '.fdignore' files in parent directories.",
),
)
.arg(
Arg::with_name("no-global-ignore-file")
.long("no-global-ignore-file")
.hidden(true)
.help("Do not respect the global ignore file")
.long_help("Do not respect the global ignore file."),
)
.arg(
Arg::with_name("rg-alias-hidden-ignore")
.short("u")
.long("unrestricted")
.overrides_with_all(&["ignore", "no-hidden"])
.multiple(true)
.hidden_short_help(true)
.help("Alias for '--no-ignore', and '--hidden' when given twice")
.long_help(
"Alias for '--no-ignore'. Can be repeated. '-uu' is an alias for \
'--no-ignore --hidden'.",
),
)
.arg(
Arg::with_name("case-sensitive")
.long("case-sensitive")
.short("s")
.overrides_with_all(&["ignore-case", "case-sensitive"])
.help("Case-sensitive search (default: smart case)")
.long_help(
"Perform a case-sensitive search. By default, fd uses case-insensitive \
searches, unless the pattern contains an uppercase character (smart \
case).",
),
)
.arg(
Arg::with_name("ignore-case")
.long("ignore-case")
.short("i")
.overrides_with_all(&["case-sensitive", "ignore-case"])
.help("Case-insensitive search (default: smart case)")
.long_help(
"Perform a case-insensitive search. By default, fd uses case-insensitive \
searches, unless the pattern contains an uppercase character (smart \
case).",
),
)
.arg(
Arg::with_name("glob")
.long("glob")
.short("g")
.conflicts_with("fixed-strings")
.overrides_with("glob")
.help("Glob-based search (default: regular expression)")
.long_help("Perform a glob-based search instead of a regular expression search."),
)
.arg(
Arg::with_name("regex")
.long("regex")
.overrides_with_all(&["glob", "regex"])
.hidden_short_help(true)
.help("Regular-expression based search (default)")
.long_help(
"Perform a regular-expression based search (default). This can be used to \
override --glob.",
),
)
.arg(
Arg::with_name("fixed-strings")
.long("fixed-strings")
.short("F")
.alias("literal")
.overrides_with("fixed-strings")
.hidden_short_help(true)
.help("Treat pattern as literal string instead of regex")
.long_help(
"Treat the pattern as a literal string instead of a regular expression. Note \
that this also performs substring comparison. If you want to match on an \
exact filename, consider using '--glob'.",
),
)
.arg(
Arg::with_name("absolute-path")
.long("absolute-path")
.short("a")
.overrides_with("absolute-path")
.help("Show absolute instead of relative paths")
.long_help(
"Shows the full path starting from the root as opposed to relative paths. \
The flag can be overridden with --relative-path.",
),
)
.arg(
Arg::with_name("relative-path")
.long("relative-path")
.overrides_with("absolute-path")
.hidden(true)
.long_help(
"Overrides --absolute-path.",
),
)
.arg(
Arg::with_name("list-details")
.long("list-details")
.short("l")
.conflicts_with("absolute-path")
.help("Use a long listing format with file metadata")
.long_help(
"Use a detailed listing format like 'ls -l'. This is basically an alias \
for '--exec-batch ls -l' with some additional 'ls' options. This can be \
used to see more metadata, to show symlink targets and to achieve a \
deterministic sort order.",
),
)
.arg(
Arg::with_name("follow")
.long("follow")
.short("L")
.alias("dereference")
.overrides_with("follow")
.help("Follow symbolic links")
.long_help(
"By default, fd does not descend into symlinked directories. Using this \
flag, symbolic links are also traversed. \
Flag can be overriden with --no-follow.",
),
)
.arg(
Arg::with_name("no-follow")
.long("no-follow")
.overrides_with("follow")
.hidden(true)
.long_help(
"Overrides --follow.",
),
)
.arg(
Arg::with_name("full-path")
.long("full-path")
.short("p")
.overrides_with("full-path")
.help("Search full abs. path (default: filename only)")
.long_help(
"By default, the search pattern is only matched against the filename (or \
directory name). Using this flag, the pattern is matched against the full \
(absolute) path. Example:\n \
fd --glob -p '**/.git/config'",
),
)
.arg(
Arg::with_name("null_separator")
.long("print0")
.short("0")
.overrides_with("print0")
.conflicts_with("list-details")
.hidden_short_help(true)
.help("Separate results by the null character")
.long_help(
"Separate search results by the null character (instead of newlines). \
Useful for piping results to 'xargs'.",
),
)
.arg(
Arg::with_name("max-depth")
.long("max-depth")
.short("d")
.takes_value(true)
.value_name("depth")
.help("Set maximum search depth (default: none)")
.long_help(
"Limit the directory traversal to a given depth. By default, there is no \
limit on the search depth.",
),
)
// support --maxdepth as well, for compatibility with rg
.arg(
Arg::with_name("rg-depth")
.long("maxdepth")
.hidden(true)
.takes_value(true)
.help("Set maximum search depth (default: none)")
)
.arg(
Arg::with_name("min-depth")
.long("min-depth")
.takes_value(true)
.value_name("depth")
.hidden_short_help(true)
.help("Only show results starting at given depth")
.long_help(
"Only show search results starting at the given depth. \
See also: '--max-depth' and '--exact-depth'",
),
)
.arg(
Arg::with_name("exact-depth")
.long("exact-depth")
.takes_value(true)
.value_name("depth")
.hidden_short_help(true)
.conflicts_with_all(&["max-depth", "min-depth"])
.help("Only show results at exact given depth")
.long_help(
"Only show search results at the exact given depth. This is an alias for \
'--min-depth <depth> --max-depth <depth>'.",
),
)
.arg(
Arg::with_name("prune")
.long("prune")
.conflicts_with_all(&["size", "exact-depth"])
.hidden_short_help(true)
.help("Do not traverse into matching directories")
.long_help("Do not traverse into directories that match the search criteria. If \
you want to exclude specific directories, use the '--exclude=…' option.")
)
.arg(
Arg::with_name("file-type")
.long("type")
.short("t")
.multiple(true)
.number_of_values(1)
.takes_value(true)
.value_name("filetype")
.possible_values(&[
"f",
"file",
"d",
"directory",
"l",
"symlink",
"x",
"executable",
"e",
"empty",
"s",
"socket",
"p",
"pipe",
])
.hide_possible_values(true)
.help(
"Filter by type: file (f), directory (d), symlink (l),\nexecutable (x), \
empty (e), socket (s), pipe (p)",
)
.long_help(
"Filter the search by type:\n \
'f' or 'file': regular files\n \
'd' or 'directory': directories\n \
'l' or 'symlink': symbolic links\n \
's' or 'socket': socket\n \
'p' or 'pipe': named pipe (FIFO)\n\n \
'x' or 'executable': executables\n \
'e' or 'empty': empty files or directories\n\n\
This option can be specified more than once to include multiple file types. \
Searching for '--type file --type symlink' will show both regular files as \
well as symlinks. Note that the 'executable' and 'empty' filters work differently: \
'--type executable' implies '--type file' by default. And '--type empty' searches \
for empty files and directories, unless either '--type file' or '--type directory' \
is specified in addition.\n\n\
Examples:\n \
- Only search for files:\n \
fd --type file …\n \
fd -tf …\n \
- Find both files and symlinks\n \
fd --type file --type symlink …\n \
fd -tf -tl …\n \
- Find executable files:\n \
fd --type executable\n \
fd -tx\n \
- Find empty files:\n \
fd --type empty --type file\n \
fd -te -tf\n \
- Find empty directories:\n \
fd --type empty --type directory\n \
fd -te -td"
),
)
.arg(
Arg::with_name("extension")
.long("extension")
.short("e")
.multiple(true)
.number_of_values(1)
.takes_value(true)
.value_name("ext")
.help("Filter by file extension")
.long_help(
"(Additionally) filter search results by their file extension. Multiple \
allowable file extensions can be specified.\n\
If you want to search for files without extension, \
you can use the regex '^[^.]+$' as a normal search pattern.",
),
)
.arg(
Arg::with_name("exec")
.long("exec")
.short("x")
.min_values(1)
.allow_hyphen_values(true)
.value_terminator(";")
.value_name("cmd")
.conflicts_with("list-details")
.help("Execute a command for each search result")
.long_help(
"Execute a command for each search result in parallel (use --threads=1 for sequential command execution). \
All positional arguments following --exec are considered to be arguments to the command - not to fd. \
It is therefore recommended to place the '-x'/'--exec' option last.\n\
The following placeholders are substituted before the command is executed:\n \
'{}': path (of the current search result)\n \
'{/}': basename\n \
'{//}': parent directory\n \
'{.}': path without file extension\n \
'{/.}': basename without file extension\n\n\
If no placeholder is present, an implicit \"{}\" at the end is assumed.\n\n\
Examples:\n\n \
- find all *.zip files and unzip them:\n\n \
fd -e zip -x unzip\n\n \
- find *.h and *.cpp files and run \"clang-format -i ..\" for each of them:\n\n \
fd -e h -e cpp -x clang-format -i\n\n \
- Convert all *.jpg files to *.png files:\n\n \
fd -e jpg -x convert {} {.}.png\
",
),
)
.arg(
Arg::with_name("exec-batch")
.long("exec-batch")
.short("X")
.min_values(1)
.allow_hyphen_values(true)
.value_terminator(";")
.value_name("cmd")
.conflicts_with_all(&["exec", "list-details"])
.help("Execute a command with all search results at once")
.long_help(
"Execute the given command once, with all search results as arguments.\n\
One of the following placeholders is substituted before the command is executed:\n \
'{}': path (of all search results)\n \
'{/}': basename\n \
'{//}': parent directory\n \
'{.}': path without file extension\n \
'{/.}': basename without file extension\n\n\
If no placeholder is present, an implicit \"{}\" at the end is assumed.\n\n\
Examples:\n\n \
- Find all test_*.py files and open them in your favorite editor:\n\n \
fd -g 'test_*.py' -X vim\n\n \
- Find all *.rs files and count the lines with \"wc -l ...\":\n\n \
fd -e rs -X wc -l\
"
),
)
.arg(
Arg::with_name("batch-size")
.long("batch-size")
.takes_value(true)
.value_name("size")
.hidden_short_help(true)
.requires("exec-batch")
.help("Max number of arguments to run as a batch with -X")
.long_help(
"Maximum number of arguments to pass to the command given with -X. \
If the number of results is greater than the given size, \
the command given with -X is run again with remaining arguments. \
A batch size of zero means there is no limit.",
),
)
.arg(
Arg::with_name("exclude")
.long("exclude")
.short("E")
.takes_value(true)
.value_name("pattern")
.number_of_values(1)
.multiple(true)
.help("Exclude entries that match the given glob pattern")
.long_help(
"Exclude files/directories that match the given glob pattern. This \
overrides any other ignore logic. Multiple exclude patterns can be \
specified.\n\n\
Examples:\n \
--exclude '*.pyc'\n \
--exclude node_modules",
),
)
.arg(
Arg::with_name("ignore-file")
.long("ignore-file")
.takes_value(true)
.value_name("path")
.number_of_values(1)
.multiple(true)
.hidden_short_help(true)
.help("Add custom ignore-file in '.gitignore' format")
.long_help(
"Add a custom ignore-file in '.gitignore' format. These files have a low \
precedence.",
),
)
.arg(
Arg::with_name("color")
.long("color")
.short("c")
.takes_value(true)
.value_name("when")
.possible_values(&["never", "auto", "always"])
.hide_possible_values(true)
.help("When to use colors: never, *auto*, always")
.long_help(
"Declare when to use color for the pattern match output:\n \
'auto': show colors if the output goes to an interactive console (default)\n \
'never': do not use colorized output\n \
'always': always use colorized output",
),
)
.arg(
Arg::with_name("threads")
.long("threads")
.short("j")
.takes_value(true)
.value_name("num")
.hidden_short_help(true)
.help("Set number of threads")
.long_help(
"Set number of threads to use for searching & executing (default: number \
of available CPU cores)",
),
)
.arg(
Arg::with_name("size")
.long("size")
.short("S")
.takes_value(true)
.number_of_values(1)
.allow_hyphen_values(true)
.multiple(true)
.help("Limit results based on the size of files")
.long_help(
"Limit results based on the size of files using the format <+-><NUM><UNIT>.\n \
'+': file size must be greater than or equal to this\n \
'-': file size must be less than or equal to this\n\
If neither '+' nor '-' is specified, file size must be exactly equal to this.\n \
'NUM': The numeric size (e.g. 500)\n \
'UNIT': The units for NUM. They are not case-sensitive.\n\
Allowed unit values:\n \
'b': bytes\n \
'k': kilobytes (base ten, 10^3 = 1000 bytes)\n \
'm': megabytes\n \
'g': gigabytes\n \
't': terabytes\n \
'ki': kibibytes (base two, 2^10 = 1024 bytes)\n \
'mi': mebibytes\n \
'gi': gibibytes\n \
'ti': tebibytes",
),
)
.arg(
Arg::with_name("max-buffer-time")
.long("max-buffer-time")
.takes_value(true)
.hidden(true)
.help("Milliseconds to buffer before streaming search results to console")
.long_help(
"Amount of time in milliseconds to buffer, before streaming the search \
results to the console.",
),
)
.arg(
Arg::with_name("changed-within")
.long("changed-within")
.alias("change-newer-than")
.alias("newer")
.takes_value(true)
.value_name("date|dur")
.number_of_values(1)
.help("Filter by file modification time (newer than)")
.long_help(
"Filter results based on the file modification time. The argument can be provided \
as a specific point in time (YYYY-MM-DD HH:MM:SS) or as a duration (10h, 1d, 35min). \
If the time is not specified, it defaults to 00:00:00. \
'--change-newer-than' or '--newer' can be used as aliases.\n\
Examples:\n \
--changed-within 2weeks\n \
--change-newer-than '2018-10-27 10:00:00'\n \
--newer 2018-10-27",
),
)
.arg(
Arg::with_name("changed-before")
.long("changed-before")
.alias("change-older-than")
.alias("older")
.takes_value(true)
.value_name("date|dur")
.number_of_values(1)
.help("Filter by file modification time (older than)")
.long_help(
"Filter results based on the file modification time. The argument can be provided \
as a specific point in time (YYYY-MM-DD HH:MM:SS) or as a duration (10h, 1d, 35min). \
'--change-older-than' or '--older' can be used as aliases.\n\
Examples:\n \
--changed-before '2018-10-27 10:00:00'\n \
--change-older-than 2weeks\n \
--older 2018-10-27",
),
)
.arg(
Arg::with_name("max-results")
.long("max-results")
.takes_value(true)
.value_name("count")
// We currently do not support --max-results in combination with
// program execution because the results that come up in a --max-results
// search are non-deterministic. Users might think that they can run the
// same search with `--exec rm` attached and get a reliable removal of
// the files they saw in the previous search.
.conflicts_with_all(&["exec", "exec-batch", "list-details"])
.hidden_short_help(true)
.help("Limit number of search results")
.long_help("Limit the number of search results to 'count' and quit immediately."),
)
.arg(
Arg::with_name("max-one-result")
.short("1")
.hidden_short_help(true)
.overrides_with("max-results")
.conflicts_with_all(&["exec", "exec-batch", "list-details"])
.help("Limit search to a single result")
.long_help("Limit the search to a single result and quit immediately. \
This is an alias for '--max-results=1'.")
)
.arg(
Arg::with_name("quiet")
.long("quiet")
.short("q")
.alias("has-results")
.hidden_short_help(true)
.conflicts_with_all(&["exec", "exec-batch", "list-details", "max-results"])
.help("Print nothing, exit code 0 if match found, 1 otherwise")
.long_help(
"When the flag is present, the program does not print anything and will \
return with an exit code of 0 if there is at least one match. Otherwise, the \
exit code will be 1. \
'--has-results' can be used as an alias."
)
)
.arg(
Arg::with_name("show-errors")
.long("show-errors")
.hidden_short_help(true)
.overrides_with("show-errors")
.help("Show filesystem errors")
.long_help(
"Enable the display of filesystem errors for situations such as \
insufficient permissions or dead symlinks.",
),
)
.arg(
Arg::with_name("base-directory")
.long("base-directory")
.takes_value(true)
.value_name("path")
.number_of_values(1)
.hidden_short_help(true)
.help("Change current working directory")
.long_help(
"Change the current working directory of fd to the provided path. This \
means that search results will be shown with respect to the given base \
path. Note that relative paths which are passed to fd via the positional \
<path> argument or the '--search-path' option will also be resolved \
relative to this directory.",
),
)
.arg(
Arg::with_name("pattern").help(
"the search pattern (a regular expression, unless '--glob' is used; optional)",
).long_help(
"the search pattern which is either a regular expression (default) or a glob \
pattern (if --glob is used). If no pattern has been specified, every entry \
is considered a match. If your pattern starts with a dash (-), make sure to \
pass '--' first, or it will be considered as a flag (fd -- '-foo').")
)
.arg(
Arg::with_name("path-separator")
.takes_value(true)
.value_name("separator")
.long("path-separator")
.hidden_short_help(true)
.help("Set path separator when printing file paths")
.long_help(
"Set the path separator to use when printing file paths. The default is \
the OS-specific separator ('/' on Unix, '\\' on Windows).",
),
)
.arg(
Arg::with_name("path")
.multiple(true)
.help("the root directory for the filesystem search (optional)")
.long_help(
"The directory where the filesystem search is rooted (optional). If \
omitted, search the current working directory.",
),
)
.arg(
Arg::with_name("search-path")
.long("search-path")
.takes_value(true)
.conflicts_with("path")
.multiple(true)
.hidden_short_help(true)
.number_of_values(1)
.help("Provide paths to search as an alternative to the positional <path>")
.long_help(
"Provide paths to search as an alternative to the positional <path> \
argument. Changes the usage to `fd [FLAGS/OPTIONS] --search-path <path> \
--search-path <path2> [<pattern>]`",
),
)
.arg(
Arg::with_name("strip-cwd-prefix")
.long("strip-cwd-prefix")
.conflicts_with_all(&["path", "search-path"])
.hidden_short_help(true)
.help("strip './' prefix from non-tty outputs")
.long_help(
"By default, relative paths are prefixed with './' when the output goes to a non \
interactive terminal (TTY). Use this flag to disable this behaviour."
)
);
if cfg!(unix) {
app = app.arg(
Arg::with_name("owner")
.long("owner")
.short("o")
.takes_value(true)
.value_name("user:group")
.help("Filter by owning user and/or group")
.long_help(
"Filter files by their user and/or group. \
Format: [(user|uid)][:(group|gid)]. Either side is optional. \
Precede either side with a '!' to exclude files instead.\n\
Examples:\n \
--owner john\n \
--owner :students\n \
--owner '!john:students'",
),
);
}
// Make `--one-file-system` available only on Unix and Windows platforms, as per the
// restrictions on the corresponding option in the `ignore` crate.
// Provide aliases `mount` and `xdev` for people coming from `find`.
if cfg!(any(unix, windows)) {
app = app.arg(
Arg::with_name("one-file-system")
.long("one-file-system")
.aliases(&["mount", "xdev"])
.hidden_short_help(true)
.help("Do not descend into a different file system")
.long_help(
"By default, fd will traverse the file system tree as far as other options \
dictate. With this flag, fd ensures that it does not descend into a \
different file system than the one it started in. Comparable to the -mount \
or -xdev filters of find(1).",
),
);
}
app
}
+849
View File
@@ -0,0 +1,849 @@
use std::path::{Path, PathBuf};
use std::time::Duration;
#[cfg(feature = "completions")]
use anyhow::anyhow;
use clap::{
error::ErrorKind, value_parser, Arg, ArgAction, ArgGroup, ArgMatches, Command, Parser,
ValueEnum,
};
#[cfg(feature = "completions")]
use clap_complete::Shell;
use normpath::PathExt;
use crate::error::print_error;
use crate::exec::CommandSet;
use crate::filesystem;
#[cfg(unix)]
use crate::filter::OwnerFilter;
use crate::filter::SizeFilter;
// Type for options that don't have any values, but are used to negate
// earlier options
struct Negations;
impl clap::FromArgMatches for Negations {
fn from_arg_matches(_: &ArgMatches) -> clap::error::Result<Self> {
Ok(Negations)
}
fn update_from_arg_matches(&mut self, _: &ArgMatches) -> clap::error::Result<()> {
Ok(())
}
}
impl clap::Args for Negations {
fn augment_args(cmd: Command) -> Command {
Self::augment_args_for_update(cmd)
}
fn augment_args_for_update(cmd: Command) -> Command {
cmd.arg(
Arg::new("no_hidden")
.action(ArgAction::Count)
.long("no-hidden")
.overrides_with("hidden")
.hide(true)
.long_help("Overrides --hidden."),
)
.arg(
Arg::new("ignore")
.action(ArgAction::Count)
.long("ignore")
.overrides_with("no_ignore")
.hide(true)
.long_help("Overrides --no-ignore."),
)
.arg(
Arg::new("ignore_vcs")
.action(ArgAction::Count)
.long("ignore-vcs")
.overrides_with("no_ignore_vcs")
.hide(true)
.long_help("Overrides --no-ignore-vcs."),
)
.arg(
Arg::new("relative_path")
.action(ArgAction::Count)
.long("relative-path")
.overrides_with("absolute_path")
.hide(true)
.long_help("Overrides --absolute-path."),
)
.arg(
Arg::new("no_follow")
.action(ArgAction::Count)
.long("no-follow")
.overrides_with("follow")
.hide(true)
.long_help("Overrides --follow."),
)
}
}
#[derive(Parser)]
#[command(
name = "fd",
version,
about = "A program to find entries in your filesystem",
after_long_help = "Bugs can be reported on GitHub: https://github.com/sharkdp/fd/issues",
max_term_width = 98,
args_override_self = true,
group(ArgGroup::new("execs").args(&["exec", "exec_batch", "list_details"]).conflicts_with_all(&[
"max_results", "has_results", "count"])),
)]
pub struct Opts {
/// Search hidden files and directories
#[arg(
long,
short = 'H',
long_help = "Include hidden directories and files in the search results (default: \
hidden files and directories are skipped). Files and directories are \
considered to be hidden if their name starts with a `.` sign (dot). \
The flag can be overridden with --no-hidden."
)]
pub hidden: bool,
/// Do not respect .(git|fd)ignore files
#[arg(
long,
short = 'I',
long_help = "Show search results from files and directories that would otherwise be \
ignored by '.gitignore', '.ignore', '.fdignore', or the global ignore file. \
The flag can be overridden with --ignore."
)]
pub no_ignore: bool,
/// Do not respect .gitignore files
#[arg(
long,
hide_short_help = true,
long_help = "Show search results from files and directories that would otherwise be \
ignored by '.gitignore' files. The flag can be overridden with --ignore-vcs."
)]
pub no_ignore_vcs: bool,
/// Do not respect .(git|fd)ignore files in parent directories
#[arg(
long,
hide_short_help = true,
long_help = "Show search results from files and directories that would otherwise be \
ignored by '.gitignore', '.ignore', or '.fdignore' files in parent directories."
)]
pub no_ignore_parent: bool,
/// Do not respect the global ignore file
#[arg(long, hide = true)]
pub no_global_ignore_file: bool,
/// Unrestricted search, alias for '--no-ignore --hidden'
#[arg(long = "unrestricted", short = 'u', overrides_with_all(&["ignore", "no_hidden"]), action(ArgAction::Count), hide_short_help = true,
long_help = "Perform an unrestricted search, including ignored and hidden files. This is \
an alias for '--no-ignore --hidden'."
)]
rg_alias_hidden_ignore: u8,
/// Case-sensitive search (default: smart case)
#[arg(
long,
short = 's',
overrides_with("ignore_case"),
long_help = "Perform a case-sensitive search. By default, fd uses case-insensitive \
searches, unless the pattern contains an uppercase character (smart \
case)."
)]
pub case_sensitive: bool,
/// Case-insensitive search (default: smart case)
#[arg(
long,
short = 'i',
overrides_with("case_sensitive"),
long_help = "Perform a case-insensitive search. By default, fd uses case-insensitive \
searches, unless the pattern contains an uppercase character (smart \
case)."
)]
pub ignore_case: bool,
/// Glob-based search (default: regular expression)
#[arg(
long,
short = 'g',
conflicts_with("fixed_strings"),
long_help = "Perform a glob-based search instead of a regular expression search."
)]
pub glob: bool,
/// Regular-expression based search (default)
#[arg(
long,
overrides_with("glob"),
hide_short_help = true,
long_help = "Perform a regular-expression based search (default). This can be used to \
override --glob."
)]
pub regex: bool,
/// Treat pattern as literal string stead of regex
#[arg(
long,
short = 'F',
alias = "literal",
hide_short_help = true,
long_help = "Treat the pattern as a literal string instead of a regular expression. Note \
that this also performs substring comparison. If you want to match on an \
exact filename, consider using '--glob'."
)]
pub fixed_strings: bool,
/// Show absolute instead of relative paths
#[arg(
long,
short = 'a',
long_help = "Shows the full path starting from the root as opposed to relative paths. \
The flag can be overridden with --relative-path."
)]
pub absolute_path: bool,
/// Use a long listing format with file metadata
#[arg(
long,
short = 'l',
conflicts_with("absolute_path"),
long_help = "Use a detailed listing format like 'ls -l'. This is basically an alias \
for '--exec-batch ls -l' with some additional 'ls' options. This can be \
used to see more metadata, to show symlink targets and to achieve a \
deterministic sort order."
)]
pub list_details: bool,
/// Follow symbolic links
#[arg(
long,
short = 'L',
alias = "dereference",
long_help = "By default, fd does not descend into symlinked directories. Using this \
flag, symbolic links are also traversed. \
Flag can be overriden with --no-follow."
)]
pub follow: bool,
/// Search full abs. path (default: filename only)
#[arg(
long,
short = 'p',
long_help = "By default, the search pattern is only matched against the filename (or \
directory name). Using this flag, the pattern is matched against the full \
(absolute) path. Example:\n \
fd --glob -p '**/.git/config'"
)]
pub full_path: bool,
/// Separate search results by the null character
#[arg(
long = "print0",
short = '0',
conflicts_with("list_details"),
hide_short_help = true,
long_help = "Separate search results by the null character (instead of newlines). \
Useful for piping results to 'xargs'."
)]
pub null_separator: bool,
/// Set maximum search depth (default: none)
#[arg(
long,
short = 'd',
value_name = "depth",
alias("maxdepth"),
long_help = "Limit the directory traversal to a given depth. By default, there is no \
limit on the search depth."
)]
max_depth: Option<usize>,
/// Only show search results starting at the given depth.
#[arg(
long,
value_name = "depth",
hide_short_help = true,
long_help = "Only show search results starting at the given depth. \
See also: '--max-depth' and '--exact-depth'"
)]
min_depth: Option<usize>,
/// Only show search results at the exact given depth
#[arg(long, value_name = "depth", hide_short_help = true, conflicts_with_all(&["max_depth", "min_depth"]),
long_help = "Only show search results at the exact given depth. This is an alias for \
'--min-depth <depth> --max-depth <depth>'.",
)]
exact_depth: Option<usize>,
/// Exclude entries that match the given glob pattern
#[arg(
long,
short = 'E',
value_name = "pattern",
long_help = "Exclude files/directories that match the given glob pattern. This \
overrides any other ignore logic. Multiple exclude patterns can be \
specified.\n\n\
Examples:\n \
--exclude '*.pyc'\n \
--exclude node_modules"
)]
pub exclude: Vec<String>,
/// Do not traverse into directories that match the search criteria. If
/// you want to exclude specific directories, use the '--exclude=…' option.
#[arg(long, hide_short_help = true, conflicts_with_all(&["size", "exact_depth"]),
long_help = "Do not traverse into directories that match the search criteria. If \
you want to exclude specific directories, use the '--exclude=…' option.",
)]
pub prune: bool,
/// Filter by type: file (f), directory (d), symlink (l),
/// executable (x), empty (e), socket (s), pipe (p)
#[arg(
long = "type",
short = 't',
value_name = "filetype",
hide_possible_values = true,
value_enum,
long_help = "Filter the search by type:\n \
'f' or 'file': regular files\n \
'd' or 'directory': directories\n \
'l' or 'symlink': symbolic links\n \
's' or 'socket': socket\n \
'p' or 'pipe': named pipe (FIFO)\n\n \
'x' or 'executable': executables\n \
'e' or 'empty': empty files or directories\n\n\
This option can be specified more than once to include multiple file types. \
Searching for '--type file --type symlink' will show both regular files as \
well as symlinks. Note that the 'executable' and 'empty' filters work differently: \
'--type executable' implies '--type file' by default. And '--type empty' searches \
for empty files and directories, unless either '--type file' or '--type directory' \
is specified in addition.\n\n\
Examples:\n \
- Only search for files:\n \
fd --type file …\n \
fd -tf …\n \
- Find both files and symlinks\n \
fd --type file --type symlink …\n \
fd -tf -tl …\n \
- Find executable files:\n \
fd --type executable\n \
fd -tx\n \
- Find empty files:\n \
fd --type empty --type file\n \
fd -te -tf\n \
- Find empty directories:\n \
fd --type empty --type directory\n \
fd -te -td"
)]
pub filetype: Option<Vec<FileType>>,
/// Filter by file extension
#[arg(
long = "extension",
short = 'e',
value_name = "ext",
long_help = "(Additionally) filter search results by their file extension. Multiple \
allowable file extensions can be specified.\n\
If you want to search for files without extension, \
you can use the regex '^[^.]+$' as a normal search pattern."
)]
pub extensions: Option<Vec<String>>,
/// Limit results based on the size of files
#[arg(long, short = 'S', value_parser = SizeFilter::from_string, allow_hyphen_values = true, verbatim_doc_comment, value_name = "size",
long_help = "Limit results based on the size of files using the format <+-><NUM><UNIT>.\n \
'+': file size must be greater than or equal to this\n \
'-': file size must be less than or equal to this\n\
If neither '+' nor '-' is specified, file size must be exactly equal to this.\n \
'NUM': The numeric size (e.g. 500)\n \
'UNIT': The units for NUM. They are not case-sensitive.\n\
Allowed unit values:\n \
'b': bytes\n \
'k': kilobytes (base ten, 10^3 = 1000 bytes)\n \
'm': megabytes\n \
'g': gigabytes\n \
't': terabytes\n \
'ki': kibibytes (base two, 2^10 = 1024 bytes)\n \
'mi': mebibytes\n \
'gi': gibibytes\n \
'ti': tebibytes",
)]
pub size: Vec<SizeFilter>,
/// Filter by file modification time (newer than)
#[arg(
long,
alias("change-newer-than"),
alias("newer"),
value_name = "date|dur",
long_help = "Filter results based on the file modification time. The argument can be provided \
as a specific point in time (YYYY-MM-DD HH:MM:SS) or as a duration (10h, 1d, 35min). \
If the time is not specified, it defaults to 00:00:00. \
'--change-newer-than' or '--newer' can be used as aliases.\n\
Examples:\n \
--changed-within 2weeks\n \
--change-newer-than '2018-10-27 10:00:00'\n \
--newer 2018-10-27"
)]
pub changed_within: Option<String>,
/// Filter by file modification time (older than)
#[arg(
long,
alias("change-older-than"),
alias("older"),
value_name = "date|dur",
long_help = "Filter results based on the file modification time. The argument can be provided \
as a specific point in time (YYYY-MM-DD HH:MM:SS) or as a duration (10h, 1d, 35min). \
'--change-older-than' or '--older' can be used as aliases.\n\
Examples:\n \
--changed-before '2018-10-27 10:00:00'\n \
--change-older-than 2weeks\n \
--older 2018-10-27"
)]
pub changed_before: Option<String>,
/// Filter by owning user and/or group
#[cfg(unix)]
#[arg(long, short = 'o', value_parser = OwnerFilter::from_string, value_name = "user:group",
long_help = "Filter files by their user and/or group. \
Format: [(user|uid)][:(group|gid)]. Either side is optional. \
Precede either side with a '!' to exclude files instead.\n\
Examples:\n \
--owner john\n \
--owner :students\n \
--owner '!john:students'"
)]
pub owner: Option<OwnerFilter>,
#[command(flatten)]
pub exec: Exec,
/// Max number of arguments to run as a batch size with -X
#[arg(
long,
value_name = "size",
hide_short_help = true,
requires("exec_batch"),
value_parser = value_parser!(usize),
default_value_t,
long_help = "Maximum number of arguments to pass to the command given with -X. \
If the number of results is greater than the given size, \
the command given with -X is run again with remaining arguments. \
A batch size of zero means there is no limit (default), but note \
that batching might still happen due to OS restrictions on the \
maximum length of command lines.",
)]
pub batch_size: usize,
/// Add a custom ignore-file in '.gitignore' format
#[arg(
long,
value_name = "path",
hide_short_help = true,
long_help = "Add a custom ignore-file in '.gitignore' format. These files have a low precedence."
)]
pub ignore_file: Vec<PathBuf>,
/// When to use colors
#[arg(
long,
short = 'c',
value_enum,
default_value_t = ColorWhen::Auto,
value_name = "when",
long_help = "Declare when to use color for the pattern match output",
)]
pub color: ColorWhen,
/// Set number of threads to use for searching & executing (default: number
/// of available CPU cores)
#[arg(long, short = 'j', value_name = "num", hide_short_help = true, value_parser = clap::value_parser!(u32).range(1..))]
pub threads: Option<u32>,
/// Milliseconds to buffer before streaming search results to console
///
/// Amount of time in milliseconds to buffer, before streaming the search
/// results to the console.
#[arg(long, hide = true, value_parser = parse_millis)]
pub max_buffer_time: Option<Duration>,
/// Limit number of search results
#[arg(
long,
value_name = "count",
hide_short_help = true,
long_help = "Limit the number of search results to 'count' and quit immediately."
)]
max_results: Option<usize>,
/// Limit search to a single result
#[arg(
short = '1',
hide_short_help = true,
overrides_with("max_results"),
long_help = "Limit the search to a single result and quit immediately. \
This is an alias for '--max-results=1'."
)]
max_one_result: bool,
/// Print nothing, exit code 0 if match found, 1 otherwise
#[arg(
long,
short = 'q',
alias = "has-results",
hide_short_help = true,
conflicts_with("max_results"),
long_help = "When the flag is present, the program does not print anything and will \
return with an exit code of 0 if there is at least one match. Otherwise, the \
exit code will be 1. \
'--has-results' can be used as an alias."
)]
pub quiet: bool,
/// Show filesystem errors
#[arg(
long,
hide_short_help = true,
long_help = "Enable the display of filesystem errors for situations such as \
insufficient permissions or dead symlinks."
)]
pub show_errors: bool,
/// Change current working directory
#[arg(
long,
value_name = "path",
hide_short_help = true,
long_help = "Change the current working directory of fd to the provided path. This \
means that search results will be shown with respect to the given base \
path. Note that relative paths which are passed to fd via the positional \
<path> argument or the '--search-path' option will also be resolved \
relative to this directory."
)]
pub base_directory: Option<PathBuf>,
/// the search pattern (a regular expression, unless '--glob' is used; optional)
#[arg(
default_value = "",
hide_default_value = true,
value_name = "pattern",
long_help = "the search pattern which is either a regular expression (default) or a glob \
pattern (if --glob is used). If no pattern has been specified, every entry \
is considered a match. If your pattern starts with a dash (-), make sure to \
pass '--' first, or it will be considered as a flag (fd -- '-foo')."
)]
pub pattern: String,
/// Set path separator when printing file paths
#[arg(
long,
value_name = "separator",
hide_short_help = true,
long_help = "Set the path separator to use when printing file paths. The default is \
the OS-specific separator ('/' on Unix, '\\' on Windows)."
)]
pub path_separator: Option<String>,
/// the root directories for the filesystem search (optional)
#[arg(action = ArgAction::Append,
value_name = "path",
long_help = "The directory where the filesystem search is rooted (optional). If \
omitted, search the current working directory.",
)]
path: Vec<PathBuf>,
/// Provides paths to search as an alternative to the positional <path> argument
#[arg(
long,
conflicts_with("path"),
value_name = "search-path",
hide_short_help = true,
long_help = "Provide paths to search as an alternative to the positional <path> \
argument. Changes the usage to `fd [OPTIONS] --search-path <path> \
--search-path <path2> [<pattern>]`"
)]
search_path: Vec<PathBuf>,
/// By default, relative paths are prefixed with './' when -x/--exec,
/// -X/--exec-batch, or -0/--print0 are given, to reduce the risk of a
/// path starting with '-' being treated as a command line option. Use
/// this flag to disable this behaviour.
#[arg(long, conflicts_with_all(&["path", "search_path"]), hide_short_help = true,
long_help = "By default, relative paths are prefixed with './' when -x/--exec, \
-X/--exec-batch, or -0/--print0 are given, to reduce the risk of a \
path starting with '-' being treated as a command line option. Use \
this flag to disable this behaviour.",
)]
pub strip_cwd_prefix: bool,
#[cfg(any(unix, windows))]
#[arg(long, aliases(&["mount", "xdev"]), hide_short_help = true,
long_help = "By default, fd will traverse the file system tree as far as other options \
dictate. With this flag, fd ensures that it does not descend into a \
different file system than the one it started in. Comparable to the -mount \
or -xdev filters of find(1).")]
pub one_file_system: bool,
#[cfg(feature = "completions")]
#[arg(long, hide = true, exclusive = true)]
gen_completions: Option<Option<Shell>>,
#[clap(flatten)]
_negations: Negations,
}
impl Opts {
pub fn search_paths(&self) -> anyhow::Result<Vec<PathBuf>> {
// would it make sense to concatenate these?
let paths = if !self.path.is_empty() {
&self.path
} else if !self.search_path.is_empty() {
&self.search_path
} else {
let current_directory = Path::new(".");
ensure_current_directory_exists(current_directory)?;
return Ok(vec![self.normalize_path(current_directory)]);
};
Ok(paths
.iter()
.filter_map(|path| {
if filesystem::is_existing_directory(path) {
Some(self.normalize_path(path))
} else {
print_error(format!(
"Search path '{}' is not a directory.",
path.to_string_lossy()
));
None
}
})
.collect())
}
fn normalize_path(&self, path: &Path) -> PathBuf {
if self.absolute_path {
filesystem::absolute_path(path.normalize().unwrap().as_path()).unwrap()
} else {
path.to_path_buf()
}
}
pub fn no_search_paths(&self) -> bool {
self.path.is_empty() && self.search_path.is_empty()
}
#[inline]
pub fn rg_alias_ignore(&self) -> bool {
self.rg_alias_hidden_ignore > 0
}
pub fn max_depth(&self) -> Option<usize> {
self.max_depth.or(self.exact_depth)
}
pub fn min_depth(&self) -> Option<usize> {
self.min_depth.or(self.exact_depth)
}
pub fn threads(&self) -> usize {
// This will panic if the number of threads passed in is more than usize::MAX in an environment
// where usize is less than 32 bits (for example 16-bit architectures). It's pretty
// unlikely fd will be running in such an environment, and even more unlikely someone would
// be trying to use that many threads on such an environment, so I think panicing is an
// appropriate way to handle that.
std::cmp::max(
self.threads
.map_or_else(num_cpus::get, |n| n.try_into().expect("too many threads")),
1,
)
}
pub fn max_results(&self) -> Option<usize> {
self.max_results
.filter(|&m| m > 0)
.or_else(|| self.max_one_result.then(|| 1))
}
#[cfg(feature = "completions")]
pub fn gen_completions(&self) -> anyhow::Result<Option<Shell>> {
self.gen_completions
.map(|maybe_shell| match maybe_shell {
Some(sh) => Ok(sh),
None => guess_shell(),
})
.transpose()
}
}
#[cfg(feature = "completions")]
fn guess_shell() -> anyhow::Result<Shell> {
let env_shell = std::env::var_os("SHELL").map(PathBuf::from);
if let Some(shell) = env_shell
.as_ref()
.and_then(|s| s.file_name())
.and_then(|s| s.to_str())
{
shell
.parse::<Shell>()
.map_err(|_| anyhow!("Unknown shell {}", shell))
} else {
// Assume powershell on windows
#[cfg(windows)]
return Ok(Shell::PowerShell);
#[cfg(not(windows))]
return Err(anyhow!("Unable to get shell from environment"));
}
}
#[derive(Copy, Clone, PartialEq, Eq, ValueEnum)]
pub enum FileType {
#[value(alias = "f")]
File,
#[value(alias = "d")]
Directory,
#[value(alias = "l")]
Symlink,
#[value(alias = "x")]
Executable,
#[value(alias = "e")]
Empty,
#[value(alias = "s")]
Socket,
#[value(alias = "p")]
Pipe,
}
#[derive(Copy, Clone, PartialEq, Eq, Debug, ValueEnum)]
pub enum ColorWhen {
/// show colors if the output goes to an interactive console (default)
Auto,
/// always use colorized output
Always,
/// do not use colorized output
Never,
}
impl ColorWhen {
pub fn as_str(&self) -> &'static str {
use ColorWhen::*;
match *self {
Auto => "auto",
Never => "never",
Always => "always",
}
}
}
// there isn't a derive api for getting grouped values yet,
// so we have to use hand-rolled parsing for exec and exec-batch
pub struct Exec {
pub command: Option<CommandSet>,
}
impl clap::FromArgMatches for Exec {
fn from_arg_matches(matches: &ArgMatches) -> clap::error::Result<Self> {
let command = matches
.grouped_values_of("exec")
.map(CommandSet::new)
.or_else(|| {
matches
.grouped_values_of("exec_batch")
.map(CommandSet::new_batch)
})
.transpose()
.map_err(|e| clap::Error::raw(ErrorKind::InvalidValue, e))?;
Ok(Exec { command })
}
fn update_from_arg_matches(&mut self, matches: &ArgMatches) -> clap::error::Result<()> {
*self = Self::from_arg_matches(matches)?;
Ok(())
}
}
impl clap::Args for Exec {
fn augment_args(cmd: Command) -> Command {
cmd.arg(Arg::new("exec")
.action(ArgAction::Append)
.long("exec")
.short('x')
.num_args(1..)
.allow_hyphen_values(true)
.value_terminator(";")
.value_name("cmd")
.conflicts_with("list_details")
.help("Execute a command for each search result")
.long_help(
"Execute a command for each search result in parallel (use --threads=1 for sequential command execution). \
All positional arguments following --exec are considered to be arguments to the command - not to fd. \
It is therefore recommended to place the '-x'/'--exec' option last.\n\
The following placeholders are substituted before the command is executed:\n \
'{}': path (of the current search result)\n \
'{/}': basename\n \
'{//}': parent directory\n \
'{.}': path without file extension\n \
'{/.}': basename without file extension\n\n\
If no placeholder is present, an implicit \"{}\" at the end is assumed.\n\n\
Examples:\n\n \
- find all *.zip files and unzip them:\n\n \
fd -e zip -x unzip\n\n \
- find *.h and *.cpp files and run \"clang-format -i ..\" for each of them:\n\n \
fd -e h -e cpp -x clang-format -i\n\n \
- Convert all *.jpg files to *.png files:\n\n \
fd -e jpg -x convert {} {.}.png\
",
),
)
.arg(
Arg::new("exec_batch")
.action(ArgAction::Append)
.long("exec-batch")
.short('X')
.num_args(1..)
.allow_hyphen_values(true)
.value_terminator(";")
.value_name("cmd")
.conflicts_with_all(&["exec", "list_details"])
.help("Execute a command with all search results at once")
.long_help(
"Execute the given command once, with all search results as arguments.\n\
One of the following placeholders is substituted before the command is executed:\n \
'{}': path (of all search results)\n \
'{/}': basename\n \
'{//}': parent directory\n \
'{.}': path without file extension\n \
'{/.}': basename without file extension\n\n\
If no placeholder is present, an implicit \"{}\" at the end is assumed.\n\n\
Examples:\n\n \
- Find all test_*.py files and open them in your favorite editor:\n\n \
fd -g 'test_*.py' -X vim\n\n \
- Find all *.rs files and count the lines with \"wc -l ...\":\n\n \
fd -e rs -X wc -l\
"
),
)
}
fn augment_args_for_update(cmd: Command) -> Command {
Self::augment_args(cmd)
}
}
fn parse_millis(arg: &str) -> Result<Duration, std::num::ParseIntError> {
Ok(Duration::from_millis(arg.parse()?))
}
fn ensure_current_directory_exists(current_directory: &Path) -> anyhow::Result<()> {
if filesystem::is_existing_directory(current_directory) {
Ok(())
} else {
Err(anyhow!(
"Could not retrieve current directory (has it been deleted?)."
))
}
}
+12 -2
View File
@@ -3,7 +3,7 @@ use std::{path::PathBuf, sync::Arc, time::Duration};
use lscolors::LsColors;
use regex::bytes::RegexSet;
use crate::exec::CommandTemplate;
use crate::exec::CommandSet;
use crate::filetypes::FileTypes;
#[cfg(unix)]
use crate::filter::OwnerFilter;
@@ -83,7 +83,7 @@ pub struct Config {
pub extensions: Option<RegexSet>,
/// If a value is supplied, each item found will be used to generate and execute commands.
pub command: Option<Arc<CommandTemplate>>,
pub command: Option<Arc<CommandSet>>,
/// Maximum number of search results to pass to each `command`. If zero, the number is
/// unlimited.
@@ -111,9 +111,19 @@ pub struct Config {
/// The separator used to print file paths.
pub path_separator: Option<String>,
/// The actual separator, either the system default separator or `path_separator`
pub actual_path_separator: String,
/// The maximum number of search results
pub max_results: Option<usize>,
/// Whether or not to strip the './' prefix for search results
pub strip_cwd_prefix: bool,
}
impl Config {
/// Check whether results are being printed.
pub fn is_printing(&self) -> bool {
self.command.is_none()
}
}
+154
View File
@@ -0,0 +1,154 @@
use std::ffi::OsString;
use std::fs::{FileType, Metadata};
use std::path::{Path, PathBuf};
use lscolors::{Colorable, LsColors, Style};
use once_cell::unsync::OnceCell;
use crate::config::Config;
use crate::filesystem::strip_current_dir;
enum DirEntryInner {
Normal(ignore::DirEntry),
BrokenSymlink(PathBuf),
}
pub struct DirEntry {
inner: DirEntryInner,
metadata: OnceCell<Option<Metadata>>,
style: OnceCell<Option<Style>>,
}
impl DirEntry {
#[inline]
pub fn normal(e: ignore::DirEntry) -> Self {
Self {
inner: DirEntryInner::Normal(e),
metadata: OnceCell::new(),
style: OnceCell::new(),
}
}
pub fn broken_symlink(path: PathBuf) -> Self {
Self {
inner: DirEntryInner::BrokenSymlink(path),
metadata: OnceCell::new(),
style: OnceCell::new(),
}
}
pub fn path(&self) -> &Path {
match &self.inner {
DirEntryInner::Normal(e) => e.path(),
DirEntryInner::BrokenSymlink(pathbuf) => pathbuf.as_path(),
}
}
pub fn into_path(self) -> PathBuf {
match self.inner {
DirEntryInner::Normal(e) => e.into_path(),
DirEntryInner::BrokenSymlink(p) => p,
}
}
/// Returns the path as it should be presented to the user.
pub fn stripped_path(&self, config: &Config) -> &Path {
if config.strip_cwd_prefix {
strip_current_dir(self.path())
} else {
self.path()
}
}
/// Returns the path as it should be presented to the user.
pub fn into_stripped_path(self, config: &Config) -> PathBuf {
if config.strip_cwd_prefix {
self.stripped_path(config).to_path_buf()
} else {
self.into_path()
}
}
pub fn file_type(&self) -> Option<FileType> {
match &self.inner {
DirEntryInner::Normal(e) => e.file_type(),
DirEntryInner::BrokenSymlink(_) => self.metadata().map(|m| m.file_type()),
}
}
pub fn metadata(&self) -> Option<&Metadata> {
self.metadata
.get_or_init(|| match &self.inner {
DirEntryInner::Normal(e) => e.metadata().ok(),
DirEntryInner::BrokenSymlink(path) => path.symlink_metadata().ok(),
})
.as_ref()
}
pub fn depth(&self) -> Option<usize> {
match &self.inner {
DirEntryInner::Normal(e) => Some(e.depth()),
DirEntryInner::BrokenSymlink(_) => None,
}
}
pub fn style(&self, ls_colors: &LsColors) -> Option<&Style> {
self.style
.get_or_init(|| ls_colors.style_for(self).cloned())
.as_ref()
}
}
impl PartialEq for DirEntry {
#[inline]
fn eq(&self, other: &Self) -> bool {
self.path() == other.path()
}
}
impl Eq for DirEntry {}
impl PartialOrd for DirEntry {
#[inline]
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
self.path().partial_cmp(other.path())
}
}
impl Ord for DirEntry {
#[inline]
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.path().cmp(other.path())
}
}
impl Colorable for DirEntry {
fn path(&self) -> PathBuf {
self.path().to_owned()
}
fn file_name(&self) -> OsString {
let name = match &self.inner {
DirEntryInner::Normal(e) => e.file_name(),
DirEntryInner::BrokenSymlink(path) => {
// Path::file_name() only works if the last component is Normal,
// but we want it for all component types, so we open code it.
// Copied from LsColors::style_for_path_with_metadata().
path.components()
.last()
.map(|c| c.as_os_str())
.unwrap_or_else(|| path.as_os_str())
}
};
name.to_owned()
}
fn file_type(&self) -> Option<FileType> {
self.file_type()
}
fn metadata(&self) -> Option<Metadata> {
self.metadata().cloned()
}
}
+89 -31
View File
@@ -1,51 +1,109 @@
use std::io;
use std::io::Write;
use std::process::Command;
use std::sync::Mutex;
use argmax::Command;
use crate::error::print_error;
use crate::exit_codes::ExitCode;
struct Outputs {
stdout: Vec<u8>,
stderr: Vec<u8>,
}
struct OutputBuffer<'a> {
output_permission: &'a Mutex<()>,
outputs: Vec<Outputs>,
}
impl<'a> OutputBuffer<'a> {
fn new(output_permission: &'a Mutex<()>) -> Self {
Self {
output_permission,
outputs: Vec::new(),
}
}
fn push(&mut self, stdout: Vec<u8>, stderr: Vec<u8>) {
self.outputs.push(Outputs { stdout, stderr });
}
fn write(self) {
// avoid taking the lock if there is nothing to do
if self.outputs.is_empty() {
return;
}
// While this lock is active, this thread will be the only thread allowed
// to write its outputs.
let _lock = self.output_permission.lock().unwrap();
let stdout = io::stdout();
let stderr = io::stderr();
let mut stdout = stdout.lock();
let mut stderr = stderr.lock();
for output in self.outputs.iter() {
let _ = stdout.write_all(&output.stdout);
let _ = stderr.write_all(&output.stderr);
}
}
}
/// Executes a command.
pub fn execute_command(
mut cmd: Command,
pub fn execute_commands<I: Iterator<Item = io::Result<Command>>>(
cmds: I,
out_perm: &Mutex<()>,
enable_output_buffering: bool,
) -> ExitCode {
// Spawn the supplied command.
let output = if enable_output_buffering {
cmd.output()
} else {
// If running on only one thread, don't buffer output
// Allows for viewing and interacting with intermediate command output
cmd.spawn().and_then(|c| c.wait_with_output())
};
let mut output_buffer = OutputBuffer::new(out_perm);
for result in cmds {
let mut cmd = match result {
Ok(cmd) => cmd,
Err(e) => return handle_cmd_error(None, e),
};
// Then wait for the command to exit, if it was spawned.
match output {
Ok(output) => {
// While this lock is active, this thread will be the only thread allowed
// to write its outputs.
let _lock = out_perm.lock().unwrap();
// Spawn the supplied command.
let output = if enable_output_buffering {
cmd.output()
} else {
// If running on only one thread, don't buffer output
// Allows for viewing and interacting with intermediate command output
cmd.spawn().and_then(|c| c.wait_with_output())
};
let stdout = io::stdout();
let stderr = io::stderr();
let _ = stdout.lock().write_all(&output.stdout);
let _ = stderr.lock().write_all(&output.stderr);
if output.status.code() == Some(0) {
ExitCode::Success
} else {
ExitCode::GeneralError
// Then wait for the command to exit, if it was spawned.
match output {
Ok(output) => {
if enable_output_buffering {
output_buffer.push(output.stdout, output.stderr);
}
if output.status.code() != Some(0) {
output_buffer.write();
return ExitCode::GeneralError;
}
}
Err(why) => {
output_buffer.write();
return handle_cmd_error(Some(&cmd), why);
}
}
Err(ref why) if why.kind() == io::ErrorKind::NotFound => {
print_error(format!("Command not found: {:?}", cmd));
}
output_buffer.write();
ExitCode::Success
}
pub fn handle_cmd_error(cmd: Option<&Command>, err: io::Error) -> ExitCode {
match (cmd, err) {
(Some(cmd), err) if err.kind() == io::ErrorKind::NotFound => {
print_error(format!(
"Command not found: {}",
cmd.get_program().to_string_lossy()
));
ExitCode::GeneralError
}
Err(why) => {
print_error(format!("Problem while executing command: {}", why));
(_, err) => {
print_error(format!("Problem while executing command: {}", err));
ExitCode::GeneralError
}
}
+2 -2
View File
@@ -5,13 +5,13 @@ use crate::filesystem::strip_current_dir;
/// Removes the parent component of the path
pub fn basename(path: &Path) -> &OsStr {
path.file_name().unwrap_or_else(|| path.as_os_str())
path.file_name().unwrap_or(path.as_os_str())
}
/// Removes the extension from the path
pub fn remove_extension(path: &Path) -> OsString {
let dirname = dirname(path);
let stem = path.file_stem().unwrap_or_else(|| path.as_os_str());
let stem = path.file_stem().unwrap_or(path.as_os_str());
let path = PathBuf::from(dirname).join(stem);
+32 -43
View File
@@ -1,34 +1,35 @@
use std::path::PathBuf;
use std::sync::mpsc::Receiver;
use std::sync::{Arc, Mutex};
use crossbeam_channel::Receiver;
use crate::config::Config;
use crate::dir_entry::DirEntry;
use crate::error::print_error;
use crate::exit_codes::{merge_exitcodes, ExitCode};
use crate::walk::WorkerResult;
use super::CommandTemplate;
use super::CommandSet;
/// An event loop that listens for inputs from the `rx` receiver. Each received input will
/// generate a command with the supplied command template. The generated command will then
/// be executed, and this process will continue until the receiver's sender has closed.
pub fn job(
rx: Arc<Mutex<Receiver<WorkerResult>>>,
cmd: Arc<CommandTemplate>,
rx: Receiver<WorkerResult>,
cmd: Arc<CommandSet>,
out_perm: Arc<Mutex<()>>,
show_filesystem_errors: bool,
buffer_output: bool,
config: &Config,
) -> ExitCode {
// Output should be buffered when only running a single thread
let buffer_output: bool = config.threads > 1;
let mut results: Vec<ExitCode> = Vec::new();
loop {
// Create a lock on the shared receiver for this thread.
let lock = rx.lock().unwrap();
// Obtain the next result from the receiver, else if the channel
// has closed, exit from the loop
let value: PathBuf = match lock.recv() {
Ok(WorkerResult::Entry(path)) => path,
let dir_entry: DirEntry = match rx.recv() {
Ok(WorkerResult::Entry(dir_entry)) => dir_entry,
Ok(WorkerResult::Error(err)) => {
if show_filesystem_errors {
if config.show_filesystem_errors {
print_error(err.to_string());
}
continue;
@@ -36,42 +37,30 @@ pub fn job(
Err(_) => break,
};
// Drop the lock so that other threads can read from the receiver.
drop(lock);
// Generate a command, execute it and store its exit code.
results.push(cmd.generate_and_execute(&value, Arc::clone(&out_perm), buffer_output))
results.push(cmd.execute(
dir_entry.stripped_path(config),
config.path_separator.as_deref(),
Arc::clone(&out_perm),
buffer_output,
))
}
// Returns error in case of any error.
merge_exitcodes(results)
}
pub fn batch(
rx: Receiver<WorkerResult>,
cmd: &CommandTemplate,
show_filesystem_errors: bool,
buffer_output: bool,
limit: usize,
) -> ExitCode {
let paths = rx.iter().filter_map(|value| match value {
WorkerResult::Entry(path) => Some(path),
WorkerResult::Error(err) => {
if show_filesystem_errors {
print_error(err.to_string());
pub fn batch(rx: Receiver<WorkerResult>, cmd: &CommandSet, config: &Config) -> ExitCode {
let paths = rx
.into_iter()
.filter_map(|worker_result| match worker_result {
WorkerResult::Entry(dir_entry) => Some(dir_entry.into_stripped_path(config)),
WorkerResult::Error(err) => {
if config.show_filesystem_errors {
print_error(err.to_string());
}
None
}
None
}
});
if limit == 0 {
// no limit
return cmd.generate_and_execute_batch(paths, buffer_output);
}
});
let mut exit_codes = Vec::new();
let mut peekable = paths.peekable();
while peekable.peek().is_some() {
let limited = peekable.by_ref().take(limit);
let exit_code = cmd.generate_and_execute_batch(limited, buffer_output);
exit_codes.push(exit_code);
}
merge_exitcodes(exit_codes)
cmd.execute_batch(paths, config.batch_size, config.path_separator.as_deref())
}
+279 -143
View File
@@ -5,23 +5,26 @@ mod token;
use std::borrow::Cow;
use std::ffi::{OsStr, OsString};
use std::io;
use std::iter;
use std::path::{Component, Path, PathBuf, Prefix};
use std::process::{Command, Stdio};
use std::process::Stdio;
use std::sync::{Arc, Mutex};
use anyhow::{anyhow, Result};
use anyhow::{bail, Result};
use argmax::Command;
use once_cell::sync::Lazy;
use regex::Regex;
use crate::exit_codes::ExitCode;
use crate::exit_codes::{merge_exitcodes, ExitCode};
use self::command::execute_command;
use self::command::{execute_commands, handle_cmd_error};
use self::input::{basename, dirname, remove_extension};
pub use self::job::{batch, job};
use self::token::Token;
/// Execution mode of the command
#[derive(Debug, Clone, Copy, PartialEq)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ExecutionMode {
/// Command is executed for each search result
OneByOne,
@@ -29,44 +32,199 @@ pub enum ExecutionMode {
Batch,
}
#[derive(Debug, Clone, PartialEq)]
pub struct CommandSet {
mode: ExecutionMode,
commands: Vec<CommandTemplate>,
}
impl CommandSet {
pub fn new<I, S>(input: I) -> Result<CommandSet>
where
I: IntoIterator<Item = Vec<S>>,
S: AsRef<str>,
{
Ok(CommandSet {
mode: ExecutionMode::OneByOne,
commands: input
.into_iter()
.map(CommandTemplate::new)
.collect::<Result<_>>()?,
})
}
pub fn new_batch<I, S>(input: I) -> Result<CommandSet>
where
I: IntoIterator<Item = Vec<S>>,
S: AsRef<str>,
{
Ok(CommandSet {
mode: ExecutionMode::Batch,
commands: input
.into_iter()
.map(|args| {
let cmd = CommandTemplate::new(args)?;
if cmd.number_of_tokens() > 1 {
bail!("Only one placeholder allowed for batch commands");
}
if cmd.args[0].has_tokens() {
bail!("First argument of exec-batch is expected to be a fixed executable");
}
Ok(cmd)
})
.collect::<Result<Vec<_>>>()?,
})
}
pub fn in_batch_mode(&self) -> bool {
self.mode == ExecutionMode::Batch
}
pub fn execute(
&self,
input: &Path,
path_separator: Option<&str>,
out_perm: Arc<Mutex<()>>,
buffer_output: bool,
) -> ExitCode {
let commands = self
.commands
.iter()
.map(|c| c.generate(input, path_separator));
execute_commands(commands, &out_perm, buffer_output)
}
pub fn execute_batch<I>(&self, paths: I, limit: usize, path_separator: Option<&str>) -> ExitCode
where
I: Iterator<Item = PathBuf>,
{
let builders: io::Result<Vec<_>> = self
.commands
.iter()
.map(|c| CommandBuilder::new(c, limit))
.collect();
match builders {
Ok(mut builders) => {
for path in paths {
for builder in &mut builders {
if let Err(e) = builder.push(&path, path_separator) {
return handle_cmd_error(Some(&builder.cmd), e);
}
}
}
for builder in &mut builders {
if let Err(e) = builder.finish() {
return handle_cmd_error(Some(&builder.cmd), e);
}
}
merge_exitcodes(builders.iter().map(|b| b.exit_code()))
}
Err(e) => handle_cmd_error(None, e),
}
}
}
/// Represents a multi-exec command as it is built.
#[derive(Debug)]
struct CommandBuilder {
pre_args: Vec<OsString>,
path_arg: ArgumentTemplate,
post_args: Vec<OsString>,
cmd: Command,
count: usize,
limit: usize,
exit_code: ExitCode,
}
impl CommandBuilder {
fn new(template: &CommandTemplate, limit: usize) -> io::Result<Self> {
let mut pre_args = vec![];
let mut path_arg = None;
let mut post_args = vec![];
for arg in &template.args {
if arg.has_tokens() {
path_arg = Some(arg.clone());
} else if path_arg == None {
pre_args.push(arg.generate("", None));
} else {
post_args.push(arg.generate("", None));
}
}
let cmd = Self::new_command(&pre_args)?;
Ok(Self {
pre_args,
path_arg: path_arg.unwrap(),
post_args,
cmd,
count: 0,
limit,
exit_code: ExitCode::Success,
})
}
fn new_command(pre_args: &[OsString]) -> io::Result<Command> {
let mut cmd = Command::new(&pre_args[0]);
cmd.stdin(Stdio::inherit());
cmd.stdout(Stdio::inherit());
cmd.stderr(Stdio::inherit());
cmd.try_args(&pre_args[1..])?;
Ok(cmd)
}
fn push(&mut self, path: &Path, separator: Option<&str>) -> io::Result<()> {
if self.limit > 0 && self.count >= self.limit {
self.finish()?;
}
let arg = self.path_arg.generate(path, separator);
if !self
.cmd
.args_would_fit(iter::once(&arg).chain(&self.post_args))
{
self.finish()?;
}
self.cmd.try_arg(arg)?;
self.count += 1;
Ok(())
}
fn finish(&mut self) -> io::Result<()> {
if self.count > 0 {
self.cmd.try_args(&self.post_args)?;
if !self.cmd.status()?.success() {
self.exit_code = ExitCode::GeneralError;
}
self.cmd = Self::new_command(&self.pre_args)?;
self.count = 0;
}
Ok(())
}
fn exit_code(&self) -> ExitCode {
self.exit_code
}
}
/// Represents a template that is utilized to generate command strings.
///
/// The template is meant to be coupled with an input in order to generate a command. The
/// `generate_and_execute()` method will be used to generate a command and execute it.
#[derive(Debug, Clone, PartialEq)]
pub struct CommandTemplate {
struct CommandTemplate {
args: Vec<ArgumentTemplate>,
mode: ExecutionMode,
path_separator: Option<String>,
}
impl CommandTemplate {
pub fn new<I, S>(input: I, path_separator: Option<String>) -> CommandTemplate
where
I: IntoIterator<Item = S>,
S: AsRef<str>,
{
Self::build(input, ExecutionMode::OneByOne, path_separator)
}
pub fn new_batch<I, S>(input: I, path_separator: Option<String>) -> Result<CommandTemplate>
where
I: IntoIterator<Item = S>,
S: AsRef<str>,
{
let cmd = Self::build(input, ExecutionMode::Batch, path_separator);
if cmd.number_of_tokens() > 1 {
return Err(anyhow!("Only one placeholder allowed for batch commands"));
}
if cmd.args[0].has_tokens() {
return Err(anyhow!(
"First argument of exec-batch is expected to be a fixed executable"
));
}
Ok(cmd)
}
fn build<I, S>(input: I, mode: ExecutionMode, path_separator: Option<String>) -> CommandTemplate
fn new<I, S>(input: I) -> Result<CommandTemplate>
where
I: IntoIterator<Item = S>,
S: AsRef<str>,
@@ -117,16 +275,21 @@ impl CommandTemplate {
args.push(ArgumentTemplate::Tokens(tokens));
}
// We need to check that we have at least one argument, because if not
// it will try to execute each file and directory it finds.
//
// Sadly, clap can't currently handle this for us, see
// https://github.com/clap-rs/clap/issues/3542
if args.is_empty() {
bail!("No executable provided for --exec or --exec-batch");
}
// If a placeholder token was not supplied, append one at the end of the command.
if !has_placeholder {
args.push(ArgumentTemplate::Tokens(vec![Token::Placeholder]));
}
CommandTemplate {
args,
mode,
path_separator,
}
Ok(CommandTemplate { args })
}
fn number_of_tokens(&self) -> usize {
@@ -136,57 +299,13 @@ impl CommandTemplate {
/// Generates and executes a command.
///
/// Using the internal `args` field, and a supplied `input` variable, a `Command` will be
/// build. Once all arguments have been processed, the command is executed.
pub fn generate_and_execute(
&self,
input: &Path,
out_perm: Arc<Mutex<()>>,
buffer_output: bool,
) -> ExitCode {
let mut cmd = Command::new(self.args[0].generate(&input, self.path_separator.as_deref()));
/// build.
fn generate(&self, input: &Path, path_separator: Option<&str>) -> io::Result<Command> {
let mut cmd = Command::new(self.args[0].generate(&input, path_separator));
for arg in &self.args[1..] {
cmd.arg(arg.generate(&input, self.path_separator.as_deref()));
}
execute_command(cmd, &out_perm, buffer_output)
}
pub fn in_batch_mode(&self) -> bool {
self.mode == ExecutionMode::Batch
}
pub fn generate_and_execute_batch<I>(&self, paths: I, buffer_output: bool) -> ExitCode
where
I: Iterator<Item = PathBuf>,
{
let mut cmd = Command::new(self.args[0].generate("", None));
cmd.stdin(Stdio::inherit());
cmd.stdout(Stdio::inherit());
cmd.stderr(Stdio::inherit());
let mut paths: Vec<_> = paths.collect();
let mut has_path = false;
for arg in &self.args[1..] {
if arg.has_tokens() {
paths.sort();
// A single `Tokens` is expected
// So we can directly consume the iterator once and for all
for path in &mut paths {
cmd.arg(arg.generate(path, self.path_separator.as_deref()));
has_path = true;
}
} else {
cmd.arg(arg.generate("", None));
}
}
if has_path {
execute_command(cmd, &Mutex::new(()), buffer_output)
} else {
ExitCode::Success
cmd.try_arg(arg.generate(&input, path_separator))?;
}
Ok(cmd)
}
}
@@ -302,15 +421,16 @@ mod tests {
#[test]
fn tokens_with_placeholder() {
assert_eq!(
CommandTemplate::new(&[&"echo", &"${SHELL}:"], None),
CommandTemplate {
args: vec![
ArgumentTemplate::Text("echo".into()),
ArgumentTemplate::Text("${SHELL}:".into()),
ArgumentTemplate::Tokens(vec![Token::Placeholder]),
],
CommandSet::new(vec![vec![&"echo", &"${SHELL}:"]]).unwrap(),
CommandSet {
commands: vec![CommandTemplate {
args: vec![
ArgumentTemplate::Text("echo".into()),
ArgumentTemplate::Text("${SHELL}:".into()),
ArgumentTemplate::Tokens(vec![Token::Placeholder]),
]
}],
mode: ExecutionMode::OneByOne,
path_separator: None,
}
);
}
@@ -318,14 +438,15 @@ mod tests {
#[test]
fn tokens_with_no_extension() {
assert_eq!(
CommandTemplate::new(&["echo", "{.}"], None),
CommandTemplate {
args: vec![
ArgumentTemplate::Text("echo".into()),
ArgumentTemplate::Tokens(vec![Token::NoExt]),
],
CommandSet::new(vec![vec!["echo", "{.}"]]).unwrap(),
CommandSet {
commands: vec![CommandTemplate {
args: vec![
ArgumentTemplate::Text("echo".into()),
ArgumentTemplate::Tokens(vec![Token::NoExt]),
],
}],
mode: ExecutionMode::OneByOne,
path_separator: None,
}
);
}
@@ -333,14 +454,15 @@ mod tests {
#[test]
fn tokens_with_basename() {
assert_eq!(
CommandTemplate::new(&["echo", "{/}"], None),
CommandTemplate {
args: vec![
ArgumentTemplate::Text("echo".into()),
ArgumentTemplate::Tokens(vec![Token::Basename]),
],
CommandSet::new(vec![vec!["echo", "{/}"]]).unwrap(),
CommandSet {
commands: vec![CommandTemplate {
args: vec![
ArgumentTemplate::Text("echo".into()),
ArgumentTemplate::Tokens(vec![Token::Basename]),
],
}],
mode: ExecutionMode::OneByOne,
path_separator: None,
}
);
}
@@ -348,14 +470,15 @@ mod tests {
#[test]
fn tokens_with_parent() {
assert_eq!(
CommandTemplate::new(&["echo", "{//}"], None),
CommandTemplate {
args: vec![
ArgumentTemplate::Text("echo".into()),
ArgumentTemplate::Tokens(vec![Token::Parent]),
],
CommandSet::new(vec![vec!["echo", "{//}"]]).unwrap(),
CommandSet {
commands: vec![CommandTemplate {
args: vec![
ArgumentTemplate::Text("echo".into()),
ArgumentTemplate::Tokens(vec![Token::Parent]),
],
}],
mode: ExecutionMode::OneByOne,
path_separator: None,
}
);
}
@@ -363,14 +486,15 @@ mod tests {
#[test]
fn tokens_with_basename_no_extension() {
assert_eq!(
CommandTemplate::new(&["echo", "{/.}"], None),
CommandTemplate {
args: vec![
ArgumentTemplate::Text("echo".into()),
ArgumentTemplate::Tokens(vec![Token::BasenameNoExt]),
],
CommandSet::new(vec![vec!["echo", "{/.}"]]).unwrap(),
CommandSet {
commands: vec![CommandTemplate {
args: vec![
ArgumentTemplate::Text("echo".into()),
ArgumentTemplate::Tokens(vec![Token::BasenameNoExt]),
],
}],
mode: ExecutionMode::OneByOne,
path_separator: None,
}
);
}
@@ -378,18 +502,19 @@ mod tests {
#[test]
fn tokens_multiple() {
assert_eq!(
CommandTemplate::new(&["cp", "{}", "{/.}.ext"], None),
CommandTemplate {
args: vec![
ArgumentTemplate::Text("cp".into()),
ArgumentTemplate::Tokens(vec![Token::Placeholder]),
ArgumentTemplate::Tokens(vec![
Token::BasenameNoExt,
Token::Text(".ext".into())
]),
],
CommandSet::new(vec![vec!["cp", "{}", "{/.}.ext"]]).unwrap(),
CommandSet {
commands: vec![CommandTemplate {
args: vec![
ArgumentTemplate::Text("cp".into()),
ArgumentTemplate::Tokens(vec![Token::Placeholder]),
ArgumentTemplate::Tokens(vec![
Token::BasenameNoExt,
Token::Text(".ext".into())
]),
],
}],
mode: ExecutionMode::OneByOne,
path_separator: None,
}
);
}
@@ -397,21 +522,32 @@ mod tests {
#[test]
fn tokens_single_batch() {
assert_eq!(
CommandTemplate::new_batch(&["echo", "{.}"], None).unwrap(),
CommandTemplate {
args: vec![
ArgumentTemplate::Text("echo".into()),
ArgumentTemplate::Tokens(vec![Token::NoExt]),
],
CommandSet::new_batch(vec![vec!["echo", "{.}"]]).unwrap(),
CommandSet {
commands: vec![CommandTemplate {
args: vec![
ArgumentTemplate::Text("echo".into()),
ArgumentTemplate::Tokens(vec![Token::NoExt]),
],
}],
mode: ExecutionMode::Batch,
path_separator: None,
}
);
}
#[test]
fn tokens_multiple_batch() {
assert!(CommandTemplate::new_batch(&["echo", "{.}", "{}"], None).is_err());
assert!(CommandSet::new_batch(vec![vec!["echo", "{.}", "{}"]]).is_err());
}
#[test]
fn template_no_args() {
assert!(CommandTemplate::new::<Vec<_>, &'static str>(vec![]).is_err());
}
#[test]
fn command_set_no_args() {
assert!(CommandSet::new(vec![vec!["echo"], vec![]]).is_err());
}
#[test]
+1 -1
View File
@@ -4,7 +4,7 @@ use std::fmt::{self, Display, Formatter};
///
/// Each `Token` contains either text, or a placeholder variant, which will be used to generate
/// commands after all tokens for a given command template have been collected.
#[derive(Clone, Debug, PartialEq)]
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum Token {
Placeholder,
Basename,
+1 -1
View File
@@ -3,7 +3,7 @@ use std::process;
#[cfg(unix)]
use nix::sys::signal::{raise, signal, SigHandler, Signal};
#[derive(Debug, Clone, Copy, PartialEq)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ExitCode {
Success,
HasResults(bool),
+8 -5
View File
@@ -7,9 +7,12 @@ use std::io;
use std::os::unix::fs::{FileTypeExt, PermissionsExt};
use std::path::{Path, PathBuf};
#[cfg(windows)]
use faccess::PathExt as _;
use normpath::PathExt;
use crate::walk;
use crate::dir_entry;
pub fn path_absolute_form(path: &Path) -> io::Result<PathBuf> {
if path.is_absolute() {
@@ -42,16 +45,16 @@ pub fn is_existing_directory(path: &Path) -> bool {
}
#[cfg(any(unix, target_os = "redox"))]
pub fn is_executable(md: &fs::Metadata) -> bool {
pub fn is_executable(_: &Path, md: &fs::Metadata) -> bool {
md.permissions().mode() & 0o111 != 0
}
#[cfg(windows)]
pub fn is_executable(_: &fs::Metadata) -> bool {
false
pub fn is_executable(path: &Path, _: &fs::Metadata) -> bool {
path.executable()
}
pub fn is_empty(entry: &walk::DirEntry) -> bool {
pub fn is_empty(entry: &dir_entry::DirEntry) -> bool {
if let Some(file_type) = entry.file_type() {
if file_type.is_dir() {
if let Ok(mut entries) = fs::read_dir(entry.path()) {
+4 -17
View File
@@ -1,7 +1,8 @@
use crate::dir_entry;
use crate::filesystem;
use crate::walk;
/// Whether or not to show
#[derive(Default)]
pub struct FileTypes {
pub files: bool,
pub directories: bool,
@@ -12,22 +13,8 @@ pub struct FileTypes {
pub empty_only: bool,
}
impl Default for FileTypes {
fn default() -> FileTypes {
FileTypes {
files: false,
directories: false,
symlinks: false,
sockets: false,
pipes: false,
executables_only: false,
empty_only: false,
}
}
}
impl FileTypes {
pub fn should_ignore(&self, entry: &walk::DirEntry) -> bool {
pub fn should_ignore(&self, entry: &dir_entry::DirEntry) -> bool {
if let Some(ref entry_type) = entry.file_type() {
(!self.files && entry_type.is_file())
|| (!self.directories && entry_type.is_dir())
@@ -37,7 +24,7 @@ impl FileTypes {
|| (self.executables_only
&& !entry
.metadata()
.map(|m| filesystem::is_executable(m))
.map(|md| filesystem::is_executable(entry.path(), md))
.unwrap_or(false))
|| (self.empty_only && !filesystem::is_empty(entry))
|| !(entry_type.is_file()
+25 -15
View File
@@ -1,13 +1,13 @@
use anyhow::{anyhow, Result};
use std::fs;
#[derive(Clone, Copy, Debug, PartialEq)]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct OwnerFilter {
uid: Check<u32>,
gid: Check<u32>,
}
#[derive(Clone, Copy, Debug, PartialEq)]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum Check<T> {
Equal(T),
NotEq(T),
@@ -15,10 +15,15 @@ enum Check<T> {
}
impl OwnerFilter {
const IGNORE: Self = OwnerFilter {
uid: Check::Ignore,
gid: Check::Ignore,
};
/// Parses an owner constraint
/// Returns an error if the string is invalid
/// Returns Ok(None) when string is acceptable but a noop (such as "" or ":")
pub fn from_string(input: &str) -> Result<Option<Self>> {
pub fn from_string(input: &str) -> Result<Self> {
let mut it = input.split(':');
let (fst, snd) = (it.next(), it.next());
@@ -42,10 +47,15 @@ impl OwnerFilter {
.ok_or_else(|| anyhow!("'{}' is not a recognized group name", s))
})?;
if let (Check::Ignore, Check::Ignore) = (uid, gid) {
Ok(None)
Ok(OwnerFilter { uid, gid })
}
/// If self is a no-op (ignore both uid and gid) then return `None`, otherwise wrap in a `Some`
pub fn filter_ignore(self) -> Option<Self> {
if self == Self::IGNORE {
None
} else {
Ok(Some(OwnerFilter { uid, gid }))
Some(self)
}
}
@@ -106,16 +116,16 @@ mod owner_parsing {
use super::Check::*;
owner_tests! {
empty: "" => Ok(None),
uid_only: "5" => Ok(Some(OwnerFilter { uid: Equal(5), gid: Ignore })),
uid_gid: "9:3" => Ok(Some(OwnerFilter { uid: Equal(9), gid: Equal(3) })),
gid_only: ":8" => Ok(Some(OwnerFilter { uid: Ignore, gid: Equal(8) })),
colon_only: ":" => Ok(None),
trailing: "5:" => Ok(Some(OwnerFilter { uid: Equal(5), gid: Ignore })),
empty: "" => Ok(OwnerFilter::IGNORE),
uid_only: "5" => Ok(OwnerFilter { uid: Equal(5), gid: Ignore }),
uid_gid: "9:3" => Ok(OwnerFilter { uid: Equal(9), gid: Equal(3) }),
gid_only: ":8" => Ok(OwnerFilter { uid: Ignore, gid: Equal(8) }),
colon_only: ":" => Ok(OwnerFilter::IGNORE),
trailing: "5:" => Ok(OwnerFilter { uid: Equal(5), gid: Ignore }),
uid_negate: "!5" => Ok(Some(OwnerFilter { uid: NotEq(5), gid: Ignore })),
both_negate:"!4:!3" => Ok(Some(OwnerFilter { uid: NotEq(4), gid: NotEq(3) })),
uid_not_gid:"6:!8" => Ok(Some(OwnerFilter { uid: Equal(6), gid: NotEq(8) })),
uid_negate: "!5" => Ok(OwnerFilter { uid: NotEq(5), gid: Ignore }),
both_negate:"!4:!3" => Ok(OwnerFilter { uid: NotEq(4), gid: NotEq(3) }),
uid_not_gid:"6:!8" => Ok(OwnerFilter { uid: Equal(6), gid: NotEq(8) }),
more_colons:"3:5:" => Err(_),
only_colons:"::" => Err(_),
+9 -3
View File
@@ -1,10 +1,11 @@
use anyhow::anyhow;
use once_cell::sync::Lazy;
use regex::Regex;
static SIZE_CAPTURES: Lazy<Regex> =
Lazy::new(|| Regex::new(r"(?i)^([+-]?)(\d+)(b|[kmgt]i?b?)$").unwrap());
#[derive(Clone, Copy, Debug, PartialEq)]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum SizeFilter {
Max(u64),
Min(u64),
@@ -24,7 +25,12 @@ const GIBI: u64 = MEBI * 1024;
const TEBI: u64 = GIBI * 1024;
impl SizeFilter {
pub fn from_string(s: &str) -> Option<Self> {
pub fn from_string(s: &str) -> anyhow::Result<Self> {
SizeFilter::parse_opt(s)
.ok_or_else(|| anyhow!("'{}' is not a valid size constraint. See 'fd --help'.", s))
}
fn parse_opt(s: &str) -> Option<Self> {
if !SIZE_CAPTURES.is_match(s) {
return None;
}
@@ -165,7 +171,7 @@ mod tests {
#[test]
fn $name() {
let i = SizeFilter::from_string($value);
assert!(i.is_none());
assert!(i.is_err());
}
)*
};
+1 -1
View File
@@ -3,7 +3,7 @@ use chrono::{offset::TimeZone, DateTime, Local, NaiveDate};
use std::time::SystemTime;
/// Filter based on time ranges.
#[derive(Debug, PartialEq)]
#[derive(Debug, PartialEq, Eq)]
pub enum TimeFilter {
Before(SystemTime),
After(SystemTime),
+123 -252
View File
@@ -1,5 +1,6 @@
mod app;
mod cli;
mod config;
mod dir_entry;
mod error;
mod exec;
mod exit_codes;
@@ -11,25 +12,25 @@ mod regex_helper;
mod walk;
use std::env;
use std::path::{Path, PathBuf};
use std::path::Path;
use std::sync::Arc;
use std::time;
use anyhow::{anyhow, Context, Result};
use anyhow::{anyhow, bail, Context, Result};
use atty::Stream;
use clap::{CommandFactory, Parser};
use globset::GlobBuilder;
use lscolors::LsColors;
use normpath::PathExt;
use regex::bytes::{RegexBuilder, RegexSetBuilder};
use crate::cli::{ColorWhen, Opts};
use crate::config::Config;
use crate::error::print_error;
use crate::exec::CommandTemplate;
use crate::exec::CommandSet;
use crate::exit_codes::ExitCode;
use crate::filetypes::FileTypes;
#[cfg(unix)]
use crate::filter::OwnerFilter;
use crate::filter::{SizeFilter, TimeFilter};
use crate::filter::TimeFilter;
use crate::regex_helper::{pattern_has_uppercase_char, pattern_matches_strings_with_leading_dot};
// We use jemalloc for performance reasons, see https://github.com/sharkdp/fd/pull/481
@@ -38,7 +39,10 @@ use crate::regex_helper::{pattern_has_uppercase_char, pattern_matches_strings_wi
not(windows),
not(target_os = "android"),
not(target_os = "macos"),
not(target_env = "musl")
not(target_os = "freebsd"),
not(all(target_env = "musl", target_pointer_width = "32")),
not(target_arch = "riscv64"),
feature = "use-jemalloc"
))]
#[global_allocator]
static ALLOC: jemallocator::Jemalloc = jemallocator::Jemalloc;
@@ -63,26 +67,41 @@ fn main() {
}
fn run() -> Result<ExitCode> {
let matches = app::build_app().get_matches_from(env::args_os());
let opts = Opts::parse();
set_working_dir(&matches)?;
let current_directory = Path::new(".");
ensure_current_directory_exists(current_directory)?;
let search_paths = extract_search_paths(&matches, current_directory)?;
#[cfg(feature = "completions")]
if let Some(shell) = opts.gen_completions()? {
return print_completions(shell);
}
let pattern = extract_search_pattern(&matches)?;
ensure_search_pattern_is_not_a_path(&matches, pattern)?;
let pattern_regex = build_pattern_regex(&matches, pattern)?;
set_working_dir(&opts)?;
let search_paths = opts.search_paths()?;
if search_paths.is_empty() {
bail!("No valid search paths given.");
}
let config = construct_config(matches, &pattern_regex)?;
ensure_search_pattern_is_not_a_path(&opts)?;
let pattern_regex = build_pattern_regex(&opts)?;
let config = construct_config(opts, &pattern_regex)?;
ensure_use_hidden_option_for_leading_dot_pattern(&config, &pattern_regex)?;
let re = build_regex(pattern_regex, &config)?;
walk::scan(&search_paths, Arc::new(re), Arc::new(config))
}
fn set_working_dir(matches: &clap::ArgMatches) -> Result<()> {
if let Some(base_directory) = matches.value_of_os("base-directory") {
let base_directory = Path::new(base_directory);
#[cfg(feature = "completions")]
#[cold]
fn print_completions(shell: clap_complete::Shell) -> Result<ExitCode> {
// The program name is the first argument.
let program_name = env::args().next().unwrap_or_else(|| "fd".to_string());
let mut cmd = Opts::command();
cmd.build();
clap_complete::generate(shell, &mut cmd, &program_name, &mut std::io::stdout());
Ok(ExitCode::Success)
}
fn set_working_dir(opts: &Opts) -> Result<()> {
if let Some(ref base_directory) = opts.base_directory {
if !filesystem::is_existing_directory(base_directory) {
return Err(anyhow!(
"The '--base-directory' path '{}' is not a directory.",
@@ -99,74 +118,11 @@ fn set_working_dir(matches: &clap::ArgMatches) -> Result<()> {
Ok(())
}
fn ensure_current_directory_exists(current_directory: &Path) -> Result<()> {
if filesystem::is_existing_directory(current_directory) {
Ok(())
} else {
Err(anyhow!(
"Could not retrieve current directory (has it been deleted?)."
))
}
}
fn extract_search_pattern<'a>(matches: &'a clap::ArgMatches) -> Result<&'a str> {
let pattern = matches
.value_of_os("pattern")
.map(|p| {
p.to_str()
.ok_or_else(|| anyhow!("The search pattern includes invalid UTF-8 sequences."))
})
.transpose()?
.unwrap_or("");
Ok(pattern)
}
fn extract_search_paths(
matches: &clap::ArgMatches,
current_directory: &Path,
) -> Result<Vec<PathBuf>> {
let mut search_paths = matches
.values_of_os("path")
.or_else(|| matches.values_of_os("search-path"))
.map_or_else(
|| vec![current_directory.to_path_buf()],
|paths| {
paths
.filter_map(|path| {
let path_buffer = PathBuf::from(path);
if filesystem::is_existing_directory(&path_buffer) {
Some(path_buffer)
} else {
print_error(format!(
"Search path '{}' is not a directory.",
path_buffer.to_string_lossy()
));
None
}
})
.collect()
},
);
if search_paths.is_empty() {
return Err(anyhow!("No valid search paths given."));
}
if matches.is_present("absolute-path") {
update_to_absolute_paths(&mut search_paths);
}
Ok(search_paths)
}
fn update_to_absolute_paths(search_paths: &mut [PathBuf]) {
for buffer in search_paths.iter_mut() {
*buffer = filesystem::absolute_path(buffer.normalize().unwrap().as_path()).unwrap();
}
}
/// Detect if the user accidentally supplied a path instead of a search pattern
fn ensure_search_pattern_is_not_a_path(matches: &clap::ArgMatches, pattern: &str) -> Result<()> {
if !matches.is_present("full-path")
&& pattern.contains(std::path::MAIN_SEPARATOR)
&& Path::new(pattern).is_dir()
fn ensure_search_pattern_is_not_a_path(opts: &Opts) -> Result<()> {
if !opts.full_path
&& opts.pattern.contains(std::path::MAIN_SEPARATOR)
&& Path::new(&opts.pattern).is_dir()
{
Err(anyhow!(
"The search pattern '{pattern}' contains a path-separation character ('{sep}') \
@@ -175,7 +131,7 @@ fn ensure_search_pattern_is_not_a_path(matches: &clap::ArgMatches, pattern: &str
fd . '{pattern}'\n\n\
Instead, if you want your pattern to match the full file path, use:\n\n \
fd --full-path '{pattern}'",
pattern = pattern,
pattern = &opts.pattern,
sep = std::path::MAIN_SEPARATOR,
))
} else {
@@ -183,11 +139,12 @@ fn ensure_search_pattern_is_not_a_path(matches: &clap::ArgMatches, pattern: &str
}
}
fn build_pattern_regex(matches: &clap::ArgMatches, pattern: &str) -> Result<String> {
Ok(if matches.is_present("glob") && !pattern.is_empty() {
fn build_pattern_regex(opts: &Opts) -> Result<String> {
let pattern = &opts.pattern;
Ok(if opts.glob && !pattern.is_empty() {
let glob = GlobBuilder::new(pattern).literal_separator(true).build()?;
glob.regex().to_owned()
} else if matches.is_present("fixed-strings") {
} else if opts.fixed_strings {
// Treat pattern as literal string if '--fixed-strings' is used
regex::escape(pattern)
} else {
@@ -209,25 +166,25 @@ fn check_path_separator_length(path_separator: Option<&str>) -> Result<()> {
}
}
fn construct_config(matches: clap::ArgMatches, pattern_regex: &str) -> Result<Config> {
fn construct_config(mut opts: Opts, pattern_regex: &str) -> Result<Config> {
// The search will be case-sensitive if the command line flag is set or
// if the pattern has an uppercase character (smart case).
let case_sensitive = !matches.is_present("ignore-case")
&& (matches.is_present("case-sensitive") || pattern_has_uppercase_char(pattern_regex));
let case_sensitive =
!opts.ignore_case && (opts.case_sensitive || pattern_has_uppercase_char(pattern_regex));
let path_separator = matches
.value_of("path-separator")
.map_or_else(filesystem::default_path_separator, |s| Some(s.to_owned()));
let path_separator = opts
.path_separator
.take()
.or_else(filesystem::default_path_separator);
let actual_path_separator = path_separator
.clone()
.unwrap_or_else(|| std::path::MAIN_SEPARATOR.to_string());
check_path_separator_length(path_separator.as_deref())?;
let size_limits = extract_size_limits(&matches)?;
let time_constraints = extract_time_constraints(&matches)?;
let size_limits = std::mem::take(&mut opts.size);
let time_constraints = extract_time_constraints(&opts)?;
#[cfg(unix)]
let owner_constraint = matches
.value_of("owner")
.map(OwnerFilter::from_string)
.transpose()?
.flatten();
let owner_constraint: Option<OwnerFilter> = opts.owner.and_then(OwnerFilter::filter_ignore);
#[cfg(windows)]
let ansi_colors_support =
@@ -236,10 +193,12 @@ fn construct_config(matches: clap::ArgMatches, pattern_regex: &str) -> Result<Co
let ansi_colors_support = true;
let interactive_terminal = atty::is(Stream::Stdout);
let colored_output = match matches.value_of("color") {
Some("always") => true,
Some("never") => false,
_ => ansi_colors_support && env::var_os("NO_COLOR").is_none() && interactive_terminal,
let colored_output = match opts.color {
ColorWhen::Always => true,
ColorWhen::Never => false,
ColorWhen::Auto => {
ansi_colors_support && env::var_os("NO_COLOR").is_none() && interactive_terminal
}
};
let ls_colors = if colored_output {
@@ -247,83 +206,43 @@ fn construct_config(matches: clap::ArgMatches, pattern_regex: &str) -> Result<Co
} else {
None
};
let command = extract_command(&matches, path_separator.as_deref(), colored_output)?;
let command = extract_command(&mut opts, colored_output)?;
let has_command = command.is_some();
Ok(Config {
case_sensitive,
search_full_path: matches.is_present("full-path"),
ignore_hidden: !(matches.is_present("hidden")
|| matches.occurrences_of("rg-alias-hidden-ignore") >= 2),
read_fdignore: !(matches.is_present("no-ignore")
|| matches.is_present("rg-alias-hidden-ignore")),
read_vcsignore: !(matches.is_present("no-ignore")
|| matches.is_present("rg-alias-hidden-ignore")
|| matches.is_present("no-ignore-vcs")),
read_parent_ignore: !(matches.is_present("no-ignore")
|| matches.is_present("rg-alias-hidden-ignore")
|| matches.is_present("no-ignore-vcs")
|| matches.is_present("no-ignore-parent")),
read_global_ignore: !(matches.is_present("no-ignore")
|| matches.is_present("rg-alias-hidden-ignore")
|| matches.is_present("no-global-ignore-file")),
follow_links: matches.is_present("follow"),
one_file_system: matches.is_present("one-file-system"),
null_separator: matches.is_present("null_separator"),
quiet: matches.is_present("quiet"),
max_depth: matches
.value_of("max-depth")
.or_else(|| matches.value_of("rg-depth"))
.or_else(|| matches.value_of("exact-depth"))
.map(|n| n.parse::<usize>())
.transpose()
.context("Failed to parse argument to --max-depth/--exact-depth")?,
min_depth: matches
.value_of("min-depth")
.or_else(|| matches.value_of("exact-depth"))
.map(|n| n.parse::<usize>())
.transpose()
.context("Failed to parse argument to --min-depth/--exact-depth")?,
prune: matches.is_present("prune"),
threads: std::cmp::max(
matches
.value_of("threads")
.map(|n| n.parse::<usize>())
.transpose()
.context("Failed to parse number of threads")?
.map(|n| {
if n > 0 {
Ok(n)
} else {
Err(anyhow!("Number of threads must be positive."))
}
})
.transpose()?
.unwrap_or_else(num_cpus::get),
1,
),
max_buffer_time: matches
.value_of("max-buffer-time")
.map(|n| n.parse::<u64>())
.transpose()
.context("Failed to parse max. buffer time argument")?
.map(time::Duration::from_millis),
search_full_path: opts.full_path,
ignore_hidden: !(opts.hidden || opts.rg_alias_ignore()),
read_fdignore: !(opts.no_ignore || opts.rg_alias_ignore()),
read_vcsignore: !(opts.no_ignore || opts.rg_alias_ignore() || opts.no_ignore_vcs),
read_parent_ignore: !opts.no_ignore_parent,
read_global_ignore: !opts.no_ignore || opts.rg_alias_ignore() || opts.no_global_ignore_file,
follow_links: opts.follow,
one_file_system: opts.one_file_system,
null_separator: opts.null_separator,
quiet: opts.quiet,
max_depth: opts.max_depth(),
min_depth: opts.min_depth(),
prune: opts.prune,
threads: opts.threads(),
max_buffer_time: opts.max_buffer_time,
ls_colors,
interactive_terminal,
file_types: matches.values_of("file-type").map(|values| {
file_types: opts.filetype.as_ref().map(|values| {
use crate::cli::FileType::*;
let mut file_types = FileTypes::default();
for value in values {
match value {
"f" | "file" => file_types.files = true,
"d" | "directory" => file_types.directories = true,
"l" | "symlink" => file_types.symlinks = true,
"x" | "executable" => {
File => file_types.files = true,
Directory => file_types.directories = true,
Symlink => file_types.symlinks = true,
Executable => {
file_types.executables_only = true;
file_types.files = true;
}
"e" | "empty" => file_types.empty_only = true,
"s" | "socket" => file_types.sockets = true,
"p" | "pipe" => file_types.pipes = true,
_ => unreachable!(),
Empty => file_types.empty_only = true,
Socket => file_types.sockets = true,
Pipe => file_types.pipes = true,
}
}
@@ -335,10 +254,12 @@ fn construct_config(matches: clap::ArgMatches, pattern_regex: &str) -> Result<Co
file_types
}),
extensions: matches
.values_of("extension")
extensions: opts
.extensions
.as_ref()
.map(|exts| {
let patterns = exts
.iter()
.map(|e| e.trim_start_matches('.'))
.map(|e| format!(r".\.{}$", regex::escape(e)));
RegexSetBuilder::new(patterns)
@@ -347,78 +268,38 @@ fn construct_config(matches: clap::ArgMatches, pattern_regex: &str) -> Result<Co
})
.transpose()?,
command: command.map(Arc::new),
batch_size: matches
.value_of("batch-size")
.map(|n| n.parse::<usize>())
.transpose()
.context("Failed to parse --batch-size argument")?
.unwrap_or_default(),
exclude_patterns: matches
.values_of("exclude")
.map(|v| v.map(|p| String::from("!") + p).collect())
.unwrap_or_else(Vec::new),
ignore_files: matches
.values_of("ignore-file")
.map(|vs| vs.map(PathBuf::from).collect())
.unwrap_or_else(Vec::new),
batch_size: opts.batch_size,
exclude_patterns: opts.exclude.iter().map(|p| String::from("!") + p).collect(),
ignore_files: std::mem::take(&mut opts.ignore_file),
size_constraints: size_limits,
time_constraints,
#[cfg(unix)]
owner_constraint,
show_filesystem_errors: matches.is_present("show-errors"),
show_filesystem_errors: opts.show_errors,
path_separator,
max_results: matches
.value_of("max-results")
.map(|n| n.parse::<usize>())
.transpose()
.context("Failed to parse --max-results argument")?
.filter(|&n| n > 0)
.or_else(|| {
if matches.is_present("max-one-result") {
Some(1)
} else {
None
}
}),
strip_cwd_prefix: (!matches.is_present("path")
&& !matches.is_present("search-path")
&& (interactive_terminal || matches.is_present("strip-cwd-prefix"))),
actual_path_separator,
max_results: opts.max_results(),
strip_cwd_prefix: (opts.no_search_paths()
&& (opts.strip_cwd_prefix || !(opts.null_separator || has_command))),
})
}
fn extract_command(
matches: &clap::ArgMatches,
path_separator: Option<&str>,
colored_output: bool,
) -> Result<Option<CommandTemplate>> {
None.or_else(|| {
matches.values_of("exec").map(|args| {
Ok(CommandTemplate::new(
args,
path_separator.map(str::to_string),
))
fn extract_command(opts: &mut Opts, colored_output: bool) -> Result<Option<CommandSet>> {
opts.exec
.command
.take()
.map(Ok)
.or_else(|| {
if !opts.list_details {
return None;
}
let color_arg = format!("--color={}", opts.color.as_str());
let res = determine_ls_command(&color_arg, colored_output)
.map(|cmd| CommandSet::new_batch([cmd]).unwrap());
Some(res)
})
})
.or_else(|| {
matches
.values_of("exec-batch")
.map(|args| CommandTemplate::new_batch(args, path_separator.map(str::to_string)))
})
.or_else(|| {
if !matches.is_present("list-details") {
return None;
}
let color = matches.value_of("color").unwrap_or("auto");
let color_arg = format!("--color={}", color);
let res = determine_ls_command(&color_arg, colored_output).map(|cmd| {
CommandTemplate::new_batch(cmd, path_separator.map(str::to_string)).unwrap()
});
Some(res)
})
.transpose()
.transpose()
}
fn determine_ls_command(color_arg: &str, colored_output: bool) -> Result<Vec<&str>> {
@@ -500,20 +381,10 @@ fn determine_ls_command(color_arg: &str, colored_output: bool) -> Result<Vec<&st
Ok(cmd)
}
fn extract_size_limits(matches: &clap::ArgMatches) -> Result<Vec<SizeFilter>> {
matches.values_of("size").map_or(Ok(Vec::new()), |vs| {
vs.map(|sf| {
SizeFilter::from_string(sf)
.ok_or_else(|| anyhow!("'{}' is not a valid size constraint. See 'fd --help'.", sf))
})
.collect::<Result<Vec<_>>>()
})
}
fn extract_time_constraints(matches: &clap::ArgMatches) -> Result<Vec<TimeFilter>> {
fn extract_time_constraints(opts: &Opts) -> Result<Vec<TimeFilter>> {
let now = time::SystemTime::now();
let mut time_constraints: Vec<TimeFilter> = Vec::new();
if let Some(t) = matches.value_of("changed-within") {
if let Some(ref t) = opts.changed_within {
if let Some(f) = TimeFilter::after(&now, t) {
time_constraints.push(f);
} else {
@@ -523,7 +394,7 @@ fn extract_time_constraints(matches: &clap::ArgMatches) -> Result<Vec<TimeFilter
));
}
}
if let Some(t) = matches.value_of("changed-before") {
if let Some(ref t) = opts.changed_before {
if let Some(f) = TimeFilter::before(&now, t) {
time_constraints.push(f);
} else {
+49 -34
View File
@@ -1,36 +1,23 @@
use std::borrow::Cow;
use std::io::{self, Write};
use std::path::Path;
use std::sync::atomic::{AtomicBool, Ordering};
use lscolors::{Indicator, LsColors, Style};
use crate::config::Config;
use crate::dir_entry::DirEntry;
use crate::error::print_error;
use crate::exit_codes::ExitCode;
use crate::filesystem::strip_current_dir;
fn replace_path_separator(path: &str, new_path_separator: &str) -> String {
path.replace(std::path::MAIN_SEPARATOR, new_path_separator)
}
// TODO: this function is performance critical and can probably be optimized
pub fn print_entry<W: Write>(
stdout: &mut W,
entry: &Path,
config: &Config,
wants_to_quit: &AtomicBool,
) {
let path = if config.strip_cwd_prefix {
strip_current_dir(entry)
} else {
entry
};
pub fn print_entry<W: Write>(stdout: &mut W, entry: &DirEntry, config: &Config) {
let r = if let Some(ref ls_colors) = config.ls_colors {
print_entry_colorized(stdout, path, config, ls_colors, wants_to_quit)
print_entry_colorized(stdout, entry, config, ls_colors)
} else {
print_entry_uncolorized(stdout, path, config)
print_entry_uncolorized(stdout, entry, config)
};
if let Err(e) = r {
@@ -44,16 +31,39 @@ pub fn print_entry<W: Write>(
}
}
// Display a trailing slash if the path is a directory and the config option is enabled.
// If the path_separator option is set, display that instead.
// The trailing slash will not be colored.
#[inline]
fn print_trailing_slash<W: Write>(
stdout: &mut W,
entry: &DirEntry,
config: &Config,
style: Option<&Style>,
) -> io::Result<()> {
if entry.file_type().map_or(false, |ft| ft.is_dir()) {
write!(
stdout,
"{}",
style
.map(Style::to_ansi_term_style)
.unwrap_or_default()
.paint(&config.actual_path_separator)
)?;
}
Ok(())
}
// TODO: this function is performance critical and can probably be optimized
fn print_entry_colorized<W: Write>(
stdout: &mut W,
path: &Path,
entry: &DirEntry,
config: &Config,
ls_colors: &LsColors,
wants_to_quit: &AtomicBool,
) -> io::Result<()> {
// Split the path between the parent and the last component
let mut offset = 0;
let path = entry.stripped_path(config);
let path_str = path.to_string_lossy();
if let Some(parent) = path.parent() {
@@ -80,66 +90,71 @@ fn print_entry_colorized<W: Write>(
write!(stdout, "{}", style.paint(parent_str))?;
}
let style = ls_colors
.style_for_path(path)
let style = entry
.style(ls_colors)
.map(Style::to_ansi_term_style)
.unwrap_or_default();
write!(stdout, "{}", style.paint(&path_str[offset..]))?;
print_trailing_slash(
stdout,
entry,
config,
ls_colors.style_for_indicator(Indicator::Directory),
)?;
if config.null_separator {
write!(stdout, "\0")?;
} else {
writeln!(stdout)?;
}
if wants_to_quit.load(Ordering::Relaxed) {
// Ignore any errors on flush, because we're about to exit anyway
let _ = stdout.flush();
ExitCode::KilledBySigint.exit();
}
Ok(())
}
// TODO: this function is performance critical and can probably be optimized
fn print_entry_uncolorized_base<W: Write>(
stdout: &mut W,
path: &Path,
entry: &DirEntry,
config: &Config,
) -> io::Result<()> {
let separator = if config.null_separator { "\0" } else { "\n" };
let path = entry.stripped_path(config);
let mut path_string = path.to_string_lossy();
if let Some(ref separator) = config.path_separator {
*path_string.to_mut() = replace_path_separator(&path_string, separator);
}
write!(stdout, "{}{}", path_string, separator)
write!(stdout, "{}", path_string)?;
print_trailing_slash(stdout, entry, config, None)?;
write!(stdout, "{}", separator)
}
#[cfg(not(unix))]
fn print_entry_uncolorized<W: Write>(
stdout: &mut W,
path: &Path,
entry: &DirEntry,
config: &Config,
) -> io::Result<()> {
print_entry_uncolorized_base(stdout, path, config)
print_entry_uncolorized_base(stdout, entry, config)
}
#[cfg(unix)]
fn print_entry_uncolorized<W: Write>(
stdout: &mut W,
path: &Path,
entry: &DirEntry,
config: &Config,
) -> io::Result<()> {
use std::os::unix::ffi::OsStrExt;
if config.interactive_terminal || config.path_separator.is_some() {
// Fall back to the base implementation
print_entry_uncolorized_base(stdout, path, config)
print_entry_uncolorized_base(stdout, entry, config)
} else {
// Print path as raw bytes, allowing invalid UTF-8 filenames to be passed to other processes
let separator = if config.null_separator { b"\0" } else { b"\n" };
stdout.write_all(path.as_os_str().as_bytes())?;
stdout.write_all(entry.stripped_path(config).as_os_str().as_bytes())?;
print_trailing_slash(stdout, entry, config, None)?;
stdout.write_all(separator)
}
}
+10 -10
View File
@@ -15,19 +15,19 @@ pub fn pattern_has_uppercase_char(pattern: &str) -> bool {
fn hir_has_uppercase_char(hir: &Hir) -> bool {
use regex_syntax::hir::*;
match *hir.kind() {
match hir.kind() {
HirKind::Literal(Literal::Unicode(c)) => c.is_uppercase(),
HirKind::Literal(Literal::Byte(b)) => char::from(b).is_uppercase(),
HirKind::Class(Class::Unicode(ref ranges)) => ranges
HirKind::Literal(Literal::Byte(b)) => char::from(*b).is_uppercase(),
HirKind::Class(Class::Unicode(ranges)) => ranges
.iter()
.any(|r| r.start().is_uppercase() || r.end().is_uppercase()),
HirKind::Class(Class::Bytes(ref ranges)) => ranges
HirKind::Class(Class::Bytes(ranges)) => ranges
.iter()
.any(|r| char::from(r.start()).is_uppercase() || char::from(r.end()).is_uppercase()),
HirKind::Group(Group { ref hir, .. }) | HirKind::Repetition(Repetition { ref hir, .. }) => {
HirKind::Group(Group { hir, .. }) | HirKind::Repetition(Repetition { hir, .. }) => {
hir_has_uppercase_char(hir)
}
HirKind::Concat(ref hirs) | HirKind::Alternation(ref hirs) => {
HirKind::Concat(hirs) | HirKind::Alternation(hirs) => {
hirs.iter().any(hir_has_uppercase_char)
}
_ => false,
@@ -52,11 +52,11 @@ fn hir_matches_strings_with_leading_dot(hir: &Hir) -> bool {
// "^\\.", i.e. a start text anchor and a literal dot character. There are a lot
// of other patterns that ONLY match hidden files, e.g. ^(\\.foo|\\.bar) which are
// not (yet) detected by this algorithm.
match *hir.kind() {
HirKind::Concat(ref hirs) => {
match hir.kind() {
HirKind::Concat(hirs) => {
let mut hirs = hirs.iter();
if let Some(hir) = hirs.next() {
if *hir.kind() != HirKind::Anchor(Anchor::StartText) {
if hir.kind() != &HirKind::Anchor(Anchor::StartText) {
return false;
}
} else {
@@ -64,7 +64,7 @@ fn hir_matches_strings_with_leading_dot(hir: &Hir) -> bool {
}
if let Some(hir) = hirs.next() {
*hir.kind() == HirKind::Literal(Literal::Unicode('.'))
hir.kind() == &HirKind::Literal(Literal::Unicode('.'))
} else {
false
}
+224 -199
View File
@@ -1,21 +1,21 @@
use std::ffi::OsStr;
use std::fs::{FileType, Metadata};
use std::io;
use std::path::{Path, PathBuf};
use std::mem;
use std::path::PathBuf;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::mpsc::{channel, Receiver, Sender};
use std::sync::{Arc, Mutex};
use std::thread;
use std::time;
use std::time::{Duration, Instant};
use std::{borrow::Cow, io::Write};
use anyhow::{anyhow, Result};
use crossbeam_channel::{bounded, Receiver, RecvTimeoutError, Sender};
use ignore::overrides::OverrideBuilder;
use ignore::{self, WalkBuilder};
use once_cell::unsync::OnceCell;
use regex::bytes::Regex;
use crate::config::Config;
use crate::dir_entry::DirEntry;
use crate::error::print_error;
use crate::exec;
use crate::exit_codes::{merge_exitcodes, ExitCode};
@@ -23,6 +23,7 @@ use crate::filesystem;
use crate::output;
/// The receiver thread can either be buffering results or directly streaming to the console.
#[derive(PartialEq)]
enum ReceiverMode {
/// Receiver is still buffering in order to sort the results, if the search finishes fast
/// enough.
@@ -33,29 +34,31 @@ enum ReceiverMode {
}
/// The Worker threads can result in a valid entry having PathBuf or an error.
#[allow(clippy::large_enum_variant)]
pub enum WorkerResult {
Entry(PathBuf),
// Errors should be rare, so it's probably better to allow large_enum_variant than
// to box the Entry variant
Entry(DirEntry),
Error(ignore::Error),
}
/// Maximum size of the output buffer before flushing results to the console
pub const MAX_BUFFER_LENGTH: usize = 1000;
/// Default duration until output buffering switches to streaming.
pub const DEFAULT_MAX_BUFFER_TIME: time::Duration = time::Duration::from_millis(100);
pub const DEFAULT_MAX_BUFFER_TIME: Duration = Duration::from_millis(100);
/// Recursively scan the given search path for files / pathnames matching the pattern.
///
/// If the `--exec` argument was supplied, this will create a thread pool for executing
/// jobs in parallel from a given command line and the discovered paths. Otherwise, each
/// path will simply be written to standard output.
pub fn scan(path_vec: &[PathBuf], pattern: Arc<Regex>, config: Arc<Config>) -> Result<ExitCode> {
let mut path_iter = path_vec.iter();
let first_path_buf = path_iter
.next()
.expect("Error: Path vector can not be empty");
let (tx, rx) = channel();
pub fn scan(paths: &[PathBuf], pattern: Arc<Regex>, config: Arc<Config>) -> Result<ExitCode> {
let first_path = &paths[0];
let mut override_builder = OverrideBuilder::new(first_path_buf.as_path());
// Channel capacity was chosen empircally to perform similarly to an unbounded channel
let (tx, rx) = bounded(0x4000 * config.threads);
let mut override_builder = OverrideBuilder::new(first_path);
for pattern in &config.exclude_patterns {
override_builder
@@ -66,11 +69,11 @@ pub fn scan(path_vec: &[PathBuf], pattern: Arc<Regex>, config: Arc<Config>) -> R
.build()
.map_err(|_| anyhow!("Mismatch in exclude patterns"))?;
let mut walker = WalkBuilder::new(first_path_buf.as_path());
let mut walker = WalkBuilder::new(first_path);
walker
.hidden(config.ignore_hidden)
.ignore(config.read_fdignore)
.parents(config.read_parent_ignore)
.parents(config.read_parent_ignore && (config.read_fdignore || config.read_vcsignore))
.git_ignore(config.read_vcsignore)
.git_global(config.read_vcsignore)
.git_exclude(config.read_vcsignore)
@@ -102,10 +105,7 @@ pub fn scan(path_vec: &[PathBuf], pattern: Arc<Regex>, config: Arc<Config>) -> R
match result {
Some(ignore::Error::Partial(_)) => (),
Some(err) => {
print_error(format!(
"Malformed pattern in global ignore file. {}.",
err.to_string()
));
print_error(format!("Malformed pattern in global ignore file. {}.", err));
}
None => (),
}
@@ -117,26 +117,31 @@ pub fn scan(path_vec: &[PathBuf], pattern: Arc<Regex>, config: Arc<Config>) -> R
match result {
Some(ignore::Error::Partial(_)) => (),
Some(err) => {
print_error(format!(
"Malformed pattern in custom ignore file. {}.",
err.to_string()
));
print_error(format!("Malformed pattern in custom ignore file. {}.", err));
}
None => (),
}
}
for path_entry in path_iter {
walker.add(path_entry.as_path());
for path in &paths[1..] {
walker.add(path);
}
let parallel_walker = walker.threads(config.threads).build_parallel();
let wants_to_quit = Arc::new(AtomicBool::new(false));
if config.ls_colors.is_some() && config.command.is_none() {
let wq = Arc::clone(&wants_to_quit);
// Flag for cleanly shutting down the parallel walk
let quit_flag = Arc::new(AtomicBool::new(false));
// Flag specifically for quitting due to ^C
let interrupt_flag = Arc::new(AtomicBool::new(false));
if config.ls_colors.is_some() && config.is_printing() {
let quit_flag = Arc::clone(&quit_flag);
let interrupt_flag = Arc::clone(&interrupt_flag);
ctrlc::set_handler(move || {
if wq.fetch_or(true, Ordering::Relaxed) {
quit_flag.store(true, Ordering::Relaxed);
if interrupt_flag.fetch_or(true, Ordering::Relaxed) {
// Ctrl-C has been pressed twice, exit NOW
ExitCode::KilledBySigint.exit();
}
@@ -145,72 +150,219 @@ pub fn scan(path_vec: &[PathBuf], pattern: Arc<Regex>, config: Arc<Config>) -> R
}
// Spawn the thread that receives all results through the channel.
let receiver_thread = spawn_receiver(&config, &wants_to_quit, rx);
let receiver_thread = spawn_receiver(&config, &quit_flag, &interrupt_flag, rx);
// Spawn the sender threads.
spawn_senders(&config, &wants_to_quit, pattern, parallel_walker, tx);
spawn_senders(&config, &quit_flag, pattern, parallel_walker, tx);
// Wait for the receiver thread to print out all results.
let exit_code = receiver_thread.join().unwrap();
if wants_to_quit.load(Ordering::Relaxed) {
if interrupt_flag.load(Ordering::Relaxed) {
Ok(ExitCode::KilledBySigint)
} else {
Ok(exit_code)
}
}
/// Wrapper for the receiver thread's buffering behavior.
struct ReceiverBuffer<W> {
/// The configuration.
config: Arc<Config>,
/// For shutting down the senders.
quit_flag: Arc<AtomicBool>,
/// The ^C notifier.
interrupt_flag: Arc<AtomicBool>,
/// Receiver for worker results.
rx: Receiver<WorkerResult>,
/// Standard output.
stdout: W,
/// The current buffer mode.
mode: ReceiverMode,
/// The deadline to switch to streaming mode.
deadline: Instant,
/// The buffer of quickly received paths.
buffer: Vec<DirEntry>,
/// Result count.
num_results: usize,
}
impl<W: Write> ReceiverBuffer<W> {
/// Create a new receiver buffer.
fn new(
config: Arc<Config>,
quit_flag: Arc<AtomicBool>,
interrupt_flag: Arc<AtomicBool>,
rx: Receiver<WorkerResult>,
stdout: W,
) -> Self {
let max_buffer_time = config.max_buffer_time.unwrap_or(DEFAULT_MAX_BUFFER_TIME);
let deadline = Instant::now() + max_buffer_time;
Self {
config,
quit_flag,
interrupt_flag,
rx,
stdout,
mode: ReceiverMode::Buffering,
deadline,
buffer: Vec::with_capacity(MAX_BUFFER_LENGTH),
num_results: 0,
}
}
/// Process results until finished.
fn process(&mut self) -> ExitCode {
loop {
if let Err(ec) = self.poll() {
self.quit_flag.store(true, Ordering::Relaxed);
return ec;
}
}
}
/// Receive the next worker result.
fn recv(&self) -> Result<WorkerResult, RecvTimeoutError> {
match self.mode {
ReceiverMode::Buffering => {
// Wait at most until we should switch to streaming
self.rx.recv_deadline(self.deadline)
}
ReceiverMode::Streaming => {
// Wait however long it takes for a result
Ok(self.rx.recv()?)
}
}
}
/// Wait for a result or state change.
fn poll(&mut self) -> Result<(), ExitCode> {
match self.recv() {
Ok(WorkerResult::Entry(dir_entry)) => {
if self.config.quiet {
return Err(ExitCode::HasResults(true));
}
match self.mode {
ReceiverMode::Buffering => {
self.buffer.push(dir_entry);
if self.buffer.len() > MAX_BUFFER_LENGTH {
self.stream()?;
}
}
ReceiverMode::Streaming => {
self.print(&dir_entry)?;
self.flush()?;
}
}
self.num_results += 1;
if let Some(max_results) = self.config.max_results {
if self.num_results >= max_results {
return self.stop();
}
}
}
Ok(WorkerResult::Error(err)) => {
if self.config.show_filesystem_errors {
print_error(err.to_string());
}
}
Err(RecvTimeoutError::Timeout) => {
self.stream()?;
}
Err(RecvTimeoutError::Disconnected) => {
return self.stop();
}
}
Ok(())
}
/// Output a path.
fn print(&mut self, entry: &DirEntry) -> Result<(), ExitCode> {
output::print_entry(&mut self.stdout, entry, &self.config);
if self.interrupt_flag.load(Ordering::Relaxed) {
// Ignore any errors on flush, because we're about to exit anyway
let _ = self.flush();
return Err(ExitCode::KilledBySigint);
}
Ok(())
}
/// Switch ourselves into streaming mode.
fn stream(&mut self) -> Result<(), ExitCode> {
self.mode = ReceiverMode::Streaming;
let buffer = mem::take(&mut self.buffer);
for path in buffer {
self.print(&path)?;
}
self.flush()
}
/// Stop looping.
fn stop(&mut self) -> Result<(), ExitCode> {
if self.mode == ReceiverMode::Buffering {
self.buffer.sort();
self.stream()?;
}
if self.config.quiet {
Err(ExitCode::HasResults(self.num_results > 0))
} else {
Err(ExitCode::Success)
}
}
/// Flush stdout if necessary.
fn flush(&mut self) -> Result<(), ExitCode> {
if self.config.interactive_terminal && self.stdout.flush().is_err() {
// Probably a broken pipe. Exit gracefully.
return Err(ExitCode::GeneralError);
}
Ok(())
}
}
fn spawn_receiver(
config: &Arc<Config>,
wants_to_quit: &Arc<AtomicBool>,
quit_flag: &Arc<AtomicBool>,
interrupt_flag: &Arc<AtomicBool>,
rx: Receiver<WorkerResult>,
) -> thread::JoinHandle<ExitCode> {
let config = Arc::clone(config);
let wants_to_quit = Arc::clone(wants_to_quit);
let quit_flag = Arc::clone(quit_flag);
let interrupt_flag = Arc::clone(interrupt_flag);
let show_filesystem_errors = config.show_filesystem_errors;
let threads = config.threads;
// This will be used to check if output should be buffered when only running a single thread
let enable_output_buffering: bool = threads > 1;
thread::spawn(move || {
// This will be set to `Some` if the `--exec` argument was supplied.
if let Some(ref cmd) = config.command {
if cmd.in_batch_mode() {
exec::batch(
rx,
cmd,
show_filesystem_errors,
enable_output_buffering,
config.batch_size,
)
exec::batch(rx, cmd, &config)
} else {
let shared_rx = Arc::new(Mutex::new(rx));
let out_perm = Arc::new(Mutex::new(()));
// Each spawned job will store it's thread handle in here.
let mut handles = Vec::with_capacity(threads);
for _ in 0..threads {
let rx = Arc::clone(&shared_rx);
let config = Arc::clone(&config);
let rx = rx.clone();
let cmd = Arc::clone(cmd);
let out_perm = Arc::clone(&out_perm);
// Spawn a job thread that will listen for and execute inputs.
let handle = thread::spawn(move || {
exec::job(
rx,
cmd,
out_perm,
show_filesystem_errors,
enable_output_buffering,
)
});
let handle = thread::spawn(move || exec::job(rx, cmd, out_perm, &config));
// Push the handle of the spawned thread into the vector for later joining.
handles.push(handle);
}
// Wait for all threads to exit before exiting the program.
let exit_codes = handles
.into_iter()
.map(|handle| handle.join().unwrap())
@@ -218,153 +370,19 @@ fn spawn_receiver(
merge_exitcodes(exit_codes)
}
} else {
let start = time::Instant::now();
// Start in buffering mode
let mut mode = ReceiverMode::Buffering;
// Maximum time to wait before we start streaming to the console.
let max_buffer_time = config.max_buffer_time.unwrap_or(DEFAULT_MAX_BUFFER_TIME);
let stdout = io::stdout();
let stdout = stdout.lock();
let mut stdout = io::BufWriter::new(stdout);
let stdout = io::BufWriter::new(stdout);
let mut num_results = 0;
let is_interactive = config.interactive_terminal;
let mut buffer = Vec::with_capacity(MAX_BUFFER_LENGTH);
for worker_result in rx {
match worker_result {
WorkerResult::Entry(path) => {
if config.quiet {
return ExitCode::HasResults(true);
}
match mode {
ReceiverMode::Buffering => {
buffer.push(path);
// Have we reached the maximum buffer size or maximum buffering time?
if buffer.len() > MAX_BUFFER_LENGTH
|| start.elapsed() > max_buffer_time
{
// Flush the buffer
for path in &buffer {
output::print_entry(
&mut stdout,
path,
&config,
&wants_to_quit,
);
}
buffer.clear();
if is_interactive && stdout.flush().is_err() {
// Probably a broken pipe. Exit gracefully.
return ExitCode::GeneralError;
}
// Start streaming
mode = ReceiverMode::Streaming;
}
}
ReceiverMode::Streaming => {
output::print_entry(&mut stdout, &path, &config, &wants_to_quit);
if is_interactive && stdout.flush().is_err() {
// Probably a broken pipe. Exit gracefully.
return ExitCode::GeneralError;
}
}
}
num_results += 1;
if let Some(max_results) = config.max_results {
if num_results >= max_results {
break;
}
}
}
WorkerResult::Error(err) => {
if show_filesystem_errors {
print_error(err.to_string());
}
}
}
}
// If we have finished fast enough (faster than max_buffer_time), we haven't streamed
// anything to the console, yet. In this case, sort the results and print them:
buffer.sort();
for value in buffer {
output::print_entry(&mut stdout, &value, &config, &wants_to_quit);
}
if config.quiet {
ExitCode::HasResults(false)
} else {
ExitCode::Success
}
let mut rxbuffer = ReceiverBuffer::new(config, quit_flag, interrupt_flag, rx, stdout);
rxbuffer.process()
}
})
}
enum DirEntryInner {
Normal(ignore::DirEntry),
BrokenSymlink(PathBuf),
}
pub struct DirEntry {
inner: DirEntryInner,
metadata: OnceCell<Option<Metadata>>,
}
impl DirEntry {
fn normal(e: ignore::DirEntry) -> Self {
Self {
inner: DirEntryInner::Normal(e),
metadata: OnceCell::new(),
}
}
fn broken_symlink(path: PathBuf) -> Self {
Self {
inner: DirEntryInner::BrokenSymlink(path),
metadata: OnceCell::new(),
}
}
pub fn path(&self) -> &Path {
match &self.inner {
DirEntryInner::Normal(e) => e.path(),
DirEntryInner::BrokenSymlink(pathbuf) => pathbuf.as_path(),
}
}
pub fn file_type(&self) -> Option<FileType> {
match &self.inner {
DirEntryInner::Normal(e) => e.file_type(),
DirEntryInner::BrokenSymlink(_) => self.metadata().map(|m| m.file_type()),
}
}
pub fn metadata(&self) -> Option<&Metadata> {
self.metadata
.get_or_init(|| match &self.inner {
DirEntryInner::Normal(e) => e.metadata().ok(),
DirEntryInner::BrokenSymlink(path) => path.symlink_metadata().ok(),
})
.as_ref()
}
pub fn depth(&self) -> Option<usize> {
match &self.inner {
DirEntryInner::Normal(e) => Some(e.depth()),
DirEntryInner::BrokenSymlink(_) => None,
}
}
}
fn spawn_senders(
config: &Arc<Config>,
wants_to_quit: &Arc<AtomicBool>,
quit_flag: &Arc<AtomicBool>,
pattern: Arc<Regex>,
parallel_walker: ignore::WalkParallel,
tx: Sender<WorkerResult>,
@@ -373,10 +391,10 @@ fn spawn_senders(
let config = Arc::clone(config);
let pattern = Arc::clone(&pattern);
let tx_thread = tx.clone();
let wants_to_quit = Arc::clone(wants_to_quit);
let quit_flag = Arc::clone(quit_flag);
Box::new(move |entry_o| {
if wants_to_quit.load(Ordering::Relaxed) {
if quit_flag.load(Ordering::Relaxed) {
return ignore::WalkState::Quit;
}
@@ -512,7 +530,14 @@ fn spawn_senders(
}
}
let send_result = tx_thread.send(WorkerResult::Entry(entry_path.to_owned()));
if config.is_printing() {
if let Some(ls_colors) = &config.ls_colors {
// Compute colors in parallel
entry.style(ls_colors);
}
}
let send_result = tx_thread.send(WorkerResult::Entry(entry));
if send_result.is_err() {
return ignore::WalkState::Quit;
+32 -13
View File
@@ -130,6 +130,17 @@ fn normalize_output(s: &str, trim_start: bool, normalize_line: bool) -> String {
lines.join("\n")
}
/// Trim whitespace from the beginning of each line.
fn trim_lines(s: &str) -> String {
s.lines()
.map(|line| line.trim_start())
.fold(String::new(), |mut str, line| {
str.push_str(line);
str.push('\n');
str
})
}
impl TestEnv {
pub fn new(directories: &[&'static str], files: &[&'static str]) -> TestEnv {
let temp_dir = create_working_directory(directories, files).expect("working directory");
@@ -174,6 +185,11 @@ impl TestEnv {
self.temp_dir.path().to_path_buf()
}
/// Get the path of the fd executable.
pub fn test_exe(&self) -> &PathBuf {
&self.fd_exe
}
/// Get the root directory of the file system.
pub fn system_root(&self) -> PathBuf {
let mut components = self.temp_dir.path().components();
@@ -203,6 +219,19 @@ impl TestEnv {
output
}
pub fn assert_success_and_get_normalized_output<P: AsRef<Path>>(
&self,
path: P,
args: &[&str],
) -> String {
let output = self.assert_success_and_get_output(path, args);
normalize_output(
&String::from_utf8_lossy(&output.stdout),
false,
self.normalize_line,
)
}
/// Assert that calling *fd* with the specified arguments produces the expected output.
pub fn assert_output(&self, args: &[&str], expected: &str) {
self.assert_output_subdirectory(".", args, expected)
@@ -224,15 +253,9 @@ impl TestEnv {
args: &[&str],
expected: &str,
) {
let output = self.assert_success_and_get_output(path, args);
// Normalize both expected and actual output.
let expected = normalize_output(expected, true, self.normalize_line);
let actual = normalize_output(
&String::from_utf8_lossy(&output.stdout),
false,
self.normalize_line,
);
let actual = self.assert_success_and_get_normalized_output(path, args);
// Compare actual output to expected output.
if expected != actual {
@@ -280,12 +303,8 @@ impl TestEnv {
if let Some(expected) = expected {
// Normalize both expected and actual output.
let expected_error = normalize_output(expected, true, self.normalize_line);
let actual_err = normalize_output(
&String::from_utf8_lossy(&output.stderr),
false,
self.normalize_line,
);
let expected_error = trim_lines(expected);
let actual_err = trim_lines(&String::from_utf8_lossy(&output.stderr));
// Compare actual output to expected output.
if !actual_err.trim_start().starts_with(&expected_error) {
+615 -397
View File
File diff suppressed because it is too large Load Diff