1
Fork 0
mirror of https://github.com/RGBCube/uutils-coreutils synced 2025-07-28 19:47:45 +00:00

Merge pull request #4796 from shinhs0506/mv-cp-update

mv, cp: add support for --update=none,all,older
This commit is contained in:
Daniel Hofstetter 2023-05-03 15:21:21 +02:00 committed by GitHub
commit a97199f72a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 634 additions and 53 deletions

View file

@ -7,3 +7,19 @@ cp [OPTION]... -t DIRECTORY SOURCE...
```
Copy SOURCE to DEST, or multiple SOURCE(s) to DIRECTORY.
## After Help
Do not copy a non-directory that has an existing destination with the same or newer modification timestamp;
instead, silently skip the file without failing. If timestamps are being preserved, the comparison is to the
source timestamp truncated to the resolutions of the destination file system and of the system calls used to
update timestamps; this avoids duplicate work if several `cp -pu` commands are executed with the same source
and destination. This option is ignored if the `-n` or `--no-clobber` option is also specified. Also, if
`--preserve=links` is also specified (like with `cp -au` for example), that will take precedence; consequently,
depending on the order that files are processed from the source, newer files in the destination may be replaced,
to mirror hard links in the source. which gives more control over which existing files in the destination are
replaced, and its value can be one of the following:
* `all` This is the default operation when an `--update` option is not specified, and results in all existing files in the destination being replaced.
* `none` This is similar to the `--no-clobber` option, in that no files in the destination are replaced, but also skipping a file does not induce a failure.
* `older` This is the default operation when `--update` is specified, and results in files being replaced if theyre older than the corresponding source file.

View file

@ -40,7 +40,10 @@ use uucore::error::{set_exit_code, UClapError, UError, UResult, UUsageError};
use uucore::fs::{
canonicalize, paths_refer_to_same_file, FileInformation, MissingHandling, ResolveMode,
};
use uucore::{crash, format_usage, help_about, help_usage, prompt_yes, show_error, show_warning};
use uucore::update_control::{self, UpdateMode};
use uucore::{
crash, format_usage, help_about, help_section, help_usage, prompt_yes, show_error, show_warning,
};
use crate::copydir::copy_directory;
@ -224,13 +227,14 @@ pub struct Options {
recursive: bool,
backup_suffix: String,
target_dir: Option<PathBuf>,
update: bool,
update: UpdateMode,
verbose: bool,
progress_bar: bool,
}
const ABOUT: &str = help_about!("cp.md");
const USAGE: &str = help_usage!("cp.md");
const AFTER_HELP: &str = help_section!("after help", "cp.md");
static EXIT_ERR: i32 = 1;
@ -264,7 +268,6 @@ mod options {
pub const STRIP_TRAILING_SLASHES: &str = "strip-trailing-slashes";
pub const SYMBOLIC_LINK: &str = "symbolic-link";
pub const TARGET_DIRECTORY: &str = "target-directory";
pub const UPDATE: &str = "update";
pub const VERBOSE: &str = "verbose";
}
@ -295,6 +298,7 @@ pub fn uu_app() -> Command {
.version(crate_version!())
.about(ABOUT)
.override_usage(format_usage(USAGE))
.after_help(AFTER_HELP)
.infer_long_args(true)
.arg(
Arg::new(options::TARGET_DIRECTORY)
@ -393,16 +397,8 @@ pub fn uu_app() -> Command {
.arg(backup_control::arguments::backup())
.arg(backup_control::arguments::backup_no_args())
.arg(backup_control::arguments::suffix())
.arg(
Arg::new(options::UPDATE)
.short('u')
.long(options::UPDATE)
.help(
"copy only when the SOURCE file is newer than the destination file \
or when the destination file is missing",
)
.action(ArgAction::SetTrue),
)
.arg(update_control::arguments::update())
.arg(update_control::arguments::update_no_args())
.arg(
Arg::new(options::REFLINK)
.long(options::REFLINK)
@ -641,7 +637,11 @@ impl CopyMode {
Self::Link
} else if matches.get_flag(options::SYMBOLIC_LINK) {
Self::SymLink
} else if matches.get_flag(options::UPDATE) {
} else if matches
.get_one::<String>(update_control::arguments::OPT_UPDATE)
.is_some()
|| matches.get_flag(update_control::arguments::OPT_UPDATE_NO_ARG)
{
Self::Update
} else if matches.get_flag(options::ATTRIBUTES_ONLY) {
Self::AttrOnly
@ -749,6 +749,7 @@ impl Options {
Err(e) => return Err(Error::Backup(format!("{e}"))),
Ok(mode) => mode,
};
let update_mode = update_control::determine_update_mode(matches);
let backup_suffix = backup_control::determine_backup_suffix(matches);
@ -826,7 +827,7 @@ impl Options {
|| matches.get_flag(options::DEREFERENCE),
one_file_system: matches.get_flag(options::ONE_FILE_SYSTEM),
parents: matches.get_flag(options::PARENTS),
update: matches.get_flag(options::UPDATE),
update: update_mode,
verbose: matches.get_flag(options::VERBOSE),
strip_trailing_slashes: matches.get_flag(options::STRIP_TRAILING_SLASHES),
reflink_mode: {
@ -1473,7 +1474,9 @@ fn copy_file(
symlinked_files: &mut HashSet<FileInformation>,
source_in_command_line: bool,
) -> CopyResult<()> {
if options.update && options.overwrite == OverwriteMode::Interactive(ClobberMode::Standard) {
if (options.update == UpdateMode::ReplaceIfOlder || options.update == UpdateMode::ReplaceNone)
&& options.overwrite == OverwriteMode::Interactive(ClobberMode::Standard)
{
// `cp -i --update old new` when `new` exists doesn't copy anything
// and exit with 0
return Ok(());
@ -1630,22 +1633,38 @@ fn copy_file(
}
CopyMode::Update => {
if dest.exists() {
let dest_metadata = fs::symlink_metadata(dest)?;
match options.update {
update_control::UpdateMode::ReplaceAll => {
copy_helper(
source,
dest,
options,
context,
source_is_symlink,
source_is_fifo,
symlinked_files,
)?;
}
update_control::UpdateMode::ReplaceNone => return Ok(()),
update_control::UpdateMode::ReplaceIfOlder => {
let dest_metadata = fs::symlink_metadata(dest)?;
let src_time = source_metadata.modified()?;
let dest_time = dest_metadata.modified()?;
if src_time <= dest_time {
return Ok(());
} else {
copy_helper(
source,
dest,
options,
context,
source_is_symlink,
source_is_fifo,
symlinked_files,
)?;
let src_time = source_metadata.modified()?;
let dest_time = dest_metadata.modified()?;
if src_time <= dest_time {
return Ok(());
} else {
copy_helper(
source,
dest,
options,
context,
source_is_symlink,
source_is_fifo,
symlinked_files,
)?;
}
}
}
} else {
copy_helper(

View file

@ -5,5 +5,17 @@ mv [OPTION]... [-T] SOURCE DEST
mv [OPTION]... SOURCE... DIRECTORY
mv [OPTION]... -t DIRECTORY SOURCE...
```
Move `SOURCE` to `DEST`, or multiple `SOURCE`(s) to `DIRECTORY`.
## After Help
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
to update timestamps; this avoids duplicate work if several `mv -u` commands are executed with the same source
and destination. This option is ignored if the `-n` or `--no-clobber` option is also specified. which gives more control
over which existing files in the destination are replaced, and its value can be one of the following:
* `all` This is the default operation when an `--update` option is not specified, and results in all existing files in the destination being replaced.
* `none` This is similar to the `--no-clobber` option, in that no files in the destination are replaced, but also skipping a file does not induce a failure.
* `older` This is the default operation when `--update` is specified, and results in files being replaced if theyre older than the corresponding source file.

View file

@ -25,7 +25,8 @@ use std::path::{Path, PathBuf};
use uucore::backup_control::{self, BackupMode};
use uucore::display::Quotable;
use uucore::error::{set_exit_code, FromIo, UError, UResult, USimpleError, UUsageError};
use uucore::{format_usage, help_about, help_usage, prompt_yes, show};
use uucore::update_control::{self, UpdateMode};
use uucore::{format_usage, help_about, help_section, help_usage, prompt_yes, show};
use fs_extra::dir::{
get_size as dir_get_size, move_dir, move_dir_with_progress, CopyOptions as DirCopyOptions,
@ -38,7 +39,7 @@ pub struct Behavior {
overwrite: OverwriteMode,
backup: BackupMode,
suffix: String,
update: bool,
update: UpdateMode,
target_dir: Option<OsString>,
no_target_dir: bool,
verbose: bool,
@ -55,6 +56,7 @@ pub enum OverwriteMode {
const ABOUT: &str = help_about!("mv.md");
const USAGE: &str = help_usage!("mv.md");
const AFTER_HELP: &str = help_section!("after help", "mv.md");
static OPT_FORCE: &str = "force";
static OPT_INTERACTIVE: &str = "interactive";
@ -62,7 +64,6 @@ static OPT_NO_CLOBBER: &str = "no-clobber";
static OPT_STRIP_TRAILING_SLASHES: &str = "strip-trailing-slashes";
static OPT_TARGET_DIRECTORY: &str = "target-directory";
static OPT_NO_TARGET_DIRECTORY: &str = "no-target-directory";
static OPT_UPDATE: &str = "update";
static OPT_VERBOSE: &str = "verbose";
static OPT_PROGRESS: &str = "progress";
static ARG_FILES: &str = "files";
@ -96,6 +97,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
let overwrite_mode = determine_overwrite_mode(&matches);
let backup_mode = backup_control::determine_backup_mode(&matches)?;
let update_mode = update_control::determine_update_mode(&matches);
if overwrite_mode == OverwriteMode::NoClobber && backup_mode != BackupMode::NoBackup {
return Err(UUsageError::new(
@ -120,7 +122,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
overwrite: overwrite_mode,
backup: backup_mode,
suffix: backup_suffix,
update: matches.get_flag(OPT_UPDATE),
update: update_mode,
target_dir,
no_target_dir: matches.get_flag(OPT_NO_TARGET_DIRECTORY),
verbose: matches.get_flag(OPT_VERBOSE),
@ -136,9 +138,8 @@ pub fn uu_app() -> Command {
.version(crate_version!())
.about(ABOUT)
.override_usage(format_usage(USAGE))
.after_help(AFTER_HELP)
.infer_long_args(true)
.arg(backup_control::arguments::backup())
.arg(backup_control::arguments::backup_no_args())
.arg(
Arg::new(OPT_FORCE)
.short('f')
@ -166,7 +167,11 @@ pub fn uu_app() -> Command {
.help("remove any trailing slashes from each SOURCE argument")
.action(ArgAction::SetTrue),
)
.arg(backup_control::arguments::backup())
.arg(backup_control::arguments::backup_no_args())
.arg(backup_control::arguments::suffix())
.arg(update_control::arguments::update())
.arg(update_control::arguments::update_no_args())
.arg(
Arg::new(OPT_TARGET_DIRECTORY)
.short('t')
@ -184,16 +189,6 @@ pub fn uu_app() -> Command {
.help("treat DEST as a normal file")
.action(ArgAction::SetTrue),
)
.arg(
Arg::new(OPT_UPDATE)
.short('u')
.long(OPT_UPDATE)
.help(
"move only when the SOURCE file is newer than the destination file \
or when the destination file is missing",
)
.action(ArgAction::SetTrue),
)
.arg(
Arg::new(OPT_VERBOSE)
.short('v')
@ -420,12 +415,24 @@ fn rename(
let mut backup_path = None;
if to.exists() {
if b.update && b.overwrite == OverwriteMode::Interactive {
if (b.update == UpdateMode::ReplaceIfOlder || b.update == UpdateMode::ReplaceNone)
&& b.overwrite == OverwriteMode::Interactive
{
// `mv -i --update old new` when `new` exists doesn't move anything
// and exit with 0
return Ok(());
}
if b.update == UpdateMode::ReplaceNone {
return Ok(());
}
if (b.update == UpdateMode::ReplaceIfOlder)
&& fs::metadata(from)?.modified()? <= fs::metadata(to)?.modified()?
{
return Ok(());
}
match b.overwrite {
OverwriteMode::NoClobber => {
return Err(io::Error::new(
@ -445,10 +452,6 @@ fn rename(
if let Some(ref backup_path) = backup_path {
rename_with_fallback(to, backup_path, multi_progress)?;
}
if b.update && fs::metadata(from)?.modified()? <= fs::metadata(to)?.modified()? {
return Ok(());
}
}
// "to" may no longer exist if it was backed up

View file

@ -26,6 +26,7 @@ pub use crate::mods::os;
pub use crate::mods::panic;
pub use crate::mods::quoting_style;
pub use crate::mods::ranges;
pub use crate::mods::update_control;
pub use crate::mods::version_cmp;
// * string parsing modules

View file

@ -6,6 +6,7 @@ pub mod error;
pub mod os;
pub mod panic;
pub mod ranges;
pub mod update_control;
pub mod version_cmp;
// dir and vdir also need access to the quoting_style module
pub mod quoting_style;

View file

@ -0,0 +1,139 @@
// This file is part of the uutils coreutils package.
//
// (c) John Shin <shinhs0506@gmail.com>
//
// For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code.
//! Implement GNU-style update functionality.
//!
//! - pre-defined [`clap`-Arguments][1] for inclusion in utilities that
//! implement updates
//! - determination of the [update mode][2]
//!
//! Update-functionality is implemented by the following utilities:
//!
//! - `cp`
//! - `mv`
//!
//!
//! [1]: arguments
//! [2]: `determine_update_mode()`
//!
//!
//! # Usage example
//!
//! ```
//! #[macro_use]
//! extern crate uucore;
//!
//! use clap::{Command, Arg, ArgMatches};
//! use uucore::update_control::{self, UpdateMode};
//!
//! fn main() {
//! let matches = Command::new("command")
//! .arg(update_control::arguments::update())
//! .arg(update_control::arguments::update_no_args())
//! .get_matches_from(vec![
//! "command", "--update=older"
//! ]);
//!
//! let update_mode = update_control::determine_update_mode(&matches);
//!
//! // handle cases
//! if update_mode == UpdateMode::ReplaceIfOlder {
//! // do
//! } else {
//! unreachable!()
//! }
//! }
//! ```
use clap::ArgMatches;
// Available update mode
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum UpdateMode {
// --update=`all`, ``
ReplaceAll,
// --update=`none`
ReplaceNone,
// --update=`older`
// -u
ReplaceIfOlder,
}
pub mod arguments {
use clap::ArgAction;
pub static OPT_UPDATE: &str = "update";
pub static OPT_UPDATE_NO_ARG: &str = "u";
// `--update` argument, defaults to `older` if no values are provided
pub fn update() -> clap::Arg {
clap::Arg::new(OPT_UPDATE)
.long("update")
.help("move only when the SOURCE file is newer than the destination file or when the destination file is missing")
.value_parser(["none", "all", "older"])
.num_args(0..=1)
.default_missing_value("older")
.require_equals(true)
.overrides_with("update")
.action(clap::ArgAction::Set)
}
// `-u` argument
pub fn update_no_args() -> clap::Arg {
clap::Arg::new(OPT_UPDATE_NO_ARG)
.short('u')
.help("like --update but does not accept an argument")
.action(ArgAction::SetTrue)
}
}
/// Determine the "mode" for the update operation to perform, if any.
///
/// Parses the backup options and converts them to an instance of
/// `UpdateMode` for further processing.
///
/// Takes [`clap::ArgMatches`] as argument which **must** contain the options
/// from [`arguments::update()`] or [`arguments::update_no_args()`]. Otherwise
/// the `ReplaceAll` mode is returned unconditionally.
///
/// # Examples
///
/// Here's how one would integrate the update mode determination into an
/// application.
///
/// ```
/// #[macro_use]
/// extern crate uucore;
/// use uucore::update_control::{self, UpdateMode};
/// use clap::{Command, Arg, ArgMatches};
///
/// fn main() {
/// let matches = Command::new("command")
/// .arg(update_control::arguments::update())
/// .arg(update_control::arguments::update_no_args())
/// .get_matches_from(vec![
/// "command", "--update=all"
/// ]);
///
/// let update_mode = update_control::determine_update_mode(&matches);
/// assert_eq!(update_mode, UpdateMode::ReplaceAll)
/// }
pub fn determine_update_mode(matches: &ArgMatches) -> UpdateMode {
if let Some(mode) = matches.get_one::<String>(arguments::OPT_UPDATE) {
match mode.as_str() {
"all" => UpdateMode::ReplaceAll,
"none" => UpdateMode::ReplaceNone,
"older" => UpdateMode::ReplaceIfOlder,
_ => unreachable!("other args restricted by clap"),
}
} else if matches.get_flag(arguments::OPT_UPDATE_NO_ARG) {
// short form of this option is equivalent to using --update=older
UpdateMode::ReplaceIfOlder
} else {
// no option was present
UpdateMode::ReplaceAll
}
}

View file

@ -244,6 +244,192 @@ fn test_cp_arg_update_interactive_error() {
.no_stdout();
}
#[test]
fn test_cp_arg_update_none() {
let (at, mut ucmd) = at_and_ucmd!();
ucmd.arg(TEST_HELLO_WORLD_SOURCE)
.arg(TEST_HOW_ARE_YOU_SOURCE)
.arg("--update=none")
.succeeds()
.no_stderr()
.no_stdout();
assert_eq!(at.read(TEST_HOW_ARE_YOU_SOURCE), "How are you?\n")
}
#[test]
fn test_cp_arg_update_all() {
let (at, mut ucmd) = at_and_ucmd!();
ucmd.arg(TEST_HELLO_WORLD_SOURCE)
.arg(TEST_HOW_ARE_YOU_SOURCE)
.arg("--update=all")
.succeeds()
.no_stderr()
.no_stdout();
assert_eq!(
at.read(TEST_HOW_ARE_YOU_SOURCE),
at.read(TEST_HELLO_WORLD_SOURCE)
)
}
#[test]
fn test_cp_arg_update_older_dest_not_older_than_src() {
let (at, mut ucmd) = at_and_ucmd!();
let old = "test_cp_arg_update_dest_not_older_file1";
let new = "test_cp_arg_update_dest_not_older_file2";
let old_content = "old content\n";
let new_content = "new content\n";
at.write(old, old_content);
at.write(new, new_content);
ucmd.arg(old)
.arg(new)
.arg("--update=older")
.succeeds()
.no_stderr()
.no_stdout();
assert_eq!(at.read(new), "new content\n")
}
#[test]
fn test_cp_arg_update_older_dest_older_than_src() {
let (at, mut ucmd) = at_and_ucmd!();
let old = "test_cp_arg_update_dest_older_file1";
let new = "test_cp_arg_update_dest_older_file2";
let old_content = "old content\n";
let new_content = "new content\n";
at.write(old, old_content);
sleep(Duration::from_secs(1));
at.write(new, new_content);
ucmd.arg(new)
.arg(old)
.arg("--update=older")
.succeeds()
.no_stderr()
.no_stdout();
assert_eq!(at.read(old), "new content\n")
}
#[test]
fn test_cp_arg_update_short_no_overwrite() {
// same as --update=older
let (at, mut ucmd) = at_and_ucmd!();
let old = "test_cp_arg_update_short_no_overwrite_file1";
let new = "test_cp_arg_update_short_no_overwrite_file2";
let old_content = "old content\n";
let new_content = "new content\n";
at.write(old, old_content);
sleep(Duration::from_secs(1));
at.write(new, new_content);
ucmd.arg(old)
.arg(new)
.arg("-u")
.succeeds()
.no_stderr()
.no_stdout();
assert_eq!(at.read(new), "new content\n")
}
#[test]
fn test_cp_arg_update_short_overwrite() {
// same as --update=older
let (at, mut ucmd) = at_and_ucmd!();
let old = "test_cp_arg_update_short_overwrite_file1";
let new = "test_cp_arg_update_short_overwrite_file2";
let old_content = "old content\n";
let new_content = "new content\n";
at.write(old, old_content);
sleep(Duration::from_secs(1));
at.write(new, new_content);
ucmd.arg(new)
.arg(old)
.arg("-u")
.succeeds()
.no_stderr()
.no_stdout();
assert_eq!(at.read(old), "new content\n")
}
#[test]
fn test_cp_arg_update_none_then_all() {
// take last if multiple update args are supplied,
// update=all wins in this case
let (at, mut ucmd) = at_and_ucmd!();
let old = "test_cp_arg_update_none_then_all_file1";
let new = "test_cp_arg_update_none_then_all_file2";
let old_content = "old content\n";
let new_content = "new content\n";
at.write(old, old_content);
sleep(Duration::from_secs(1));
at.write(new, new_content);
ucmd.arg(old)
.arg(new)
.arg("--update=none")
.arg("--update=all")
.succeeds()
.no_stderr()
.no_stdout();
assert_eq!(at.read(new), "old content\n")
}
#[test]
fn test_cp_arg_update_all_then_none() {
// take last if multiple update args are supplied,
// update=none wins in this case
let (at, mut ucmd) = at_and_ucmd!();
let old = "test_cp_arg_update_all_then_none_file1";
let new = "test_cp_arg_update_all_then_none_file2";
let old_content = "old content\n";
let new_content = "new content\n";
at.write(old, old_content);
sleep(Duration::from_secs(1));
at.write(new, new_content);
ucmd.arg(old)
.arg(new)
.arg("--update=all")
.arg("--update=none")
.succeeds()
.no_stderr()
.no_stdout();
assert_eq!(at.read(new), "new content\n")
}
#[test]
fn test_cp_arg_interactive() {
let (at, mut ucmd) = at_and_ucmd!();

View file

@ -1,5 +1,7 @@
use crate::common::util::TestScenario;
use filetime::FileTime;
use std::thread::sleep;
use std::time::Duration;
#[test]
fn test_invalid_arg() {
@ -716,6 +718,208 @@ fn test_mv_update_option() {
assert!(!at.file_exists(file_b));
}
#[test]
fn test_mv_arg_update_none() {
let (at, mut ucmd) = at_and_ucmd!();
let file1 = "test_mv_arg_update_none_file1";
let file2 = "test_mv_arg_update_none_file2";
let file1_content = "file1 content\n";
let file2_content = "file2 content\n";
at.write(file1, file1_content);
at.write(file2, file2_content);
ucmd.arg(file1)
.arg(file2)
.arg("--update=none")
.succeeds()
.no_stderr()
.no_stdout();
assert_eq!(at.read(file2), file2_content)
}
#[test]
fn test_mv_arg_update_all() {
let (at, mut ucmd) = at_and_ucmd!();
let file1 = "test_mv_arg_update_none_file1";
let file2 = "test_mv_arg_update_none_file2";
let file1_content = "file1 content\n";
let file2_content = "file2 content\n";
at.write(file1, file1_content);
at.write(file2, file2_content);
ucmd.arg(file1)
.arg(file2)
.arg("--update=all")
.succeeds()
.no_stderr()
.no_stdout();
assert_eq!(at.read(file2), file1_content)
}
#[test]
fn test_mv_arg_update_older_dest_not_older() {
let (at, mut ucmd) = at_and_ucmd!();
let old = "test_mv_arg_update_none_file1";
let new = "test_mv_arg_update_none_file2";
let old_content = "file1 content\n";
let new_content = "file2 content\n";
at.write(old, old_content);
sleep(Duration::from_secs(1));
at.write(new, new_content);
ucmd.arg(old)
.arg(new)
.arg("--update=older")
.succeeds()
.no_stderr()
.no_stdout();
assert_eq!(at.read(new), new_content)
}
#[test]
fn test_mv_arg_update_none_then_all() {
// take last if multiple update args are supplied,
// update=all wins in this case
let (at, mut ucmd) = at_and_ucmd!();
let old = "test_mv_arg_update_none_then_all_file1";
let new = "test_mv_arg_update_none_then_all_file2";
let old_content = "old content\n";
let new_content = "new content\n";
at.write(old, old_content);
sleep(Duration::from_secs(1));
at.write(new, new_content);
ucmd.arg(old)
.arg(new)
.arg("--update=none")
.arg("--update=all")
.succeeds()
.no_stderr()
.no_stdout();
assert_eq!(at.read(new), "old content\n")
}
#[test]
fn test_mv_arg_update_all_then_none() {
// take last if multiple update args are supplied,
// update=none wins in this case
let (at, mut ucmd) = at_and_ucmd!();
let old = "test_mv_arg_update_all_then_none_file1";
let new = "test_mv_arg_update_all_then_none_file2";
let old_content = "old content\n";
let new_content = "new content\n";
at.write(old, old_content);
sleep(Duration::from_secs(1));
at.write(new, new_content);
ucmd.arg(old)
.arg(new)
.arg("--update=all")
.arg("--update=none")
.succeeds()
.no_stderr()
.no_stdout();
assert_eq!(at.read(new), "new content\n")
}
#[test]
fn test_mv_arg_update_older_dest_older() {
let (at, mut ucmd) = at_and_ucmd!();
let old = "test_mv_arg_update_none_file1";
let new = "test_mv_arg_update_none_file2";
let old_content = "file1 content\n";
let new_content = "file2 content\n";
at.write(old, old_content);
sleep(Duration::from_secs(1));
at.write(new, new_content);
ucmd.arg(new)
.arg(old)
.arg("--update=all")
.succeeds()
.no_stderr()
.no_stdout();
assert_eq!(at.read(old), new_content)
}
#[test]
fn test_mv_arg_update_short_overwrite() {
// same as --update=older
let (at, mut ucmd) = at_and_ucmd!();
let old = "test_mv_arg_update_none_file1";
let new = "test_mv_arg_update_none_file2";
let old_content = "file1 content\n";
let new_content = "file2 content\n";
at.write(old, old_content);
sleep(Duration::from_secs(1));
at.write(new, new_content);
ucmd.arg(new)
.arg(old)
.arg("-u")
.succeeds()
.no_stderr()
.no_stdout();
assert_eq!(at.read(old), new_content)
}
#[test]
fn test_mv_arg_update_short_no_overwrite() {
// same as --update=older
let (at, mut ucmd) = at_and_ucmd!();
let old = "test_mv_arg_update_none_file1";
let new = "test_mv_arg_update_none_file2";
let old_content = "file1 content\n";
let new_content = "file2 content\n";
at.write(old, old_content);
sleep(Duration::from_secs(1));
at.write(new, new_content);
ucmd.arg(old)
.arg(new)
.arg("-u")
.succeeds()
.no_stderr()
.no_stdout();
assert_eq!(at.read(new), new_content)
}
#[test]
fn test_mv_target_dir() {
let (at, mut ucmd) = at_and_ucmd!();