Repository: uutils/coreutils (Rust)

Issue:
mv: -i -n should have different behavior than -n -i if target exists
When using both `-i/--interactive` and `-n/--no-clobber`, GNU mv only uses the argument that comes last if the target exists.

```
$ touch aa.txt
$ touch bb.txt
$ mv aa.txt bb.txt -i -n  # ignores -i
mv: not replacing 'bb.txt'
$ mv aa.txt bb.txt -n -i  # ignores -n
mv: overwrite 'bb.txt'?
```

With uutils mv, `-n/--no-clobber` "wins" in both cases:

```
$ touch aa.txt
$ touch bb.txt
$ cargo run mv aa.txt bb.txt -i -n
mv: not replacing 'bb.txt'
$ cargo run mv aa.txt bb.txt -n -i
mv: not replacing 'bb.txt'
```


Discussion:
Looks like we just default to -n
https://github.com/uutils/coreutils/blob/a97199f72a0f3d19b7ab1dc4492ccab399a3902f/src/uu/mv/src/mv.rs#L219-L232



Patch:
diff --git a/src/uu/mv/mv.md b/src/uu/mv/mv.md
index c31c6d07c40..6fcee46973f 100644
--- a/src/uu/mv/mv.md
+++ b/src/uu/mv/mv.md
@@ -9,6 +9,8 @@ Move `SOURCE` to `DEST`, or multiple `SOURCE`(s) to `DIRECTORY`.
 
 ## After Help
 
+When specifying more than one of -i, -f, -n, only the final one will take effect.
+
 Do not move a non-directory that has an existing destination with the same or newer modification timestamp;
 instead, silently skip the file without failing. If the move is across file system boundaries, the comparison is
 to the source timestamp truncated to the resolutions of the destination file system and of the system calls used
diff --git a/src/uu/mv/src/mv.rs b/src/uu/mv/src/mv.rs
index 9632ddabf2d..f26920227ae 100644
--- a/src/uu/mv/src/mv.rs
+++ b/src/uu/mv/src/mv.rs
@@ -70,7 +70,7 @@ static ARG_FILES: &str = "files";
 
 #[uucore::main]
 pub fn uumain(args: impl uucore::Args) -> UResult<()> {
-    let mut app = uu_app().after_help(backup_control::BACKUP_CONTROL_LONG_HELP);
+    let mut app = uu_app();
     let matches = app.try_get_matches_from_mut(args)?;
 
     if !matches.contains_id(OPT_TARGET_DIRECTORY)
@@ -138,13 +138,17 @@ pub fn uu_app() -> Command {
         .version(crate_version!())
         .about(ABOUT)
         .override_usage(format_usage(USAGE))
-        .after_help(AFTER_HELP)
+        .after_help(format!(
+            "{AFTER_HELP}\n\n{}",
+            backup_control::BACKUP_CONTROL_LONG_HELP
+        ))
         .infer_long_args(true)
         .arg(
             Arg::new(OPT_FORCE)
                 .short('f')
                 .long(OPT_FORCE)
                 .help("do not prompt before overwriting")
+                .overrides_with_all([OPT_INTERACTIVE, OPT_NO_CLOBBER])
                 .action(ArgAction::SetTrue),
         )
         .arg(
@@ -152,6 +156,7 @@ pub fn uu_app() -> Command {
                 .short('i')
                 .long(OPT_INTERACTIVE)
                 .help("prompt before override")
+                .overrides_with_all([OPT_FORCE, OPT_NO_CLOBBER])
                 .action(ArgAction::SetTrue),
         )
         .arg(
@@ -159,6 +164,7 @@ pub fn uu_app() -> Command {
                 .short('n')
                 .long(OPT_NO_CLOBBER)
                 .help("do not overwrite an existing file")
+                .overrides_with_all([OPT_FORCE, OPT_INTERACTIVE])
                 .action(ArgAction::SetTrue),
         )
         .arg(


Test patch:
diff --git a/tests/by-util/test_mv.rs b/tests/by-util/test_mv.rs
index b3ecb33c2f2..edeb47a96d5 100644
--- a/tests/by-util/test_mv.rs
+++ b/tests/by-util/test_mv.rs
@@ -280,6 +280,41 @@ fn test_mv_interactive_dir_to_file_not_affirmative() {
     assert!(at.dir_exists(dir));
 }
 
+#[test]
+fn test_mv_interactive_no_clobber_force_last_arg_wins() {
+    let scene = TestScenario::new(util_name!());
+    let at = &scene.fixtures;
+
+    let file_a = "a.txt";
+    let file_b = "b.txt";
+
+    at.touch(file_a);
+    at.touch(file_b);
+
+    scene
+        .ucmd()
+        .args(&[file_a, file_b, "-f", "-i", "-n"])
+        .fails()
+        .stderr_is(format!("mv: not replacing '{file_b}'\n"));
+
+    scene
+        .ucmd()
+        .args(&[file_a, file_b, "-n", "-f", "-i"])
+        .fails()
+        .stderr_is(format!("mv: overwrite '{file_b}'? "));
+
+    at.write(file_a, "aa");
+
+    scene
+        .ucmd()
+        .args(&[file_a, file_b, "-i", "-n", "-f"])
+        .succeeds()
+        .no_output();
+
+    assert!(!at.file_exists(file_a));
+    assert_eq!("aa", at.read(file_b));
+}
+
 #[test]
 fn test_mv_arg_update_interactive() {
     let (at, mut ucmd) = at_and_ucmd!();