diff --git a/src/uu/cp/src/cp.rs b/src/uu/cp/src/cp.rs index c6c8789e2..9ef767d05 100644 --- a/src/uu/cp/src/cp.rs +++ b/src/uu/cp/src/cp.rs @@ -1375,10 +1375,19 @@ pub fn copy(sources: &[PathBuf], target: &Path, options: &Options) -> CopyResult { // There is already a file and it isn't a symlink (managed in a different place) if copied_destinations.contains(&dest) && options.backup != BackupMode::Numbered { - // If the target file was already created in this cp call, do not overwrite - return Err(CpError::Error( - translate!("cp-error-will-not-overwrite-just-created", "dest" => dest.quote(), "source" => source.quote()), - )); + // If the target was already created in this cp call, check if it's a directory. + // Directories should be merged (GNU cp behavior), but files should not be overwritten. + let dest_is_dir = fs::metadata(&dest).is_ok_and(|m| m.is_dir()); + let source_is_dir = fs::metadata(source).is_ok_and(|m| m.is_dir()); + + // Only prevent overwriting if both source and dest are files (not directories) + // Directories should be merged, which is handled by copy_directory + if !dest_is_dir || !source_is_dir { + // If the target file was already created in this cp call, do not overwrite + return Err(CpError::Error( + translate!("cp-error-will-not-overwrite-just-created", "dest" => dest.quote(), "source" => source.quote()), + )); + } } } diff --git a/tests/by-util/test_cp.rs b/tests/by-util/test_cp.rs index 3c5b3242e..c6f0d1c77 100644 --- a/tests/by-util/test_cp.rs +++ b/tests/by-util/test_cp.rs @@ -142,6 +142,41 @@ fn test_cp_duplicate_folder() { assert!(at.dir_exists(format!("{TEST_COPY_TO_FOLDER}/{TEST_COPY_FROM_FOLDER}").as_str())); } +#[test] +fn test_cp_duplicate_directories_merge() { + let (at, mut ucmd) = at_and_ucmd!(); + + // Source directory 1 + at.mkdir_all("src_dir/subdir"); + at.write("src_dir/subdir/file1.txt", "content1"); + at.write("src_dir/subdir/file2.txt", "content2"); + + // Source directory 2 + at.mkdir_all("src_dir2/subdir"); + at.write("src_dir2/subdir/file1.txt", "content3"); + + // Destination + at.mkdir("dest"); + + // Perform merge copy + ucmd.arg("-r") + .arg("src_dir/subdir") + .arg("src_dir2/subdir") + .arg("dest") + .succeeds(); + + // Verify directory exists + assert!(at.dir_exists("dest/subdir")); + + // file1.txt should be overwritten by src_dir2/subdir/file1.txt + assert!(at.file_exists("dest/subdir/file1.txt")); + assert_eq!(at.read("dest/subdir/file1.txt"), "content3"); + + // file2.txt should remain from first copy + assert!(at.file_exists("dest/subdir/file2.txt")); + assert_eq!(at.read("dest/subdir/file2.txt"), "content2"); +} + #[test] fn test_cp_duplicate_files_normalized_path() { let (at, mut ucmd) = at_and_ucmd!();