diff --git a/src/uu/install/locales/en-US.ftl b/src/uu/install/locales/en-US.ftl index 41dbeb335e6..7af2c3562bf 100644 --- a/src/uu/install/locales/en-US.ftl +++ b/src/uu/install/locales/en-US.ftl @@ -46,7 +46,6 @@ install-error-extra-operand = extra operand { $operand } install-error-not-permitted = cannot remove { $path }: Operation not permitted install-error-invalid-mode = Invalid mode string: { $error } install-error-mutually-exclusive-target = Options --target-directory and --no-target-directory are mutually exclusive -install-error-mutually-exclusive-compare-preserve = Options --compare and --preserve-timestamps are mutually exclusive install-error-mutually-exclusive-compare-strip = Options --compare and --strip are mutually exclusive install-error-missing-file-operand = missing file operand install-error-missing-destination-operand = missing destination file operand after { $path } diff --git a/src/uu/install/locales/fr-FR.ftl b/src/uu/install/locales/fr-FR.ftl index 7edcd4a7faf..b1a7212958e 100644 --- a/src/uu/install/locales/fr-FR.ftl +++ b/src/uu/install/locales/fr-FR.ftl @@ -45,7 +45,6 @@ install-error-extra-operand = opérande supplémentaire { $operand } { $usage } install-error-invalid-mode = Chaîne de mode invalide : { $error } install-error-mutually-exclusive-target = Les options --target-directory et --no-target-directory sont mutuellement exclusives -install-error-mutually-exclusive-compare-preserve = Les options --compare et --preserve-timestamps sont mutuellement exclusives install-error-mutually-exclusive-compare-strip = Les options --compare et --strip sont mutuellement exclusives install-error-missing-file-operand = opérande de fichier manquant install-error-missing-destination-operand = opérande de fichier de destination manquant après { $path } diff --git a/src/uu/install/src/install.rs b/src/uu/install/src/install.rs index c70873bdebc..d5998de9f9a 100644 --- a/src/uu/install/src/install.rs +++ b/src/uu/install/src/install.rs @@ -396,13 +396,6 @@ fn behavior(matches: &ArgMatches) -> UResult { } .to_string(); - if preserve_timestamps && compare { - show_error!( - "{}", - translate!("install-error-mutually-exclusive-compare-preserve") - ); - return Err(1.into()); - } if compare && strip { show_error!( "{}", @@ -1313,6 +1306,12 @@ fn need_copy(from: &Path, to: &Path, b: &Behavior) -> bool { return true; } + // When preserving timestamps, a difference in modification time also + // requires a copy so the destination ends up with the source's timestamp. + if b.preserve_timestamps && from_meta.modified().ok() != to_meta.modified().ok() { + return true; + } + if b.privileged { #[cfg(all(feature = "selinux", any(target_os = "linux", target_os = "android")))] if b.preserve_context && contexts_differ(from, to) { diff --git a/tests/by-util/test_install.rs b/tests/by-util/test_install.rs index eddcc36c224..746662b711d 100644 --- a/tests/by-util/test_install.rs +++ b/tests/by-util/test_install.rs @@ -491,6 +491,33 @@ fn test_install_preserve_timestamps() { ); } +#[test] +#[cfg(not(target_os = "openbsd"))] +fn test_install_compare_preserve_timestamps() { + use std::time::Duration; + + let (at, mut ucmd) = at_and_ucmd!(); + let source = "source_file"; + let dest = "dest_file"; + + // Same contents, but destination has a newer timestamp than the source. + at.write(source, "data"); + at.write(dest, "data"); + let old = std::time::SystemTime::UNIX_EPOCH + Duration::from_secs(1_000_000); + filetime::set_file_mtime(at.plus(source), FileTime::from_system_time(old)).unwrap(); + + // With --preserve-timestamps, the timestamp difference forces the copy so + // the destination ends up with the source's modification time. + ucmd.args(&["-C", "--preserve-timestamps", source, dest]) + .succeeds() + .no_output(); + + assert_eq!( + at.metadata(source).modified().ok(), + at.metadata(dest).modified().ok() + ); +} + // These two tests are failing but should work #[test] fn test_install_copy_file() { @@ -1856,11 +1883,12 @@ fn test_install_compare_option() { .args(&["-Cv", first, second]) .succeeds() .stdout_contains(format!("removed '{second}'\n'{first}' -> '{second}'")); + // -C and --preserve-timestamps are no longer mutually exclusive scene .ucmd() .args(&["-C", "--preserve-timestamps", first, second]) - .fails_with_code(1) - .stderr_contains("Options --compare and --preserve-timestamps are mutually exclusive"); + .succeeds() + .no_output(); scene .ucmd() .args(&["-C", "--strip", "--strip-program=echo", first, second])