From 776413a9f93242a8eb3085491a6e1c0a0a952bab Mon Sep 17 00:00:00 2001 From: tommady Date: Wed, 16 Jul 2025 11:53:40 +0000 Subject: [PATCH] use copy dir when decompress rename failed Signed-off-by: tommady --- src/commands/decompress.rs | 20 ++++++++++++++- src/utils/fs.rs | 13 ++++++++++ src/utils/mod.rs | 2 +- tests/integration.rs | 50 -------------------------------------- 4 files changed, 33 insertions(+), 52 deletions(-) diff --git a/src/commands/decompress.rs b/src/commands/decompress.rs index f5dc712..7197e0e 100644 --- a/src/commands/decompress.rs +++ b/src/commands/decompress.rs @@ -413,7 +413,25 @@ fn smart_unpack( }; // Rename the temporary directory to the archive name, which is output_file_path - fs::rename(&previous_path, &new_path)?; + if fs::rename(&previous_path, &new_path).is_err() { + utils::copy_dir(&previous_path, &new_path)?; + // println!("################### {:?}", e); + // if e.kind() == io::ErrorKind::CrossesDevices || e.raw_os_error() == Some(18) { + // println!("⚠️ Rename failed due to a cross-device link. Falling back to copy-and-delete."); + + // // The rename failed, so we fall back to a copy and remove. + // // This is more expensive but works across devices. + // fs::copy(&previous_path, &new_path)?; // Copy the file to the new location. + // fs::symlink_metadata(path) + + // println!("✅ Successfully moved file via copy-and-delete fallback."); + // Ok(()) + // } else { + // // If it's a different error (e.g., permissions), we should fail. + // println!("❌ Rename failed with an unrecoverable error."); + // Err(e) + // } + }; info_accessible(format!( "Successfully moved \"{}\" to \"{}\"", nice_directory_display(&previous_path), diff --git a/src/utils/fs.rs b/src/utils/fs.rs index 0f55252..59a11cb 100644 --- a/src/utils/fs.rs +++ b/src/utils/fs.rs @@ -209,3 +209,16 @@ pub fn try_infer_extension(path: &Path) -> Option { None } } + +pub fn copy_dir(src: &Path, dst: &Path) -> crate::Result<()> { + for entry in fs::read_dir(src)? { + let entry = entry?; + let ty = entry.file_type()?; + if ty.is_dir() { + copy_dir(entry.path().as_path(), dst.join(entry.file_name()).as_path())?; + } else { + fs::copy(entry.path(), dst.join(entry.file_name()).as_path())?; + } + } + Ok(()) +} diff --git a/src/utils/mod.rs b/src/utils/mod.rs index 444cf1f..060a7ed 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -18,7 +18,7 @@ pub use self::{ EscapedPathDisplay, }, fs::{ - cd_into_same_dir_as, create_dir_if_non_existent, is_path_stdin, remove_file_or_dir, + cd_into_same_dir_as, copy_dir, create_dir_if_non_existent, is_path_stdin, remove_file_or_dir, rename_for_available_filename, resolve_path_conflict, try_infer_extension, }, question::{ diff --git a/tests/integration.rs b/tests/integration.rs index 4d46c31..3353020 100644 --- a/tests/integration.rs +++ b/tests/integration.rs @@ -870,56 +870,6 @@ fn unpack_multiple_sources_into_the_same_destination_with_merge( assert_eq!(5, out_path.as_path().read_dir()?.count()); } -#[cfg(feature = "allow_piped_choice")] -#[test] -fn unpack_multiple_sources_into_the_same_destination_with_merge_issue_825() { - let temp_dir = tempdir().unwrap(); - let root_path = temp_dir.path(); - let source_path = root_path.join("parent"); - - fs::create_dir_all(&source_path).unwrap(); - fs::File::create(source_path.join("file1.txt")).unwrap(); - fs::File::create(source_path.join("file2.txt")).unwrap(); - fs::File::create(source_path.join("file3.txt")).unwrap(); - - crate::utils::cargo_bin() - .arg("compress") - .arg(&source_path) - .arg("a.tar") - .assert() - .success(); - - fs::remove_dir_all(&source_path).unwrap(); - fs::create_dir_all(&source_path).unwrap(); - fs::File::create(source_path.join("file3.txt")).unwrap(); - fs::File::create(source_path.join("file4.txt")).unwrap(); - fs::File::create(source_path.join("file5.txt")).unwrap(); - - crate::utils::cargo_bin() - .arg("compress") - .arg(&source_path) - .arg("b.tar") - .assert() - .success(); - - fs::remove_dir_all(&source_path).unwrap(); - - crate::utils::cargo_bin() - .arg("decompress") - .arg("a.tar") - .assert() - .success(); - - crate::utils::cargo_bin() - .arg("decompress") - .arg("b.tar") - .write_stdin("m") - .assert() - .success(); - - assert_eq!(5, source_path.read_dir().unwrap().count()); -} - #[test] fn reading_nested_archives_with_two_archive_extensions_adjacent() { let archive_formats = ["tar", "zip", "7z"].into_iter();