diff --git a/src/uu/cp/src/cp.rs b/src/uu/cp/src/cp.rs index 1502a7ada72..5663872d145 100644 --- a/src/uu/cp/src/cp.rs +++ b/src/uu/cp/src/cp.rs @@ -689,7 +689,12 @@ pub fn uu_app() -> Command { Arg::new(options::NO_DEREFERENCE) .short('P') .long(options::NO_DEREFERENCE) - .overrides_with(options::DEREFERENCE) + .overrides_with_all([ + options::DEREFERENCE, + options::CLI_SYMBOLIC_LINKS, + options::ARCHIVE, + options::NO_DEREFERENCE_PRESERVE_LINKS, + ]) // -d sets this option .help(translate!("cp-help-no-dereference")) .action(ArgAction::SetTrue), @@ -698,13 +703,24 @@ pub fn uu_app() -> Command { Arg::new(options::DEREFERENCE) .short('L') .long(options::DEREFERENCE) - .overrides_with(options::NO_DEREFERENCE) + .overrides_with_all([ + options::NO_DEREFERENCE, + options::CLI_SYMBOLIC_LINKS, + options::ARCHIVE, + options::NO_DEREFERENCE_PRESERVE_LINKS, + ]) .help(translate!("cp-help-dereference")) .action(ArgAction::SetTrue), ) .arg( Arg::new(options::CLI_SYMBOLIC_LINKS) .short('H') + .overrides_with_all([ + options::DEREFERENCE, + options::NO_DEREFERENCE, + options::ARCHIVE, + options::NO_DEREFERENCE_PRESERVE_LINKS, + ]) .help(translate!("cp-help-cli-symbolic-links")) .action(ArgAction::SetTrue), ) @@ -712,12 +728,24 @@ pub fn uu_app() -> Command { Arg::new(options::ARCHIVE) .short('a') .long(options::ARCHIVE) + .overrides_with_all([ + options::DEREFERENCE, + options::NO_DEREFERENCE, + options::CLI_SYMBOLIC_LINKS, + options::NO_DEREFERENCE_PRESERVE_LINKS, + ]) .help(translate!("cp-help-archive")) .action(ArgAction::SetTrue), ) .arg( Arg::new(options::NO_DEREFERENCE_PRESERVE_LINKS) .short('d') + .overrides_with_all([ + options::DEREFERENCE, + options::NO_DEREFERENCE, + options::CLI_SYMBOLIC_LINKS, + options::ARCHIVE, + ]) .help(translate!("cp-help-no-dereference-preserve-links")) .action(ArgAction::SetTrue), ) diff --git a/tests/by-util/test_cp.rs b/tests/by-util/test_cp.rs index 2563e533ae4..5f4a44c4aff 100644 --- a/tests/by-util/test_cp.rs +++ b/tests/by-util/test_cp.rs @@ -7400,3 +7400,47 @@ fn test_cp_recurse_verbose_output_with_symlink_already_exists() { .no_stderr() .stdout_is(output); } + +#[test] +#[cfg(unix)] +fn test_cp_hlp_flag_ordering() { + // GNU cp: "If more than one of -H, -L, and -P is specified, only the final one takes effect" + let (at, mut ucmd) = at_and_ucmd!(); + at.touch("file.txt"); + at.symlink_file("file.txt", "symlink"); + + // -HP: P wins, copy symlink as symlink + ucmd.args(&["-HP", "symlink", "dest_hp"]).succeeds(); + assert!(at.is_symlink("dest_hp")); + + // -PH: H wins, copy target file + let (at, mut ucmd) = at_and_ucmd!(); + at.touch("file.txt"); + at.symlink_file("file.txt", "symlink"); + ucmd.args(&["-PH", "symlink", "dest_ph"]).succeeds(); + assert!(!at.is_symlink("dest_ph")); + assert!(at.file_exists("dest_ph")); +} + +#[test] +#[cfg(unix)] +fn test_cp_archive_deref_flag_ordering() { + // (flags, expect_symlink): last flag wins; a/d imply -P, H/L dereference + for (flags, expect_symlink) in [ + ("-Ha", true), + ("-aH", false), + ("-Hd", true), + ("-dH", false), + ("-La", true), + ("-aL", false), + ("-Ld", true), + ("-dL", false), + ] { + let (at, mut ucmd) = at_and_ucmd!(); + at.touch("file.txt"); + at.symlink_file("file.txt", "symlink"); + let dest = format!("dest{flags}"); + ucmd.args(&[flags, "symlink", &dest]).succeeds(); + assert_eq!(at.is_symlink(&dest), expect_symlink, "failed for {flags}"); + } +}