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:
commit
a97199f72a
9 changed files with 634 additions and 53 deletions
|
@ -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 they’re older than the corresponding source 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,6 +1633,20 @@ fn copy_file(
|
|||
}
|
||||
CopyMode::Update => {
|
||||
if dest.exists() {
|
||||
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()?;
|
||||
|
@ -1647,6 +1664,8 @@ fn copy_file(
|
|||
symlinked_files,
|
||||
)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
copy_helper(
|
||||
source,
|
||||
|
|
|
@ -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 they’re older than the corresponding source 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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
139
src/uucore/src/lib/mods/update_control.rs
Normal file
139
src/uucore/src/lib/mods/update_control.rs
Normal 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
|
||||
}
|
||||
}
|
|
@ -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!();
|
||||
|
|
|
@ -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!();
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue