From 10db248507573c2c3608efe36c50953ea6df9265 Mon Sep 17 00:00:00 2001 From: Alexander Weiss Date: Sat, 15 Apr 2023 21:42:45 +0200 Subject: [PATCH] Update to clap v4 --- Cargo.lock | 151 ++++++++++++++++++++++++----------- Cargo.toml | 4 +- changelog/new.txt | 2 + src/backend/ignore.rs | 16 ++-- src/blob/tree.rs | 8 +- src/commands/backup.rs | 66 ++++++++++----- src/commands/cat.rs | 9 ++- src/commands/check.rs | 2 +- src/commands/completions.rs | 1 + src/commands/config.rs | 3 +- src/commands/copy.rs | 14 ++-- src/commands/diff.rs | 15 ++-- src/commands/dump.rs | 9 ++- src/commands/forget.rs | 33 ++++---- src/commands/helpers.rs | 4 +- src/commands/init.rs | 4 +- src/commands/key.rs | 5 +- src/commands/list.rs | 2 +- src/commands/ls.rs | 15 ++-- src/commands/merge_cmd.rs | 15 ++-- src/commands/mod.rs | 40 ++++++---- src/commands/prune.rs | 7 +- src/commands/repair.rs | 5 +- src/commands/restore.rs | 53 +++++------- src/commands/snapshots.rs | 10 +-- src/commands/tag.rs | 31 ++++--- src/repofile/snapshotfile.rs | 6 +- src/repository/mod.rs | 15 ++-- 28 files changed, 314 insertions(+), 231 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d5c42f7..2034430 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -75,6 +75,55 @@ dependencies = [ "libc", ] +[[package]] +name = "anstream" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e579a7752471abc2a8268df8b20005e3eadd975f585398f17efcfd8d4927371" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is-terminal", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41ed9a86bf92ae6580e0a31281f65a1b1d867c0cc68d5346e2ae128dddfa6a7d" + +[[package]] +name = "anstyle-parse" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e765fd216e48e067936442276d1d57399e37bce53c264d6fefbe298080cb57ee" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b" +dependencies = [ + "windows-sys 0.48.0", +] + +[[package]] +name = "anstyle-wincon" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bcd8291a340dd8ac70e18878bc4501dd7b4ff970cfa21c207d36ece51ea88fd" +dependencies = [ + "anstyle", + "windows-sys 0.48.0", +] + [[package]] name = "anyhow" version = "1.0.70" @@ -87,17 +136,6 @@ version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d62b7694a562cdf5a74227903507c56ab2cc8bdd1f781ed5cb4cf9c9f810bfc" -[[package]] -name = "atty" -version = "0.2.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" -dependencies = [ - "hermit-abi 0.1.19", - "libc", - "winapi", -] - [[package]] name = "autocfg" version = "1.1.0" @@ -258,51 +296,55 @@ dependencies = [ [[package]] name = "clap" -version = "3.2.23" +version = "4.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71655c45cb9845d3270c9d6df84ebe72b4dad3c2ba3f7023ad47c144e4e473a5" +checksum = "9b802d85aaf3a1cdb02b224ba472ebdea62014fccfcb269b95a4d76443b5ee5a" dependencies = [ - "atty", - "bitflags", + "clap_builder", "clap_derive", - "clap_lex", - "indexmap", "once_cell", +] + +[[package]] +name = "clap_builder" +version = "4.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14a1a858f532119338887a4b8e1af9c60de8249cd7bafd68036a489e261e37b6" +dependencies = [ + "anstream", + "anstyle", + "bitflags", + "clap_lex", "strsim", - "termcolor", - "textwrap", + "terminal_size", ] [[package]] name = "clap_complete" -version = "3.2.5" +version = "4.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f7a2e0a962c45ce25afce14220bc24f9dade0a1787f185cecf96bfba7847cd8" +checksum = "01c22dcfb410883764b29953103d9ef7bb8fe21b3fa1158bc99986c2067294bd" dependencies = [ "clap", ] [[package]] name = "clap_derive" -version = "3.2.18" +version = "4.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea0c8bce528c4be4da13ea6fead8965e95b6073585a2f05204bd8f4119f82a65" +checksum = "3f9644cd56d6b87dbe899ef8b053e331c0637664e9e21a33dfcdc36093f5c5c4" dependencies = [ "heck", - "proc-macro-error", "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.14", ] [[package]] name = "clap_lex" -version = "0.2.4" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5" -dependencies = [ - "os_str_bytes", -] +checksum = "8a2dd5a6fe8c6e3502f568a6353e5273bbb15193ad9a89e457b9970798efbea1" [[package]] name = "codespan-reporting" @@ -314,6 +356,12 @@ dependencies = [ "unicode-width", ] +[[package]] +name = "colorchoice" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" + [[package]] name = "comfy-table" version = "6.1.4" @@ -932,15 +980,6 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" -[[package]] -name = "hermit-abi" -version = "0.1.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" -dependencies = [ - "libc", -] - [[package]] name = "hermit-abi" version = "0.2.6" @@ -1175,6 +1214,18 @@ version = "2.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "12b6ee2129af8d4fb011108c73d99a1b83a85977f23b82460c0ae2e25bb4b57f" +[[package]] +name = "is-terminal" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adcf93614601c8129ddf72e2d5633df827ba6551541c6d8c59520a371475be1f" +dependencies = [ + "hermit-abi 0.3.1", + "io-lifetimes", + "rustix", + "windows-sys 0.48.0", +] + [[package]] name = "itertools" version = "0.10.5" @@ -1413,12 +1464,6 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" -[[package]] -name = "os_str_bytes" -version = "6.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ceedf44fb00f2d1984b0bc98102627ce622e083e49a5bacdb3e514fa4238e267" - [[package]] name = "owo-colors" version = "3.5.0" @@ -2302,10 +2347,14 @@ dependencies = [ ] [[package]] -name = "textwrap" -version = "0.16.0" +name = "terminal_size" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "222a222a5bfe1bba4a77b45ec488a741b3cb8872e5e499451fd7d0129c9c7c3d" +checksum = "8e6bf6f19e9f8ed8d4048dc22981458ebcf406d67e94cd422e5ecd73d63b3237" +dependencies = [ + "rustix", + "windows-sys 0.48.0", +] [[package]] name = "thiserror" @@ -2573,6 +2622,12 @@ dependencies = [ "log", ] +[[package]] +name = "utf8parse" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" + [[package]] name = "version_check" version = "0.9.4" diff --git a/Cargo.toml b/Cargo.toml index 7d9d5d5..0ef944e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -70,8 +70,8 @@ semver = "1" dirs = "5" cachedir = "0.3" # commands -clap = { version = "3", features = ["derive", "env"] } -clap_complete = "3.2.4" +clap = { version = "4", features = ["derive", "env", "wrap_help"] } +clap_complete = "4" directories = "5" nom = "7" toml = "0.7" diff --git a/changelog/new.txt b/changelog/new.txt index f2f6a60..dfbc833 100644 --- a/changelog/new.txt +++ b/changelog/new.txt @@ -3,5 +3,7 @@ Changes in version x.x.x: Breaking changes: Bugs fixed: +- restore: Warm-up options given by the command line didn't work. This has been fixed. New features: +- Updated to clap v4 diff --git a/src/backend/ignore.rs b/src/backend/ignore.rs index c503529..f536e38 100644 --- a/src/backend/ignore.rs +++ b/src/backend/ignore.rs @@ -46,42 +46,42 @@ pub struct LocalSourceOptions { ignore_devid: bool, /// Glob pattern to exclude/include (can be specified multiple times) - #[clap(long, help_heading = "EXCLUDE OPTIONS")] + #[clap(long, help_heading = "Exclude options")] #[merge(strategy = merge::vec::overwrite_empty)] glob: Vec, /// Same as --glob pattern but ignores the casing of filenames - #[clap(long, value_name = "GLOB", help_heading = "EXCLUDE OPTIONS")] + #[clap(long, value_name = "GLOB", help_heading = "Exclude options")] #[merge(strategy = merge::vec::overwrite_empty)] iglob: Vec, /// Read glob patterns to exclude/include from this file (can be specified multiple times) - #[clap(long, value_name = "FILE", help_heading = "EXCLUDE OPTIONS")] + #[clap(long, value_name = "FILE", help_heading = "Exclude options")] #[merge(strategy = merge::vec::overwrite_empty)] glob_file: Vec, /// Same as --glob-file ignores the casing of filenames in patterns - #[clap(long, value_name = "FILE", help_heading = "EXCLUDE OPTIONS")] + #[clap(long, value_name = "FILE", help_heading = "Exclude options")] #[merge(strategy = merge::vec::overwrite_empty)] iglob_file: Vec, /// Ignore files based on .gitignore files - #[clap(long, help_heading = "EXCLUDE OPTIONS")] + #[clap(long, help_heading = "Exclude options")] #[merge(strategy = merge::bool::overwrite_false)] git_ignore: bool, /// Exclude contents of directories containing this filename (can be specified multiple times) - #[clap(long, value_name = "FILE", help_heading = "EXCLUDE OPTIONS")] + #[clap(long, value_name = "FILE", help_heading = "Exclude options")] #[merge(strategy = merge::vec::overwrite_empty)] exclude_if_present: Vec, /// Exclude other file systems, don't cross filesystem boundaries and subvolumes - #[clap(long, short = 'x', help_heading = "EXCLUDE OPTIONS")] + #[clap(long, short = 'x', help_heading = "Exclude options")] #[merge(strategy = merge::bool::overwrite_false)] one_file_system: bool, /// Maximum size of files to be backuped. Larger files will be excluded. - #[clap(long, value_name = "SIZE", help_heading = "EXCLUDE OPTIONS")] + #[clap(long, value_name = "SIZE", help_heading = "Exclude options")] #[serde_as(as = "Option")] exclude_larger_than: Option, } diff --git a/src/blob/tree.rs b/src/blob/tree.rs index b86f309..41999d9 100644 --- a/src/blob/tree.rs +++ b/src/blob/tree.rs @@ -109,19 +109,19 @@ impl IntoIterator for Tree { #[derive(Default, Clone, Parser)] pub struct TreeStreamerOptions { /// Glob pattern to exclude/include (can be specified multiple times) - #[clap(long, help_heading = "EXCLUDE OPTIONS")] + #[clap(long, help_heading = "Exclude options")] glob: Vec, /// Same as --glob pattern but ignores the casing of filenames - #[clap(long, value_name = "GLOB", help_heading = "EXCLUDE OPTIONS")] + #[clap(long, value_name = "GLOB", help_heading = "Exclude options")] iglob: Vec, /// Read glob patterns to exclude/include from this file (can be specified multiple times) - #[clap(long, value_name = "FILE", help_heading = "EXCLUDE OPTIONS")] + #[clap(long, value_name = "FILE", help_heading = "Exclude options")] glob_file: Vec, /// Same as --glob-file ignores the casing of filenames in patterns - #[clap(long, value_name = "FILE", help_heading = "EXCLUDE OPTIONS")] + #[clap(long, value_name = "FILE", help_heading = "Exclude options")] iglob_file: Vec, } diff --git a/src/commands/backup.rs b/src/commands/backup.rs index ad18b14..3786df3 100644 --- a/src/commands/backup.rs +++ b/src/commands/backup.rs @@ -3,7 +3,7 @@ use std::str::FromStr; use anyhow::{bail, Result}; use chrono::Local; -use clap::{AppSettings, Parser}; +use clap::Parser; use log::*; use merge::Merge; use path_dedot::ParseDot; @@ -20,13 +20,14 @@ use crate::repofile::{ use crate::repository::OpenRepository; #[derive(Clone, Default, Parser, Deserialize, Merge)] -#[clap(global_setting(AppSettings::DeriveDisplayOrder))] -#[serde(default, rename_all = "kebab-case", deny_unknown_fields)] +#[serde(default, rename_all = "kebab-case")] pub(super) struct Opts { - /// Output generated snapshot in json format - #[clap(long)] - #[merge(strategy = merge::bool::overwrite_false)] - json: bool, + /// Backup source (can be specified multiple times), use - for stdin. If no source is given, uses all + /// sources defined in the config file + #[clap(value_name = "SOURCE")] + #[merge(skip)] + #[serde(skip)] + cli_sources: Vec, /// Do not upload or write any data, just show what would be done #[clap(long, short = 'n')] @@ -34,25 +35,48 @@ pub(super) struct Opts { dry_run: bool, /// Group snapshots by any combination of host,label,paths,tags to find a suitable parent (default: host,label,paths) - #[clap(long, short = 'g', value_name = "CRITERION")] + #[clap( + long, + short = 'g', + value_name = "CRITERION", + help_heading = "Options for parent processing" + )] group_by: Option, /// Snapshot to use as parent - #[clap(long, value_name = "SNAPSHOT", conflicts_with = "force")] + #[clap( + long, + value_name = "SNAPSHOT", + conflicts_with = "force", + help_heading = "Options for parent processing" + )] parent: Option, /// Use no parent, read all files - #[clap(long, short, conflicts_with = "parent")] + #[clap( + long, + short, + conflicts_with = "parent", + help_heading = "Options for parent processing" + )] #[merge(strategy = merge::bool::overwrite_false)] force: bool, /// Ignore ctime changes when checking for modified files - #[clap(long, conflicts_with = "force")] + #[clap( + long, + conflicts_with = "force", + help_heading = "Options for parent processing" + )] #[merge(strategy = merge::bool::overwrite_false)] ignore_ctime: bool, /// Ignore inode number changes when checking for modified files - #[clap(long, conflicts_with = "force")] + #[clap( + long, + conflicts_with = "force", + help_heading = "Options for parent processing" + )] #[merge(strategy = merge::bool::overwrite_false)] ignore_inode: bool, @@ -65,20 +89,18 @@ pub(super) struct Opts { #[clap(long, value_name = "PATH")] as_path: Option, - #[clap(flatten)] - #[serde(flatten)] - snap_opts: SnapshotOptions, - #[clap(flatten)] #[serde(flatten)] ignore_opts: LocalSourceOptions, - /// Backup source (can be specified multiple times), use - for stdin. If no source is given, uses all - /// sources defined in the config file - #[clap(value_name = "SOURCE")] - #[merge(skip)] - #[serde(skip)] - cli_sources: Vec, + #[clap(flatten, next_help_heading = "Snapshot options")] + #[serde(flatten)] + snap_opts: SnapshotOptions, + + /// Output generated snapshot in json format + #[clap(long)] + #[merge(strategy = merge::bool::overwrite_false)] + json: bool, // This is a hack to support serde(deny_unknown_fields) for deserializing the backup options from TOML // while still being able to use [[backup.sources]] in the config file. diff --git a/src/commands/cat.rs b/src/commands/cat.rs index cc44947..9df078f 100644 --- a/src/commands/cat.rs +++ b/src/commands/cat.rs @@ -43,12 +43,15 @@ struct IdOpt { #[derive(Parser)] struct TreeOpts { - #[clap(flatten, help_heading = "SNAPSHOT FILTER OPTIONS (when using latest)")] - filter: SnapshotFilter, - /// Snapshot/path of the tree to display #[clap(value_name = "SNAPSHOT[:PATH]")] snap: String, + + #[clap( + flatten, + next_help_heading = "Snapshot filter options (when using latest)" + )] + filter: SnapshotFilter, } pub(super) fn execute(repo: OpenRepository, opts: Opts, config_file: RusticConfig) -> Result<()> { diff --git a/src/commands/check.rs b/src/commands/check.rs index dc90502..752ebc9 100644 --- a/src/commands/check.rs +++ b/src/commands/check.rs @@ -24,7 +24,7 @@ use crate::repository::OpenRepository; #[derive(Parser)] pub(super) struct Opts { /// Don't verify the data saved in the cache - #[clap(long, conflicts_with = "no-cache")] + #[clap(long, conflicts_with = "no_cache")] trust_cache: bool, /// Read all data blobs diff --git a/src/commands/completions.rs b/src/commands/completions.rs index f328cca..5e4d3fa 100644 --- a/src/commands/completions.rs +++ b/src/commands/completions.rs @@ -5,6 +5,7 @@ use clap_complete::{generate, shells, Generator}; #[derive(Parser)] pub(super) struct Opts { + /// Shell to generate completions for #[clap(value_enum)] sh: Variant, } diff --git a/src/commands/config.rs b/src/commands/config.rs index 10ce6b4..dc863a8 100644 --- a/src/commands/config.rs +++ b/src/commands/config.rs @@ -1,6 +1,6 @@ use anyhow::{bail, Result}; use bytesize::ByteSize; -use clap::{AppSettings, Parser}; +use clap::Parser; use crate::backend::{DecryptBackend, DecryptWriteBackend}; use crate::repofile::ConfigFile; @@ -40,7 +40,6 @@ pub(super) fn execute(mut repo: OpenRepository, opts: Opts) -> Result<()> { } #[derive(Parser)] -#[clap(global_setting(AppSettings::DeriveDisplayOrder))] pub(super) struct ConfigOpts { /// Set compression level. Allowed levels are 1 to 22 and -1 to -7, see . /// Note that 0 equals to no compression diff --git a/src/commands/copy.rs b/src/commands/copy.rs index 4d7f63d..2850869 100644 --- a/src/commands/copy.rs +++ b/src/commands/copy.rs @@ -1,7 +1,7 @@ use std::collections::BTreeSet; use anyhow::{bail, Result}; -use clap::{AppSettings, Parser}; +use clap::Parser; use log::*; use rayon::prelude::*; @@ -13,22 +13,20 @@ use crate::repofile::{Id, SnapshotFile, SnapshotFilter}; use crate::repository::{OpenRepository, Repository, RepositoryOptions}; #[derive(Parser)] -#[clap(global_setting(AppSettings::DeriveDisplayOrder))] pub(super) struct Opts { + /// Snapshots to copy. If none is given, use filter options to filter from all snapshots. + #[clap(value_name = "ID")] + ids: Vec, + /// Don't copy any snapshot, only show what would be done #[clap(long, short = 'n')] dry_run: bool, #[clap( flatten, - help_heading = "SNAPSHOT FILTER OPTIONS (if no snapshot is given)" + next_help_heading = "Snapshot filter options (if no snapshot is given)" )] filter: SnapshotFilter, - - /// Snapshots to copy. If none is given, use filter to filter from all - /// snapshots. - #[clap(value_name = "ID")] - ids: Vec, } pub(super) fn execute( diff --git a/src/commands/diff.rs b/src/commands/diff.rs index 32fc41e..92cd70b 100644 --- a/src/commands/diff.rs +++ b/src/commands/diff.rs @@ -15,12 +15,6 @@ use crate::repository::OpenRepository; #[derive(Parser)] pub(super) struct Opts { - #[clap(flatten)] - ignore_opts: LocalSourceOptions, - - #[clap(flatten, help_heading = "SNAPSHOT FILTER OPTIONS (when using latest)")] - filter: SnapshotFilter, - /// Reference snapshot/path #[clap(value_name = "SNAPSHOT1[:PATH1]")] snap1: String, @@ -36,6 +30,15 @@ pub(super) struct Opts { /// don't check for different file contents #[clap(long)] no_content: bool, + + #[clap(flatten)] + ignore_opts: LocalSourceOptions, + + #[clap( + flatten, + next_help_heading = "Snapshot filter options (when using latest)" + )] + filter: SnapshotFilter, } pub(super) fn execute( diff --git a/src/commands/dump.rs b/src/commands/dump.rs index c46cac8..3819ec2 100644 --- a/src/commands/dump.rs +++ b/src/commands/dump.rs @@ -12,12 +12,15 @@ use super::{progress_counter, RusticConfig}; #[derive(Parser)] pub(super) struct Opts { - #[clap(flatten, help_heading = "SNAPSHOT FILTER OPTIONS (when using latest)")] - filter: SnapshotFilter, - /// file from snapshot to dump #[clap(value_name = "SNAPSHOT[:PATH]")] snap: String, + + #[clap( + flatten, + next_help_heading = "Snapshot filter options (when using latest)" + )] + filter: SnapshotFilter, } pub(super) fn execute( diff --git a/src/commands/forget.rs b/src/commands/forget.rs index 5812299..6de004a 100644 --- a/src/commands/forget.rs +++ b/src/commands/forget.rs @@ -2,7 +2,7 @@ use std::str::FromStr; use anyhow::Result; use chrono::{DateTime, Datelike, Duration, Local, Timelike}; -use clap::{AppSettings, Parser}; +use clap::Parser; use derivative::Derivative; use merge::Merge; use serde::Deserialize; @@ -16,41 +16,44 @@ use crate::repofile::{ use crate::repository::OpenRepository; #[derive(Parser)] -#[clap(global_setting(AppSettings::DeriveDisplayOrder))] pub(super) struct Opts { + /// Snapshots to forget. If none is given, use filter options to filter from all snapshots + #[clap(value_name = "ID")] + ids: Vec, + #[clap(flatten)] config: ConfigOpts, - /// Also prune the repository - #[clap(long)] - prune: bool, - - #[clap(flatten, help_heading = "PRUNE OPTIONS (only when used with --prune)")] + #[clap( + flatten, + 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, - - /// Snapshots to forget - ids: Vec, } #[serde_as] #[derive(Default, Parser, Deserialize, Merge)] -#[clap(global_setting(AppSettings::DeriveDisplayOrder))] -#[serde(default, rename_all = "kebab-case", deny_unknown_fields)] +#[serde(default, rename_all = "kebab-case")] struct ConfigOpts { /// Group snapshots by any combination of host,label,paths,tags (default: "host,label,paths") #[clap(long, short = 'g', value_name = "CRITERION")] #[serde_as(as = "Option")] group_by: Option, - #[clap(flatten, help_heading = "SNAPSHOT FILTER OPTIONS")] + /// Also prune the repository + #[clap(long)] + #[merge(strategy = merge::bool::overwrite_false)] + prune: bool, + + #[clap(flatten, next_help_heading = "Snapshot filter options")] #[serde(flatten)] filter: SnapshotFilter, - #[clap(flatten, help_heading = "RETENTION OPTIONS")] + #[clap(flatten, next_help_heading = "Retention options")] #[serde(flatten)] keep: KeepOptions, } @@ -153,7 +156,7 @@ pub(super) fn execute( } } - if opts.prune { + if opts.config.prune { prune::execute(repo, opts.prune_opts, forget_snaps)?; } diff --git a/src/commands/helpers.rs b/src/commands/helpers.rs index 4266aaf..3fb672b 100644 --- a/src/commands/helpers.rs +++ b/src/commands/helpers.rs @@ -92,8 +92,8 @@ pub fn warm_up_wait( packs: impl ExactSizeIterator, wait: bool, ) -> Result<()> { - if repo.opts.warm_up_command.is_some() { - warm_up_command(packs, repo.opts.warm_up_command.as_ref().unwrap())?; + if let Some(command) = &repo.opts.warm_up_command { + warm_up_command(packs, command)?; } else if repo.opts.warm_up { warm_up(&repo.be, packs)?; } diff --git a/src/commands/init.rs b/src/commands/init.rs index 267da7c..54d1449 100644 --- a/src/commands/init.rs +++ b/src/commands/init.rs @@ -13,10 +13,10 @@ use crate::repofile::{ConfigFile, KeyFile}; #[derive(Parser)] pub(super) struct Opts { - #[clap(flatten, help_heading = "KEY OPTIONS")] + #[clap(flatten, next_help_heading = "Key options")] key_opts: KeyOpts, - #[clap(flatten, help_heading = "CONFIG OPTIONS")] + #[clap(flatten, next_help_heading = "Config options")] config_opts: ConfigOpts, } diff --git a/src/commands/key.rs b/src/commands/key.rs index 1b50771..cd71b45 100644 --- a/src/commands/key.rs +++ b/src/commands/key.rs @@ -2,7 +2,7 @@ use std::fs::File; use std::io::BufReader; use anyhow::Result; -use clap::{AppSettings, Parser, Subcommand}; +use clap::{Parser, Subcommand}; use rpassword::{prompt_password, read_password_from_bufread}; use crate::backend::{FileType, WriteBackend}; @@ -18,11 +18,11 @@ pub(super) struct Opts { #[derive(Subcommand)] enum Command { + /// Add a new key to the repository Add(AddOpts), } #[derive(Parser)] -#[clap(global_setting(AppSettings::DeriveDisplayOrder))] pub(crate) struct AddOpts { /// File from which to read the new password #[clap(long)] @@ -33,7 +33,6 @@ pub(crate) struct AddOpts { } #[derive(Parser)] -#[clap(global_setting(AppSettings::DeriveDisplayOrder))] pub(crate) struct KeyOpts { /// Set 'hostname' in public key information #[clap(long)] diff --git a/src/commands/list.rs b/src/commands/list.rs index 10318e0..9e2a5a2 100644 --- a/src/commands/list.rs +++ b/src/commands/list.rs @@ -9,7 +9,7 @@ use crate::repository::OpenRepository; #[derive(Parser)] pub(super) struct Opts { /// File type to list - #[clap(possible_values=["blobs", "index", "packs", "snapshots", "keys"])] + #[clap(value_parser=["blobs", "index", "packs", "snapshots", "keys"])] tpe: String, } diff --git a/src/commands/ls.rs b/src/commands/ls.rs index 5be4ecf..8aa963e 100644 --- a/src/commands/ls.rs +++ b/src/commands/ls.rs @@ -11,19 +11,22 @@ use crate::repository::OpenRepository; #[derive(Parser)] pub(super) struct Opts { - #[clap(flatten, help_heading = "SNAPSHOT FILTER OPTIONS (when using latest)")] - filter: SnapshotFilter, + /// Snapshot/path to list + #[clap(value_name = "SNAPSHOT[:PATH]")] + snap: String, /// recursively list the dir (default when no PATH is given) #[clap(long)] recursive: bool, + #[clap( + flatten, + next_help_heading = "Snapshot filter options (when using latest)" + )] + filter: SnapshotFilter, + #[clap(flatten)] streamer_opts: TreeStreamerOptions, - - /// Snapshot/path to list - #[clap(value_name = "SNAPSHOT[:PATH]")] - snap: String, } pub(super) fn execute( diff --git a/src/commands/merge_cmd.rs b/src/commands/merge_cmd.rs index 8ea7476..cea9feb 100644 --- a/src/commands/merge_cmd.rs +++ b/src/commands/merge_cmd.rs @@ -1,6 +1,6 @@ use anyhow::Result; use chrono::Local; -use clap::{AppSettings, Parser}; +use clap::Parser; use log::*; use crate::backend::{DecryptWriteBackend, FileType}; @@ -13,8 +13,11 @@ use super::helpers::{progress_counter, progress_spinner}; use super::rustic_config::RusticConfig; #[derive(Default, Parser)] -#[clap(global_setting(AppSettings::DeriveDisplayOrder))] pub(super) struct Opts { + /// Snapshots to merge. If none is given, use filter options to filter from all snapshots. + #[clap(value_name = "ID")] + ids: Vec, + /// Output generated snapshot in json format #[clap(long)] json: bool, @@ -23,15 +26,11 @@ pub(super) struct Opts { #[clap(long)] delete: bool, - #[clap(flatten)] + #[clap(flatten, next_help_heading = "Snapshot options")] snap_opts: SnapshotOptions, - #[clap(flatten, help_heading = "SNAPSHOT FILTER OPTIONS")] + #[clap(flatten, next_help_heading = "Snapshot filter options")] filter: SnapshotFilter, - - /// Snapshots to merge. If none is given, use filter to filter from all snapshots. - #[clap(value_name = "ID")] - ids: Vec, } pub(super) fn execute( diff --git a/src/commands/mod.rs b/src/commands/mod.rs index 41227e9..e19c8d6 100644 --- a/src/commands/mod.rs +++ b/src/commands/mod.rs @@ -40,24 +40,25 @@ mod tag; use rustic_config::RusticConfig; #[derive(Parser)] -#[clap(about, name="rustic", version = option_env!("PROJECT_VERSION").unwrap_or(env!("CARGO_PKG_VERSION")))] +#[clap(about, version, name="rustic", version = option_env!("PROJECT_VERSION").unwrap_or(env!("CARGO_PKG_VERSION")))] struct Opts { - #[clap(flatten, help_heading = "GLOBAL OPTIONS")] - global: GlobalOpts, - - #[clap(flatten, help_heading = "REPOSITORY OPTIONS")] - repository: RepositoryOptions, - /// Config profile to use. This parses the file `.toml` in the config directory. #[clap( short = 'P', long, value_name = "PROFILE", global = true, - default_value = "rustic" + default_value = "rustic", + help_heading = "Global options" )] config_profile: String, + #[clap(flatten, next_help_heading = "Global options")] + global: GlobalOpts, + + #[clap(flatten, next_help_heading = "Repository options")] + repository: RepositoryOptions, + #[clap(subcommand)] command: Command, } @@ -87,7 +88,7 @@ struct GlobalOpts { global = true, env = "RUSTIC_PROGRESS_INTERVAL", value_name = "DURATION", - conflicts_with = "no-progress" + conflicts_with = "no_progress" )] #[serde_as(as = "Option")] progress_interval: Option, @@ -110,11 +111,10 @@ enum Command { /// Check the repository Check(check::Opts), - /// Copy snapshots to another repository + /// Copy snapshots to other repositories. Note: The target repositories must be given in the config file! Copy(copy::Opts), /// Compare two snapshots/paths - /// /// Note that the exclude options only apply for comparison with a local path Diff(diff::Opts), @@ -167,12 +167,12 @@ pub fn execute() -> Result<()> { // get global options from command line / env and config file let config_file = RusticConfig::new(&args.config_profile)?; - let mut opts = args.global; - config_file.merge_into("global", &mut opts)?; + let mut gopts = args.global; + config_file.merge_into("global", &mut gopts)?; // start logger - let level_filter = opts.log_level.unwrap_or(LevelFilter::Info); - match opts.log_file { + let level_filter = gopts.log_level.unwrap_or(LevelFilter::Info); + match gopts.log_file { None => TermLogger::init( level_filter, ConfigBuilder::new() @@ -199,12 +199,12 @@ pub fn execute() -> Result<()> { ])?, } - if opts.no_progress { + if gopts.no_progress { let mut no_progress = NO_PROGRESS.lock().unwrap(); *no_progress = true; } - if let Some(duration) = opts.progress_interval { + if let Some(duration) = gopts.progress_interval { let mut interval = PROGRESS_INTERVAL.lock().unwrap(); *interval = *duration; } @@ -263,3 +263,9 @@ pub fn execute() -> Result<()> { Ok(()) } + +#[test] +fn verify_cli() { + use clap::CommandFactory; + Opts::command().debug_assert() +} diff --git a/src/commands/prune.rs b/src/commands/prune.rs index f55e6db..e79fa79 100644 --- a/src/commands/prune.rs +++ b/src/commands/prune.rs @@ -6,7 +6,7 @@ use std::sync::{Arc, Mutex}; use anyhow::{anyhow, bail, Result}; use bytesize::ByteSize; use chrono::{DateTime, Duration, Local}; -use clap::{AppSettings, Parser}; +use clap::Parser; use derive_more::Add; use itertools::Itertools; use log::*; @@ -24,7 +24,7 @@ use crate::repofile::{HeaderEntry, IndexBlob, IndexFile, IndexPack, SnapshotFile use crate::repository::OpenRepository; #[derive(Parser)] -#[clap(global_setting(AppSettings::DeriveDisplayOrder))] +#[group(id = "prune_opts")] pub(super) struct Opts { /// Don't remove anything, only show what would be done #[clap(long, short = 'n')] @@ -59,7 +59,7 @@ pub(super) struct Opts { /// Repack packs containing uncompressed blobs. This cannot be used with --fast-repack. /// Implies --max-unused=0. - #[clap(long, conflicts_with = "fast-repack")] + #[clap(long, conflicts_with = "fast_repack")] repack_uncompressed: bool, /// Only repack packs which are cacheable [default: true for a hot/cold repository, else false] @@ -139,6 +139,7 @@ pub(super) fn execute(repo: OpenRepository, opts: Opts, ignore_snaps: Vec) - Ok(()) } +#[derive(Clone)] enum LimitOption { Size(ByteSize), Percentage(u64), diff --git a/src/commands/repair.rs b/src/commands/repair.rs index 7ead9dc..54dfefe 100644 --- a/src/commands/repair.rs +++ b/src/commands/repair.rs @@ -1,7 +1,7 @@ use std::collections::{HashMap, HashSet}; use anyhow::Result; -use clap::{AppSettings, Parser, Subcommand}; +use clap::{Parser, Subcommand}; use log::*; use crate::backend::{ @@ -46,9 +46,8 @@ struct IndexOpts { } #[derive(Default, Parser)] -#[clap(global_setting(AppSettings::DeriveDisplayOrder))] struct SnapOpts { - #[clap(flatten, help_heading = "SNAPSHOT FILTER OPTIONS")] + #[clap(flatten, next_help_heading = "Snapshot filter options")] filter: SnapshotFilter, /// Only show what would be repaired diff --git a/src/commands/restore.rs b/src/commands/restore.rs index e90c12b..46757d6 100644 --- a/src/commands/restore.rs +++ b/src/commands/restore.rs @@ -4,9 +4,9 @@ use std::io::Read; use std::num::NonZeroU32; use std::path::{Path, PathBuf}; -use anyhow::{anyhow, bail, Context, Result}; +use anyhow::{anyhow, Context, Result}; use chrono::{DateTime, Local, Utc}; -use clap::{AppSettings, Parser}; +use clap::Parser; use derive_getters::Dissolve; use ignore::{DirEntry, WalkBuilder}; use log::*; @@ -24,20 +24,21 @@ use crate::repofile::{SnapshotFile, SnapshotFilter}; use crate::repository::OpenRepository; #[derive(Parser)] -#[clap(global_setting(AppSettings::DeriveDisplayOrder))] pub(super) struct Opts { - #[clap(flatten, help_heading = "SNAPSHOT FILTER OPTIONS (when using latest)")] - filter: SnapshotFilter, + /// Snapshot/path to restore + #[clap(value_name = "SNAPSHOT[:PATH]")] + snap: String, + + /// Restore destination + #[clap(value_name = "DESTINATION")] + dest: String, /// Dry-run: don't restore, only show what would be done #[clap(long, short = 'n')] dry_run: bool, - #[clap(flatten)] - streamer_opts: TreeStreamerOptions, - /// Remove all files/dirs in destination which are not contained in snapshot. - /// WARNING: Use with care, maybe first try this first with --dry-run? + /// WARNING: Use with care, maybe first try this with --dry-run? #[clap(long)] delete: bool, @@ -46,32 +47,21 @@ pub(super) struct Opts { numeric_id: bool, /// Don't restore ownership (user/group) - #[clap(long, conflicts_with = "numeric-id")] + #[clap(long, conflicts_with = "numeric_id")] no_ownership: bool, - /// Warm up needed data pack files by only requesting them without processing - #[clap(long)] - warm_up: bool, - /// Always read and verify existing files (don't trust correct modification time and file size) #[clap(long)] verify_existing: bool, - /// Warm up needed data pack files by running the command with %id replaced by pack id - #[clap(long, conflicts_with = "warm-up")] - warm_up_command: Option, + #[clap(flatten)] + streamer_opts: TreeStreamerOptions, - /// Duration (e.g. 10m) to wait after warm up before doing the actual restore - #[clap(long, value_name = "DURATION", conflicts_with = "dry-run")] - warm_up_wait: Option, - - /// Snapshot/path to restore - #[clap(value_name = "SNAPSHOT[:PATH]")] - snap: String, - - /// Restore destination - #[clap(value_name = "DESTINATION")] - dest: String, + #[clap( + flatten, + next_help_heading = "Snapshot filter options (when using latest)" + )] + filter: SnapshotFilter, } pub(super) fn execute( @@ -82,13 +72,6 @@ pub(super) fn execute( let be = &repo.dbe; config_file.merge_into("snapshot-filter", &mut opts.filter)?; - if let Some(command) = &opts.warm_up_command { - if !command.contains("%id") { - bail!("warm-up command must contain %id!"); - } - info!("using warm-up command {command}"); - } - let (id, path) = opts.snap.split_once(':').unwrap_or((&opts.snap, "")); let snap = SnapshotFile::from_str(be, id, |sn| sn.matches(&opts.filter), progress_counter(""))?; diff --git a/src/commands/snapshots.rs b/src/commands/snapshots.rs index 4d074b6..ae8c71f 100644 --- a/src/commands/snapshots.rs +++ b/src/commands/snapshots.rs @@ -14,8 +14,9 @@ use crate::repository::OpenRepository; #[derive(Parser)] pub(super) struct Opts { - #[clap(flatten, help_heading = "SNAPSHOT FILTER OPTIONS")] - filter: SnapshotFilter, + /// Snapshots to show. If none is given, use filter options to filter from all snapshots + #[clap(value_name = "ID")] + ids: Vec, /// Group snapshots by any combination of host,label,paths,tags #[clap( @@ -38,9 +39,8 @@ pub(super) struct Opts { #[clap(long, conflicts_with_all = &["long", "json"])] all: bool, - /// Snapshots to show - #[clap(value_name = "ID")] - ids: Vec, + #[clap(flatten, next_help_heading = "Snapshot filter options")] + filter: SnapshotFilter, } pub(super) fn execute( diff --git a/src/commands/tag.rs b/src/commands/tag.rs index 21a47c6..3822d81 100644 --- a/src/commands/tag.rs +++ b/src/commands/tag.rs @@ -1,6 +1,6 @@ use anyhow::Result; use chrono::{Duration, Local}; -use clap::{AppSettings, Parser}; +use clap::Parser; use super::{progress_counter, RusticConfig}; use crate::backend::{DecryptWriteBackend, FileType}; @@ -9,15 +9,19 @@ use crate::repofile::{DeleteOption, SnapshotFile, SnapshotFilter, StringList}; use crate::repository::OpenRepository; #[derive(Parser)] -#[clap(global_setting(AppSettings::DeriveDisplayOrder))] pub(super) struct Opts { + /// Snapshots to change tags. If none is given, use filter to filter from all + /// snapshots. + #[clap(value_name = "ID")] + ids: Vec, + /// Don't change any snapshot, only show which would be modified #[clap(long, short = 'n')] dry_run: bool, #[clap( flatten, - help_heading = "SNAPSHOT FILTER OPTIONS (if no snapshot is given)" + next_help_heading = "Snapshot filter options (if no snapshot is given)" )] filter: SnapshotFilter, @@ -26,12 +30,12 @@ pub(super) struct Opts { long, value_name = "TAG[,TAG,..]", conflicts_with = "remove", - help_heading = "TAG OPTIONS" + help_heading = "Tag options" )] add: Vec, /// Tags to remove (can be specified multiple times) - #[clap(long, value_name = "TAG[,TAG,..]", help_heading = "TAG OPTIONS")] + #[clap(long, value_name = "TAG[,TAG,..]", help_heading = "Tag options")] remove: Vec, /// Tag list to set (can be specified multiple times) @@ -39,34 +43,29 @@ pub(super) struct Opts { long, value_name = "TAG[,TAG,..]", conflicts_with = "remove", - help_heading = "TAG OPTIONS" + help_heading = "Tag options" )] set: Vec, /// Remove any delete mark #[clap( long, - conflicts_with_all = &["set-delete-never", "set-delete-after"], - help_heading = "DELETE MARK OPTIONS" + conflicts_with_all = &["set_delete_never", "set_delete_after"], + help_heading = "Delete mark options" )] remove_delete: bool, /// Mark snapshot as uneraseable #[clap( long, - conflicts_with = "set-delete-after", - help_heading = "DELETE MARK OPTIONS" + conflicts_with = "set_delete_after", + help_heading = "Delete mark options" )] set_delete_never: bool, /// Mark snapshot to be deleted after given duration (e.g. 10d) - #[clap(long, value_name = "DURATION", help_heading = "DELETE MARK OPTIONS")] + #[clap(long, value_name = "DURATION", help_heading = "Delete mark options")] set_delete_after: Option, - - /// Snapshots to change tags. If none is given, use filter to filter from all - /// snapshots. - #[clap(value_name = "ID")] - ids: Vec, } pub(super) fn execute( diff --git a/src/repofile/snapshotfile.rs b/src/repofile/snapshotfile.rs index edbd8a4..fe044d5 100644 --- a/src/repofile/snapshotfile.rs +++ b/src/repofile/snapshotfile.rs @@ -5,7 +5,7 @@ use std::{cmp::Ordering, fmt::Display}; use anyhow::{anyhow, bail, Result}; use chrono::{DateTime, Duration, Local}; -use clap::{AppSettings, Parser}; +use clap::Parser; use derivative::Derivative; use dunce::canonicalize; use gethostname::gethostname; @@ -25,7 +25,6 @@ use crate::repository::parse_command; #[serde_as] #[derive(Clone, Default, Parser, Deserialize, Merge)] -#[clap(global_setting(AppSettings::DeriveDisplayOrder))] #[serde(default, rename_all = "kebab-case", deny_unknown_fields)] pub struct SnapshotOptions { /// Label snapshot with given label @@ -47,7 +46,7 @@ pub struct SnapshotOptions { description_from: Option, /// Mark snapshot as uneraseable - #[clap(long, conflicts_with = "delete-after")] + #[clap(long, conflicts_with = "delete_after")] #[merge(strategy = merge::bool::overwrite_false)] delete_never: bool, @@ -420,6 +419,7 @@ impl Ord for SnapshotFile { } } +#[derive(Clone)] struct SnapshotFn(FnPtr, AST); impl FromStr for SnapshotFn { type Err = anyhow::Error; diff --git a/src/repository/mod.rs b/src/repository/mod.rs index 3d50448..e234e1a 100644 --- a/src/repository/mod.rs +++ b/src/repository/mod.rs @@ -49,7 +49,6 @@ pub struct RepositoryOptions { short, long, global = true, - parse(from_os_str), env = "RUSTIC_PASSWORD_FILE", conflicts_with = "password" )] @@ -60,7 +59,7 @@ pub struct RepositoryOptions { long, global = true, env = "RUSTIC_PASSWORD_COMMAND", - conflicts_with_all = &["password", "password-file"], + conflicts_with_all = &["password", "password_file"], )] password_command: Option, @@ -73,8 +72,7 @@ pub struct RepositoryOptions { #[clap( long, global = true, - parse(from_os_str), - conflicts_with = "no-cache", + conflicts_with = "no_cache", env = "RUSTIC_CACHE_DIR" )] cache_dir: Option, @@ -85,7 +83,7 @@ pub struct RepositoryOptions { pub(crate) warm_up: bool, /// Warm up needed data pack files by running the command with %id replaced by pack id - #[clap(long, global = true, conflicts_with = "warm-up")] + #[clap(long, global = true, conflicts_with = "warm_up")] pub(crate) warm_up_command: Option, /// Duration (e.g. 10m) to wait after warm up @@ -149,6 +147,13 @@ impl Repository { None => bail!("No repository given. Please use the --repository option."), }; + if let Some(command) = &opts.warm_up_command { + if !command.contains("%id") { + bail!("warm-up command must contain %id!"); + } + info!("using warm-up command {command}"); + } + let be_hot = opts .repo_hot .as_ref()