cp: display symlink creation with --verbose

cp: display removed files with --verbose
This commit is contained in:
Christopher Armstrong 2025-10-15 12:25:45 -04:00 committed by Dorian Péron
parent e2e5c76a7c
commit 37d00ae89b
5 changed files with 83 additions and 21 deletions

View file

@ -108,6 +108,7 @@ cp-warning-source-specified-more-than-once = source { $file_type } { $source } s
# Verbose and debug messages
cp-verbose-copied = { $source } -> { $dest }
cp-debug-skipped = skipped { $path }
cp-verbose-removed = removed { $path }
cp-verbose-created-directory = { $source } -> { $dest }
cp-debug-copy-offload = copy offload: { $offload }, reflink: { $reflink }, sparse detection: { $sparse }

View file

@ -108,6 +108,7 @@ cp-warning-source-specified-more-than-once = { $file_type } source { $source } s
# Messages verbeux et de débogage
cp-verbose-copied = { $source } -> { $dest }
cp-debug-skipped = { $path } ignoré
cp-verbose-removed = removed { $path }
cp-verbose-created-directory = { $source } -> { $dest }
cp-debug-copy-offload = copy offload : { $offload }, reflink : { $reflink }, sparse detection : { $sparse }

View file

@ -29,7 +29,6 @@ use walkdir::{DirEntry, WalkDir};
use crate::{
CopyResult, CpError, Options, aligned_ancestors, context_for, copy_attributes, copy_file,
copy_link,
};
/// Ensure a Windows path starts with a `\\?`.
@ -259,17 +258,6 @@ fn copy_direntry(
entry_is_dir_no_follow
};
// If the source is a symbolic link and the options tell us not to
// dereference the link, then copy the link object itself.
if source_is_symlink && !options.dereference {
return copy_link(
&entry.source_absolute,
&entry.local_to_target,
symlinked_files,
options,
);
}
// If the source is a directory and the destination does not
// exist, ...
if source_is_dir && !entry.local_to_target.exists() {

View file

@ -1937,7 +1937,6 @@ fn delete_dest_if_needed_and_allowed(
let delete_dest = match options.overwrite {
OverwriteMode::Clobber(cl) | OverwriteMode::Interactive(cl) => {
match cl {
// FIXME: print that the file was removed if --verbose is enabled
ClobberMode::Force => {
// TODO
// Using `readonly` here to check if `dest` needs to be deleted is not correct:
@ -1976,13 +1975,26 @@ fn delete_dest_if_needed_and_allowed(
};
if delete_dest {
match fs::remove_file(dest) {
Ok(()) => {}
Err(err) if err.kind() == io::ErrorKind::NotFound => {
// target could have been deleted earlier (e.g. same-file with --remove-destination)
delete_path(dest, options)
} else {
Ok(())
}
}
fn delete_path(path: &Path, options: &Options) -> CopyResult<()> {
match fs::remove_file(path) {
Ok(()) => {
if options.verbose {
println!(
"{}",
translate!("cp-verbose-removed", "path" => path.quote())
);
}
Err(err) => return Err(err.into()),
}
Err(err) if err.kind() == io::ErrorKind::NotFound => {
// target could have been deleted earlier (e.g. same-file with --remove-destination)
}
Err(err) => return Err(err.into()),
}
Ok(())
@ -2666,7 +2678,7 @@ fn copy_link(
// we always need to remove the file to be able to create a symlink,
// even if it is writeable.
if dest.is_symlink() || dest.is_file() {
fs::remove_file(dest)?;
delete_path(dest, options)?;
}
symlink_file(&link, dest, symlinked_files)?;
copy_attributes(source, dest, &options.attributes)

View file

@ -385,7 +385,6 @@ fn test_cp_arg_no_target_directory_with_recursive() {
}
#[test]
#[ignore = "disabled until https://github.com/uutils/coreutils/issues/7455 is fixed"]
fn test_cp_arg_no_target_directory_with_recursive_target_does_not_exists() {
let (at, mut ucmd) = at_and_ucmd!();
@ -768,7 +767,7 @@ fn test_cp_f_i_verbose_non_writeable_destination_y() {
.pipe_in("y")
.succeeds()
.stderr_is("cp: replace 'b', overriding mode 0000 (---------)? ")
.stdout_is("'a' -> 'b'\n");
.stdout_is("removed 'b'\n'a' -> 'b'\n");
}
#[test]
@ -7167,3 +7166,64 @@ fn test_cp_recurse_verbose_output() {
.no_stderr()
.stdout_is(output);
}
#[test]
fn test_cp_recurse_verbose_output_with_symlink() {
let source_dir = "source_dir";
let target_dir = "target_dir";
let file = "file";
let symlink = "symlink";
#[cfg(not(windows))]
let output = format!(
"'{source_dir}' -> '{target_dir}/'\n'{source_dir}/{symlink}' -> '{target_dir}/{symlink}'\n"
);
#[cfg(windows)]
let output = format!(
"'{source_dir}' -> '{target_dir}\\'\n'{source_dir}\\{symlink}' -> '{target_dir}\\{symlink}'\n"
);
let (at, mut ucmd) = at_and_ucmd!();
at.mkdir(source_dir);
at.touch(file);
at.symlink_file(file, format!("{source_dir}/{symlink}").as_str());
ucmd.arg(source_dir)
.arg(target_dir)
.arg("-r")
.arg("--verbose")
.succeeds()
.no_stderr()
.stdout_is(output);
}
#[test]
fn test_cp_recurse_verbose_output_with_symlink_already_exists() {
let source_dir = "source_dir";
let target_dir = "target_dir";
let file = "file";
let symlink = "symlink";
#[cfg(not(windows))]
let output = format!(
"removed '{target_dir}/{symlink}'\n'{source_dir}/{symlink}' -> '{target_dir}/{symlink}'\n"
);
#[cfg(windows)]
let output = format!(
"removed '{target_dir}\\{symlink}'\n'{source_dir}\\{symlink}' -> '{target_dir}\\{symlink}'\n"
);
let (at, mut ucmd) = at_and_ucmd!();
at.mkdir(source_dir);
at.touch(file);
at.symlink_file(file, format!("{source_dir}/{symlink}").as_str());
at.mkdir(target_dir);
at.symlink_file(file, format!("{target_dir}/{symlink}").as_str());
ucmd.arg(source_dir)
.arg(target_dir)
.arg("-r")
.arg("--verbose")
.arg("-T")
.succeeds()
.no_stderr()
.stdout_is(output);
}