Skip to content

Commit 0121dce

Browse files
committed
mv: revert error message to GNU default when overwriting non-empty directory
1 parent d2bd2d3 commit 0121dce

File tree

5 files changed

+89
-49
lines changed

5 files changed

+89
-49
lines changed

src/uu/mv/locales/en-US.ftl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ mv-error-backup-might-destroy-source = backing up {$target} might destroy source
3232
mv-error-will-not-overwrite-just-created = will not overwrite just-created {$target} with {$source}
3333
mv-error-not-replacing = not replacing {$target}
3434
mv-error-cannot-move = cannot move {$source} to {$target}
35+
mv-error-cannot-overwrite-non-empty-directory = cannot overwrite {$target}
3536
mv-error-directory-not-empty = Directory not empty
3637
mv-error-dangling-symlink = can't determine symlink type, since it is dangling
3738
mv-error-no-symlink-support = your operating system does not support symlinks

src/uu/mv/locales/fr-FR.ftl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ mv-error-backup-might-destroy-source = sauvegarder {$target} pourrait détruire
3232
mv-error-will-not-overwrite-just-created = ne va pas écraser le fichier qui vient d'être créé {$target} avec {$source}
3333
mv-error-not-replacing = ne remplace pas {$target}
3434
mv-error-cannot-move = impossible de déplacer {$source} vers {$target}
35+
mv-error-cannot-overwrite-non-empty-directory = impossible d'écraser {$target}
3536
mv-error-directory-not-empty = Répertoire non vide
3637
mv-error-dangling-symlink = impossible de déterminer le type de lien symbolique, car il est suspendu
3738
mv-error-no-symlink-support = votre système d'exploitation ne prend pas en charge les liens symboliques

src/uu/mv/src/mv.rs

Lines changed: 40 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ use crate::hardlink::{
3636
};
3737
use uucore::backup_control::{self, source_is_target_backup};
3838
use uucore::display::Quotable;
39-
use uucore::error::{FromIo, UResult, USimpleError, UUsageError, set_exit_code};
39+
use uucore::error::{FromIo, UError, UResult, USimpleError, UUsageError, set_exit_code};
4040
#[cfg(unix)]
4141
use uucore::fs::display_permissions_unix;
4242
#[cfg(unix)]
@@ -415,8 +415,20 @@ fn handle_two_paths(source: &Path, target: &Path, opts: &Options) -> UResult<()>
415415
hardlink_params.0,
416416
hardlink_params.1,
417417
)
418-
.map_err_context(|| {
419-
translate!("mv-error-cannot-move", "source" => source.quote(), "target" => target.quote())
418+
.map_err(|e| -> Box<dyn UError> {
419+
let message = if is_directory_not_empty_error(&e) {
420+
translate!(
421+
"mv-error-cannot-overwrite-non-empty-directory",
422+
"target" => target.quote()
423+
)
424+
} else {
425+
translate!(
426+
"mv-error-cannot-move",
427+
"source" => source.quote(),
428+
"target" => target.quote()
429+
)
430+
};
431+
e.map_err_context(|| message)
420432
})
421433
} else {
422434
Err(MvError::DirectoryToNonDirectory(target.quote().to_string()).into())
@@ -680,20 +692,31 @@ fn move_files_into_dir(files: &[PathBuf], target_dir: &Path, options: &Options)
680692
) {
681693
Err(e) if e.to_string().is_empty() => set_exit_code(1),
682694
Err(e) => {
683-
let e = e.map_err_context(|| {
684-
translate!("mv-error-cannot-move", "source" => sourcepath.quote(), "target" => targetpath.quote())
685-
});
695+
let message = if is_directory_not_empty_error(&e) {
696+
translate!(
697+
"mv-error-cannot-overwrite-non-empty-directory",
698+
"target" => targetpath.quote()
699+
)
700+
} else {
701+
translate!(
702+
"mv-error-cannot-move",
703+
"source" => sourcepath.quote(),
704+
"target" => targetpath.quote()
705+
)
706+
};
707+
let e = e.map_err_context(|| message);
686708
match display_manager {
687709
Some(ref pb) => pb.suspend(|| show!(e)),
688710
None => show!(e),
689711
}
690712
}
691-
Ok(()) => (),
713+
Ok(()) => {
714+
moved_destinations.insert(targetpath.clone());
715+
}
692716
}
693717
if let Some(ref pb) = count_progress {
694718
pb.inc(1);
695719
}
696-
moved_destinations.insert(targetpath.clone());
697720
}
698721
Ok(())
699722
}
@@ -761,7 +784,10 @@ fn rename(
761784
if is_empty_dir(to) {
762785
fs::remove_dir(to)?;
763786
} else {
764-
return Err(io::Error::other(translate!("mv-error-directory-not-empty")));
787+
return Err(io::Error::new(
788+
io::ErrorKind::DirectoryNotEmpty,
789+
translate!("mv-error-directory-not-empty"),
790+
));
765791
}
766792
}
767793
}
@@ -806,6 +832,10 @@ fn rename(
806832
Ok(())
807833
}
808834

835+
fn is_directory_not_empty_error(err: &io::Error) -> bool {
836+
err.kind() == io::ErrorKind::DirectoryNotEmpty
837+
}
838+
809839
#[cfg(unix)]
810840
fn is_fifo(filetype: fs::FileType) -> bool {
811841
filetype.is_fifo()
@@ -937,10 +967,7 @@ fn rename_symlink_fallback(from: &Path, to: &Path) -> io::Result<()> {
937967
#[cfg(not(any(windows, unix)))]
938968
fn rename_symlink_fallback(from: &Path, to: &Path) -> io::Result<()> {
939969
let path_symlink_points_to = fs::read_link(from)?;
940-
Err(io::Error::new(
941-
io::ErrorKind::Other,
942-
translate!("mv-error-no-symlink-support"),
943-
))
970+
Err(io::Error::other(translate!("mv-error-no-symlink-support")))
944971
}
945972

946973
fn rename_dir_fallback(

tests/by-util/test_mv.rs

Lines changed: 47 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ fn test_mv_move_file_into_file_with_target_arg() {
119119
.arg(file1)
120120
.arg(file2)
121121
.fails()
122-
.stderr_is(format!("mv: target directory '{file1}': Not a directory\n"));
122+
.stderr_only(format!("mv: target directory '{file1}': Not a directory\n"));
123123

124124
assert!(at.file_exists(file1));
125125
}
@@ -139,7 +139,7 @@ fn test_mv_move_multiple_files_into_file() {
139139
.arg(file2)
140140
.arg(file3)
141141
.fails()
142-
.stderr_is(format!("mv: target '{file3}': Not a directory\n"));
142+
.stderr_only(format!("mv: target '{file3}': Not a directory\n"));
143143

144144
assert!(at.file_exists(file1));
145145
assert!(at.file_exists(file2));
@@ -332,7 +332,7 @@ fn test_mv_interactive_no_clobber_force_last_arg_wins() {
332332
.ucmd()
333333
.args(&[file_a, file_b, "-n", "-f", "-i"])
334334
.fails()
335-
.stderr_is(format!("mv: overwrite '{file_b}'? "));
335+
.stderr_only(format!("mv: overwrite '{file_b}'? "));
336336

337337
at.write(file_a, "aa");
338338

@@ -518,7 +518,7 @@ fn test_mv_same_file() {
518518
ucmd.arg(file_a)
519519
.arg(file_a)
520520
.fails()
521-
.stderr_is(format!("mv: '{file_a}' and '{file_a}' are the same file\n"));
521+
.stderr_only(format!("mv: '{file_a}' and '{file_a}' are the same file\n"));
522522
}
523523

524524
#[test]
@@ -535,7 +535,7 @@ fn test_mv_same_hardlink() {
535535
ucmd.arg(file_a)
536536
.arg(file_b)
537537
.fails()
538-
.stderr_is(format!("mv: '{file_a}' and '{file_b}' are the same file\n"));
538+
.stderr_only(format!("mv: '{file_a}' and '{file_b}' are the same file\n"));
539539
}
540540

541541
#[test]
@@ -566,7 +566,7 @@ fn test_mv_same_symlink() {
566566
ucmd.arg(file_b)
567567
.arg(file_a)
568568
.fails()
569-
.stderr_is(format!("mv: '{file_b}' and '{file_a}' are the same file\n"));
569+
.stderr_only(format!("mv: '{file_b}' and '{file_a}' are the same file\n"));
570570

571571
let (at2, mut ucmd2) = at_and_ucmd!();
572572
at2.touch(file_a);
@@ -596,7 +596,7 @@ fn test_mv_same_symlink() {
596596
.arg(file_c)
597597
.arg(file_a)
598598
.fails()
599-
.stderr_is(format!("mv: '{file_c}' and '{file_a}' are the same file\n"));
599+
.stderr_only(format!("mv: '{file_c}' and '{file_a}' are the same file\n"));
600600
}
601601

602602
#[test]
@@ -609,7 +609,7 @@ fn test_mv_same_broken_symlink() {
609609
ucmd.arg("broken")
610610
.arg("broken")
611611
.fails()
612-
.stderr_is("mv: 'broken' and 'broken' are the same file\n");
612+
.stderr_only("mv: 'broken' and 'broken' are the same file\n");
613613
}
614614

615615
#[test]
@@ -707,7 +707,7 @@ fn test_mv_same_file_not_dot_dir() {
707707
let dir = "test_mv_errors_dir";
708708

709709
at.mkdir(dir);
710-
ucmd.arg(dir).arg(dir).fails().stderr_is(format!(
710+
ucmd.arg(dir).arg(dir).fails().stderr_only(format!(
711711
"mv: cannot move '{dir}' to a subdirectory of itself, '{dir}/{dir}'\n",
712712
));
713713
}
@@ -719,7 +719,7 @@ fn test_mv_same_file_dot_dir() {
719719
ucmd.arg(".")
720720
.arg(".")
721721
.fails()
722-
.stderr_is("mv: '.' and '.' are the same file\n");
722+
.stderr_only("mv: '.' and '.' are the same file\n");
723723
}
724724

725725
#[test]
@@ -1452,15 +1452,34 @@ fn test_mv_overwrite_nonempty_dir() {
14521452
at.mkdir(dir_a);
14531453
at.mkdir(dir_b);
14541454
at.touch(dummy);
1455-
// Not same error as GNU; the error message is a rust builtin
1456-
// TODO: test (and implement) correct error message (or at least decide whether to do so)
1457-
// Current: "mv: couldn't rename path (Directory not empty; from=a; to=b)"
1458-
// GNU: "mv: cannot move 'a' to 'b': Directory not empty"
1459-
14601455
// Verbose output for the move should not be shown on failure
1461-
let result = ucmd.arg("-vT").arg(dir_a).arg(dir_b).fails();
1462-
result.no_stdout();
1463-
assert!(!result.stderr_str().is_empty());
1456+
ucmd.arg("-vT")
1457+
.arg(dir_a)
1458+
.arg(dir_b)
1459+
.fails()
1460+
.stderr_contains("cannot overwrite");
1461+
1462+
assert!(at.dir_exists(dir_a));
1463+
assert!(at.dir_exists(dir_b));
1464+
}
1465+
1466+
#[test]
1467+
fn test_mv_overwrite_nonempty_dir_into_dir() {
1468+
let (at, mut ucmd) = at_and_ucmd!();
1469+
let dir_a = "test_mv_overwrite_nonempty_dir_into_dir_a";
1470+
let dir_b = "test_mv_overwrite_nonempty_dir_into_dir_b";
1471+
let target_dir = format!("{dir_b}/{dir_a}");
1472+
let dummy = format!("{target_dir}/file");
1473+
1474+
at.mkdir(dir_a);
1475+
at.mkdir(dir_b);
1476+
at.mkdir(&target_dir);
1477+
at.touch(&dummy);
1478+
1479+
ucmd.arg(dir_a)
1480+
.arg(dir_b)
1481+
.fails()
1482+
.stderr_contains("cannot overwrite");
14641483

14651484
assert!(at.dir_exists(dir_a));
14661485
assert!(at.dir_exists(dir_b));
@@ -1499,7 +1518,6 @@ fn test_mv_errors() {
14991518
at.touch(file_b);
15001519

15011520
// $ mv -T -t a b
1502-
// mv: cannot combine --target-directory (-t) and --no-target-directory (-T)
15031521
scene
15041522
.ucmd()
15051523
.arg("-T")
@@ -1512,29 +1530,26 @@ fn test_mv_errors() {
15121530

15131531
// $ at.touch file && at.mkdir dir
15141532
// $ mv -T file dir
1515-
// err == mv: cannot overwrite directory 'dir' with non-directory
15161533
scene
15171534
.ucmd()
15181535
.arg("-T")
15191536
.arg(file_a)
15201537
.arg(dir)
15211538
.fails()
1522-
.stderr_is(format!(
1539+
.stderr_only(format!(
15231540
"mv: cannot overwrite directory '{dir}' with non-directory\n"
15241541
));
15251542

15261543
// $ at.mkdir dir && at.touch file
15271544
// $ mv dir file
1528-
// err == mv: cannot overwrite non-directory 'file' with directory 'dir'
1529-
assert!(
1530-
!scene
1531-
.ucmd()
1532-
.arg(dir)
1533-
.arg(file_a)
1534-
.fails()
1535-
.stderr_str()
1536-
.is_empty()
1537-
);
1545+
scene
1546+
.ucmd()
1547+
.arg(dir)
1548+
.arg(file_a)
1549+
.fails()
1550+
.stderr_only(format!(
1551+
"mv: cannot overwrite non-directory '{file_a}' with directory '{dir}'\n"
1552+
));
15381553
}
15391554

15401555
#[test]
@@ -1621,8 +1636,7 @@ fn test_mv_arg_interactive_skipped() {
16211636
.pipe_in("N\n")
16221637
.ignore_stdin_write_error()
16231638
.fails()
1624-
.stderr_is("mv: overwrite 'b'? ")
1625-
.no_stdout();
1639+
.stderr_only("mv: overwrite 'b'? ");
16261640
}
16271641

16281642
#[test]

util/build-gnu.sh

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -282,9 +282,6 @@ sed -i -Ez "s/\n([^\n#]*pad-3\.2[^\n]*)\n([^\n]*)\n([^\n]*)/\n# uutils\/numfmt s
282282
sed -i -e "s/\$prog: multiple field specifications/error: the argument '--field <FIELDS>' cannot be used multiple times\n\nUsage: numfmt [OPTION]... [NUMBER]...\n\nFor more information, try '--help'./g" tests/numfmt/numfmt.pl
283283
sed -i -e "s/Try 'mv --help' for more information/For more information, try '--help'/g" -e "s/mv: missing file operand/error: the following required arguments were not provided:\n <files>...\n\nUsage: mv [OPTION]... [-T] SOURCE DEST\n mv [OPTION]... SOURCE... DIRECTORY\n mv [OPTION]... -t DIRECTORY SOURCE...\n/g" -e "s/mv: missing destination file operand after 'no-file'/error: The argument '<files>...' requires at least 2 values, but only 1 was provided\n\nUsage: mv [OPTION]... [-T] SOURCE DEST\n mv [OPTION]... SOURCE... DIRECTORY\n mv [OPTION]... -t DIRECTORY SOURCE...\n/g" tests/mv/diag.sh
284284

285-
# our error message is better
286-
sed -i -e "s|mv: cannot overwrite 'a/t': Directory not empty|mv: cannot move 'b/t' to 'a/t': Directory not empty|" tests/mv/dir2dir.sh
287-
288285
# GNU doesn't support width > INT_MAX
289286
# disable these test cases
290287
sed -i -E "s|^([^#]*2_31.*)$|#\1|g" tests/printf/printf-cov.pl

0 commit comments

Comments
 (0)