1
Fork 0
mirror of https://github.com/RGBCube/uutils-coreutils synced 2025-07-31 13:07:46 +00:00

Merge pull request #2457 from Funky185540/install-implement-backup

install: implement --backup, -b and -S
This commit is contained in:
Sylvestre Ledru 2021-07-04 09:57:45 +02:00 committed by GitHub
commit d3652cc3c0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 441 additions and 20 deletions

View file

@ -15,6 +15,7 @@ extern crate uucore;
use clap::{crate_version, App, Arg, ArgMatches}; use clap::{crate_version, App, Arg, ArgMatches};
use file_diff::diff; use file_diff::diff;
use filetime::{set_file_times, FileTime}; use filetime::{set_file_times, FileTime};
use uucore::backup_control::{self, BackupMode};
use uucore::entries::{grp2gid, usr2uid}; use uucore::entries::{grp2gid, usr2uid};
use uucore::perms::{wrap_chgrp, wrap_chown, Verbosity}; use uucore::perms::{wrap_chgrp, wrap_chown, Verbosity};
@ -33,6 +34,7 @@ const DEFAULT_STRIP_PROGRAM: &str = "strip";
pub struct Behavior { pub struct Behavior {
main_function: MainFunction, main_function: MainFunction,
specified_mode: Option<u32>, specified_mode: Option<u32>,
backup_mode: BackupMode,
suffix: String, suffix: String,
owner: String, owner: String,
group: String, group: String,
@ -68,7 +70,7 @@ static ABOUT: &str = "Copy SOURCE to DEST or multiple SOURCE(s) to the existing
static OPT_COMPARE: &str = "compare"; static OPT_COMPARE: &str = "compare";
static OPT_BACKUP: &str = "backup"; static OPT_BACKUP: &str = "backup";
static OPT_BACKUP_2: &str = "backup2"; static OPT_BACKUP_NO_ARG: &str = "backup2";
static OPT_DIRECTORY: &str = "directory"; static OPT_DIRECTORY: &str = "directory";
static OPT_IGNORED: &str = "ignored"; static OPT_IGNORED: &str = "ignored";
static OPT_CREATE_LEADING: &str = "create-leading"; static OPT_CREATE_LEADING: &str = "create-leading";
@ -130,14 +132,17 @@ pub fn uu_app() -> App<'static, 'static> {
.arg( .arg(
Arg::with_name(OPT_BACKUP) Arg::with_name(OPT_BACKUP)
.long(OPT_BACKUP) .long(OPT_BACKUP)
.help("(unimplemented) make a backup of each existing destination file") .help("make a backup of each existing destination file")
.takes_value(true)
.require_equals(true)
.min_values(0)
.value_name("CONTROL") .value_name("CONTROL")
) )
.arg( .arg(
// TODO implement flag // TODO implement flag
Arg::with_name(OPT_BACKUP_2) Arg::with_name(OPT_BACKUP_NO_ARG)
.short("b") .short("b")
.help("(unimplemented) like --backup but does not accept an argument") .help("like --backup but does not accept an argument")
) )
.arg( .arg(
Arg::with_name(OPT_IGNORED) Arg::with_name(OPT_IGNORED)
@ -210,7 +215,7 @@ pub fn uu_app() -> App<'static, 'static> {
Arg::with_name(OPT_SUFFIX) Arg::with_name(OPT_SUFFIX)
.short("S") .short("S")
.long(OPT_SUFFIX) .long(OPT_SUFFIX)
.help("(unimplemented) override the usual backup suffix") .help("override the usual backup suffix")
.value_name("SUFFIX") .value_name("SUFFIX")
.takes_value(true) .takes_value(true)
.min_values(1) .min_values(1)
@ -265,13 +270,7 @@ pub fn uu_app() -> App<'static, 'static> {
/// ///
/// ///
fn check_unimplemented<'a>(matches: &ArgMatches) -> Result<(), &'a str> { fn check_unimplemented<'a>(matches: &ArgMatches) -> Result<(), &'a str> {
if matches.is_present(OPT_BACKUP) { if matches.is_present(OPT_NO_TARGET_DIRECTORY) {
Err("--backup")
} else if matches.is_present(OPT_BACKUP_2) {
Err("-b")
} else if matches.is_present(OPT_SUFFIX) {
Err("--suffix, -S")
} else if matches.is_present(OPT_NO_TARGET_DIRECTORY) {
Err("--no-target-directory, -T") Err("--no-target-directory, -T")
} else if matches.is_present(OPT_PRESERVE_CONTEXT) { } else if matches.is_present(OPT_PRESERVE_CONTEXT) {
Err("--preserve-context, -P") Err("--preserve-context, -P")
@ -309,18 +308,16 @@ fn behavior(matches: &ArgMatches) -> Result<Behavior, i32> {
None None
}; };
let backup_suffix = if matches.is_present(OPT_SUFFIX) {
matches.value_of(OPT_SUFFIX).ok_or(1)?
} else {
"~"
};
let target_dir = matches.value_of(OPT_TARGET_DIRECTORY).map(|d| d.to_owned()); let target_dir = matches.value_of(OPT_TARGET_DIRECTORY).map(|d| d.to_owned());
Ok(Behavior { Ok(Behavior {
main_function, main_function,
specified_mode, specified_mode,
suffix: backup_suffix.to_string(), backup_mode: backup_control::determine_backup_mode(
matches.is_present(OPT_BACKUP_NO_ARG) || matches.is_present(OPT_BACKUP),
matches.value_of(OPT_BACKUP),
),
suffix: backup_control::determine_backup_suffix(matches.value_of(OPT_SUFFIX)),
owner: matches.value_of(OPT_OWNER).unwrap_or("").to_string(), owner: matches.value_of(OPT_OWNER).unwrap_or("").to_string(),
group: matches.value_of(OPT_GROUP).unwrap_or("").to_string(), group: matches.value_of(OPT_GROUP).unwrap_or("").to_string(),
verbose: matches.is_present(OPT_VERBOSE), verbose: matches.is_present(OPT_VERBOSE),
@ -517,6 +514,28 @@ fn copy(from: &Path, to: &Path, b: &Behavior) -> Result<(), ()> {
if b.compare && !need_copy(from, to, b) { if b.compare && !need_copy(from, to, b) {
return Ok(()); return Ok(());
} }
// Declare the path here as we may need it for the verbose output below.
let mut backup_path = None;
// Perform backup, if any, before overwriting 'to'
//
// The codes actually making use of the backup process don't seem to agree
// on how best to approach the issue. (mv and ln, for example)
if to.exists() {
backup_path = backup_control::get_backup_path(b.backup_mode, to, &b.suffix);
if let Some(ref backup_path) = backup_path {
// TODO!!
if let Err(err) = fs::rename(to, backup_path) {
show_error!(
"install: cannot backup file '{}' to '{}': {}",
to.display(),
backup_path.display(),
err
);
return Err(());
}
}
}
if from.to_string_lossy() == "/dev/null" { if from.to_string_lossy() == "/dev/null" {
/* workaround a limitation of fs::copy /* workaround a limitation of fs::copy
@ -624,7 +643,11 @@ fn copy(from: &Path, to: &Path, b: &Behavior) -> Result<(), ()> {
} }
if b.verbose { if b.verbose {
show_error!("'{}' -> '{}'", from.display(), to.display()); print!("'{}' -> '{}'", from.display(), to.display());
match backup_path {
Some(path) => println!(" (backup: '{}')", path.display()),
None => println!(),
}
} }
Ok(()) Ok(())

View file

@ -37,6 +37,19 @@ pub fn determine_backup_suffix(supplied_suffix: Option<&str>) -> String {
} }
} }
/// # TODO
///
/// This function currently deviates slightly from how the [manual][1] describes
/// that it should work. In particular, the current implementation:
///
/// 1. Doesn't strictly respect the order in which to determine the backup type,
/// which is (in order of precedence)
/// 1. Take a valid value to the '--backup' option
/// 2. Take the value of the `VERSION_CONTROL` env var
/// 3. default to 'existing'
/// 2. Doesn't accept abbreviations to the 'backup_option' parameter
///
/// [1]: https://www.gnu.org/software/coreutils/manual/html_node/Backup-options.html
pub fn determine_backup_mode(backup_opt_exists: bool, backup_opt: Option<&str>) -> BackupMode { pub fn determine_backup_mode(backup_opt_exists: bool, backup_opt: Option<&str>) -> BackupMode {
if backup_opt_exists { if backup_opt_exists {
match backup_opt.map(String::from) { match backup_opt.map(String::from) {

View file

@ -696,3 +696,388 @@ fn test_install_dir() {
assert!(at.file_exists(&format!("{}/{}", dir, file1))); assert!(at.file_exists(&format!("{}/{}", dir, file1)));
assert!(at.file_exists(&format!("{}/{}", dir, file2))); assert!(at.file_exists(&format!("{}/{}", dir, file2)));
} }
//
// test backup functionality
#[test]
fn test_install_backup_short_no_args_files() {
let scene = TestScenario::new(util_name!());
let at = &scene.fixtures;
let file_a = "test_install_simple_backup_file_a";
let file_b = "test_install_simple_backup_file_b";
at.touch(file_a);
at.touch(file_b);
scene
.ucmd()
.arg("-b")
.arg(file_a)
.arg(file_b)
.succeeds()
.no_stderr();
assert!(at.file_exists(file_a));
assert!(at.file_exists(file_b));
assert!(at.file_exists(&format!("{}~", file_b)));
}
#[test]
fn test_install_backup_short_no_args_file_to_dir() {
let scene = TestScenario::new(util_name!());
let at = &scene.fixtures;
let file = "test_install_simple_backup_file_a";
let dest_dir = "test_install_dest/";
let expect = format!("{}{}", dest_dir, file);
at.touch(file);
at.mkdir(dest_dir);
at.touch(&expect);
scene
.ucmd()
.arg("-b")
.arg(file)
.arg(dest_dir)
.succeeds()
.no_stderr();
assert!(at.file_exists(file));
assert!(at.file_exists(&expect));
assert!(at.file_exists(&format!("{}~", expect)));
}
// Long --backup option is tested separately as it requires a slightly different
// handling than '-b' does.
#[test]
fn test_install_backup_long_no_args_files() {
let scene = TestScenario::new(util_name!());
let at = &scene.fixtures;
let file_a = "test_install_simple_backup_file_a";
let file_b = "test_install_simple_backup_file_b";
at.touch(file_a);
at.touch(file_b);
scene
.ucmd()
.arg("--backup")
.arg(file_a)
.arg(file_b)
.succeeds()
.no_stderr();
assert!(at.file_exists(file_a));
assert!(at.file_exists(file_b));
assert!(at.file_exists(&format!("{}~", file_b)));
}
#[test]
fn test_install_backup_long_no_args_file_to_dir() {
let scene = TestScenario::new(util_name!());
let at = &scene.fixtures;
let file = "test_install_simple_backup_file_a";
let dest_dir = "test_install_dest/";
let expect = format!("{}{}", dest_dir, file);
at.touch(file);
at.mkdir(dest_dir);
at.touch(&expect);
scene
.ucmd()
.arg("--backup")
.arg(file)
.arg(dest_dir)
.succeeds()
.no_stderr();
assert!(at.file_exists(file));
assert!(at.file_exists(&expect));
assert!(at.file_exists(&format!("{}~", expect)));
}
#[test]
fn test_install_backup_short_custom_suffix() {
let scene = TestScenario::new(util_name!());
let at = &scene.fixtures;
let file_a = "test_install_backup_custom_suffix_file_a";
let file_b = "test_install_backup_custom_suffix_file_b";
let suffix = "super-suffix-of-the-century";
at.touch(file_a);
at.touch(file_b);
scene
.ucmd()
.arg("-b")
.arg(format!("--suffix={}", suffix))
.arg(file_a)
.arg(file_b)
.succeeds()
.no_stderr();
assert!(at.file_exists(file_a));
assert!(at.file_exists(file_b));
assert!(at.file_exists(&format!("{}{}", file_b, suffix)));
}
#[test]
fn test_install_backup_custom_suffix_via_env() {
let scene = TestScenario::new(util_name!());
let at = &scene.fixtures;
let file_a = "test_install_backup_custom_suffix_file_a";
let file_b = "test_install_backup_custom_suffix_file_b";
let suffix = "super-suffix-of-the-century";
at.touch(file_a);
at.touch(file_b);
scene
.ucmd()
.arg("-b")
.env("SIMPLE_BACKUP_SUFFIX", suffix)
.arg(file_a)
.arg(file_b)
.succeeds()
.no_stderr();
assert!(at.file_exists(file_a));
assert!(at.file_exists(file_b));
assert!(at.file_exists(&format!("{}{}", file_b, suffix)));
}
#[test]
fn test_install_backup_numbered_with_t() {
let scene = TestScenario::new(util_name!());
let at = &scene.fixtures;
let file_a = "test_install_backup_numbering_file_a";
let file_b = "test_install_backup_numbering_file_b";
at.touch(file_a);
at.touch(file_b);
scene
.ucmd()
.arg("--backup=t")
.arg(file_a)
.arg(file_b)
.succeeds()
.no_stderr();
assert!(at.file_exists(file_a));
assert!(at.file_exists(file_b));
assert!(at.file_exists(&format!("{}.~1~", file_b)));
}
#[test]
fn test_install_backup_numbered_with_numbered() {
let scene = TestScenario::new(util_name!());
let at = &scene.fixtures;
let file_a = "test_install_backup_numbering_file_a";
let file_b = "test_install_backup_numbering_file_b";
at.touch(file_a);
at.touch(file_b);
scene
.ucmd()
.arg("--backup=numbered")
.arg(file_a)
.arg(file_b)
.succeeds()
.no_stderr();
assert!(at.file_exists(file_a));
assert!(at.file_exists(file_b));
assert!(at.file_exists(&format!("{}.~1~", file_b)));
}
#[test]
fn test_install_backup_existing() {
let scene = TestScenario::new(util_name!());
let at = &scene.fixtures;
let file_a = "test_install_backup_numbering_file_a";
let file_b = "test_install_backup_numbering_file_b";
at.touch(file_a);
at.touch(file_b);
scene
.ucmd()
.arg("--backup=existing")
.arg(file_a)
.arg(file_b)
.succeeds()
.no_stderr();
assert!(at.file_exists(file_a));
assert!(at.file_exists(file_b));
assert!(at.file_exists(&format!("{}~", file_b)));
}
#[test]
fn test_install_backup_nil() {
let scene = TestScenario::new(util_name!());
let at = &scene.fixtures;
let file_a = "test_install_backup_numbering_file_a";
let file_b = "test_install_backup_numbering_file_b";
at.touch(file_a);
at.touch(file_b);
scene
.ucmd()
.arg("--backup=nil")
.arg(file_a)
.arg(file_b)
.succeeds()
.no_stderr();
assert!(at.file_exists(file_a));
assert!(at.file_exists(file_b));
assert!(at.file_exists(&format!("{}~", file_b)));
}
#[test]
fn test_install_backup_numbered_if_existing_backup_existing() {
let scene = TestScenario::new(util_name!());
let at = &scene.fixtures;
let file_a = "test_install_backup_numbering_file_a";
let file_b = "test_install_backup_numbering_file_b";
let file_b_backup = "test_install_backup_numbering_file_b.~1~";
at.touch(file_a);
at.touch(file_b);
at.touch(file_b_backup);
scene
.ucmd()
.arg("--backup=existing")
.arg(file_a)
.arg(file_b)
.succeeds()
.no_stderr();
assert!(at.file_exists(file_a));
assert!(at.file_exists(file_b));
assert!(at.file_exists(file_b_backup));
assert!(at.file_exists(&*format!("{}.~2~", file_b)));
}
#[test]
fn test_install_backup_numbered_if_existing_backup_nil() {
let scene = TestScenario::new(util_name!());
let at = &scene.fixtures;
let file_a = "test_install_backup_numbering_file_a";
let file_b = "test_install_backup_numbering_file_b";
let file_b_backup = "test_install_backup_numbering_file_b.~1~";
at.touch(file_a);
at.touch(file_b);
at.touch(file_b_backup);
scene
.ucmd()
.arg("--backup=nil")
.arg(file_a)
.arg(file_b)
.succeeds()
.no_stderr();
assert!(at.file_exists(file_a));
assert!(at.file_exists(file_b));
assert!(at.file_exists(file_b_backup));
assert!(at.file_exists(&*format!("{}.~2~", file_b)));
}
#[test]
fn test_install_backup_simple() {
let scene = TestScenario::new(util_name!());
let at = &scene.fixtures;
let file_a = "test_install_backup_numbering_file_a";
let file_b = "test_install_backup_numbering_file_b";
at.touch(file_a);
at.touch(file_b);
scene
.ucmd()
.arg("--backup=simple")
.arg(file_a)
.arg(file_b)
.succeeds()
.no_stderr();
assert!(at.file_exists(file_a));
assert!(at.file_exists(file_b));
assert!(at.file_exists(&format!("{}~", file_b)));
}
#[test]
fn test_install_backup_never() {
let scene = TestScenario::new(util_name!());
let at = &scene.fixtures;
let file_a = "test_install_backup_numbering_file_a";
let file_b = "test_install_backup_numbering_file_b";
at.touch(file_a);
at.touch(file_b);
scene
.ucmd()
.arg("--backup=never")
.arg(file_a)
.arg(file_b)
.succeeds()
.no_stderr();
assert!(at.file_exists(file_a));
assert!(at.file_exists(file_b));
assert!(at.file_exists(&format!("{}~", file_b)));
}
#[test]
fn test_install_backup_none() {
let scene = TestScenario::new(util_name!());
let at = &scene.fixtures;
let file_a = "test_install_backup_numbering_file_a";
let file_b = "test_install_backup_numbering_file_b";
at.touch(file_a);
at.touch(file_b);
scene
.ucmd()
.arg("--backup=none")
.arg(file_a)
.arg(file_b)
.succeeds()
.no_stderr();
assert!(at.file_exists(file_a));
assert!(at.file_exists(file_b));
assert!(!at.file_exists(&format!("{}~", file_b)));
}
#[test]
fn test_install_backup_off() {
let scene = TestScenario::new(util_name!());
let at = &scene.fixtures;
let file_a = "test_install_backup_numbering_file_a";
let file_b = "test_install_backup_numbering_file_b";
at.touch(file_a);
at.touch(file_b);
scene
.ucmd()
.arg("--backup=off")
.arg(file_a)
.arg(file_b)
.succeeds()
.no_stderr();
assert!(at.file_exists(file_a));
assert!(at.file_exists(file_b));
assert!(!at.file_exists(&format!("{}~", file_b)));
}