1
Fork 0
mirror of https://github.com/RGBCube/uutils-coreutils synced 2025-07-28 03:27:44 +00:00

mv: Fix stderr output mv file into dir and dir into file where both are files (#5464)

* Add tests mv file into dir and dir into file where both are files

* Fix test_mv_dir_into_file_where_both_are_files

* Fix test_mv_file_into_dir_where_both_are_files

* Store String in error instead of PathBuf

* Implement path_ends_with_terminator for windows

* Fix compilation on windows
This commit is contained in:
Mick van Gelderen 2023-10-28 15:04:51 +02:00 committed by GitHub
parent 5dbbdb4788
commit 5c100dd088
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 67 additions and 2 deletions

View file

@ -10,6 +10,7 @@ use uucore::error::UError;
#[derive(Debug)]
pub enum MvError {
NoSuchFile(String),
CannotStatNotADirectory(String),
SameFile(String, String),
SelfSubdirectory(String),
SelfTargetSubdirectory(String, String),
@ -17,6 +18,7 @@ pub enum MvError {
NonDirectoryToDirectory(String, String),
NotADirectory(String),
TargetNotADirectory(String),
FailedToAccessNotADirectory(String),
}
impl Error for MvError {}
@ -25,6 +27,7 @@ impl Display for MvError {
fn fmt(&self, f: &mut Formatter) -> Result {
match self {
Self::NoSuchFile(s) => write!(f, "cannot stat {s}: No such file or directory"),
Self::CannotStatNotADirectory(s) => write!(f, "cannot stat {s}: Not a directory"),
Self::SameFile(s, t) => write!(f, "{s} and {t} are the same file"),
Self::SelfSubdirectory(s) => write!(
f,
@ -42,6 +45,10 @@ impl Display for MvError {
}
Self::NotADirectory(t) => write!(f, "target {t}: Not a directory"),
Self::TargetNotADirectory(t) => write!(f, "target directory {t}: Not a directory"),
Self::FailedToAccessNotADirectory(t) => {
write!(f, "failed to access {t}: Not a directory")
}
}
}
}

View file

@ -103,6 +103,25 @@ static OPT_VERBOSE: &str = "verbose";
static OPT_PROGRESS: &str = "progress";
static ARG_FILES: &str = "files";
/// Returns true if the passed `path` ends with a path terminator.
#[cfg(unix)]
fn path_ends_with_terminator(path: &Path) -> bool {
use std::os::unix::prelude::OsStrExt;
path.as_os_str()
.as_bytes()
.last()
.map_or(false, |&byte| byte == b'/' || byte == b'\\')
}
#[cfg(windows)]
fn path_ends_with_terminator(path: &Path) -> bool {
use std::os::windows::prelude::OsStrExt;
path.as_os_str()
.encode_wide()
.last()
.map_or(false, |wide| wide == b'/'.into() || wide == b'\\'.into())
}
#[uucore::main]
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
let mut app = uu_app();
@ -299,7 +318,11 @@ fn handle_two_paths(source: &Path, target: &Path, opts: &Options) -> UResult<()>
.into());
}
if source.symlink_metadata().is_err() {
return Err(MvError::NoSuchFile(source.quote().to_string()).into());
return Err(if path_ends_with_terminator(source) {
MvError::CannotStatNotADirectory(source.quote().to_string()).into()
} else {
MvError::NoSuchFile(source.quote().to_string()).into()
});
}
if (source.eq(target)
@ -316,7 +339,13 @@ fn handle_two_paths(source: &Path, target: &Path, opts: &Options) -> UResult<()>
}
}
if target.is_dir() {
let target_is_dir = target.is_dir();
if path_ends_with_terminator(target) && !target_is_dir {
return Err(MvError::FailedToAccessNotADirectory(target.quote().to_string()).into());
}
if target_is_dir {
if opts.no_target_dir {
if source.is_dir() {
rename(source, target, opts, None).map_err_context(|| {

View file

@ -1414,6 +1414,35 @@ fn test_mv_directory_into_subdirectory_of_itself_fails() {
"mv: cannot move 'mydir/' to a subdirectory of itself, 'mydir/mydir_2/mydir/'",
);
}
#[test]
fn test_mv_file_into_dir_where_both_are_files() {
let scene = TestScenario::new(util_name!());
let at = &scene.fixtures;
at.touch("a");
at.touch("b");
scene
.ucmd()
.arg("a")
.arg("b/")
.fails()
.stderr_contains("mv: failed to access 'b/': Not a directory");
}
#[test]
fn test_mv_dir_into_file_where_both_are_files() {
let scene = TestScenario::new(util_name!());
let at = &scene.fixtures;
at.touch("a");
at.touch("b");
scene
.ucmd()
.arg("a/")
.arg("b")
.fails()
.stderr_contains("mv: cannot stat 'a/': Not a directory");
}
// Todo:
// $ at.touch a b