mirror of
https://github.com/uutils/coreutils.git
synced 2026-06-10 15:48:22 -07:00
cp: allow directory merging when destination was just created (#9325)
* cp: allow directory merging when destination was just created Previously, when copying to a destination that was created in the same cp call, the operation would fail with "will not overwrite just-created" for both files and directories. This change allows directories to be merged (matching GNU cp behavior) while still preventing file overwrites. The fix checks if both the source and destination are directories before allowing the merge. If either is a file, the original error behavior is preserved to prevent accidental file overwrites. Fixes the case where copying multiple directories to the same destination path would incorrectly error instead of merging their contents. fixes: #9318 * test_cp: Update with the suggestion Co-authored-by: Daniel Hofstetter <daniel.hofstetter@42dh.com> --------- Co-authored-by: Daniel Hofstetter <daniel.hofstetter@42dh.com>
This commit is contained in:
co-authored by
Daniel Hofstetter
parent
376fa6408b
commit
15d22c285e
+13
-4
@@ -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()),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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!();
|
||||
|
||||
Reference in New Issue
Block a user