Make --strip-cwd-prefix apply to -x/-X

Fixes #898.
This commit is contained in:
Tavian Barnes 2022-09-27 16:09:21 -04:00 committed by David Peter
parent 5039d2db99
commit 4ffc34956f
7 changed files with 66 additions and 44 deletions

View file

@ -724,8 +724,10 @@ pub fn build_app() -> Command<'static> {
.hide_short_help(true) .hide_short_help(true)
.help("strip './' prefix from -0/--print0 output") .help("strip './' prefix from -0/--print0 output")
.long_help( .long_help(
"By default, relative paths are prefixed with './' when -0/--print0 is given, to \ "By default, relative paths are prefixed with './' when -x/--exec, \
make them safer for use with xargs. Use this flag to disable this behaviour." -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."
) )
); );

View file

@ -5,6 +5,9 @@ use std::{
use once_cell::unsync::OnceCell; use once_cell::unsync::OnceCell;
use crate::config::Config;
use crate::filesystem::strip_current_dir;
enum DirEntryInner { enum DirEntryInner {
Normal(ignore::DirEntry), Normal(ignore::DirEntry),
BrokenSymlink(PathBuf), BrokenSymlink(PathBuf),
@ -45,6 +48,24 @@ impl DirEntry {
} }
} }
/// 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> { pub fn file_type(&self) -> Option<FileType> {
match &self.inner { match &self.inner {
DirEntryInner::Normal(e) => e.file_type(), DirEntryInner::Normal(e) => e.file_type(),

View file

@ -1,6 +1,7 @@
use std::sync::mpsc::Receiver; use std::sync::mpsc::Receiver;
use std::sync::{Arc, Mutex}; use std::sync::{Arc, Mutex};
use crate::config::Config;
use crate::dir_entry::DirEntry; use crate::dir_entry::DirEntry;
use crate::error::print_error; use crate::error::print_error;
use crate::exit_codes::{merge_exitcodes, ExitCode}; use crate::exit_codes::{merge_exitcodes, ExitCode};
@ -15,9 +16,11 @@ pub fn job(
rx: Arc<Mutex<Receiver<WorkerResult>>>, rx: Arc<Mutex<Receiver<WorkerResult>>>,
cmd: Arc<CommandSet>, cmd: Arc<CommandSet>,
out_perm: Arc<Mutex<()>>, out_perm: Arc<Mutex<()>>,
show_filesystem_errors: bool, config: &Config,
buffer_output: bool,
) -> ExitCode { ) -> 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(); let mut results: Vec<ExitCode> = Vec::new();
loop { loop {
// Create a lock on the shared receiver for this thread. // Create a lock on the shared receiver for this thread.
@ -28,7 +31,7 @@ pub fn job(
let dir_entry: DirEntry = match lock.recv() { let dir_entry: DirEntry = match lock.recv() {
Ok(WorkerResult::Entry(dir_entry)) => dir_entry, Ok(WorkerResult::Entry(dir_entry)) => dir_entry,
Ok(WorkerResult::Error(err)) => { Ok(WorkerResult::Error(err)) => {
if show_filesystem_errors { if config.show_filesystem_errors {
print_error(err.to_string()); print_error(err.to_string());
} }
continue; continue;
@ -39,29 +42,28 @@ pub fn job(
// Drop the lock so that other threads can read from the receiver. // Drop the lock so that other threads can read from the receiver.
drop(lock); drop(lock);
// Generate a command, execute it and store its exit code. // Generate a command, execute it and store its exit code.
results.push(cmd.execute(dir_entry.path(), Arc::clone(&out_perm), buffer_output)) results.push(cmd.execute(
dir_entry.stripped_path(config),
Arc::clone(&out_perm),
buffer_output,
))
} }
// Returns error in case of any error. // Returns error in case of any error.
merge_exitcodes(results) merge_exitcodes(results)
} }
pub fn batch( pub fn batch(rx: Receiver<WorkerResult>, cmd: &CommandSet, config: &Config) -> ExitCode {
rx: Receiver<WorkerResult>,
cmd: &CommandSet,
show_filesystem_errors: bool,
limit: usize,
) -> ExitCode {
let paths = rx let paths = rx
.into_iter() .into_iter()
.filter_map(|worker_result| match worker_result { .filter_map(|worker_result| match worker_result {
WorkerResult::Entry(dir_entry) => Some(dir_entry.into_path()), WorkerResult::Entry(dir_entry) => Some(dir_entry.into_stripped_path(config)),
WorkerResult::Error(err) => { WorkerResult::Error(err) => {
if show_filesystem_errors { if config.show_filesystem_errors {
print_error(err.to_string()); print_error(err.to_string());
} }
None None
} }
}); });
cmd.execute_batch(paths, limit) cmd.execute_batch(paths, config.batch_size)
} }

View file

@ -383,9 +383,12 @@ fn construct_config(matches: clap::ArgMatches, pattern_regex: &str) -> Result<Co
None None
} }
}), }),
strip_cwd_prefix: (!matches.is_present("path") strip_cwd_prefix: !matches.is_present("path")
&& !matches.is_present("search-path") && !matches.is_present("search-path")
&& (!matches.is_present("null_separator") || matches.is_present("strip-cwd-prefix"))), && (matches.is_present("strip-cwd-prefix")
|| !(matches.is_present("null_separator")
|| matches.is_present("exec")
|| matches.is_present("exec-batch"))),
}) })
} }

View file

@ -1,6 +1,5 @@
use std::borrow::Cow; use std::borrow::Cow;
use std::io::{self, Write}; use std::io::{self, Write};
use std::path::Path;
use lscolors::{Indicator, LsColors, Style}; use lscolors::{Indicator, LsColors, Style};
@ -8,21 +7,11 @@ use crate::config::Config;
use crate::dir_entry::DirEntry; use crate::dir_entry::DirEntry;
use crate::error::print_error; use crate::error::print_error;
use crate::exit_codes::ExitCode; use crate::exit_codes::ExitCode;
use crate::filesystem::strip_current_dir;
fn replace_path_separator(path: &str, new_path_separator: &str) -> String { fn replace_path_separator(path: &str, new_path_separator: &str) -> String {
path.replace(std::path::MAIN_SEPARATOR, new_path_separator) path.replace(std::path::MAIN_SEPARATOR, new_path_separator)
} }
fn stripped_path<'a>(entry: &'a DirEntry, config: &Config) -> &'a Path {
let path = entry.path();
if config.strip_cwd_prefix {
strip_current_dir(path)
} else {
path
}
}
// TODO: this function is performance critical and can probably be optimized // TODO: this function is performance critical and can probably be optimized
pub fn print_entry<W: Write>(stdout: &mut W, entry: &DirEntry, config: &Config) { pub fn print_entry<W: Write>(stdout: &mut W, entry: &DirEntry, config: &Config) {
let r = if let Some(ref ls_colors) = config.ls_colors { let r = if let Some(ref ls_colors) = config.ls_colors {
@ -74,7 +63,7 @@ fn print_entry_colorized<W: Write>(
) -> io::Result<()> { ) -> io::Result<()> {
// Split the path between the parent and the last component // Split the path between the parent and the last component
let mut offset = 0; let mut offset = 0;
let path = stripped_path(entry, config); let path = entry.stripped_path(config);
let path_str = path.to_string_lossy(); let path_str = path.to_string_lossy();
if let Some(parent) = path.parent() { if let Some(parent) = path.parent() {
@ -130,7 +119,7 @@ fn print_entry_uncolorized_base<W: Write>(
config: &Config, config: &Config,
) -> io::Result<()> { ) -> io::Result<()> {
let separator = if config.null_separator { "\0" } else { "\n" }; let separator = if config.null_separator { "\0" } else { "\n" };
let path = stripped_path(entry, config); let path = entry.stripped_path(config);
let mut path_string = path.to_string_lossy(); let mut path_string = path.to_string_lossy();
if let Some(ref separator) = config.path_separator { if let Some(ref separator) = config.path_separator {
@ -164,7 +153,7 @@ fn print_entry_uncolorized<W: Write>(
} else { } else {
// Print path as raw bytes, allowing invalid UTF-8 filenames to be passed to other processes // 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" }; let separator = if config.null_separator { b"\0" } else { b"\n" };
stdout.write_all(stripped_path(entry, config).as_os_str().as_bytes())?; stdout.write_all(entry.stripped_path(config).as_os_str().as_bytes())?;
print_trailing_slash(stdout, entry, config, None)?; print_trailing_slash(stdout, entry, config, None)?;
stdout.write_all(separator) stdout.write_all(separator)
} }

View file

@ -341,15 +341,12 @@ fn spawn_receiver(
let quit_flag = Arc::clone(quit_flag); let quit_flag = Arc::clone(quit_flag);
let interrupt_flag = Arc::clone(interrupt_flag); let interrupt_flag = Arc::clone(interrupt_flag);
let show_filesystem_errors = config.show_filesystem_errors;
let threads = config.threads; 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 || { thread::spawn(move || {
// This will be set to `Some` if the `--exec` argument was supplied. // This will be set to `Some` if the `--exec` argument was supplied.
if let Some(ref cmd) = config.command { if let Some(ref cmd) = config.command {
if cmd.in_batch_mode() { if cmd.in_batch_mode() {
exec::batch(rx, cmd, show_filesystem_errors, config.batch_size) exec::batch(rx, cmd, &config)
} else { } else {
let shared_rx = Arc::new(Mutex::new(rx)); let shared_rx = Arc::new(Mutex::new(rx));
@ -358,20 +355,13 @@ fn spawn_receiver(
// Each spawned job will store it's thread handle in here. // Each spawned job will store it's thread handle in here.
let mut handles = Vec::with_capacity(threads); let mut handles = Vec::with_capacity(threads);
for _ in 0..threads { for _ in 0..threads {
let config = Arc::clone(&config);
let rx = Arc::clone(&shared_rx); let rx = Arc::clone(&shared_rx);
let cmd = Arc::clone(cmd); let cmd = Arc::clone(cmd);
let out_perm = Arc::clone(&out_perm); let out_perm = Arc::clone(&out_perm);
// Spawn a job thread that will listen for and execute inputs. // Spawn a job thread that will listen for and execute inputs.
let handle = thread::spawn(move || { let handle = thread::spawn(move || exec::job(rx, cmd, out_perm, &config));
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. // Push the handle of the spawned thread into the vector for later joining.
handles.push(handle); handles.push(handle);

View file

@ -1327,6 +1327,16 @@ fn test_exec() {
./one/two/three/directory_foo", ./one/two/three/directory_foo",
); );
te.assert_output(
&["foo", "--strip-cwd-prefix", "--exec", "echo", "{}"],
"a.foo
one/b.foo
one/two/C.Foo2
one/two/c.foo
one/two/three/d.foo
one/two/three/directory_foo",
);
te.assert_output( te.assert_output(
&["foo", "--exec", "echo", "{.}"], &["foo", "--exec", "echo", "{.}"],
"a "a
@ -1452,6 +1462,11 @@ fn test_exec_batch() {
"./a.foo ./one/b.foo ./one/two/C.Foo2 ./one/two/c.foo ./one/two/three/d.foo ./one/two/three/directory_foo", "./a.foo ./one/b.foo ./one/two/C.Foo2 ./one/two/c.foo ./one/two/three/d.foo ./one/two/three/directory_foo",
); );
te.assert_output(
&["foo", "--strip-cwd-prefix", "--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",
);
te.assert_output( te.assert_output(
&["foo", "--exec-batch", "echo", "{/}"], &["foo", "--exec-batch", "echo", "{/}"],
"a.foo b.foo C.Foo2 c.foo d.foo directory_foo", "a.foo b.foo C.Foo2 c.foo d.foo directory_foo",