mirror of
https://github.com/uutils/coreutils.git
synced 2025-12-23 08:47:37 +00:00
cp: allow directory merging when destination was just created (#9325)
Some checks failed
CICD / Style/cargo-deny (push) Waiting to run
CICD / Style/deps (push) Waiting to run
CICD / Documentation/warnings (push) Waiting to run
CICD / MinRustV (push) Waiting to run
CICD / Separate Builds (push) Waiting to run
CICD / Dependencies (push) Waiting to run
CICD / Build/Makefile (push) Blocked by required conditions
CICD / Build/stable (push) Blocked by required conditions
CICD / Build/nightly (push) Blocked by required conditions
CICD / Binary sizes (push) Blocked by required conditions
CICD / Build (push) Blocked by required conditions
CICD / Tests/BusyBox test suite (push) Blocked by required conditions
CICD / Tests/Toybox test suite (push) Blocked by required conditions
CICD / Code Coverage (push) Waiting to run
CICD / Test all features separately (push) Blocked by required conditions
CICD / Build/SELinux (push) Blocked by required conditions
CICD / Build/SELinux-Stubs (Non-Linux) (push) Blocked by required conditions
CICD / Safe Traversal Security Check (push) Blocked by required conditions
GnuTests / Run GNU tests (native) (push) Waiting to run
GnuTests / Run GNU tests (SELinux) (push) Waiting to run
GnuTests / Aggregate GNU test results (push) Blocked by required conditions
Android / Test builds (push) Waiting to run
Benchmarks / Run benchmarks (CodSpeed) (push) Waiting to run
Code Quality / Style/format (push) Waiting to run
Code Quality / Style/lint (push) Waiting to run
Code Quality / Style/spelling (push) Waiting to run
Code Quality / Style/toml (push) Waiting to run
Code Quality / Style/Python (push) Waiting to run
Code Quality / Pre-commit hooks (push) Waiting to run
Devcontainer / Verify devcontainer (push) Waiting to run
FreeBSD / Style and Lint (push) Waiting to run
FreeBSD / Tests (push) Waiting to run
OpenBSD / Style and Lint (push) Waiting to run
OpenBSD / Tests (push) Waiting to run
WSL2 / Test (push) Waiting to run
CheckScripts / ShellScript/Check (push) Has been cancelled
CheckScripts / ShellScript/Format (push) Has been cancelled
Some checks failed
CICD / Style/cargo-deny (push) Waiting to run
CICD / Style/deps (push) Waiting to run
CICD / Documentation/warnings (push) Waiting to run
CICD / MinRustV (push) Waiting to run
CICD / Separate Builds (push) Waiting to run
CICD / Dependencies (push) Waiting to run
CICD / Build/Makefile (push) Blocked by required conditions
CICD / Build/stable (push) Blocked by required conditions
CICD / Build/nightly (push) Blocked by required conditions
CICD / Binary sizes (push) Blocked by required conditions
CICD / Build (push) Blocked by required conditions
CICD / Tests/BusyBox test suite (push) Blocked by required conditions
CICD / Tests/Toybox test suite (push) Blocked by required conditions
CICD / Code Coverage (push) Waiting to run
CICD / Test all features separately (push) Blocked by required conditions
CICD / Build/SELinux (push) Blocked by required conditions
CICD / Build/SELinux-Stubs (Non-Linux) (push) Blocked by required conditions
CICD / Safe Traversal Security Check (push) Blocked by required conditions
GnuTests / Run GNU tests (native) (push) Waiting to run
GnuTests / Run GNU tests (SELinux) (push) Waiting to run
GnuTests / Aggregate GNU test results (push) Blocked by required conditions
Android / Test builds (push) Waiting to run
Benchmarks / Run benchmarks (CodSpeed) (push) Waiting to run
Code Quality / Style/format (push) Waiting to run
Code Quality / Style/lint (push) Waiting to run
Code Quality / Style/spelling (push) Waiting to run
Code Quality / Style/toml (push) Waiting to run
Code Quality / Style/Python (push) Waiting to run
Code Quality / Pre-commit hooks (push) Waiting to run
Devcontainer / Verify devcontainer (push) Waiting to run
FreeBSD / Style and Lint (push) Waiting to run
FreeBSD / Tests (push) Waiting to run
OpenBSD / Style and Lint (push) Waiting to run
OpenBSD / Tests (push) Waiting to run
WSL2 / Test (push) Waiting to run
CheckScripts / ShellScript/Check (push) Has been cancelled
CheckScripts / ShellScript/Format (push) Has been cancelled
* 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:
parent
376fa6408b
commit
15d22c285e
2 changed files with 48 additions and 4 deletions
|
|
@ -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!();
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue