Make --dry-run a global option

This commit is contained in:
Alexander Weiss 2023-04-16 09:53:19 +02:00
parent fa3604b98e
commit c4b8ebf15c
10 changed files with 70 additions and 78 deletions

View File

@ -6,4 +6,5 @@ Bugs fixed:
- restore: Warm-up options given by the command line didn't work. This has been fixed.
New features:
- Option --dry-run is now a global option and can also be defined in the config file or via env variable
- Updated to clap v4

View File

@ -10,7 +10,7 @@ use path_dedot::ParseDot;
use serde::Deserialize;
use toml::Value;
use super::{bytes, progress_bytes, progress_counter, RusticConfig};
use super::{bytes, progress_bytes, progress_counter, GlobalOpts, RusticConfig};
use crate::archiver::Archiver;
use crate::backend::{DryRunBackend, LocalSource, LocalSourceOptions, StdinSource};
use crate::index::IndexBackend;
@ -29,11 +29,6 @@ pub(super) struct Opts {
#[serde(skip)]
cli_sources: Vec<String>,
/// Do not upload or write any data, just show what would be done
#[clap(long, short = 'n')]
#[merge(strategy = merge::bool::overwrite_false)]
dry_run: bool,
/// Group snapshots by any combination of host,label,paths,tags to find a suitable parent (default: host,label,paths)
#[clap(
long,
@ -120,6 +115,7 @@ pub(super) struct Opts {
pub(super) fn execute(
repo: OpenRepository,
gopts: GlobalOpts,
opts: Opts,
config_file: RusticConfig,
command: String,
@ -187,7 +183,7 @@ pub(super) fn execute(
// merge "backup" section from config file, if given
config_file.merge_into("backup", &mut opts)?;
let be = DryRunBackend::new(repo.dbe.clone(), opts.dry_run);
let be = DryRunBackend::new(repo.dbe.clone(), gopts.dry_run);
info!("starting to backup {source}...");
let as_path = match opts.as_path {
None => None,

View File

@ -5,7 +5,7 @@ use clap::Parser;
use log::*;
use rayon::prelude::*;
use super::{progress_counter, table_with_titles, RusticConfig};
use super::{progress_counter, table_with_titles, GlobalOpts, RusticConfig};
use crate::backend::DecryptWriteBackend;
use crate::blob::{BlobType, NodeType, Packer, TreeStreamerOnce};
use crate::index::{IndexBackend, IndexedBackend, Indexer, ReadIndex};
@ -18,10 +18,6 @@ pub(super) struct Opts {
#[clap(value_name = "ID")]
ids: Vec<String>,
/// Don't copy any snapshot, only show what would be done
#[clap(long, short = 'n')]
dry_run: bool,
#[clap(
flatten,
next_help_heading = "Snapshot filter options (if no snapshot is given)"
@ -31,6 +27,7 @@ pub(super) struct Opts {
pub(super) fn execute(
repo: OpenRepository,
gopts: GlobalOpts,
mut opts: Opts,
config_file: RusticConfig,
) -> Result<()> {
@ -60,7 +57,7 @@ pub(super) fn execute(
if poly != repo_dest.config.poly()? {
bail!("cannot copy to repository with different chunker parameter (re-chunking not implemented)!");
}
copy(&snapshots, index.clone(), repo_dest, &opts)?;
copy(&snapshots, index.clone(), repo_dest, &gopts, &opts)?;
}
Ok(())
}
@ -69,12 +66,13 @@ fn copy(
snapshots: &[SnapshotFile],
index: impl IndexedBackend,
repo_dest: OpenRepository,
gopts: &GlobalOpts,
opts: &Opts,
) -> Result<()> {
let be_dest = &repo_dest.dbe;
let snapshots = relevant_snapshots(snapshots, &repo_dest, &opts.filter)?;
match (snapshots.len(), opts.dry_run) {
match (snapshots.len(), gopts.dry_run) {
(count, true) => {
info!("would have copied {count} snapshots");
return Ok(());

View File

@ -8,7 +8,7 @@ use merge::Merge;
use serde::Deserialize;
use serde_with::{serde_as, DisplayFromStr};
use super::{progress_counter, prune, table_with_titles, RusticConfig};
use super::{progress_counter, prune, table_with_titles, GlobalOpts, RusticConfig};
use crate::backend::{DecryptWriteBackend, FileType};
use crate::repofile::{
SnapshotFile, SnapshotFilter, SnapshotGroup, SnapshotGroupCriterion, StringList,
@ -29,10 +29,6 @@ pub(super) struct Opts {
next_help_heading = "PRUNE OPTIONS (only when used with --prune)"
)]
prune_opts: prune::Opts,
/// Don't remove anything, only show what would be done
#[clap(skip)]
dry_run: bool,
}
#[serde_as]
@ -60,6 +56,7 @@ struct ConfigOpts {
pub(super) fn execute(
repo: OpenRepository,
gopts: GlobalOpts,
mut opts: Opts,
config_file: RusticConfig,
) -> Result<()> {
@ -69,7 +66,6 @@ pub(super) fn execute(
// merge "snapshot-filter" section from config file, if given
config_file.merge_into("snapshot-filter", &mut opts.config.filter)?;
opts.dry_run = opts.prune_opts.dry_run;
let group_by = opts
.config
.group_by
@ -147,7 +143,7 @@ pub(super) fn execute(
println!();
}
match (forget_snaps.is_empty(), opts.dry_run) {
match (forget_snaps.is_empty(), gopts.dry_run) {
(true, _) => println!("nothing to remove"),
(false, true) => println!("would have removed the following snapshots:\n {forget_snaps:?}"),
(false, false) => {
@ -157,7 +153,7 @@ pub(super) fn execute(
}
if opts.config.prune {
prune::execute(repo, opts.prune_opts, forget_snaps)?;
prune::execute(repo, gopts, opts.prune_opts, forget_snaps)?;
}
Ok(())

View File

@ -67,6 +67,11 @@ struct Opts {
#[derive(Default, Parser, Deserialize, Merge)]
#[serde(default, rename_all = "kebab-case", deny_unknown_fields)]
struct GlobalOpts {
/// Only show what would be done without modifying anything. Does not affect read-only commands
#[clap(long, short = 'n', global = true, env = "RUSTIC_DRY_RUN")]
#[merge(strategy = merge::bool::overwrite_false)]
dry_run: bool,
/// Use this log level [default: info]
#[clap(long, global = true, env = "RUSTIC_LOG_LEVEL")]
#[serde_as(as = "Option<DisplayFromStr>")]
@ -172,7 +177,7 @@ pub fn execute() -> Result<()> {
// start logger
let level_filter = gopts.log_level.unwrap_or(LevelFilter::Info);
match gopts.log_file {
match &gopts.log_file {
None => TermLogger::init(
level_filter,
ConfigBuilder::new()
@ -238,15 +243,15 @@ pub fn execute() -> Result<()> {
#[allow(clippy::match_same_arms)]
match args.command {
Command::Backup(opts) => backup::execute(repo, opts, config_file, command)?,
Command::Backup(opts) => backup::execute(repo, gopts, opts, config_file, command)?,
Command::Config(opts) => config::execute(repo, opts)?,
Command::Cat(opts) => cat::execute(repo, opts, config_file)?,
Command::Check(opts) => check::execute(repo, opts)?,
Command::Completions(_) => {} // already handled above
Command::Copy(opts) => copy::execute(repo, opts, config_file)?,
Command::Copy(opts) => copy::execute(repo, gopts, opts, config_file)?,
Command::Diff(opts) => diff::execute(repo, opts, config_file)?,
Command::Dump(opts) => dump::execute(repo, opts, config_file)?,
Command::Forget(opts) => forget::execute(repo, opts, config_file)?,
Command::Forget(opts) => forget::execute(repo, gopts, opts, config_file)?,
Command::Init(_) => {} // already handled above
Command::Key(opts) => key::execute(repo, opts)?,
Command::List(opts) => list::execute(repo, opts)?,
@ -254,11 +259,11 @@ pub fn execute() -> Result<()> {
Command::Merge(opts) => merge_cmd::execute(repo, opts, config_file, command)?,
Command::SelfUpdate(_) => {} // already handled above
Command::Snapshots(opts) => snapshots::execute(repo, opts, config_file)?,
Command::Prune(opts) => prune::execute(repo, opts, vec![])?,
Command::Restore(opts) => restore::execute(repo, opts, config_file)?,
Command::Repair(opts) => repair::execute(repo, opts, config_file)?,
Command::Prune(opts) => prune::execute(repo, gopts, opts, vec![])?,
Command::Restore(opts) => restore::execute(repo, gopts, opts, config_file)?,
Command::Repair(opts) => repair::execute(repo, gopts, opts, config_file)?,
Command::Repoinfo(opts) => repoinfo::execute(repo, opts)?,
Command::Tag(opts) => tag::execute(repo, opts, config_file)?,
Command::Tag(opts) => tag::execute(repo, gopts, opts, config_file)?,
};
Ok(())

View File

@ -12,7 +12,7 @@ use itertools::Itertools;
use log::*;
use rayon::prelude::*;
use super::{bytes, no_progress, progress_bytes, progress_counter, warm_up_wait};
use super::{bytes, no_progress, progress_bytes, progress_counter, warm_up_wait, GlobalOpts};
use crate::backend::{DecryptReadBackend, DecryptWriteBackend, FileType, ReadBackend};
use crate::blob::{
BlobType, BlobTypeMap, Initialize, NodeType, PackSizer, Repacker, Sum, TreeStreamerOnce,
@ -26,10 +26,6 @@ use crate::repository::OpenRepository;
#[derive(Parser)]
#[group(id = "prune_opts")]
pub(super) struct Opts {
/// Don't remove anything, only show what would be done
#[clap(long, short = 'n')]
pub(crate) dry_run: bool,
/// Define maximum data to repack in % of reposize or as size (e.g. '5b', '2 kB', '3M', '4TiB') or 'unlimited'
#[clap(long, value_name = "LIMIT", default_value = "unlimited")]
max_repack: LimitOption,
@ -71,7 +67,12 @@ pub(super) struct Opts {
no_resize: bool,
}
pub(super) fn execute(repo: OpenRepository, opts: Opts, ignore_snaps: Vec<Id>) -> Result<()> {
pub(super) fn execute(
repo: OpenRepository,
gopts: GlobalOpts,
opts: Opts,
ignore_snaps: Vec<Id>,
) -> Result<()> {
let be = &repo.dbe;
if repo.config.version < 2 && opts.repack_uncompressed {
bail!("--repack-uncompressed makes no sense for v1 repo!");
@ -131,9 +132,9 @@ pub(super) fn execute(repo: OpenRepository, opts: Opts, ignore_snaps: Vec<Id>) -
pruner.filter_index_files(opts.instant_delete);
pruner.print_stats();
warm_up_wait(&repo, pruner.repack_packs().into_iter(), !opts.dry_run)?;
warm_up_wait(&repo, pruner.repack_packs().into_iter(), !gopts.dry_run)?;
if !opts.dry_run {
if !gopts.dry_run {
pruner.do_prune(repo, opts)?;
}
Ok(())

View File

@ -18,7 +18,7 @@ use crate::repofile::{
use crate::repository::OpenRepository;
use super::rustic_config::RusticConfig;
use super::{progress_counter, progress_spinner, warm_up_wait};
use super::{progress_counter, progress_spinner, warm_up_wait, GlobalOpts};
#[derive(Parser)]
pub(super) struct Opts {
@ -36,10 +36,6 @@ enum Command {
#[derive(Default, Parser)]
struct IndexOpts {
// Only show what would be repaired
#[clap(long, short = 'n')]
dry_run: bool,
// Read all data packs, i.e. completely re-create the index
#[clap(long)]
read_all: bool,
@ -50,10 +46,6 @@ struct SnapOpts {
#[clap(flatten, next_help_heading = "Snapshot filter options")]
filter: SnapshotFilter,
/// Only show what would be repaired
#[clap(long, short = 'n')]
dry_run: bool,
/// Also remove defect snapshots - WARNING: This can result in data loss!
#[clap(long)]
delete: bool,
@ -71,14 +63,19 @@ struct SnapOpts {
ids: Vec<String>,
}
pub(super) fn execute(repo: OpenRepository, opts: Opts, config_file: RusticConfig) -> Result<()> {
pub(super) fn execute(
repo: OpenRepository,
gopts: GlobalOpts,
opts: Opts,
config_file: RusticConfig,
) -> Result<()> {
match opts.command {
Command::Index(opt) => repair_index(&repo, opt),
Command::Snapshots(opt) => repair_snaps(&repo.dbe, opt, config_file, &repo.config),
Command::Index(opt) => repair_index(&repo, gopts, opt),
Command::Snapshots(opt) => repair_snaps(&repo.dbe, gopts, opt, config_file, &repo.config),
}
}
fn repair_index(repo: &OpenRepository, opts: IndexOpts) -> Result<()> {
fn repair_index(repo: &OpenRepository, gopts: GlobalOpts, opts: IndexOpts) -> Result<()> {
let be = &repo.dbe;
let p = progress_spinner("listing packs...");
let mut packs: HashMap<_, _> = be.list_with_size(FileType::Pack)?.into_iter().collect();
@ -137,7 +134,7 @@ fn repair_index(repo: &OpenRepository, opts: IndexOpts) -> Result<()> {
for p in index.packs_to_delete {
process_pack(p, true, &mut new_index, &mut changed);
}
match (changed, opts.dry_run) {
match (changed, gopts.dry_run) {
(true, true) => info!("would have modified index file {index_id}"),
(true, false) => {
if !new_index.packs.is_empty() || !new_index.packs_to_delete.is_empty() {
@ -172,7 +169,7 @@ fn repair_index(repo: &OpenRepository, opts: IndexOpts) -> Result<()> {
pack.blobs = header.into_blobs();
}
}
if !opts.dry_run {
if !gopts.dry_run {
indexer.write().unwrap().add_with(pack, to_delete)?;
}
p.inc(1);
@ -185,6 +182,7 @@ fn repair_index(repo: &OpenRepository, opts: IndexOpts) -> Result<()> {
fn repair_snaps(
be: &impl DecryptFullBackend,
gopts: GlobalOpts,
mut opts: SnapOpts,
config_file: RusticConfig,
config: &ConfigFile,
@ -219,6 +217,7 @@ fn repair_snaps(
Some(snap.tree),
&mut replaced,
&mut seen,
&gopts,
&opts,
)? {
(Changed::None, _) => {
@ -235,7 +234,7 @@ fn repair_snaps(
}
snap.set_tags(opts.tag.clone());
snap.tree = id;
if opts.dry_run {
if gopts.dry_run {
info!("would have modified snapshot {snap_id}.");
} else {
let new_id = be.save_file(&snap)?;
@ -246,13 +245,13 @@ fn repair_snaps(
}
}
if !opts.dry_run {
if !gopts.dry_run {
packer.finalize()?;
indexer.write().unwrap().finalize()?;
}
if opts.delete {
if opts.dry_run {
if gopts.dry_run {
info!("would have removed {} snapshots.", delete.len());
} else {
be.delete_list(
@ -280,6 +279,7 @@ fn repair_tree<BE: DecryptWriteBackend>(
id: Option<Id>,
replaced: &mut HashMap<Id, (Changed, Id)>,
seen: &mut HashSet<Id>,
gopts: &GlobalOpts,
opts: &SnapOpts,
) -> Result<(Changed, Id)> {
let (tree, changed) = match id {
@ -331,7 +331,7 @@ fn repair_tree<BE: DecryptWriteBackend>(
}
NodeType::Dir {} => {
let (c, tree_id) =
repair_tree(be, packer, node.subtree, replaced, seen, opts)?;
repair_tree(be, packer, node.subtree, replaced, seen, gopts, opts)?;
match c {
Changed::None => {}
Changed::This => {
@ -363,7 +363,7 @@ fn repair_tree<BE: DecryptWriteBackend>(
(_, c) => {
// the tree has been changed => save it
let (chunk, new_id) = tree.serialize()?;
if !be.has_tree(&new_id) && !opts.dry_run {
if !be.has_tree(&new_id) && !gopts.dry_run {
packer.add(&chunk, &new_id)?;
}
if let Some(id) = id {

View File

@ -13,7 +13,7 @@ use log::*;
use rayon::ThreadPoolBuilder;
use super::rustic_config::RusticConfig;
use super::{bytes, progress_bytes, progress_counter, warm_up_wait};
use super::{bytes, progress_bytes, progress_counter, warm_up_wait, GlobalOpts};
use crate::backend::{DecryptReadBackend, FileType, LocalDestination};
use crate::blob::{Node, NodeStreamer, NodeType, Tree, TreeStreamerOptions};
use crate::commands::helpers::progress_spinner;
@ -33,10 +33,6 @@ pub(super) struct Opts {
#[clap(value_name = "DESTINATION")]
dest: String,
/// Dry-run: don't restore, only show what would be done
#[clap(long, short = 'n')]
dry_run: bool,
/// Remove all files/dirs in destination which are not contained in snapshot.
/// WARNING: Use with care, maybe first try this with --dry-run?
#[clap(long)]
@ -66,6 +62,7 @@ pub(super) struct Opts {
pub(super) fn execute(
repo: OpenRepository,
gopts: GlobalOpts,
mut opts: Opts,
config_file: RusticConfig,
) -> Result<()> {
@ -81,7 +78,7 @@ pub(super) fn execute(
let dest = LocalDestination::new(&opts.dest, true, !node.is_dir())?;
let p = progress_spinner("collecting file information...");
let (file_infos, stats) = allocate_and_collect(&dest, index.clone(), &node, &opts)?;
let (file_infos, stats) = allocate_and_collect(&dest, index.clone(), &node, &gopts, &opts)?;
p.finish();
let fs = stats.file;
@ -106,13 +103,13 @@ pub(super) fn execute(
if file_infos.restore_size == 0 {
info!("all file contents are fine.");
} else {
warm_up_wait(&repo, file_infos.to_packs().into_iter(), !opts.dry_run)?;
if !opts.dry_run {
warm_up_wait(&repo, file_infos.to_packs().into_iter(), !gopts.dry_run)?;
if !gopts.dry_run {
restore_contents(be, &dest, file_infos)?;
}
}
if !opts.dry_run {
if !gopts.dry_run {
let p = progress_spinner("setting metadata...");
restore_metadata(&dest, index, &node, &opts)?;
p.finish();
@ -142,6 +139,7 @@ fn allocate_and_collect(
dest: &LocalDestination,
index: impl IndexedBackend + Unpin,
node: &Node,
gopts: &GlobalOpts,
opts: &Opts,
) -> Result<(FileInfos, RestoreStats)> {
let dest_path = Path::new(&opts.dest);
@ -165,7 +163,7 @@ fn allocate_and_collect(
}
match (
opts.delete,
opts.dry_run,
gopts.dry_run,
entry.file_type().unwrap().is_dir(),
) {
(true, true, true) => {
@ -210,7 +208,7 @@ fn allocate_and_collect(
} else {
stats.dir.restore += 1;
debug!("to restore: {path:?}");
if !opts.dry_run {
if !gopts.dry_run {
dest.create_dir(path)
.with_context(|| format!("error creating {path:?}"))?;
}
@ -239,7 +237,7 @@ fn allocate_and_collect(
(true, AddFileResult::New(size) | AddFileResult::Modify(size)) => {
stats.file.modify += 1;
debug!("to modify: {path:?}");
if !opts.dry_run {
if !gopts.dry_run {
// set the right file size
dest.set_length(path, size)
.with_context(|| format!("error setting length for {path:?}"))?;
@ -248,7 +246,7 @@ fn allocate_and_collect(
(false, AddFileResult::New(size) | AddFileResult::Modify(size)) => {
stats.file.restore += 1;
debug!("to restore: {path:?}");
if !opts.dry_run {
if !gopts.dry_run {
// create the file as it doesn't exist
dest.set_length(path, size)
.with_context(|| format!("error creating {path:?}"))?;

View File

@ -5,7 +5,7 @@ use self_update::cargo_crate_version;
#[derive(Parser)]
pub(super) struct Opts {
/// Do not ask before processing the self-update
#[clap(long)]
#[clap(long, conflicts_with = "dry_run")]
force: bool,
}

View File

@ -2,7 +2,7 @@ use anyhow::Result;
use chrono::{Duration, Local};
use clap::Parser;
use super::{progress_counter, RusticConfig};
use super::{progress_counter, GlobalOpts, RusticConfig};
use crate::backend::{DecryptWriteBackend, FileType};
use crate::id::Id;
use crate::repofile::{DeleteOption, SnapshotFile, SnapshotFilter, StringList};
@ -15,10 +15,6 @@ pub(super) struct Opts {
#[clap(value_name = "ID")]
ids: Vec<String>,
/// Don't change any snapshot, only show which would be modified
#[clap(long, short = 'n')]
dry_run: bool,
#[clap(
flatten,
next_help_heading = "Snapshot filter options (if no snapshot is given)"
@ -70,6 +66,7 @@ pub(super) struct Opts {
pub(super) fn execute(
repo: OpenRepository,
gopts: GlobalOpts,
mut opts: Opts,
config_file: RusticConfig,
) -> Result<()> {
@ -102,7 +99,7 @@ pub(super) fn execute(
snap.id = Id::default();
}
match (old_snap_ids.is_empty(), opts.dry_run) {
match (old_snap_ids.is_empty(), gopts.dry_run) {
(true, _) => println!("no snapshot changed."),
(false, true) => {
println!("would have modified the following snapshots:\n {old_snap_ids:?}");