mirror of
https://github.com/RGBCube/uutils-coreutils
synced 2025-07-28 19:47:45 +00:00
Merge pull request #1580 from sylvestre/archive3
feature(cp): implement archive & -L
This commit is contained in:
commit
0cfb83a040
2 changed files with 449 additions and 37 deletions
|
@ -337,7 +337,8 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
|
||||||
.arg(Arg::with_name(OPT_RECURSIVE)
|
.arg(Arg::with_name(OPT_RECURSIVE)
|
||||||
.short("r")
|
.short("r")
|
||||||
.long(OPT_RECURSIVE)
|
.long(OPT_RECURSIVE)
|
||||||
.help("copy directories recursively"))
|
// --archive sets this option
|
||||||
|
.help("copy directories recursively"))
|
||||||
.arg(Arg::with_name(OPT_RECURSIVE_ALIAS)
|
.arg(Arg::with_name(OPT_RECURSIVE_ALIAS)
|
||||||
.short("R")
|
.short("R")
|
||||||
.help("same as -r"))
|
.help("same as -r"))
|
||||||
|
@ -395,7 +396,9 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
|
||||||
.use_delimiter(true)
|
.use_delimiter(true)
|
||||||
.possible_values(PRESERVABLE_ATTRIBUTES)
|
.possible_values(PRESERVABLE_ATTRIBUTES)
|
||||||
.value_name("ATTR_LIST")
|
.value_name("ATTR_LIST")
|
||||||
.conflicts_with_all(&[OPT_PRESERVE_DEFAULT_ATTRIBUTES, OPT_NO_PRESERVE, OPT_ARCHIVE])
|
.conflicts_with_all(&[OPT_PRESERVE_DEFAULT_ATTRIBUTES, OPT_NO_PRESERVE])
|
||||||
|
// -d sets this option
|
||||||
|
// --archive sets this option
|
||||||
.help("Preserve the specified attributes (default: mode(unix only),ownership,timestamps),\
|
.help("Preserve the specified attributes (default: mode(unix only),ownership,timestamps),\
|
||||||
if possible additional attributes: context, links, xattr, all"))
|
if possible additional attributes: context, links, xattr, all"))
|
||||||
.arg(Arg::with_name(OPT_PRESERVE_DEFAULT_ATTRIBUTES)
|
.arg(Arg::with_name(OPT_PRESERVE_DEFAULT_ATTRIBUTES)
|
||||||
|
@ -413,26 +416,27 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
|
||||||
.short("-P")
|
.short("-P")
|
||||||
.long(OPT_NO_DEREFERENCE)
|
.long(OPT_NO_DEREFERENCE)
|
||||||
.conflicts_with(OPT_DEREFERENCE)
|
.conflicts_with(OPT_DEREFERENCE)
|
||||||
|
// -d sets this option
|
||||||
.help("never follow symbolic links in SOURCE"))
|
.help("never follow symbolic links in SOURCE"))
|
||||||
|
|
||||||
// TODO: implement the following args
|
|
||||||
.arg(Arg::with_name(OPT_ARCHIVE)
|
|
||||||
.short("a")
|
|
||||||
.long(OPT_ARCHIVE)
|
|
||||||
.conflicts_with_all(&[OPT_PRESERVE_DEFAULT_ATTRIBUTES, OPT_PRESERVE, OPT_NO_PRESERVE])
|
|
||||||
.help("NotImplemented: same as -dR --preserve=all"))
|
|
||||||
.arg(Arg::with_name(OPT_COPY_CONTENTS)
|
|
||||||
.long(OPT_COPY_CONTENTS)
|
|
||||||
.conflicts_with(OPT_ATTRIBUTES_ONLY)
|
|
||||||
.help("NotImplemented: copy contents of special files when recursive"))
|
|
||||||
.arg(Arg::with_name(OPT_NO_DEREFERENCE_PRESERVE_LINKS)
|
|
||||||
.short("d")
|
|
||||||
.help("NotImplemented: same as --no-dereference --preserve=links"))
|
|
||||||
.arg(Arg::with_name(OPT_DEREFERENCE)
|
.arg(Arg::with_name(OPT_DEREFERENCE)
|
||||||
.short("L")
|
.short("L")
|
||||||
.long(OPT_DEREFERENCE)
|
.long(OPT_DEREFERENCE)
|
||||||
.conflicts_with(OPT_NO_DEREFERENCE)
|
.conflicts_with(OPT_NO_DEREFERENCE)
|
||||||
.help("NotImplemented: always follow symbolic links in SOURCE"))
|
.help("always follow symbolic links in SOURCE"))
|
||||||
|
.arg(Arg::with_name(OPT_ARCHIVE)
|
||||||
|
.short("a")
|
||||||
|
.long(OPT_ARCHIVE)
|
||||||
|
.conflicts_with_all(&[OPT_PRESERVE_DEFAULT_ATTRIBUTES, OPT_PRESERVE, OPT_NO_PRESERVE])
|
||||||
|
.help("Same as -dR --preserve=all"))
|
||||||
|
.arg(Arg::with_name(OPT_NO_DEREFERENCE_PRESERVE_LINKS)
|
||||||
|
.short("d")
|
||||||
|
.help("same as --no-dereference --preserve=links"))
|
||||||
|
|
||||||
|
// TODO: implement the following args
|
||||||
|
.arg(Arg::with_name(OPT_COPY_CONTENTS)
|
||||||
|
.long(OPT_COPY_CONTENTS)
|
||||||
|
.conflicts_with(OPT_ATTRIBUTES_ONLY)
|
||||||
|
.help("NotImplemented: copy contents of special files when recursive"))
|
||||||
.arg(Arg::with_name(OPT_PARENTS)
|
.arg(Arg::with_name(OPT_PARENTS)
|
||||||
.long(OPT_PARENTS)
|
.long(OPT_PARENTS)
|
||||||
.help("NotImplemented: use full source file name under DIRECTORY"))
|
.help("NotImplemented: use full source file name under DIRECTORY"))
|
||||||
|
@ -542,19 +546,28 @@ impl FromStr for Attribute {
|
||||||
return Err(Error::InvalidArgument(format!(
|
return Err(Error::InvalidArgument(format!(
|
||||||
"invalid attribute '{}'",
|
"invalid attribute '{}'",
|
||||||
value
|
value
|
||||||
)))
|
)));
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn add_all_attributes() -> Vec<Attribute> {
|
||||||
|
let mut attr = Vec::new();
|
||||||
|
#[cfg(unix)]
|
||||||
|
attr.push(Attribute::Mode);
|
||||||
|
attr.push(Attribute::Ownership);
|
||||||
|
attr.push(Attribute::Timestamps);
|
||||||
|
attr.push(Attribute::Context);
|
||||||
|
attr.push(Attribute::Xattr);
|
||||||
|
attr.push(Attribute::Links);
|
||||||
|
attr
|
||||||
|
}
|
||||||
|
|
||||||
impl Options {
|
impl Options {
|
||||||
fn from_matches(matches: &ArgMatches) -> CopyResult<Options> {
|
fn from_matches(matches: &ArgMatches) -> CopyResult<Options> {
|
||||||
let not_implemented_opts = vec![
|
let not_implemented_opts = vec![
|
||||||
OPT_ARCHIVE,
|
|
||||||
OPT_COPY_CONTENTS,
|
OPT_COPY_CONTENTS,
|
||||||
OPT_NO_DEREFERENCE_PRESERVE_LINKS,
|
|
||||||
OPT_DEREFERENCE,
|
|
||||||
OPT_PARENTS,
|
OPT_PARENTS,
|
||||||
OPT_SPARSE,
|
OPT_SPARSE,
|
||||||
OPT_STRIP_TRAILING_SLASHES,
|
OPT_STRIP_TRAILING_SLASHES,
|
||||||
|
@ -590,13 +603,7 @@ impl Options {
|
||||||
let mut attributes = Vec::new();
|
let mut attributes = Vec::new();
|
||||||
for attribute_str in attribute_strs {
|
for attribute_str in attribute_strs {
|
||||||
if attribute_str == "all" {
|
if attribute_str == "all" {
|
||||||
#[cfg(unix)]
|
attributes = add_all_attributes();
|
||||||
attributes.push(Attribute::Mode);
|
|
||||||
attributes.push(Attribute::Ownership);
|
|
||||||
attributes.push(Attribute::Timestamps);
|
|
||||||
attributes.push(Attribute::Context);
|
|
||||||
attributes.push(Attribute::Xattr);
|
|
||||||
attributes.push(Attribute::Links);
|
|
||||||
break;
|
break;
|
||||||
} else {
|
} else {
|
||||||
attributes.push(Attribute::from_str(attribute_str)?);
|
attributes.push(Attribute::from_str(attribute_str)?);
|
||||||
|
@ -605,6 +612,11 @@ impl Options {
|
||||||
attributes
|
attributes
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else if matches.is_present(OPT_ARCHIVE) {
|
||||||
|
// --archive is used. Same as --preserve=all
|
||||||
|
add_all_attributes()
|
||||||
|
} else if matches.is_present(OPT_NO_DEREFERENCE_PRESERVE_LINKS) {
|
||||||
|
vec![Attribute::Links]
|
||||||
} else if matches.is_present(OPT_PRESERVE_DEFAULT_ATTRIBUTES) {
|
} else if matches.is_present(OPT_PRESERVE_DEFAULT_ATTRIBUTES) {
|
||||||
DEFAULT_ATTRIBUTES.to_vec()
|
DEFAULT_ATTRIBUTES.to_vec()
|
||||||
} else {
|
} else {
|
||||||
|
@ -616,7 +628,10 @@ impl Options {
|
||||||
copy_contents: matches.is_present(OPT_COPY_CONTENTS),
|
copy_contents: matches.is_present(OPT_COPY_CONTENTS),
|
||||||
copy_mode: CopyMode::from_matches(matches),
|
copy_mode: CopyMode::from_matches(matches),
|
||||||
dereference: matches.is_present(OPT_DEREFERENCE),
|
dereference: matches.is_present(OPT_DEREFERENCE),
|
||||||
no_dereference: matches.is_present(OPT_NO_DEREFERENCE),
|
// No dereference is set with -p, -d and --archive
|
||||||
|
no_dereference: matches.is_present(OPT_NO_DEREFERENCE)
|
||||||
|
|| matches.is_present(OPT_NO_DEREFERENCE_PRESERVE_LINKS)
|
||||||
|
|| matches.is_present(OPT_ARCHIVE),
|
||||||
one_file_system: matches.is_present(OPT_ONE_FILE_SYSTEM),
|
one_file_system: matches.is_present(OPT_ONE_FILE_SYSTEM),
|
||||||
overwrite: OverwriteMode::from_matches(matches),
|
overwrite: OverwriteMode::from_matches(matches),
|
||||||
parents: matches.is_present(OPT_PARENTS),
|
parents: matches.is_present(OPT_PARENTS),
|
||||||
|
@ -633,7 +648,7 @@ impl Options {
|
||||||
return Err(Error::InvalidArgument(format!(
|
return Err(Error::InvalidArgument(format!(
|
||||||
"invalid argument '{}' for \'reflink\'",
|
"invalid argument '{}' for \'reflink\'",
|
||||||
value
|
value
|
||||||
)))
|
)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -797,7 +812,6 @@ fn copy(sources: &[Source], target: &Target, options: &Options) -> CopyResult<()
|
||||||
let dest = construct_dest_path(source, target, &target_type, options)?;
|
let dest = construct_dest_path(source, target, &target_type, options)?;
|
||||||
preserve_hardlinks(&mut hard_links, source, dest, &mut found_hard_link).unwrap();
|
preserve_hardlinks(&mut hard_links, source, dest, &mut found_hard_link).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
if !found_hard_link {
|
if !found_hard_link {
|
||||||
if let Err(error) = copy_source(source, target, &target_type, options) {
|
if let Err(error) = copy_source(source, target, &target_type, options) {
|
||||||
show_error!("{}", error);
|
show_error!("{}", error);
|
||||||
|
@ -912,7 +926,7 @@ fn copy_directory(root: &Path, target: &Target, options: &Options) -> CopyResult
|
||||||
for path in WalkDir::new(root) {
|
for path in WalkDir::new(root) {
|
||||||
let p = or_continue!(path);
|
let p = or_continue!(path);
|
||||||
let is_symlink = fs::symlink_metadata(p.path())?.file_type().is_symlink();
|
let is_symlink = fs::symlink_metadata(p.path())?.file_type().is_symlink();
|
||||||
let path = if options.no_dereference && is_symlink {
|
let path = if (options.no_dereference || options.dereference) && is_symlink {
|
||||||
// we are dealing with a symlink. Don't follow it
|
// we are dealing with a symlink. Don't follow it
|
||||||
match env::current_dir() {
|
match env::current_dir() {
|
||||||
Ok(cwd) => cwd.join(resolve_relative_path(p.path())),
|
Ok(cwd) => cwd.join(resolve_relative_path(p.path())),
|
||||||
|
@ -953,7 +967,19 @@ fn copy_directory(root: &Path, target: &Target, options: &Options) -> CopyResult
|
||||||
let dest = local_to_target.as_path().to_path_buf();
|
let dest = local_to_target.as_path().to_path_buf();
|
||||||
preserve_hardlinks(&mut hard_links, &source, dest, &mut found_hard_link).unwrap();
|
preserve_hardlinks(&mut hard_links, &source, dest, &mut found_hard_link).unwrap();
|
||||||
if !found_hard_link {
|
if !found_hard_link {
|
||||||
copy_file(path.as_path(), local_to_target.as_path(), options)?;
|
match copy_file(path.as_path(), local_to_target.as_path(), options) {
|
||||||
|
Ok(_) => Ok(()),
|
||||||
|
Err(err) => {
|
||||||
|
if fs::symlink_metadata(&source)?.file_type().is_symlink() {
|
||||||
|
// silent the error with a symlink
|
||||||
|
// In case we do --archive, we might copy the symlink
|
||||||
|
// before the file itself
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}?;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
copy_file(path.as_path(), local_to_target.as_path(), options)?;
|
copy_file(path.as_path(), local_to_target.as_path(), options)?;
|
||||||
|
@ -1026,12 +1052,16 @@ fn copy_attribute(source: &Path, dest: &Path, attribute: &Attribute) -> CopyResu
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(windows))]
|
#[cfg(not(windows))]
|
||||||
fn symlink_file(source: &Path, dest: &Path, context: &str) -> CopyResult<()> {
|
fn symlink_file(source: &Path, dest: &Path, context: &str) -> CopyResult<()> {
|
||||||
Ok(std::os::unix::fs::symlink(source, dest).context(context)?)
|
match std::os::unix::fs::symlink(source, dest).context(context) {
|
||||||
|
Ok(_) => Ok(()),
|
||||||
|
Err(_) => Ok(()),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
|
@ -1144,11 +1174,9 @@ fn copy_file(source: &Path, dest: &Path, options: &Options) -> CopyResult<()> {
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
for attribute in &options.preserve_attributes {
|
for attribute in &options.preserve_attributes {
|
||||||
copy_attribute(source, dest, attribute)?;
|
copy_attribute(source, dest, attribute)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -8,8 +8,16 @@ use std::os::unix::fs;
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
use std::os::windows::fs::symlink_file;
|
use std::os::windows::fs::symlink_file;
|
||||||
|
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
use filetime::FileTime;
|
||||||
#[cfg(not(windows))]
|
#[cfg(not(windows))]
|
||||||
use std::env;
|
use std::env;
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
use std::fs as std_fs;
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
use std::thread::sleep;
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
static TEST_EXISTING_FILE: &str = "existing_file.txt";
|
static TEST_EXISTING_FILE: &str = "existing_file.txt";
|
||||||
static TEST_HELLO_WORLD_SOURCE: &str = "hello_world.txt";
|
static TEST_HELLO_WORLD_SOURCE: &str = "hello_world.txt";
|
||||||
|
@ -209,7 +217,7 @@ fn test_cp_arg_interactive() {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[cfg(target_os = "unix")]
|
#[cfg(target_os = "linux")]
|
||||||
fn test_cp_arg_link() {
|
fn test_cp_arg_link() {
|
||||||
use std::os::linux::fs::MetadataExt;
|
use std::os::linux::fs::MetadataExt;
|
||||||
|
|
||||||
|
@ -343,6 +351,60 @@ fn test_cp_arg_suffix() {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_cp_deref_conflicting_options() {
|
||||||
|
let (_at, mut ucmd) = at_and_ucmd!();
|
||||||
|
|
||||||
|
ucmd.arg("-LP")
|
||||||
|
.arg(TEST_COPY_TO_FOLDER)
|
||||||
|
.arg(TEST_HELLO_WORLD_SOURCE)
|
||||||
|
.fails();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_cp_deref() {
|
||||||
|
let scene = TestScenario::new(util_name!());
|
||||||
|
let at = &scene.fixtures;
|
||||||
|
|
||||||
|
#[cfg(not(windows))]
|
||||||
|
let _r = fs::symlink(
|
||||||
|
TEST_HELLO_WORLD_SOURCE,
|
||||||
|
at.subdir.join(TEST_HELLO_WORLD_SOURCE_SYMLINK),
|
||||||
|
);
|
||||||
|
#[cfg(windows)]
|
||||||
|
let _r = symlink_file(
|
||||||
|
TEST_HELLO_WORLD_SOURCE,
|
||||||
|
at.subdir.join(TEST_HELLO_WORLD_SOURCE_SYMLINK),
|
||||||
|
);
|
||||||
|
//using -L option
|
||||||
|
let result = scene
|
||||||
|
.ucmd()
|
||||||
|
.arg("-L")
|
||||||
|
.arg(TEST_HELLO_WORLD_SOURCE)
|
||||||
|
.arg(TEST_HELLO_WORLD_SOURCE_SYMLINK)
|
||||||
|
.arg(TEST_COPY_TO_FOLDER)
|
||||||
|
.run();
|
||||||
|
|
||||||
|
// Check that the exit code represents a successful copy.
|
||||||
|
let exit_success = result.success;
|
||||||
|
assert!(exit_success);
|
||||||
|
let path_to_new_symlink = at
|
||||||
|
.subdir
|
||||||
|
.join(TEST_COPY_TO_FOLDER)
|
||||||
|
.join(TEST_HELLO_WORLD_SOURCE_SYMLINK);
|
||||||
|
// unlike -P/--no-deref, we expect a file, not a link
|
||||||
|
assert!(at.file_exists(
|
||||||
|
&path_to_new_symlink
|
||||||
|
.clone()
|
||||||
|
.into_os_string()
|
||||||
|
.into_string()
|
||||||
|
.unwrap()
|
||||||
|
));
|
||||||
|
// Check the content of the destination file that was copied.
|
||||||
|
assert_eq!(at.read(TEST_COPY_TO_FOLDER_FILE), "Hello, World!\n");
|
||||||
|
let path_to_check = path_to_new_symlink.to_str().unwrap();
|
||||||
|
assert_eq!(at.read(&path_to_check), "Hello, World!\n");
|
||||||
|
}
|
||||||
#[test]
|
#[test]
|
||||||
fn test_cp_no_deref() {
|
fn test_cp_no_deref() {
|
||||||
let scene = TestScenario::new(util_name!());
|
let scene = TestScenario::new(util_name!());
|
||||||
|
@ -387,6 +449,111 @@ fn test_cp_no_deref() {
|
||||||
assert_eq!(at.read(&path_to_check), "Hello, World!\n");
|
assert_eq!(at.read(&path_to_check), "Hello, World!\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
// For now, disable the test on Windows. Symlinks aren't well support on Windows.
|
||||||
|
// It works on Unix for now and it works locally when run from a powershell
|
||||||
|
#[cfg(not(windows))]
|
||||||
|
fn test_cp_deref_folder_to_folder() {
|
||||||
|
let scene = TestScenario::new(util_name!());
|
||||||
|
let at = &scene.fixtures;
|
||||||
|
|
||||||
|
let cwd = env::current_dir().unwrap();
|
||||||
|
|
||||||
|
let path_to_new_symlink = at.subdir.join(TEST_COPY_FROM_FOLDER);
|
||||||
|
|
||||||
|
// Change the cwd to have a correct symlink
|
||||||
|
assert!(env::set_current_dir(&path_to_new_symlink).is_ok());
|
||||||
|
|
||||||
|
#[cfg(not(windows))]
|
||||||
|
let _r = fs::symlink(TEST_HELLO_WORLD_SOURCE, TEST_HELLO_WORLD_SOURCE_SYMLINK);
|
||||||
|
#[cfg(windows)]
|
||||||
|
let _r = symlink_file(TEST_HELLO_WORLD_SOURCE, TEST_HELLO_WORLD_SOURCE_SYMLINK);
|
||||||
|
|
||||||
|
// Back to the initial cwd (breaks the other tests)
|
||||||
|
assert!(env::set_current_dir(&cwd).is_ok());
|
||||||
|
|
||||||
|
//using -P -R option
|
||||||
|
let result = scene
|
||||||
|
.ucmd()
|
||||||
|
.arg("-L")
|
||||||
|
.arg("-R")
|
||||||
|
.arg("-v")
|
||||||
|
.arg(TEST_COPY_FROM_FOLDER)
|
||||||
|
.arg(TEST_COPY_TO_FOLDER_NEW)
|
||||||
|
.run();
|
||||||
|
println!("cp output {}", result.stdout);
|
||||||
|
|
||||||
|
// Check that the exit code represents a successful copy.
|
||||||
|
let exit_success = result.success;
|
||||||
|
assert!(exit_success);
|
||||||
|
|
||||||
|
#[cfg(not(windows))]
|
||||||
|
{
|
||||||
|
let scene2 = TestScenario::new("ls");
|
||||||
|
let result = scene2.cmd("ls").arg("-al").arg(path_to_new_symlink).run();
|
||||||
|
println!("ls source {}", result.stdout);
|
||||||
|
|
||||||
|
let path_to_new_symlink = at.subdir.join(TEST_COPY_TO_FOLDER_NEW);
|
||||||
|
|
||||||
|
let result = scene2.cmd("ls").arg("-al").arg(path_to_new_symlink).run();
|
||||||
|
println!("ls dest {}", result.stdout);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(windows)]
|
||||||
|
{
|
||||||
|
// No action as this test is disabled but kept in case we want to
|
||||||
|
// try to make it work in the future.
|
||||||
|
let a = Command::new("cmd").args(&["/C", "dir"]).output();
|
||||||
|
println!("output {:#?}", a);
|
||||||
|
|
||||||
|
let a = Command::new("cmd")
|
||||||
|
.args(&["/C", "dir", &at.as_string()])
|
||||||
|
.output();
|
||||||
|
println!("output {:#?}", a);
|
||||||
|
|
||||||
|
let a = Command::new("cmd")
|
||||||
|
.args(&["/C", "dir", path_to_new_symlink.to_str().unwrap()])
|
||||||
|
.output();
|
||||||
|
println!("output {:#?}", a);
|
||||||
|
|
||||||
|
let path_to_new_symlink = at.subdir.join(TEST_COPY_FROM_FOLDER);
|
||||||
|
|
||||||
|
let a = Command::new("cmd")
|
||||||
|
.args(&["/C", "dir", path_to_new_symlink.to_str().unwrap()])
|
||||||
|
.output();
|
||||||
|
println!("output {:#?}", a);
|
||||||
|
|
||||||
|
let path_to_new_symlink = at.subdir.join(TEST_COPY_TO_FOLDER_NEW);
|
||||||
|
|
||||||
|
let a = Command::new("cmd")
|
||||||
|
.args(&["/C", "dir", path_to_new_symlink.to_str().unwrap()])
|
||||||
|
.output();
|
||||||
|
println!("output {:#?}", a);
|
||||||
|
}
|
||||||
|
|
||||||
|
let path_to_new_symlink = at
|
||||||
|
.subdir
|
||||||
|
.join(TEST_COPY_TO_FOLDER_NEW)
|
||||||
|
.join(TEST_HELLO_WORLD_SOURCE_SYMLINK);
|
||||||
|
assert!(at.file_exists(
|
||||||
|
&path_to_new_symlink
|
||||||
|
.clone()
|
||||||
|
.into_os_string()
|
||||||
|
.into_string()
|
||||||
|
.unwrap()
|
||||||
|
));
|
||||||
|
|
||||||
|
let path_to_new = at.subdir.join(TEST_COPY_TO_FOLDER_NEW_FILE);
|
||||||
|
|
||||||
|
// Check the content of the destination file that was copied.
|
||||||
|
let path_to_check = path_to_new.to_str().unwrap();
|
||||||
|
assert_eq!(at.read(path_to_check), "Hello, World!\n");
|
||||||
|
|
||||||
|
// Check the content of the symlink
|
||||||
|
let path_to_check = path_to_new_symlink.to_str().unwrap();
|
||||||
|
assert_eq!(at.read(&path_to_check), "Hello, World!\n");
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
// For now, disable the test on Windows. Symlinks aren't well support on Windows.
|
// For now, disable the test on Windows. Symlinks aren't well support on Windows.
|
||||||
// It works on Unix for now and it works locally when run from a powershell
|
// It works on Unix for now and it works locally when run from a powershell
|
||||||
|
@ -491,3 +658,220 @@ fn test_cp_no_deref_folder_to_folder() {
|
||||||
let path_to_check = path_to_new_symlink.to_str().unwrap();
|
let path_to_check = path_to_new_symlink.to_str().unwrap();
|
||||||
assert_eq!(at.read(&path_to_check), "Hello, World!\n");
|
assert_eq!(at.read(&path_to_check), "Hello, World!\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
fn test_cp_archive() {
|
||||||
|
let (at, mut ucmd) = at_and_ucmd!();
|
||||||
|
let ts = time::now().to_timespec();
|
||||||
|
let previous = FileTime::from_unix_time(ts.sec as i64 - 3600, ts.nsec as u32);
|
||||||
|
// set the file creation/modif an hour ago
|
||||||
|
filetime::set_file_times(
|
||||||
|
at.plus_as_string(TEST_HELLO_WORLD_SOURCE),
|
||||||
|
previous,
|
||||||
|
previous,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
let result = ucmd
|
||||||
|
.arg(TEST_HELLO_WORLD_SOURCE)
|
||||||
|
.arg("--archive")
|
||||||
|
.arg(TEST_HOW_ARE_YOU_SOURCE)
|
||||||
|
.run();
|
||||||
|
|
||||||
|
assert!(result.success);
|
||||||
|
assert_eq!(at.read(TEST_HOW_ARE_YOU_SOURCE), "Hello, World!\n");
|
||||||
|
|
||||||
|
let metadata = std_fs::metadata(at.subdir.join(TEST_HELLO_WORLD_SOURCE)).unwrap();
|
||||||
|
let creation = metadata.modified().unwrap();
|
||||||
|
|
||||||
|
let metadata2 = std_fs::metadata(at.subdir.join(TEST_HOW_ARE_YOU_SOURCE)).unwrap();
|
||||||
|
let creation2 = metadata2.modified().unwrap();
|
||||||
|
|
||||||
|
let scene2 = TestScenario::new("ls");
|
||||||
|
let result = scene2.cmd("ls").arg("-al").arg(at.subdir).run();
|
||||||
|
|
||||||
|
println!("ls dest {}", result.stdout);
|
||||||
|
assert_eq!(creation, creation2);
|
||||||
|
assert!(result.success);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[cfg(target_os = "unix")]
|
||||||
|
fn test_cp_archive_recursive() {
|
||||||
|
let (at, mut ucmd) = at_and_ucmd!();
|
||||||
|
let cwd = env::current_dir().unwrap();
|
||||||
|
|
||||||
|
// creates
|
||||||
|
// dir/1
|
||||||
|
// dir/1.link => dir/1
|
||||||
|
// dir/2
|
||||||
|
// dir/2.link => dir/2
|
||||||
|
|
||||||
|
let file_1 = at.subdir.join(TEST_COPY_TO_FOLDER).join("1");
|
||||||
|
let file_1_link = at.subdir.join(TEST_COPY_TO_FOLDER).join("1.link");
|
||||||
|
let file_2 = at.subdir.join(TEST_COPY_TO_FOLDER).join("2");
|
||||||
|
let file_2_link = at.subdir.join(TEST_COPY_TO_FOLDER).join("2.link");
|
||||||
|
|
||||||
|
at.touch(&file_1.to_string_lossy());
|
||||||
|
at.touch(&file_2.to_string_lossy());
|
||||||
|
|
||||||
|
// Change the cwd to have a correct symlink
|
||||||
|
assert!(env::set_current_dir(&at.subdir.join(TEST_COPY_TO_FOLDER)).is_ok());
|
||||||
|
|
||||||
|
#[cfg(not(windows))]
|
||||||
|
{
|
||||||
|
let _r = fs::symlink("1", &file_1_link);
|
||||||
|
let _r = fs::symlink("2", &file_2_link);
|
||||||
|
}
|
||||||
|
#[cfg(windows)]
|
||||||
|
{
|
||||||
|
let _r = symlink_file("1", &file_1_link);
|
||||||
|
let _r = symlink_file("2", &file_2_link);
|
||||||
|
}
|
||||||
|
// Back to the initial cwd (breaks the other tests)
|
||||||
|
assert!(env::set_current_dir(&cwd).is_ok());
|
||||||
|
|
||||||
|
let resultg = ucmd
|
||||||
|
.arg("--archive")
|
||||||
|
.arg(TEST_COPY_TO_FOLDER)
|
||||||
|
.arg(TEST_COPY_TO_FOLDER_NEW)
|
||||||
|
.run();
|
||||||
|
|
||||||
|
let scene2 = TestScenario::new("ls");
|
||||||
|
let result = scene2
|
||||||
|
.cmd("ls")
|
||||||
|
.arg("-al")
|
||||||
|
.arg(&at.subdir.join(TEST_COPY_TO_FOLDER))
|
||||||
|
.run();
|
||||||
|
|
||||||
|
println!("ls dest {}", result.stdout);
|
||||||
|
|
||||||
|
let scene2 = TestScenario::new("ls");
|
||||||
|
let result = scene2
|
||||||
|
.cmd("ls")
|
||||||
|
.arg("-al")
|
||||||
|
.arg(&at.subdir.join(TEST_COPY_TO_FOLDER_NEW))
|
||||||
|
.run();
|
||||||
|
|
||||||
|
println!("ls dest {}", result.stdout);
|
||||||
|
assert!(at.file_exists(
|
||||||
|
&at.subdir
|
||||||
|
.join(TEST_COPY_TO_FOLDER_NEW)
|
||||||
|
.join("1.link")
|
||||||
|
.to_string_lossy()
|
||||||
|
));
|
||||||
|
assert!(at.file_exists(
|
||||||
|
&at.subdir
|
||||||
|
.join(TEST_COPY_TO_FOLDER_NEW)
|
||||||
|
.join("2.link")
|
||||||
|
.to_string_lossy()
|
||||||
|
));
|
||||||
|
assert!(at.file_exists(
|
||||||
|
&at.subdir
|
||||||
|
.join(TEST_COPY_TO_FOLDER_NEW)
|
||||||
|
.join("1")
|
||||||
|
.to_string_lossy()
|
||||||
|
));
|
||||||
|
assert!(at.file_exists(
|
||||||
|
&at.subdir
|
||||||
|
.join(TEST_COPY_TO_FOLDER_NEW)
|
||||||
|
.join("2")
|
||||||
|
.to_string_lossy()
|
||||||
|
));
|
||||||
|
|
||||||
|
assert!(at.is_symlink(
|
||||||
|
&at.subdir
|
||||||
|
.join(TEST_COPY_TO_FOLDER_NEW)
|
||||||
|
.join("1.link")
|
||||||
|
.to_string_lossy()
|
||||||
|
));
|
||||||
|
assert!(at.is_symlink(
|
||||||
|
&at.subdir
|
||||||
|
.join(TEST_COPY_TO_FOLDER_NEW)
|
||||||
|
.join("2.link")
|
||||||
|
.to_string_lossy()
|
||||||
|
));
|
||||||
|
|
||||||
|
// fails for now
|
||||||
|
assert!(resultg.success);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
fn test_cp_preserve_timestamps() {
|
||||||
|
let (at, mut ucmd) = at_and_ucmd!();
|
||||||
|
let ts = time::now().to_timespec();
|
||||||
|
let previous = FileTime::from_unix_time(ts.sec as i64 - 3600, ts.nsec as u32);
|
||||||
|
// set the file creation/modif an hour ago
|
||||||
|
filetime::set_file_times(
|
||||||
|
at.plus_as_string(TEST_HELLO_WORLD_SOURCE),
|
||||||
|
previous,
|
||||||
|
previous,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
let result = ucmd
|
||||||
|
.arg(TEST_HELLO_WORLD_SOURCE)
|
||||||
|
.arg("--preserve=timestamps")
|
||||||
|
.arg(TEST_HOW_ARE_YOU_SOURCE)
|
||||||
|
.run();
|
||||||
|
|
||||||
|
assert!(result.success);
|
||||||
|
assert_eq!(at.read(TEST_HOW_ARE_YOU_SOURCE), "Hello, World!\n");
|
||||||
|
|
||||||
|
let metadata = std_fs::metadata(at.subdir.join(TEST_HELLO_WORLD_SOURCE)).unwrap();
|
||||||
|
let creation = metadata.modified().unwrap();
|
||||||
|
|
||||||
|
let metadata2 = std_fs::metadata(at.subdir.join(TEST_HOW_ARE_YOU_SOURCE)).unwrap();
|
||||||
|
let creation2 = metadata2.modified().unwrap();
|
||||||
|
|
||||||
|
let scene2 = TestScenario::new("ls");
|
||||||
|
let result = scene2.cmd("ls").arg("-al").arg(at.subdir).run();
|
||||||
|
|
||||||
|
println!("ls dest {}", result.stdout);
|
||||||
|
assert_eq!(creation, creation2);
|
||||||
|
assert!(result.success);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
fn test_cp_dont_preserve_timestamps() {
|
||||||
|
let (at, mut ucmd) = at_and_ucmd!();
|
||||||
|
let ts = time::now().to_timespec();
|
||||||
|
let previous = FileTime::from_unix_time(ts.sec as i64 - 3600, ts.nsec as u32);
|
||||||
|
// set the file creation/modif an hour ago
|
||||||
|
filetime::set_file_times(
|
||||||
|
at.plus_as_string(TEST_HELLO_WORLD_SOURCE),
|
||||||
|
previous,
|
||||||
|
previous,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
sleep(Duration::from_secs(3));
|
||||||
|
|
||||||
|
let result = ucmd
|
||||||
|
.arg(TEST_HELLO_WORLD_SOURCE)
|
||||||
|
.arg("--no-preserve=timestamps")
|
||||||
|
.arg(TEST_HOW_ARE_YOU_SOURCE)
|
||||||
|
.run();
|
||||||
|
|
||||||
|
assert!(result.success);
|
||||||
|
assert_eq!(at.read(TEST_HOW_ARE_YOU_SOURCE), "Hello, World!\n");
|
||||||
|
|
||||||
|
let metadata = std_fs::metadata(at.subdir.join(TEST_HELLO_WORLD_SOURCE)).unwrap();
|
||||||
|
let creation = metadata.modified().unwrap();
|
||||||
|
|
||||||
|
let metadata2 = std_fs::metadata(at.subdir.join(TEST_HOW_ARE_YOU_SOURCE)).unwrap();
|
||||||
|
let creation2 = metadata2.modified().unwrap();
|
||||||
|
|
||||||
|
let scene2 = TestScenario::new("ls");
|
||||||
|
let result = scene2.cmd("ls").arg("-al").arg(at.subdir).run();
|
||||||
|
|
||||||
|
println!("ls dest {}", result.stdout);
|
||||||
|
println!("creation {:?} / {:?}", creation, creation2);
|
||||||
|
|
||||||
|
assert_ne!(creation, creation2);
|
||||||
|
let res = creation.elapsed().unwrap() - creation2.elapsed().unwrap();
|
||||||
|
// Some margins with time check
|
||||||
|
assert!(res.as_secs() > 3595);
|
||||||
|
assert!(res.as_secs() < 3605);
|
||||||
|
assert!(result.success);
|
||||||
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue