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:
parent
5dbbdb4788
commit
5c100dd088
3 changed files with 67 additions and 2 deletions
|
@ -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")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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(|| {
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue