mirror of
https://github.com/RGBCube/uutils-coreutils
synced 2025-07-27 19:17:43 +00:00
cp: correct --verbose --parents output for dirs
This commit corrects the behavior of `cp -r --parents --verbose` when the source path is a directory, so that it prints the copied ancestor directories. For example, $ mkdir -p a/b/c d $ cp -r --verbose --parents a/b/c d a -> d/a a/b -> d/a/b 'a/b/c' -> 'd/a/b/c'
This commit is contained in:
parent
bd665ea44a
commit
1a839fb2c4
2 changed files with 111 additions and 4 deletions
|
@ -24,8 +24,8 @@ use uucore::uio_error;
|
|||
use walkdir::{DirEntry, WalkDir};
|
||||
|
||||
use crate::{
|
||||
copy_attributes, copy_file, copy_link, preserve_hardlinks, CopyResult, Error, Options,
|
||||
TargetSlice,
|
||||
aligned_ancestors, context_for, copy_attributes, copy_file, copy_link, preserve_hardlinks,
|
||||
CopyResult, Error, Options, TargetSlice,
|
||||
};
|
||||
|
||||
/// Ensure a Windows path starts with a `\\?`.
|
||||
|
@ -172,6 +172,27 @@ impl Entry {
|
|||
}
|
||||
}
|
||||
|
||||
/// Decide whether the given path ends with `/.`.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust,ignore
|
||||
/// assert!(ends_with_slash_dot("/."));
|
||||
/// assert!(ends_with_slash_dot("./."));
|
||||
/// assert!(ends_with_slash_dot("a/."));
|
||||
///
|
||||
/// assert!(!ends_with_slash_dot("."));
|
||||
/// assert!(!ends_with_slash_dot("./"));
|
||||
/// assert!(!ends_with_slash_dot("a/.."));
|
||||
/// ```
|
||||
fn ends_with_slash_dot<P>(path: P) -> bool
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
// `path.ends_with(".")` does not seem to work
|
||||
path.as_ref().display().to_string().ends_with("/.")
|
||||
}
|
||||
|
||||
/// Copy a single entry during a directory traversal.
|
||||
fn copy_direntry(
|
||||
progress_bar: &Option<ProgressBar>,
|
||||
|
@ -196,7 +217,10 @@ fn copy_direntry(
|
|||
|
||||
// If the source is a directory and the destination does not
|
||||
// exist, ...
|
||||
if source_absolute.is_dir() && !local_to_target.exists() {
|
||||
if source_absolute.is_dir()
|
||||
&& !ends_with_slash_dot(&source_absolute)
|
||||
&& !local_to_target.exists()
|
||||
{
|
||||
if target_is_file {
|
||||
return Err("cannot overwrite non-directory with directory".into());
|
||||
} else {
|
||||
|
@ -205,7 +229,10 @@ fn copy_direntry(
|
|||
// `create_dir_all()` will have any benefit over
|
||||
// `create_dir()`, since all the ancestor directories
|
||||
// should have already been created.
|
||||
fs::create_dir_all(local_to_target)?;
|
||||
fs::create_dir_all(&local_to_target)?;
|
||||
if options.verbose {
|
||||
println!("{}", context_for(&source_relative, &local_to_target));
|
||||
}
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
|
@ -324,6 +351,19 @@ pub(crate) fn copy_directory(
|
|||
if let Some(parent) = root.parent() {
|
||||
let new_target = target.join(parent);
|
||||
std::fs::create_dir_all(&new_target)?;
|
||||
|
||||
if options.verbose {
|
||||
// For example, if copying file `a/b/c` and its parents
|
||||
// to directory `d/`, then print
|
||||
//
|
||||
// a -> d/a
|
||||
// a/b -> d/a/b
|
||||
//
|
||||
for (x, y) in aligned_ancestors(root, &target.join(root)) {
|
||||
println!("{} -> {}", x.display(), y.display());
|
||||
}
|
||||
}
|
||||
|
||||
new_target
|
||||
} else {
|
||||
target.to_path_buf()
|
||||
|
@ -393,3 +433,25 @@ pub fn path_has_prefix(p1: &Path, p2: &Path) -> io::Result<bool> {
|
|||
|
||||
Ok(pathbuf1.starts_with(pathbuf2))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::ends_with_slash_dot;
|
||||
|
||||
#[test]
|
||||
fn test_ends_with_slash_dot() {
|
||||
assert!(ends_with_slash_dot("/."));
|
||||
assert!(ends_with_slash_dot("./."));
|
||||
assert!(ends_with_slash_dot("../."));
|
||||
assert!(ends_with_slash_dot("a/."));
|
||||
assert!(ends_with_slash_dot("/a/."));
|
||||
|
||||
assert!(!ends_with_slash_dot(""));
|
||||
assert!(!ends_with_slash_dot("."));
|
||||
assert!(!ends_with_slash_dot("./"));
|
||||
assert!(!ends_with_slash_dot(".."));
|
||||
assert!(!ends_with_slash_dot("/.."));
|
||||
assert!(!ends_with_slash_dot("a/.."));
|
||||
assert!(!ends_with_slash_dot("/a/.."));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2087,6 +2087,36 @@ fn test_cp_parents_2_link() {
|
|||
assert!(at.file_exists("d/a/link/c"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_cp_parents_2_dir() {
|
||||
let (at, mut ucmd) = at_and_ucmd!();
|
||||
at.mkdir_all("a/b/c");
|
||||
at.mkdir("d");
|
||||
#[cfg(not(windows))]
|
||||
let expected_stdout = "a -> d/a\na/b -> d/a/b\n'a/b/c' -> 'd/a/b/c'\n";
|
||||
#[cfg(windows)]
|
||||
let expected_stdout = "a -> d\\a\na/b -> d\\a/b\n'a/b/c' -> 'd\\a/b\\c'\n";
|
||||
ucmd.args(&["--verbose", "-r", "--parents", "a/b/c", "d"])
|
||||
.succeeds()
|
||||
.stdout_only(expected_stdout);
|
||||
assert!(at.dir_exists("d/a/b/c"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_cp_parents_2_deep_dir() {
|
||||
let (at, mut ucmd) = at_and_ucmd!();
|
||||
at.mkdir_all("a/b/c");
|
||||
at.mkdir_all("d/e");
|
||||
#[cfg(not(windows))]
|
||||
let expected_stdout = "a -> d/e/a\na/b -> d/e/a/b\n'a/b/c' -> 'd/e/a/b/c'\n";
|
||||
#[cfg(windows)]
|
||||
let expected_stdout = "a -> d/e\\a\na/b -> d/e\\a/b\n'a/b/c' -> 'd/e\\a/b\\c'\n";
|
||||
ucmd.args(&["--verbose", "-r", "--parents", "a/b/c", "d/e"])
|
||||
.succeeds()
|
||||
.stdout_only(expected_stdout);
|
||||
assert!(at.dir_exists("d/e/a/b/c"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_cp_copy_symlink_contents_recursive() {
|
||||
let (at, mut ucmd) = at_and_ucmd!();
|
||||
|
@ -2409,3 +2439,18 @@ fn test_symbolic_link_file() {
|
|||
Path::new("src")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_src_base_dot() {
|
||||
let ts = TestScenario::new(util_name!());
|
||||
let at = ts.fixtures.clone();
|
||||
at.mkdir("x");
|
||||
at.mkdir("y");
|
||||
let mut ucmd = UCommand::new(ts.bin_path, &Some(ts.util_name), at.plus("y"), true);
|
||||
|
||||
ucmd.args(&["--verbose", "-r", "../x/.", "."])
|
||||
.succeeds()
|
||||
.no_stderr()
|
||||
.no_stdout();
|
||||
assert!(!at.dir_exists("y/x"));
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue