diff --git a/src/uu/mv/src/mv.rs b/src/uu/mv/src/mv.rs index dec6d3ad3..75a295502 100644 --- a/src/uu/mv/src/mv.rs +++ b/src/uu/mv/src/mv.rs @@ -10,6 +10,7 @@ mod error; use clap::builder::ValueParser; use clap::{crate_version, error::ErrorKind, Arg, ArgAction, ArgMatches, Command}; use indicatif::{MultiProgress, ProgressBar, ProgressStyle}; +use std::collections::HashSet; use std::env; use std::ffi::OsString; use std::fs; @@ -434,6 +435,9 @@ pub fn mv(files: &[OsString], opts: &Options) -> UResult<()> { #[allow(clippy::cognitive_complexity)] fn move_files_into_dir(files: &[PathBuf], target_dir: &Path, opts: &Options) -> UResult<()> { + // remember the moved destinations for further usage + let mut moved_destinations: HashSet = HashSet::with_capacity(files.len()); + if !target_dir.is_dir() { return Err(MvError::NotADirectory(target_dir.quote().to_string()).into()); } @@ -471,6 +475,18 @@ fn move_files_into_dir(files: &[PathBuf], target_dir: &Path, opts: &Options) -> } }; + if moved_destinations.contains(&targetpath) && opts.backup != BackupMode::NumberedBackup { + // If the target file was already created in this mv call, do not overwrite + return Err(USimpleError::new( + 1, + format!( + "will not overwrite just-created '{}' with '{}'", + targetpath.display(), + sourcepath.display() + ), + )); + } + // Check if we have mv dir1 dir2 dir2 // And generate an error if this is the case if let Ok(canonicalized_source) = sourcepath.canonicalize() { @@ -513,6 +529,7 @@ fn move_files_into_dir(files: &[PathBuf], target_dir: &Path, opts: &Options) -> if let Some(ref pb) = count_progress { pb.inc(1); } + moved_destinations.insert(targetpath.clone()); } Ok(()) } diff --git a/tests/by-util/test_mv.rs b/tests/by-util/test_mv.rs index 75500ac63..2e2dc3d81 100644 --- a/tests/by-util/test_mv.rs +++ b/tests/by-util/test_mv.rs @@ -1469,6 +1469,32 @@ fn test_mv_file_into_dir_where_both_are_files() { .stderr_contains("mv: failed to access 'b/': Not a directory"); } +#[test] +fn test_mv_seen_file() { + let ts = TestScenario::new(util_name!()); + let at = &ts.fixtures; + + at.mkdir("a"); + at.mkdir("b"); + at.mkdir("c"); + at.write("a/f", "a"); + at.write("b/f", "b"); + + ts.ucmd() + .arg("a/f") + .arg("b/f") + .arg("c") + .fails() + .stderr_contains("will not overwrite just-created 'c/f' with 'b/f'"); + + // a/f has been moved into c/f + assert!(at.plus("c").join("f").exists()); + // b/f still exists + assert!(at.plus("b").join("f").exists()); + // a/f no longer exists + assert!(!at.plus("a").join("f").exists()); +} + #[test] fn test_mv_dir_into_file_where_both_are_files() { let scene = TestScenario::new(util_name!());