mirror of
https://github.com/uutils/coreutils
synced 2024-11-17 02:08:09 +00:00
Merge pull request #1051 from millerjs/millerjs-implement-copy
implement many copy flags
This commit is contained in:
commit
c5fe9d57c2
7 changed files with 1103 additions and 208 deletions
2
Cargo.lock
generated
2
Cargo.lock
generated
|
@ -305,8 +305,10 @@ dependencies = [
|
||||||
name = "cp"
|
name = "cp"
|
||||||
version = "0.0.1"
|
version = "0.0.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"clap 2.25.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"getopts 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)",
|
"getopts 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"libc 0.2.26 (registry+https://github.com/rust-lang/crates.io-index)",
|
"libc 0.2.26 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"quick-error 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"uucore 0.0.1",
|
"uucore 0.0.1",
|
||||||
"walkdir 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
"walkdir 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
|
@ -1,7 +1,10 @@
|
||||||
[package]
|
[package]
|
||||||
name = "cp"
|
name = "cp"
|
||||||
version = "0.0.1"
|
version = "0.0.1"
|
||||||
authors = []
|
authors = [
|
||||||
|
"Jordy Dickinson <jordy.dickinson@gmail.com>",
|
||||||
|
"Joshua S. Miller <jsmiller@uchicago.edu>",
|
||||||
|
]
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
name = "uu_cp"
|
name = "uu_cp"
|
||||||
|
@ -10,8 +13,10 @@ path = "cp.rs"
|
||||||
[dependencies]
|
[dependencies]
|
||||||
getopts = "0.2.14"
|
getopts = "0.2.14"
|
||||||
libc = "0.2.26"
|
libc = "0.2.26"
|
||||||
uucore = { path="../uucore" }
|
|
||||||
walkdir = "1.0.7"
|
walkdir = "1.0.7"
|
||||||
|
clap = "2.20.0"
|
||||||
|
quick-error = "1.1.0"
|
||||||
|
uucore = { path="../uucore" }
|
||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
name = "cp"
|
name = "cp"
|
||||||
|
|
38
src/cp/README.md
Normal file
38
src/cp/README.md
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
## Feature list
|
||||||
|
|
||||||
|
### To Do
|
||||||
|
|
||||||
|
- [ ] archive
|
||||||
|
- [ ] attributes-only
|
||||||
|
- [ ] copy-contents
|
||||||
|
- [ ] no-dereference-preserve-linkgs
|
||||||
|
- [ ] dereference
|
||||||
|
- [ ] no-dereference
|
||||||
|
- [ ] preserve-default-attributes
|
||||||
|
- [ ] preserve
|
||||||
|
- [ ] no-preserve
|
||||||
|
- [ ] parents
|
||||||
|
- [ ] reflink
|
||||||
|
- [ ] sparse
|
||||||
|
- [ ] strip-trailing-slashes
|
||||||
|
- [ ] update
|
||||||
|
- [ ] one-file-system
|
||||||
|
- [ ] context
|
||||||
|
- [ ] cli-symbolic-links
|
||||||
|
|
||||||
|
### Completed
|
||||||
|
|
||||||
|
- [x] backup
|
||||||
|
- [x] force (Not implemented on Windows)
|
||||||
|
- [x] interactive
|
||||||
|
- [x] link
|
||||||
|
- [x] no-clobber
|
||||||
|
- [x] no-target-directory
|
||||||
|
- [x] paths
|
||||||
|
- [x] recursive
|
||||||
|
- [x] remove-destination (On Windows, current only works for writeable files)
|
||||||
|
- [x] suffix
|
||||||
|
- [x] symbolic-link
|
||||||
|
- [x] target-directory
|
||||||
|
- [x] verbose
|
||||||
|
- [x] version
|
1039
src/cp/cp.rs
1039
src/cp/cp.rs
File diff suppressed because it is too large
Load diff
1
tests/fixtures/cp/existing_file.txt
vendored
Normal file
1
tests/fixtures/cp/existing_file.txt
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
Cogito ergo sum.
|
1
tests/fixtures/cp/how_are_you.txt
vendored
Normal file
1
tests/fixtures/cp/how_are_you.txt
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
How are you?
|
221
tests/test_cp.rs
221
tests/test_cp.rs
|
@ -1,11 +1,15 @@
|
||||||
use common::util::*;
|
use common::util::*;
|
||||||
|
use std::fs::set_permissions;
|
||||||
|
|
||||||
static TEST_HELLO_WORLD_SOURCE: &'static str = "hello_world.txt";
|
static TEST_EXISTING_FILE: &str = "existing_file.txt";
|
||||||
static TEST_HELLO_WORLD_DEST: &'static str = "copy_of_hello_world.txt";
|
static TEST_HELLO_WORLD_SOURCE: &str = "hello_world.txt";
|
||||||
static TEST_COPY_TO_FOLDER: &'static str = "hello_dir/";
|
static TEST_HELLO_WORLD_DEST: &str = "copy_of_hello_world.txt";
|
||||||
static TEST_COPY_TO_FOLDER_FILE: &'static str = "hello_dir/hello_world.txt";
|
static TEST_HOW_ARE_YOU_SOURCE: &str = "how_are_you.txt";
|
||||||
static TEST_COPY_FROM_FOLDER: &'static str = "hello_dir_with_file/";
|
static TEST_HOW_ARE_YOU_DEST: &str = "hello_dir/how_are_you.txt";
|
||||||
static TEST_COPY_FROM_FOLDER_FILE: &'static str = "hello_dir_with_file/hello_world.txt";
|
static TEST_COPY_TO_FOLDER: &str = "hello_dir/";
|
||||||
|
static TEST_COPY_TO_FOLDER_FILE: &str = "hello_dir/hello_world.txt";
|
||||||
|
static TEST_COPY_FROM_FOLDER: &str = "hello_dir_with_file/";
|
||||||
|
static TEST_COPY_FROM_FOLDER_FILE: &str = "hello_dir_with_file/hello_world.txt";
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_cp_cp() {
|
fn test_cp_cp() {
|
||||||
|
@ -23,6 +27,58 @@ fn test_cp_cp() {
|
||||||
assert_eq!(at.read(TEST_HELLO_WORLD_DEST), "Hello, World!\n");
|
assert_eq!(at.read(TEST_HELLO_WORLD_DEST), "Hello, World!\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_cp_duplicate_files() {
|
||||||
|
let (at, mut ucmd) = at_and_ucmd!();
|
||||||
|
let result = ucmd.arg(TEST_HELLO_WORLD_SOURCE)
|
||||||
|
.arg(TEST_HELLO_WORLD_SOURCE)
|
||||||
|
.arg(TEST_COPY_TO_FOLDER)
|
||||||
|
.run();
|
||||||
|
|
||||||
|
assert!(result.success);
|
||||||
|
assert!(result.stderr.contains("specified more than once"));
|
||||||
|
assert_eq!(at.read(TEST_COPY_TO_FOLDER_FILE), "Hello, World!\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_cp_multiple_files_target_is_file() {
|
||||||
|
let (_, mut ucmd) = at_and_ucmd!();
|
||||||
|
let result = ucmd.arg(TEST_HELLO_WORLD_SOURCE)
|
||||||
|
.arg(TEST_HELLO_WORLD_SOURCE)
|
||||||
|
.arg(TEST_EXISTING_FILE)
|
||||||
|
.run();
|
||||||
|
|
||||||
|
assert!(!result.success);
|
||||||
|
assert!(result.stderr.contains("not a directory"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_cp_directory_not_recursive() {
|
||||||
|
let (_, mut ucmd) = at_and_ucmd!();
|
||||||
|
let result = ucmd.arg(TEST_COPY_TO_FOLDER)
|
||||||
|
.arg(TEST_HELLO_WORLD_DEST)
|
||||||
|
.run();
|
||||||
|
|
||||||
|
assert!(!result.success);
|
||||||
|
assert!(result.stderr.contains("omitting directory"));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_cp_multiple_files() {
|
||||||
|
let (at, mut ucmd) = at_and_ucmd!();
|
||||||
|
let result = ucmd.arg(TEST_HELLO_WORLD_SOURCE)
|
||||||
|
.arg(TEST_HOW_ARE_YOU_SOURCE)
|
||||||
|
.arg(TEST_COPY_TO_FOLDER)
|
||||||
|
.run();
|
||||||
|
|
||||||
|
assert!(result.success);
|
||||||
|
assert_eq!(at.read(TEST_COPY_TO_FOLDER_FILE), "Hello, World!\n");
|
||||||
|
assert_eq!(at.read(TEST_HOW_ARE_YOU_DEST), "How are you?\n");
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_cp_recurse() {
|
fn test_cp_recurse() {
|
||||||
//let (at, mut ucmd) = at_and_ucmd!();
|
//let (at, mut ucmd) = at_and_ucmd!();
|
||||||
|
@ -82,3 +138,156 @@ fn test_cp_with_dirs() {
|
||||||
assert!(result_from_dir.success);
|
assert!(result_from_dir.success);
|
||||||
assert_eq!(at.read(TEST_HELLO_WORLD_DEST), "Hello, World!\n");
|
assert_eq!(at.read(TEST_HELLO_WORLD_DEST), "Hello, World!\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_cp_arg_target_directory() {
|
||||||
|
let (at, mut ucmd) = at_and_ucmd!();
|
||||||
|
let result = ucmd.arg(TEST_HELLO_WORLD_SOURCE)
|
||||||
|
.arg("-t")
|
||||||
|
.arg(TEST_COPY_TO_FOLDER)
|
||||||
|
.run();
|
||||||
|
|
||||||
|
assert!(result.success);
|
||||||
|
assert_eq!(at.read(TEST_COPY_TO_FOLDER_FILE), "Hello, World!\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_cp_arg_no_target_directory() {
|
||||||
|
let (_, mut ucmd) = at_and_ucmd!();
|
||||||
|
let result = ucmd.arg(TEST_HELLO_WORLD_SOURCE)
|
||||||
|
.arg("-v")
|
||||||
|
.arg("-T")
|
||||||
|
.arg(TEST_COPY_TO_FOLDER)
|
||||||
|
.run();
|
||||||
|
|
||||||
|
assert!(!result.success);
|
||||||
|
assert!(result.stderr.contains("cannot overwrite directory"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_cp_arg_interactive() {
|
||||||
|
let (_, mut ucmd) = at_and_ucmd!();
|
||||||
|
let result = ucmd.arg(TEST_HELLO_WORLD_SOURCE)
|
||||||
|
.arg(TEST_HOW_ARE_YOU_SOURCE)
|
||||||
|
.arg("-i")
|
||||||
|
.pipe_in("N\n")
|
||||||
|
.run();
|
||||||
|
|
||||||
|
assert!(result.success);
|
||||||
|
assert!(result.stderr.contains("Not overwriting"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[cfg(target_os="unix")]
|
||||||
|
fn test_cp_arg_link() {
|
||||||
|
use std::os::linux::fs::MetadataExt;
|
||||||
|
|
||||||
|
let (at, mut ucmd) = at_and_ucmd!();
|
||||||
|
let result = ucmd.arg(TEST_HELLO_WORLD_SOURCE)
|
||||||
|
.arg("--link")
|
||||||
|
.arg(TEST_HELLO_WORLD_DEST)
|
||||||
|
.run();
|
||||||
|
|
||||||
|
assert!(result.success);
|
||||||
|
assert_eq!(at.metadata(TEST_HELLO_WORLD_SOURCE).st_nlink(), 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_cp_arg_symlink() {
|
||||||
|
let (at, mut ucmd) = at_and_ucmd!();
|
||||||
|
let result = ucmd.arg(TEST_HELLO_WORLD_SOURCE)
|
||||||
|
.arg("--symbolic-link")
|
||||||
|
.arg(TEST_HELLO_WORLD_DEST)
|
||||||
|
.run();
|
||||||
|
|
||||||
|
assert!(result.success);
|
||||||
|
assert!(at.is_symlink(TEST_HELLO_WORLD_DEST));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_cp_arg_no_clobber() {
|
||||||
|
let (at, mut ucmd) = at_and_ucmd!();
|
||||||
|
let result = ucmd.arg(TEST_HELLO_WORLD_SOURCE)
|
||||||
|
.arg("--no-clobber")
|
||||||
|
.arg(TEST_HOW_ARE_YOU_SOURCE)
|
||||||
|
.run();
|
||||||
|
|
||||||
|
assert!(result.success);
|
||||||
|
assert_eq!(at.read(TEST_HOW_ARE_YOU_SOURCE), "How are you?\n");
|
||||||
|
assert!(result.stderr.contains("Not overwriting"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[cfg(not(windows))]
|
||||||
|
fn test_cp_arg_force() {
|
||||||
|
let (at, mut ucmd) = at_and_ucmd!();
|
||||||
|
|
||||||
|
// create dest without write permissions
|
||||||
|
let mut permissions = at.make_file(TEST_HELLO_WORLD_DEST).metadata().unwrap().permissions();
|
||||||
|
permissions.set_readonly(true);
|
||||||
|
set_permissions(at.plus(TEST_HELLO_WORLD_DEST), permissions).unwrap();
|
||||||
|
|
||||||
|
let result = ucmd.arg(TEST_HELLO_WORLD_SOURCE)
|
||||||
|
.arg("--force")
|
||||||
|
.arg(TEST_HELLO_WORLD_DEST)
|
||||||
|
.run();
|
||||||
|
|
||||||
|
println!("{:?}", result.stderr);
|
||||||
|
println!("{:?}", result.stdout);
|
||||||
|
|
||||||
|
assert!(result.success);
|
||||||
|
assert_eq!(at.read(TEST_HELLO_WORLD_DEST), "Hello, World!\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// TODO: write a better test that differentiates --remove-destination
|
||||||
|
/// from --force. Also this test currently doesn't work on
|
||||||
|
/// Windows. This test originally checked file timestamps, which
|
||||||
|
/// proved to be unreliable per target / CI platform
|
||||||
|
#[test]
|
||||||
|
#[cfg(not(windows))]
|
||||||
|
fn test_cp_arg_remove_destination() {
|
||||||
|
let (at, mut ucmd) = at_and_ucmd!();
|
||||||
|
|
||||||
|
// create dest without write permissions
|
||||||
|
let mut permissions = at.make_file(TEST_HELLO_WORLD_DEST).metadata().unwrap().permissions();
|
||||||
|
permissions.set_readonly(true);
|
||||||
|
set_permissions(at.plus(TEST_HELLO_WORLD_DEST), permissions).unwrap();
|
||||||
|
|
||||||
|
let result = ucmd.arg(TEST_HELLO_WORLD_SOURCE)
|
||||||
|
.arg("--remove-destination")
|
||||||
|
.arg(TEST_HELLO_WORLD_DEST)
|
||||||
|
.run();
|
||||||
|
|
||||||
|
assert!(result.success);
|
||||||
|
assert_eq!(at.read(TEST_HELLO_WORLD_DEST), "Hello, World!\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_cp_arg_backup() {
|
||||||
|
let (at, mut ucmd) = at_and_ucmd!();
|
||||||
|
|
||||||
|
let result = ucmd.arg(TEST_HELLO_WORLD_SOURCE)
|
||||||
|
.arg("--backup")
|
||||||
|
.arg(TEST_HOW_ARE_YOU_SOURCE)
|
||||||
|
.run();
|
||||||
|
|
||||||
|
assert!(result.success);
|
||||||
|
assert_eq!(at.read(TEST_HOW_ARE_YOU_SOURCE), "Hello, World!\n");
|
||||||
|
assert_eq!(at.read(&*format!("{}~", TEST_HOW_ARE_YOU_SOURCE)), "How are you?\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_cp_arg_suffix() {
|
||||||
|
let (at, mut ucmd) = at_and_ucmd!();
|
||||||
|
|
||||||
|
let result = ucmd.arg(TEST_HELLO_WORLD_SOURCE)
|
||||||
|
.arg("--suffix")
|
||||||
|
.arg(".bak")
|
||||||
|
.arg(TEST_HOW_ARE_YOU_SOURCE)
|
||||||
|
.run();
|
||||||
|
|
||||||
|
assert!(result.success);
|
||||||
|
assert_eq!(at.read(TEST_HOW_ARE_YOU_SOURCE), "Hello, World!\n");
|
||||||
|
assert_eq!(at.read(&*format!("{}.bak", TEST_HOW_ARE_YOU_SOURCE)), "How are you?\n");
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue