mirror of
https://github.com/sharkdp/fd.git
synced 2024-09-28 13:01:30 +02:00
Merge branch 'master' into pr/opposing-options
This commit is contained in:
commit
c749c95136
1
.github/ISSUE_TEMPLATE/config.yml
vendored
1
.github/ISSUE_TEMPLATE/config.yml
vendored
@ -1,2 +1 @@
|
||||
blank_issues_enabled: true
|
||||
|
||||
|
2
.github/ISSUE_TEMPLATE/feature_request.md
vendored
2
.github/ISSUE_TEMPLATE/feature_request.md
vendored
@ -6,5 +6,3 @@ labels: feature-request
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
|
||||
|
15
.github/workflows/CICD.yml
vendored
15
.github/workflows/CICD.yml
vendored
@ -27,16 +27,22 @@ jobs:
|
||||
toolchain: ${{ env.MIN_SUPPORTED_RUST_VERSION }}
|
||||
default: true
|
||||
profile: minimal # minimal component installation (ie, no documentation)
|
||||
components: clippy
|
||||
components: clippy, rustfmt
|
||||
- name: Ensure `cargo fmt` has been run
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: fmt
|
||||
args: -- --check
|
||||
- name: Run clippy (on minimum supported rust version to prevent warnings we can't fix)
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: clippy
|
||||
args: --all-targets --all-features
|
||||
args: --locked --all-targets --all-features
|
||||
- name: Run tests
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: test
|
||||
args: --locked
|
||||
|
||||
build:
|
||||
name: ${{ matrix.job.os }} (${{ matrix.job.target }})
|
||||
@ -100,7 +106,7 @@ jobs:
|
||||
with:
|
||||
use-cross: ${{ matrix.job.use-cross }}
|
||||
command: build
|
||||
args: --release --target=${{ matrix.job.target }}
|
||||
args: --locked --release --target=${{ matrix.job.target }}
|
||||
|
||||
- name: Strip debug information from executable
|
||||
id: strip
|
||||
@ -142,6 +148,7 @@ jobs:
|
||||
id: test-options
|
||||
shell: bash
|
||||
run: |
|
||||
# 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}
|
||||
@ -151,7 +158,7 @@ jobs:
|
||||
with:
|
||||
use-cross: ${{ matrix.job.use-cross }}
|
||||
command: test
|
||||
args: --target=${{ matrix.job.target }} ${{ steps.test-options.outputs.CARGO_TEST_OPTIONS}}
|
||||
args: --locked --target=${{ matrix.job.target }} ${{ steps.test-options.outputs.CARGO_TEST_OPTIONS}}
|
||||
|
||||
- name: Create tarball
|
||||
id: package
|
||||
|
@ -1,6 +1,9 @@
|
||||
# Upcoming release
|
||||
|
||||
## Features
|
||||
- Don't buffer command output from `--exec` when using a single thread. See #522
|
||||
|
||||
- Add new `-q, --quiet` flag, see #303 (@Asha20)
|
||||
|
||||
- Add opposing command-line options, see #595 (@Asha20)
|
||||
|
||||
|
40
doc/fd.1
vendored
40
doc/fd.1
vendored
@ -32,19 +32,38 @@ Include hidden files and directories in the search results
|
||||
.TP
|
||||
.B \-I, \-\-no\-ignore
|
||||
Show search results from files and directories that would otherwise be ignored by
|
||||
.IR .gitignore ,
|
||||
.IR .ignore ,
|
||||
.IR .fdignore ,
|
||||
or the global ignore file.
|
||||
.RS
|
||||
.IP \[bu] 2
|
||||
.I .gitignore
|
||||
.IP \[bu]
|
||||
.I .git/info/exclude
|
||||
.IP \[bu]
|
||||
The global gitignore configuration (by default
|
||||
.IR $HOME/.config/git/ignore )
|
||||
.IP \[bu]
|
||||
.I .ignore
|
||||
.IP \[bu]
|
||||
.I .fdignore
|
||||
.IP \[bu]
|
||||
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'.
|
||||
.TP
|
||||
.B \-\-no\-ignore\-vcs
|
||||
Show search results from files and directories that would otherwise be ignored by
|
||||
.I .gitignore
|
||||
files.
|
||||
Show search results from files and directories that would otherwise be ignored by gitignore files
|
||||
including
|
||||
.IR .gitignore ,
|
||||
.IR .git/info/exclude ,
|
||||
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
|
||||
@ -96,6 +115,13 @@ Limit the number of search results to 'count' and quit immediately.
|
||||
.B \-1
|
||||
Limit the search to a single result and quit immediately. This is an alias for '--max-results=1'.
|
||||
.TP
|
||||
.B \-q, \-\-quiet
|
||||
When the flag is present, the program does not print anything and will instead exit with a code of 0 if there is at least one search result.
|
||||
Otherwise, the exit code will be 1.
|
||||
This is mainly for usage in scripts and can be faster than checking for output because the search can be stopped early after the first match.
|
||||
.B \-\-has\-results
|
||||
can be used as an alias.
|
||||
.TP
|
||||
.B \-\-show-errors
|
||||
Enable the display of filesystem errors for situations such as insufficient
|
||||
permissions or dead symlinks.
|
||||
|
2
doc/screencast.sh
vendored
2
doc/screencast.sh
vendored
@ -20,7 +20,7 @@ enter() {
|
||||
}
|
||||
|
||||
prompt() {
|
||||
printf '%b ' $PROMPT | pv -q
|
||||
printf '%b ' "$PROMPT" | pv -q
|
||||
}
|
||||
|
||||
type() {
|
||||
|
14
src/app.rs
14
src/app.rs
@ -554,6 +554,20 @@ pub fn build_app() -> App<'static, 'static> {
|
||||
.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"])
|
||||
.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")
|
||||
|
@ -7,9 +7,19 @@ use crate::error::print_error;
|
||||
use crate::exit_codes::ExitCode;
|
||||
|
||||
/// Executes a command.
|
||||
pub fn execute_command(mut cmd: Command, out_perm: &Mutex<()>) -> ExitCode {
|
||||
pub fn execute_command(
|
||||
mut cmd: Command,
|
||||
out_perm: &Mutex<()>,
|
||||
enable_output_buffering: bool,
|
||||
) -> ExitCode {
|
||||
// Spawn the supplied command.
|
||||
let output = cmd.output();
|
||||
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())
|
||||
};
|
||||
|
||||
// Then wait for the command to exit, if it was spawned.
|
||||
match output {
|
||||
|
@ -16,6 +16,7 @@ pub fn job(
|
||||
cmd: Arc<CommandTemplate>,
|
||||
out_perm: Arc<Mutex<()>>,
|
||||
show_filesystem_errors: bool,
|
||||
buffer_output: bool,
|
||||
) -> ExitCode {
|
||||
let mut results: Vec<ExitCode> = Vec::new();
|
||||
loop {
|
||||
@ -38,7 +39,7 @@ pub fn job(
|
||||
// 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)))
|
||||
results.push(cmd.generate_and_execute(&value, Arc::clone(&out_perm), buffer_output))
|
||||
}
|
||||
// Returns error in case of any error.
|
||||
merge_exitcodes(&results)
|
||||
@ -48,6 +49,7 @@ pub fn batch(
|
||||
rx: Receiver<WorkerResult>,
|
||||
cmd: &CommandTemplate,
|
||||
show_filesystem_errors: bool,
|
||||
buffer_output: bool,
|
||||
) -> ExitCode {
|
||||
let paths = rx.iter().filter_map(|value| match value {
|
||||
WorkerResult::Entry(val) => Some(val),
|
||||
@ -58,5 +60,5 @@ pub fn batch(
|
||||
None
|
||||
}
|
||||
});
|
||||
cmd.generate_and_execute_batch(paths)
|
||||
cmd.generate_and_execute_batch(paths, buffer_output)
|
||||
}
|
||||
|
@ -139,7 +139,12 @@ impl CommandTemplate {
|
||||
///
|
||||
/// 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<()>>) -> ExitCode {
|
||||
pub fn generate_and_execute(
|
||||
&self,
|
||||
input: &Path,
|
||||
out_perm: Arc<Mutex<()>>,
|
||||
buffer_output: bool,
|
||||
) -> ExitCode {
|
||||
let input = strip_current_dir(input);
|
||||
|
||||
let mut cmd = Command::new(self.args[0].generate(&input, self.path_separator.as_deref()));
|
||||
@ -147,14 +152,14 @@ impl CommandTemplate {
|
||||
cmd.arg(arg.generate(&input, self.path_separator.as_deref()));
|
||||
}
|
||||
|
||||
execute_command(cmd, &out_perm)
|
||||
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) -> ExitCode
|
||||
pub fn generate_and_execute_batch<I>(&self, paths: I, buffer_output: bool) -> ExitCode
|
||||
where
|
||||
I: Iterator<Item = PathBuf>,
|
||||
{
|
||||
@ -182,7 +187,7 @@ impl CommandTemplate {
|
||||
}
|
||||
|
||||
if has_path {
|
||||
execute_command(cmd, &Mutex::new(()))
|
||||
execute_command(cmd, &Mutex::new(()), buffer_output)
|
||||
} else {
|
||||
ExitCode::Success
|
||||
}
|
||||
@ -445,11 +450,11 @@ mod tests {
|
||||
// This is uncommon, but valid
|
||||
check!(r"C:foo\bar", "C:foo#bar");
|
||||
|
||||
// forward slashses should get normalized and interpreted as separators
|
||||
// forward slashes should get normalized and interpreted as separators
|
||||
check!("C:/foo/bar", "C:#foo#bar");
|
||||
check!("C:foo/bar", "C:foo#bar");
|
||||
|
||||
// Rust does not intrepret "//server/share" as a UNC path, but rather as a normal
|
||||
// Rust does not interpret "//server/share" as a UNC path, but rather as a normal
|
||||
// absolute path that begins with RootDir, and the two slashes get combined together as
|
||||
// a single path separator during normalization.
|
||||
//check!("//server/share/path", "##server#share#path");
|
||||
|
@ -1,6 +1,7 @@
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub enum ExitCode {
|
||||
Success,
|
||||
HasResults(bool),
|
||||
GeneralError,
|
||||
KilledBySigint,
|
||||
}
|
||||
@ -9,6 +10,7 @@ impl From<ExitCode> for i32 {
|
||||
fn from(code: ExitCode) -> Self {
|
||||
match code {
|
||||
ExitCode::Success => 0,
|
||||
ExitCode::HasResults(has_results) => !has_results as i32,
|
||||
ExitCode::GeneralError => 1,
|
||||
ExitCode::KilledBySigint => 130,
|
||||
}
|
||||
@ -17,7 +19,7 @@ impl From<ExitCode> for i32 {
|
||||
|
||||
impl ExitCode {
|
||||
fn is_error(self) -> bool {
|
||||
self != ExitCode::Success
|
||||
i32::from(self) != 0
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -346,6 +346,7 @@ fn run() -> Result<ExitCode> {
|
||||
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"))
|
||||
|
@ -54,6 +54,10 @@ pub struct Options {
|
||||
/// The number of threads to use.
|
||||
pub threads: usize,
|
||||
|
||||
/// If true, the program doesn't print anything and will instead return an exit code of 0
|
||||
/// if there's at least one match. Otherwise, the exit code will be 1.
|
||||
pub quiet: bool,
|
||||
|
||||
/// Time to buffer results internally before streaming to the console. This is useful to
|
||||
/// provide a sorted output, in case the total execution time is shorter than
|
||||
/// `max_buffer_time`.
|
||||
|
24
src/walk.rs
24
src/walk.rs
@ -170,12 +170,13 @@ fn spawn_receiver(
|
||||
|
||||
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)
|
||||
exec::batch(rx, cmd, show_filesystem_errors, enable_output_buffering)
|
||||
} else {
|
||||
let shared_rx = Arc::new(Mutex::new(rx));
|
||||
|
||||
@ -189,8 +190,15 @@ fn spawn_receiver(
|
||||
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));
|
||||
let handle = thread::spawn(move || {
|
||||
exec::job(
|
||||
rx,
|
||||
cmd,
|
||||
out_perm,
|
||||
show_filesystem_errors,
|
||||
enable_output_buffering,
|
||||
)
|
||||
});
|
||||
|
||||
// Push the handle of the spawned thread into the vector for later joining.
|
||||
handles.push(handle);
|
||||
@ -225,6 +233,10 @@ fn spawn_receiver(
|
||||
for worker_result in rx {
|
||||
match worker_result {
|
||||
WorkerResult::Entry(value) => {
|
||||
if config.quiet {
|
||||
return ExitCode::HasResults(true);
|
||||
}
|
||||
|
||||
match mode {
|
||||
ReceiverMode::Buffering => {
|
||||
buffer.push(value);
|
||||
@ -278,8 +290,12 @@ fn spawn_receiver(
|
||||
}
|
||||
}
|
||||
|
||||
if config.quiet {
|
||||
ExitCode::HasResults(false)
|
||||
} else {
|
||||
ExitCode::Success
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -1426,6 +1426,19 @@ fn test_exec_with_separator() {
|
||||
);
|
||||
}
|
||||
|
||||
/// Non-zero exit code (--quiet)
|
||||
#[test]
|
||||
fn test_quiet() {
|
||||
let dirs = &[];
|
||||
let files = &["a.foo", "b.foo"];
|
||||
let te = TestEnv::new(dirs, files);
|
||||
|
||||
te.assert_output(&["-q"], "");
|
||||
te.assert_output(&["--quiet"], "");
|
||||
te.assert_output(&["--has-results"], "");
|
||||
te.assert_failure_with_error(&["--quiet", "c.foo"], "")
|
||||
}
|
||||
|
||||
/// Literal search (--fixed-strings)
|
||||
#[test]
|
||||
fn test_fixed_strings() {
|
||||
|
Loading…
Reference in New Issue
Block a user