mirror of
https://github.com/sharkdp/fd.git
synced 2026-08-30 17:07:43 +08:00
Merge remote-tracking branch 'origin/master' into no-strip/760
This commit is contained in:
@@ -1,28 +0,0 @@
|
||||
---
|
||||
name: Bug Report
|
||||
about: Report a bug.
|
||||
title: ""
|
||||
labels: bug
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Describe the bug you encountered:**
|
||||
|
||||
<!--
|
||||
Please check out the troubleshooting section first:
|
||||
https://github.com/sharkdp/fd#troubleshooting
|
||||
-->
|
||||
|
||||
|
||||
**Describe what you expected to happen:**
|
||||
|
||||
|
||||
**What version of `fd` are you using?**
|
||||
<!-- paste the output of `fd --version` here -->
|
||||
|
||||
**Which operating system / distribution are you on?**
|
||||
<!--
|
||||
Unix: paste the output of `uname -srm` and `lsb_release -a` here.
|
||||
Windows: please tell us your Windows version
|
||||
-->
|
||||
@@ -0,0 +1,41 @@
|
||||
name: Bug Report
|
||||
description: Report a bug.
|
||||
title: "[BUG] "
|
||||
labels: bug
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Please check out the [troubleshooting section](https://github.com/sharkdp/fd#troubleshooting) first.
|
||||
- type: checkboxes
|
||||
attributes:
|
||||
options:
|
||||
- label: I have read the troubleshooting section and still think this is a bug.
|
||||
required: true
|
||||
- type: textarea
|
||||
id: bug
|
||||
attributes:
|
||||
label: "Describe the bug you encountered:"
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: expected
|
||||
attributes:
|
||||
label: "Describe what you expected to happen:"
|
||||
- type: input
|
||||
id: version
|
||||
attributes:
|
||||
label: "What version of `fd` are you using?"
|
||||
placeholder: "paste the output of `fd --version` here"
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: os
|
||||
attributes:
|
||||
label: Which operating system / distribution are you on?
|
||||
placeholder: |
|
||||
Unix: paste the output of `uname -srm` and `lsb_release -a` here.
|
||||
Windows: please tell us your Windows version
|
||||
render: shell
|
||||
validations:
|
||||
required: true
|
||||
+7
-2
@@ -4,13 +4,17 @@
|
||||
|
||||
- File metadata is now cached between the different filters that require it (e.g. `--owner`,
|
||||
`--size`), reducing the number of `stat` syscalls when multiple filters are used; see #863
|
||||
- Colorized output is now significantly faster, see #720 and #853 (@tavianator)
|
||||
|
||||
## Features
|
||||
|
||||
- Don't buffer command output from `--exec` when using a single thread. See #522
|
||||
|
||||
- Add new `-q, --quiet` flag, see #303 (@Asha20)
|
||||
|
||||
- Add new `--no-ignore-parent` flag, see #787 (@will459)
|
||||
- Add new `--batch-size` flag, see #410 (@devonhollowood)
|
||||
- Add opposing command-line options, see #595 (@Asha20)
|
||||
- Add support for more filesystem indicators in `LS_COLORS`, see
|
||||
https://github.com/sharkdp/lscolors/pull/35 (@tavianator)
|
||||
|
||||
## Bugfixes
|
||||
|
||||
@@ -23,6 +27,7 @@
|
||||
- Support `--list-details` on more platforms (like BusyBox), see #783
|
||||
- The filters `--owner`, `--size`, and `--changed-{within,before}` now apply to symbolic links
|
||||
themselves, rather than the link target, except when `--follow` is specified; see #863
|
||||
- Change time comparisons to be exclusive, see #794 (@jacobmischka)
|
||||
|
||||
## Changes
|
||||
|
||||
|
||||
Generated
+70
-21
@@ -31,9 +31,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.44"
|
||||
version = "1.0.45"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "61604a8f862e1d5c3229fdd78f8b02c68dcf73a4c4b05fd636d12240aaa242c1"
|
||||
checksum = "ee10e43ae4a853c0a3591d4e2ada1719e553be18199d9da9d4a83f5927c2f5c7"
|
||||
|
||||
[[package]]
|
||||
name = "atty"
|
||||
@@ -54,24 +54,24 @@ checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "1.2.1"
|
||||
version = "1.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
|
||||
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
||||
|
||||
[[package]]
|
||||
name = "bstr"
|
||||
version = "0.2.16"
|
||||
version = "0.2.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "90682c8d613ad3373e66de8c6411e0ae2ab2571e879d2efbf73558cc66f21279"
|
||||
checksum = "ba3569f383e8f1598449f1a423e72e99569137b47740b1da11ef19af3d5c3223"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.0.70"
|
||||
version = "1.0.72"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d26a6ce4b6a484fa3edb70f7efa6fc430fd2b87285fe8b84304fd0936faa0dc0"
|
||||
checksum = "22a9137b95ea06864e018375b72adfb7db6e6f68cfc8df5a04d00288050485ee"
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
@@ -120,9 +120,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ctrlc"
|
||||
version = "3.2.0"
|
||||
version = "3.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "377c9b002a72a0b2c1a18c62e2f3864bdfea4a015e3683a96e24aa45dd6c02d1"
|
||||
checksum = "a19c6cedffdc8c03a3346d723eb20bd85a13362bb96dc2ac000842c6381ec7bf"
|
||||
dependencies = [
|
||||
"nix",
|
||||
"winapi",
|
||||
@@ -172,15 +172,16 @@ dependencies = [
|
||||
"humantime",
|
||||
"ignore",
|
||||
"jemallocator",
|
||||
"lazy_static",
|
||||
"libc",
|
||||
"lscolors",
|
||||
"nix",
|
||||
"normpath",
|
||||
"num_cpus",
|
||||
"once_cell",
|
||||
"regex",
|
||||
"regex-syntax",
|
||||
"tempdir",
|
||||
"test-case",
|
||||
"users",
|
||||
"version_check",
|
||||
]
|
||||
@@ -301,9 +302,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.103"
|
||||
version = "0.2.107"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dd8f7255a17a627354f321ef0055d63b898c6fb27eff628af4d1b66b7331edf6"
|
||||
checksum = "fbe5e23404da5b4f555ef85ebed98fb4083e55a00c317800bc2a50ede9f3d219"
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
@@ -316,9 +317,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "lscolors"
|
||||
version = "0.7.1"
|
||||
version = "0.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d24b894c45c9da468621cdd615a5a79ee5e5523dd4f75c76ebc03d458940c16e"
|
||||
checksum = "9dd58d8727f3035fa6d5272f16b519741fd4875936b99d8a7cde21291b7d9174"
|
||||
dependencies = [
|
||||
"ansi_term 0.12.1",
|
||||
]
|
||||
@@ -340,9 +341,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nix"
|
||||
version = "0.22.1"
|
||||
version = "0.23.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e7555d6c7164cc913be1ce7f95cbecdabda61eb2ccd89008524af306fb7f5031"
|
||||
checksum = "f305c2c2e4c39a82f7bf0bf65fb557f9070ce06781d4f2454295cc34b1c43188"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"cc",
|
||||
@@ -353,9 +354,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "normpath"
|
||||
version = "0.3.0"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "27e6e8f70e9fbbe3752d330d769e3424f24b9458ce266df93a3b456902fd696a"
|
||||
checksum = "640c20e9df4a2d4a5adad5b47e17d76dac3e824346b181931c3ec9f7a85687b1"
|
||||
dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
@@ -395,6 +396,24 @@ version = "1.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "692fcb63b64b1758029e0a96ee63e049ce8c5948587f2f7208df04625e5f6b56"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.32"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ba508cc11742c0dc5c1659771673afbab7a0efab23aa17e854cbab0837ed0b43"
|
||||
dependencies = [
|
||||
"unicode-xid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "38bc8cc6a5f2e3655e0899c1b848643b2562f853f114bfec7be120678e3ace05"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand"
|
||||
version = "0.4.6"
|
||||
@@ -492,6 +511,17 @@ version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "1.0.81"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f2afee18b8beb5a596ecb4a2dce128c719b4ba399d34126b9e4396e3f9860966"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-xid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tempdir"
|
||||
version = "0.3.7"
|
||||
@@ -512,6 +542,19 @@ dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "test-case"
|
||||
version = "1.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c7cad0a06f9a61e94355aa3b3dc92d85ab9c83406722b1ca5e918d4297c12c23"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "textwrap"
|
||||
version = "0.11.0"
|
||||
@@ -543,9 +586,15 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "unicode-width"
|
||||
version = "0.1.8"
|
||||
version = "0.1.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3"
|
||||
checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-xid"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3"
|
||||
|
||||
[[package]]
|
||||
name = "users"
|
||||
|
||||
+4
-3
@@ -37,13 +37,12 @@ version_check = "0.9"
|
||||
ansi_term = "0.12"
|
||||
atty = "0.2"
|
||||
ignore = "0.4.3"
|
||||
lazy_static = "1.1.0"
|
||||
num_cpus = "1.8"
|
||||
regex = "1.5.4"
|
||||
regex-syntax = "0.6"
|
||||
ctrlc = "3.2"
|
||||
humantime = "2.1"
|
||||
lscolors = "0.7"
|
||||
lscolors = "0.8"
|
||||
globset = "0.4"
|
||||
anyhow = "1.0"
|
||||
dirs-next = "2.0"
|
||||
@@ -57,6 +56,7 @@ features = ["suggestions", "color", "wrap_help"]
|
||||
|
||||
[target.'cfg(unix)'.dependencies]
|
||||
users = "0.11.0"
|
||||
nix = "0.23.0"
|
||||
|
||||
[target.'cfg(all(unix, not(target_os = "redox")))'.dependencies]
|
||||
libc = "0.2"
|
||||
@@ -70,7 +70,8 @@ jemallocator = "0.3.0"
|
||||
[dev-dependencies]
|
||||
diff = "0.1"
|
||||
tempdir = "0.3"
|
||||
filetime = "0.2.15"
|
||||
filetime = "0.2"
|
||||
test-case = "1.2"
|
||||
|
||||
[profile.release]
|
||||
lto = true
|
||||
|
||||
@@ -418,6 +418,14 @@ use a character class with a single hyphen character:
|
||||
> fd '[-]pattern'
|
||||
```
|
||||
|
||||
### "Command not found" for `alias`es or shell functions
|
||||
|
||||
Shell `alias`es and shell functions can not be used for command execution via `fd -x` or
|
||||
`fd -X`. In `zsh`, you can make the alias global via `alias -g myalias="…"`. In `bash`,
|
||||
you can use `export -f my_function` to make available to child processes. You would still
|
||||
need to call `fd -x bash -c 'my_function "$1"' bash`. For other use cases or shells, use
|
||||
a (temporary) shell script.
|
||||
|
||||
## Integration with other programs
|
||||
|
||||
### Using fd with `fzf`
|
||||
@@ -667,7 +675,6 @@ cargo install --path .
|
||||
- [sharkdp](https://github.com/sharkdp)
|
||||
- [tmccombs](https://github.com/tmccombs)
|
||||
- [tavianator](https://github.com/tavianator)
|
||||
- [pemistahl](https://github.com/pemistahl/)
|
||||
|
||||
## License
|
||||
|
||||
|
||||
@@ -138,6 +138,7 @@ _fd() {
|
||||
+ '(exec-cmds)' # execute command
|
||||
'(long-listing max-results)'{-x+,--exec=}'[execute command for each search result]:command: _command_names -e:*\;::program arguments: _normal'
|
||||
'(long-listing max-results)'{-X+,--exec-batch=}'[execute command for all search results at once]:command: _command_names -e:*\;::program arguments: _normal'
|
||||
'(long-listing max-results)--batch-size=[max number of args for each -X call]:size'
|
||||
|
||||
+ other
|
||||
'!(--max-buffer-time)--max-buffer-time=[set amount of time to buffer before showing output]:time (ms)'
|
||||
|
||||
@@ -28,7 +28,7 @@ is a simple, fast and user-friendly alternative to
|
||||
.TP
|
||||
.B \-H, \-\-hidden
|
||||
Include hidden files and directories in the search results
|
||||
(default: hidden files and directories are skipped).
|
||||
(default: hidden files and directories are skipped). The flag can be overridden with '--no-hidden'.
|
||||
.TP
|
||||
.B \-I, \-\-no\-ignore
|
||||
Show search results from files and directories that would otherwise be ignored by
|
||||
@@ -49,6 +49,8 @@ The global fd ignore file (usually
|
||||
.I $HOME/.config/fd/ignore
|
||||
)
|
||||
.RE
|
||||
.IP
|
||||
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'.
|
||||
@@ -62,6 +64,7 @@ and the global gitignore configuration
|
||||
.RI ( core.excludesFile
|
||||
git setting, which defaults to
|
||||
.IR $HOME/.config/git/ignore ).
|
||||
The flag can be overridden with '--ignore-vcs'.
|
||||
.TP
|
||||
.B \-s, \-\-case\-sensitive
|
||||
Perform a case-sensitive search. By default, fd uses case-insensitive searches, unless the
|
||||
@@ -84,6 +87,7 @@ performs substring comparison. If you want to match on an exact filename, consid
|
||||
.TP
|
||||
.B \-a, \-\-absolute\-path
|
||||
Shows the full path starting from the root as opposed to relative paths.
|
||||
The flag can be overridden with '--relative-path'.
|
||||
.TP
|
||||
.B \-l, \-\-list\-details
|
||||
Use a detailed listing format like 'ls -l'. This is basically an alias
|
||||
@@ -93,7 +97,7 @@ sort order.
|
||||
.TP
|
||||
.B \-L, \-\-follow
|
||||
By default, fd does not descend into symlinked directories. Using this flag, symbolic links are
|
||||
also traversed.
|
||||
also traversed. The flag can be overridden with '--no-follow'.
|
||||
.TP
|
||||
.B \-p, \-\-full\-path
|
||||
By default, the search pattern is only matched against the filename (or directory name). Using
|
||||
@@ -154,18 +158,40 @@ regular files
|
||||
directories
|
||||
.IP "l, symlink"
|
||||
symbolic links
|
||||
.IP "x, executable"
|
||||
executable (files)
|
||||
.IP "e, empty"
|
||||
empty files or directories
|
||||
.IP "s, socket"
|
||||
sockets
|
||||
.IP "p, pipe"
|
||||
named pipes (FIFOs)
|
||||
.IP "x, executable"
|
||||
executable (files)
|
||||
.IP "e, empty"
|
||||
empty files or directories
|
||||
.RE
|
||||
|
||||
.RS
|
||||
This option can be used repeatedly to allow for multiple file types.
|
||||
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.
|
||||
|
||||
Examples:
|
||||
- Only search for files:
|
||||
fd --type file …
|
||||
fd -tf …
|
||||
- Find both files and symlinks
|
||||
fd --type file --type symlink …
|
||||
fd -tf -tl …
|
||||
- Find executable files:
|
||||
fd --type executable
|
||||
fd -tx
|
||||
- Find empty files:
|
||||
fd --type empty --type file
|
||||
fd -te -tf
|
||||
- Find empty directories:
|
||||
fd --type empty --type directory
|
||||
fd -te -td
|
||||
.RE
|
||||
.TP
|
||||
.BI "\-e, \-\-extension " ext
|
||||
@@ -243,7 +269,7 @@ tebibytes
|
||||
.TP
|
||||
.BI "\-\-changed-within " date|duration
|
||||
Filter results based on the file modification time.
|
||||
Files with modification times greater than or equal to the argument will be returned.
|
||||
Files with modification times greater than the argument will be returned.
|
||||
The argument can be provided as a duration (\fI10h, 1d, 35min\fR) or as a specific point
|
||||
in time in either full RFC3339 format with time zone, or as a date or datetime in the
|
||||
local time zone (\fIYYYY-MM-DD\fR or \fIYYYY-MM-DD HH:MM:SS\fR).
|
||||
@@ -256,7 +282,7 @@ Examples:
|
||||
.TP
|
||||
.BI "\-\-changed-before " date|duration
|
||||
Filter results based on the file modification time.
|
||||
Files with modification times less than or equal to the argument will be returned.
|
||||
Files with modification times less than the argument will be returned.
|
||||
The argument can be provided as a duration (\fI10h, 1d, 35min\fR) or as a specific point
|
||||
in time in either full RFC3339 format with time zone, or as a date or datetime in the
|
||||
local time zone (\fIYYYY-MM-DD\fR or \fIYYYY-MM-DD HH:MM:SS\fR).
|
||||
@@ -405,5 +431,11 @@ $ 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 SEE ALSO
|
||||
.BR find (1)
|
||||
|
||||
+102
-13
@@ -25,7 +25,17 @@ pub fn build_app() -> App<'static, 'static> {
|
||||
.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).",
|
||||
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(
|
||||
@@ -36,7 +46,17 @@ pub fn build_app() -> App<'static, 'static> {
|
||||
.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.",
|
||||
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(
|
||||
@@ -47,7 +67,16 @@ pub fn build_app() -> App<'static, 'static> {
|
||||
.help("Do not respect .gitignore files")
|
||||
.long_help(
|
||||
"Show search results from files and directories that would otherwise be \
|
||||
ignored by '.gitignore' files.",
|
||||
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(
|
||||
@@ -72,6 +101,7 @@ pub fn build_app() -> App<'static, 'static> {
|
||||
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")
|
||||
@@ -145,7 +175,17 @@ pub fn build_app() -> App<'static, 'static> {
|
||||
.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.",
|
||||
"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(
|
||||
@@ -170,7 +210,17 @@ pub fn build_app() -> App<'static, 'static> {
|
||||
.help("Follow symbolic links")
|
||||
.long_help(
|
||||
"By default, fd does not descend into symlinked directories. Using this \
|
||||
flag, symbolic links are also traversed.",
|
||||
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(
|
||||
@@ -178,11 +228,12 @@ pub fn build_app() -> App<'static, 'static> {
|
||||
.long("full-path")
|
||||
.short("p")
|
||||
.overrides_with("full-path")
|
||||
.help("Search full path (default: file-/dirname only)")
|
||||
.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 path.",
|
||||
directory name). Using this flag, the pattern is matched against the full \
|
||||
(absolute) path. Example:\n \
|
||||
fd --glob -p '**/.git/config'",
|
||||
),
|
||||
)
|
||||
.arg(
|
||||
@@ -249,7 +300,8 @@ pub fn build_app() -> App<'static, 'static> {
|
||||
.conflicts_with_all(&["size", "exact-depth"])
|
||||
.hidden_short_help(true)
|
||||
.help("Do not traverse into matching directories")
|
||||
.long_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")
|
||||
@@ -281,14 +333,36 @@ pub fn build_app() -> App<'static, 'static> {
|
||||
empty (e), socket (s), pipe (p)",
|
||||
)
|
||||
.long_help(
|
||||
"Filter the search by type (multiple allowable filetypes can be specified):\n \
|
||||
"Filter the search by type:\n \
|
||||
'f' or 'file': regular files\n \
|
||||
'd' or 'directory': directories\n \
|
||||
'l' or 'symlink': symbolic links\n \
|
||||
'x' or 'executable': executables\n \
|
||||
'e' or 'empty': empty files or directories\n \
|
||||
's' or 'socket': socket\n \
|
||||
'p' or 'pipe': named pipe (FIFO)",
|
||||
'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(
|
||||
@@ -365,6 +439,21 @@ pub fn build_app() -> App<'static, 'static> {
|
||||
"
|
||||
),
|
||||
)
|
||||
.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")
|
||||
|
||||
@@ -85,6 +85,10 @@ pub struct Config {
|
||||
/// If a value is supplied, each item found will be used to generate and execute commands.
|
||||
pub command: Option<Arc<CommandTemplate>>,
|
||||
|
||||
/// Maximum number of search results to pass to each `command`. If zero, the number is
|
||||
/// unlimited.
|
||||
pub batch_size: usize,
|
||||
|
||||
/// A list of glob patterns that should be excluded from the search.
|
||||
pub exclude_patterns: Vec<String>,
|
||||
|
||||
|
||||
+14
-1
@@ -50,6 +50,7 @@ pub fn batch(
|
||||
cmd: &CommandTemplate,
|
||||
show_filesystem_errors: bool,
|
||||
buffer_output: bool,
|
||||
limit: usize,
|
||||
) -> ExitCode {
|
||||
let paths = rx.iter().filter_map(|value| match value {
|
||||
WorkerResult::Entry(val) => Some(val),
|
||||
@@ -60,5 +61,17 @@ pub fn batch(
|
||||
None
|
||||
}
|
||||
});
|
||||
cmd.generate_and_execute_batch(paths, buffer_output)
|
||||
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)
|
||||
}
|
||||
|
||||
+3
-4
@@ -10,7 +10,7 @@ use std::process::{Command, Stdio};
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use anyhow::{anyhow, Result};
|
||||
use lazy_static::lazy_static;
|
||||
use once_cell::sync::Lazy;
|
||||
use regex::Regex;
|
||||
|
||||
use crate::exit_codes::ExitCode;
|
||||
@@ -71,9 +71,8 @@ impl CommandTemplate {
|
||||
I: IntoIterator<Item = S>,
|
||||
S: AsRef<str>,
|
||||
{
|
||||
lazy_static! {
|
||||
static ref PLACEHOLDER_PATTERN: Regex = Regex::new(r"\{(/?\.?|//)\}").unwrap();
|
||||
}
|
||||
static PLACEHOLDER_PATTERN: Lazy<Regex> =
|
||||
Lazy::new(|| Regex::new(r"\{(/?\.?|//)\}").unwrap());
|
||||
|
||||
let mut args = Vec::new();
|
||||
let mut has_placeholder = false;
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
use std::process;
|
||||
|
||||
#[cfg(unix)]
|
||||
use nix::sys::signal::{raise, signal, SigHandler, Signal};
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub enum ExitCode {
|
||||
Success,
|
||||
@@ -21,6 +26,21 @@ impl ExitCode {
|
||||
fn is_error(self) -> bool {
|
||||
i32::from(self) != 0
|
||||
}
|
||||
|
||||
/// Exit the process with the appropriate code.
|
||||
pub fn exit(self) -> ! {
|
||||
#[cfg(unix)]
|
||||
if self == ExitCode::KilledBySigint {
|
||||
// Get rid of the SIGINT handler, if present, and raise SIGINT
|
||||
unsafe {
|
||||
if signal(Signal::SIGINT, SigHandler::SigDfl).is_ok() {
|
||||
let _ = raise(Signal::SIGINT);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
process::exit(self.into())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn merge_exitcodes(results: impl IntoIterator<Item = ExitCode>) -> ExitCode {
|
||||
|
||||
+3
-4
@@ -1,9 +1,8 @@
|
||||
use lazy_static::lazy_static;
|
||||
use once_cell::sync::Lazy;
|
||||
use regex::Regex;
|
||||
|
||||
lazy_static! {
|
||||
static ref SIZE_CAPTURES: Regex = Regex::new(r"(?i)^([+-]?)(\d+)(b|[kmgt]i?b?)$").unwrap();
|
||||
}
|
||||
static SIZE_CAPTURES: Lazy<Regex> =
|
||||
Lazy::new(|| Regex::new(r"(?i)^([+-]?)(\d+)(b|[kmgt]i?b?)$").unwrap());
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub enum SizeFilter {
|
||||
|
||||
+2
-2
@@ -39,8 +39,8 @@ impl TimeFilter {
|
||||
|
||||
pub fn applies_to(&self, t: &SystemTime) -> bool {
|
||||
match self {
|
||||
TimeFilter::Before(limit) => t <= limit,
|
||||
TimeFilter::After(limit) => t >= limit,
|
||||
TimeFilter::Before(limit) => t < limit,
|
||||
TimeFilter::After(limit) => t > limit,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+8
-3
@@ -12,7 +12,6 @@ mod walk;
|
||||
|
||||
use std::env;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::process;
|
||||
use std::sync::Arc;
|
||||
use std::time;
|
||||
|
||||
@@ -54,11 +53,11 @@ fn main() {
|
||||
let result = run();
|
||||
match result {
|
||||
Ok(exit_code) => {
|
||||
process::exit(exit_code.into());
|
||||
exit_code.exit();
|
||||
}
|
||||
Err(err) => {
|
||||
eprintln!("[fd error]: {:#}", err);
|
||||
process::exit(ExitCode::GeneralError.into());
|
||||
ExitCode::GeneralError.exit();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -348,6 +347,12 @@ 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())
|
||||
|
||||
+42
-23
@@ -1,10 +1,10 @@
|
||||
use std::borrow::Cow;
|
||||
use std::io::{self, StdoutLock, Write};
|
||||
use std::path::Path;
|
||||
use std::process;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::sync::Arc;
|
||||
|
||||
use lscolors::{LsColors, Style};
|
||||
use lscolors::{Indicator, LsColors, Style};
|
||||
|
||||
use crate::config::Config;
|
||||
use crate::error::print_error;
|
||||
@@ -37,10 +37,10 @@ pub fn print_entry(
|
||||
if let Err(e) = r {
|
||||
if e.kind() == ::std::io::ErrorKind::BrokenPipe {
|
||||
// Exit gracefully in case of a broken pipe (e.g. 'fd ... | head -n 3').
|
||||
process::exit(0);
|
||||
ExitCode::Success.exit();
|
||||
} else {
|
||||
print_error(format!("Could not write to output: {}", e));
|
||||
process::exit(ExitCode::GeneralError.into());
|
||||
ExitCode::GeneralError.exit();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -53,32 +53,51 @@ fn print_entry_colorized(
|
||||
ls_colors: &LsColors,
|
||||
wants_to_quit: &Arc<AtomicBool>,
|
||||
) -> io::Result<()> {
|
||||
let default_style = ansi_term::Style::default();
|
||||
// Split the path between the parent and the last component
|
||||
let mut offset = 0;
|
||||
let path_str = path.to_string_lossy();
|
||||
|
||||
// Traverse the path and colorize each component
|
||||
for (component, style) in ls_colors.style_for_path_components(path) {
|
||||
let style = style
|
||||
.map(Style::to_ansi_term_style)
|
||||
.unwrap_or(default_style);
|
||||
|
||||
let mut path_string = component.to_string_lossy();
|
||||
if let Some(ref separator) = config.path_separator {
|
||||
*path_string.to_mut() = replace_path_separator(&path_string, separator);
|
||||
}
|
||||
write!(stdout, "{}", style.paint(path_string))?;
|
||||
|
||||
// TODO: can we move this out of the if-statement? Why do we call it that often?
|
||||
if wants_to_quit.load(Ordering::Relaxed) {
|
||||
writeln!(stdout)?;
|
||||
process::exit(ExitCode::KilledBySigint.into());
|
||||
if let Some(parent) = path.parent() {
|
||||
offset = parent.to_string_lossy().len();
|
||||
for c in path_str[offset..].chars() {
|
||||
if std::path::is_separator(c) {
|
||||
offset += c.len_utf8();
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if offset > 0 {
|
||||
let mut parent_str = Cow::from(&path_str[..offset]);
|
||||
if let Some(ref separator) = config.path_separator {
|
||||
*parent_str.to_mut() = replace_path_separator(&parent_str, separator);
|
||||
}
|
||||
|
||||
let style = ls_colors
|
||||
.style_for_indicator(Indicator::Directory)
|
||||
.map(Style::to_ansi_term_style)
|
||||
.unwrap_or_default();
|
||||
write!(stdout, "{}", style.paint(parent_str))?;
|
||||
}
|
||||
|
||||
let style = ls_colors
|
||||
.style_for_path(path)
|
||||
.map(Style::to_ansi_term_style)
|
||||
.unwrap_or_default();
|
||||
write!(stdout, "{}", style.paint(&path_str[offset..]))?;
|
||||
|
||||
if config.null_separator {
|
||||
write!(stdout, "\0")
|
||||
write!(stdout, "\0")?;
|
||||
} else {
|
||||
writeln!(stdout)
|
||||
writeln!(stdout)?;
|
||||
}
|
||||
|
||||
if wants_to_quit.load(Ordering::Relaxed) {
|
||||
ExitCode::KilledBySigint.exit();
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// TODO: this function is performance critical and can probably be optimized
|
||||
|
||||
+9
-6
@@ -3,7 +3,6 @@ use std::ffi::OsStr;
|
||||
use std::fs::{FileType, Metadata};
|
||||
use std::io;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::process;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::sync::mpsc::{channel, Receiver, Sender};
|
||||
use std::sync::{Arc, Mutex};
|
||||
@@ -137,11 +136,9 @@ pub fn scan(path_vec: &[PathBuf], pattern: Arc<Regex>, config: Arc<Config>) -> R
|
||||
if config.ls_colors.is_some() && config.command.is_none() {
|
||||
let wq = Arc::clone(&wants_to_quit);
|
||||
ctrlc::set_handler(move || {
|
||||
if wq.load(Ordering::Relaxed) {
|
||||
if wq.fetch_or(true, Ordering::Relaxed) {
|
||||
// Ctrl-C has been pressed twice, exit NOW
|
||||
process::exit(ExitCode::KilledBySigint.into());
|
||||
} else {
|
||||
wq.store(true, Ordering::Relaxed);
|
||||
ExitCode::KilledBySigint.exit();
|
||||
}
|
||||
})
|
||||
.unwrap();
|
||||
@@ -179,7 +176,13 @@ fn spawn_receiver(
|
||||
// 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)
|
||||
exec::batch(
|
||||
rx,
|
||||
cmd,
|
||||
show_filesystem_errors,
|
||||
enable_output_buffering,
|
||||
config.batch_size,
|
||||
)
|
||||
} else {
|
||||
let shared_rx = Arc::new(Mutex::new(rx));
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ use std::fs;
|
||||
use std::io::Write;
|
||||
use std::path::Path;
|
||||
use std::time::{Duration, SystemTime};
|
||||
use test_case::test_case;
|
||||
|
||||
use normpath::PathExt;
|
||||
use regex::escape;
|
||||
@@ -1418,6 +1419,48 @@ fn test_exec_batch() {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_exec_batch_with_limit() {
|
||||
// TODO Test for windows
|
||||
if cfg!(windows) {
|
||||
return;
|
||||
}
|
||||
|
||||
let te = TestEnv::new(DEFAULT_DIRS, DEFAULT_FILES);
|
||||
|
||||
te.assert_output(
|
||||
&["foo", "--batch-size", "0", "--exec-batch", "echo", "{}"],
|
||||
"./a.foo ./one/b.foo ./one/two/C.Foo2 ./one/two/c.foo ./one/two/three/d.foo ./one/two/three/directory_foo",
|
||||
);
|
||||
|
||||
let output = te.assert_success_and_get_output(
|
||||
".",
|
||||
&["foo", "--batch-size=2", "--exec-batch", "echo", "{}"],
|
||||
);
|
||||
let stdout = String::from_utf8_lossy(&output.stdout);
|
||||
|
||||
for line in stdout.lines() {
|
||||
assert_eq!(2, line.split_whitespace().count());
|
||||
}
|
||||
|
||||
let mut paths: Vec<_> = stdout
|
||||
.lines()
|
||||
.flat_map(|line| line.split_whitespace())
|
||||
.collect();
|
||||
paths.sort_unstable();
|
||||
assert_eq!(
|
||||
&paths,
|
||||
&[
|
||||
"./a.foo",
|
||||
"./one/b.foo",
|
||||
"./one/two/C.Foo2",
|
||||
"./one/two/c.foo",
|
||||
"./one/two/three/d.foo",
|
||||
"./one/two/three/directory_foo"
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
/// Shell script execution (--exec) with a custom --path-separator
|
||||
#[test]
|
||||
fn test_exec_with_separator() {
|
||||
@@ -1899,6 +1942,30 @@ fn test_number_parsing_errors() {
|
||||
te.assert_failure(&["--max-results=a"]);
|
||||
}
|
||||
|
||||
#[test_case("--hidden", &["--no-hidden"] ; "hidden")]
|
||||
#[test_case("--no-ignore", &["--ignore"] ; "no-ignore")]
|
||||
#[test_case("--no-ignore-vcs", &["--ignore-vcs"] ; "no-ignore-vcs")]
|
||||
#[test_case("--follow", &["--no-follow"] ; "follow")]
|
||||
#[test_case("--absolute-path", &["--relative-path"] ; "absolute-path")]
|
||||
#[test_case("-u", &["--ignore"] ; "u")]
|
||||
#[test_case("-uu", &["--ignore", "--no-hidden"] ; "uu")]
|
||||
fn test_opposing(flag: &str, opposing_flags: &[&str]) {
|
||||
let te = TestEnv::new(DEFAULT_DIRS, DEFAULT_FILES);
|
||||
|
||||
let mut flags = vec![flag];
|
||||
flags.extend_from_slice(opposing_flags);
|
||||
let out_no_flags = te.assert_success_and_get_output(".", &[]);
|
||||
let out_opposing_flags = te.assert_success_and_get_output(".", &flags);
|
||||
|
||||
assert_eq!(
|
||||
out_no_flags,
|
||||
out_opposing_flags,
|
||||
"{} should override {}",
|
||||
opposing_flags.join(" "),
|
||||
flag
|
||||
);
|
||||
}
|
||||
|
||||
/// Print error if search pattern starts with a dot and --hidden is not set
|
||||
/// (Unix only, hidden files on Windows work differently)
|
||||
#[test]
|
||||
|
||||
Reference in New Issue
Block a user