Merge remote-tracking branch 'origin/master' into no-strip/760

This commit is contained in:
David Peter 2021-11-26 18:18:25 +01:00
commit 92bd7850d0
19 changed files with 446 additions and 120 deletions

View file

@ -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
-->

41
.github/ISSUE_TEMPLATE/bug_report.yaml vendored Normal file
View file

@ -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

View file

@ -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

91
Cargo.lock generated
View file

@ -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"

View file

@ -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

View file

@ -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

View file

@ -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)'

50
doc/fd.1 vendored
View file

@ -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)

View file

@ -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")

View file

@ -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>,

View file

@ -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)
}

View file

@ -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;

View file

@ -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 {

View file

@ -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 {

View file

@ -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,
}
}
}

View file

@ -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())

View file

@ -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

View file

@ -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));

View file

@ -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]