1
Fork 0
mirror of https://github.com/RGBCube/uutils-coreutils synced 2025-07-28 03:27:44 +00:00

Merge pull request #4925 from sylvestre/cp-i

cp: -i prompts in the right place and other improv
This commit is contained in:
Daniel Hofstetter 2023-06-02 11:41:58 +02:00 committed by GitHub
commit d2bf531a7b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 51 additions and 19 deletions

View file

@ -43,7 +43,8 @@ use uucore::fs::{
}; };
use uucore::update_control::{self, UpdateMode}; use uucore::update_control::{self, UpdateMode};
use uucore::{ use uucore::{
crash, format_usage, help_about, help_section, help_usage, prompt_yes, show_error, show_warning, crash, format_usage, help_about, help_section, help_usage, prompt_yes, show_error,
show_warning, util_name,
}; };
use crate::copydir::copy_directory; use crate::copydir::copy_directory;
@ -1102,23 +1103,21 @@ fn preserve_hardlinks(
} }
/// When handling errors, we don't always want to show them to the user. This function handles that. /// When handling errors, we don't always want to show them to the user. This function handles that.
/// If the error is printed, returns true, false otherwise. fn show_error_if_needed(error: &Error) {
fn show_error_if_needed(error: &Error) -> bool {
match error { match error {
// When using --no-clobber, we don't want to show // When using --no-clobber, we don't want to show
// an error message // an error message
Error::NotAllFilesCopied => (), Error::NotAllFilesCopied => {
// Need to return an error code
}
Error::Skipped => { Error::Skipped => {
// touch a b && echo "n"|cp -i a b && echo $? // touch a b && echo "n"|cp -i a b && echo $?
// should return an error from GNU 9.2 // should return an error from GNU 9.2
return true;
} }
_ => { _ => {
show_error!("{}", error); show_error!("{}", error);
return true;
} }
} }
false
} }
/// Copy all `sources` to `target`. Returns an /// Copy all `sources` to `target`. Returns an
@ -1175,9 +1174,8 @@ fn copy(sources: &[Source], target: &TargetSlice, options: &Options) -> CopyResu
options, options,
&mut symlinked_files, &mut symlinked_files,
) { ) {
if show_error_if_needed(&error) { show_error_if_needed(&error);
non_fatal_errors = true; non_fatal_errors = true;
}
} }
} }
seen_sources.insert(source); seen_sources.insert(source);
@ -1254,13 +1252,23 @@ fn copy_source(
} }
impl OverwriteMode { impl OverwriteMode {
fn verify(&self, path: &Path) -> CopyResult<()> { fn verify(&self, path: &Path, verbose: bool) -> CopyResult<()> {
match *self { match *self {
Self::NoClobber => Err(Error::NotAllFilesCopied), Self::NoClobber => {
if verbose {
println!("skipped {}", path.quote());
} else {
eprintln!("{}: not replacing {}", util_name(), path.quote());
}
Err(Error::NotAllFilesCopied)
}
Self::Interactive(_) => { Self::Interactive(_) => {
if prompt_yes!("overwrite {}?", path.quote()) { if prompt_yes!("overwrite {}?", path.quote()) {
Ok(()) Ok(())
} else { } else {
if verbose {
println!("skipped {}", path.quote());
}
Err(Error::Skipped) Err(Error::Skipped)
} }
} }
@ -1468,7 +1476,7 @@ fn handle_existing_dest(
return Err(format!("{} and {} are the same file", source.quote(), dest.quote()).into()); return Err(format!("{} and {} are the same file", source.quote(), dest.quote()).into());
} }
options.overwrite.verify(dest)?; options.overwrite.verify(dest, options.verbose)?;
let backup_path = backup_control::get_backup_path(options.backup, dest, &options.backup_suffix); let backup_path = backup_control::get_backup_path(options.backup, dest, &options.backup_suffix);
if let Some(backup_path) = backup_path { if let Some(backup_path) = backup_path {
@ -1826,7 +1834,7 @@ fn copy_helper(
File::create(dest).context(dest.display().to_string())?; File::create(dest).context(dest.display().to_string())?;
} else if source_is_fifo && options.recursive && !options.copy_contents { } else if source_is_fifo && options.recursive && !options.copy_contents {
#[cfg(unix)] #[cfg(unix)]
copy_fifo(dest, options.overwrite)?; copy_fifo(dest, options.overwrite, options.verbose)?;
} else if source_is_symlink { } else if source_is_symlink {
copy_link(source, dest, symlinked_files)?; copy_link(source, dest, symlinked_files)?;
} else { } else {
@ -1851,9 +1859,9 @@ fn copy_helper(
// "Copies" a FIFO by creating a new one. This workaround is because Rust's // "Copies" a FIFO by creating a new one. This workaround is because Rust's
// built-in fs::copy does not handle FIFOs (see rust-lang/rust/issues/79390). // built-in fs::copy does not handle FIFOs (see rust-lang/rust/issues/79390).
#[cfg(unix)] #[cfg(unix)]
fn copy_fifo(dest: &Path, overwrite: OverwriteMode) -> CopyResult<()> { fn copy_fifo(dest: &Path, overwrite: OverwriteMode, verbose: bool) -> CopyResult<()> {
if dest.exists() { if dest.exists() {
overwrite.verify(dest)?; overwrite.verify(dest, verbose)?;
fs::remove_file(dest)?; fs::remove_file(dest)?;
} }

View file

@ -456,6 +456,29 @@ fn test_cp_arg_interactive_update() {
.no_stdout(); .no_stdout();
} }
#[test]
#[cfg(not(target_os = "android"))]
fn test_cp_arg_interactive_verbose() {
let (at, mut ucmd) = at_and_ucmd!();
at.touch("a");
at.touch("b");
ucmd.args(&["-vi", "a", "b"])
.pipe_in("N\n")
.fails()
.stdout_is("skipped 'b'\n");
}
#[test]
#[cfg(not(target_os = "android"))]
fn test_cp_arg_interactive_verbose_clobber() {
let (at, mut ucmd) = at_and_ucmd!();
at.touch("a");
at.touch("b");
ucmd.args(&["-vin", "a", "b"])
.fails()
.stdout_is("skipped 'b'\n");
}
#[test] #[test]
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
fn test_cp_arg_link() { fn test_cp_arg_link() {
@ -487,7 +510,8 @@ fn test_cp_arg_no_clobber() {
ucmd.arg(TEST_HELLO_WORLD_SOURCE) ucmd.arg(TEST_HELLO_WORLD_SOURCE)
.arg(TEST_HOW_ARE_YOU_SOURCE) .arg(TEST_HOW_ARE_YOU_SOURCE)
.arg("--no-clobber") .arg("--no-clobber")
.succeeds(); .fails()
.stderr_contains("not replacing");
assert_eq!(at.read(TEST_HOW_ARE_YOU_SOURCE), "How are you?\n"); assert_eq!(at.read(TEST_HOW_ARE_YOU_SOURCE), "How are you?\n");
} }
@ -498,7 +522,7 @@ fn test_cp_arg_no_clobber_inferred_arg() {
ucmd.arg(TEST_HELLO_WORLD_SOURCE) ucmd.arg(TEST_HELLO_WORLD_SOURCE)
.arg(TEST_HOW_ARE_YOU_SOURCE) .arg(TEST_HOW_ARE_YOU_SOURCE)
.arg("--no-clob") .arg("--no-clob")
.succeeds(); .fails();
assert_eq!(at.read(TEST_HOW_ARE_YOU_SOURCE), "How are you?\n"); assert_eq!(at.read(TEST_HOW_ARE_YOU_SOURCE), "How are you?\n");
} }
@ -525,7 +549,7 @@ fn test_cp_arg_no_clobber_twice() {
.arg("--no-clobber") .arg("--no-clobber")
.arg("source.txt") .arg("source.txt")
.arg("dest.txt") .arg("dest.txt")
.succeeds(); .fails();
assert_eq!(at.read("source.txt"), "some-content"); assert_eq!(at.read("source.txt"), "some-content");
// Should be empty as the "no-clobber" should keep // Should be empty as the "no-clobber" should keep