diff --git a/src/uu/cp/src/copydir.rs b/src/uu/cp/src/copydir.rs index a4901903d..a5c7e76c1 100644 --- a/src/uu/cp/src/copydir.rs +++ b/src/uu/cp/src/copydir.rs @@ -9,8 +9,9 @@ #[cfg(windows)] use std::borrow::Cow; use std::collections::{HashMap, HashSet}; +use std::convert::identity; use std::env; -use std::fs; +use std::fs::{self, exists}; use std::io; use std::path::{Path, PathBuf, StripPrefixError}; @@ -20,10 +21,9 @@ use uucore::error::UIoError; use uucore::fs::{ FileInformation, MissingHandling, ResolveMode, canonicalize, path_ends_with_terminator, }; -use uucore::translate; - use uucore::show; use uucore::show_error; +use uucore::translate; use uucore::uio_error; use walkdir::{DirEntry, WalkDir}; @@ -194,15 +194,23 @@ impl Entry { get_local_to_root_parent(&source_absolute, context.root_parent.as_deref())?; if no_target_dir { let source_is_dir = source.is_dir(); - if path_ends_with_terminator(context.target) && source_is_dir { + if path_ends_with_terminator(context.target) + && source_is_dir + && !exists(context.target).is_ok_and(identity) + { if let Err(e) = fs::create_dir_all(context.target) { eprintln!( "{}", translate!("cp-error-failed-to-create-directory", "error" => e) ); } - } else { - descendant = descendant.strip_prefix(context.root)?.to_path_buf(); + } else if let Some(stripped) = context + .root + .components() + .next_back() + .and_then(|stripped| descendant.strip_prefix(stripped).ok()) + { + descendant = stripped.to_path_buf(); } } else if context.root == Path::new(".") && context.target.is_dir() { // Special case: when copying current directory (.) to an existing directory, diff --git a/tests/by-util/test_cp.rs b/tests/by-util/test_cp.rs index 618d789a8..cd4f8ee73 100644 --- a/tests/by-util/test_cp.rs +++ b/tests/by-util/test_cp.rs @@ -7090,3 +7090,49 @@ fn test_cp_recursive_files_ending_in_backslash() { ts.ucmd().args(&["-r", "a", "b"]).succeeds(); assert!(at.file_exists("b/foo\\")); } + +#[test] +fn test_cp_no_preserve_target_directory() { + /* Expected result: + ├── a + │ └── b + │ └── c + │ └── d + │ └── f1 + ├── d + │ └── f1 + └── e + ├── b + │ └── c + │ └── d + │ ├── c + │ │ └── d + │ │ └── f1 + │ └── f1 + ├── d + │ └── f1 + ├── f2 + └── f3 + */ + + let ts = TestScenario::new(util_name!()); + let at = &ts.fixtures; + at.mkdir_all("a/b/c/d"); + at.touch("a/b/c/d/f1"); + ts.ucmd().args(&["-rT", "a", "e"]).succeeds(); + at.touch("e/f2"); + ts.ucmd().args(&["-rT", "a/", "e/"]).succeeds(); + at.touch("e/f3"); + ts.ucmd().args(&["-rvT", "a/b/c", "e/"]).succeeds(); + ts.ucmd().args(&["-rvT", "a/b/", "e/b/c/d/"]).succeeds(); + ts.ucmd().args(&["-rT", "a/b/c", "."]).succeeds(); + assert!(!at.dir_exists("e/a")); + assert!(at.file_exists("e/b/c/d/f1")); + assert!(at.file_exists("e/b/c/d/c/d/f1")); + assert!(!at.dir_exists("e/c")); + assert!(!at.dir_exists("e/c/d/b")); + assert!(at.file_exists("e/d/f1")); + assert!(at.file_exists("./d/f1")); + assert!(at.file_exists("e/f2")); + assert!(at.file_exists("e/f3")); +}