From dc9b6378f3eb9ca6bfc73dbeb057f58d7ee3e580 Mon Sep 17 00:00:00 2001 From: Michael Eischer Date: Thu, 6 Feb 2025 21:52:02 +0100 Subject: [PATCH 1/8] move cli flags into AddFlags on option structs --- cmd/restic/cmd_backup.go | 99 +++++++++++++++--------------- cmd/restic/cmd_cache.go | 13 ++-- cmd/restic/cmd_check.go | 21 ++++--- cmd/restic/cmd_copy.go | 11 ++-- cmd/restic/cmd_debug.go | 13 ++-- cmd/restic/cmd_diff.go | 9 ++- cmd/restic/cmd_dump.go | 13 ++-- cmd/restic/cmd_find.go | 33 +++++----- cmd/restic/cmd_forget.go | 61 +++++++++--------- cmd/restic/cmd_generate.go | 16 +++-- cmd/restic/cmd_init.go | 13 ++-- cmd/restic/cmd_ls.go | 21 ++++--- cmd/restic/cmd_migrate.go | 8 ++- cmd/restic/cmd_mount.go | 27 ++++---- cmd/restic/cmd_prune.go | 29 +++++---- cmd/restic/cmd_repair_index.go | 10 +-- cmd/restic/cmd_repair_snapshots.go | 15 +++-- cmd/restic/cmd_restore.go | 35 ++++++----- cmd/restic/cmd_rewrite.go | 23 ++++--- cmd/restic/cmd_self_update.go | 9 ++- cmd/restic/cmd_snapshots.go | 25 ++++---- cmd/restic/cmd_stats.go | 11 ++-- cmd/restic/cmd_tag.go | 15 +++-- cmd/restic/cmd_unlock.go | 8 ++- 24 files changed, 307 insertions(+), 231 deletions(-) diff --git a/cmd/restic/cmd_backup.go b/cmd/restic/cmd_backup.go index 93b4556c7..b7f0d7af6 100644 --- a/cmd/restic/cmd_backup.go +++ b/cmd/restic/cmd_backup.go @@ -15,6 +15,7 @@ import ( "time" "github.com/spf13/cobra" + "github.com/spf13/pflag" "golang.org/x/sync/errgroup" "github.com/restic/restic/internal/archiver" @@ -97,6 +98,55 @@ type BackupOptions struct { SkipIfUnchanged bool } +func (opts *BackupOptions) AddFlags(f *pflag.FlagSet) { + f.StringVar(&opts.Parent, "parent", "", "use this parent `snapshot` (default: latest snapshot in the group determined by --group-by and not newer than the timestamp determined by --time)") + opts.GroupBy = restic.SnapshotGroupByOptions{Host: true, Path: true} + f.VarP(&opts.GroupBy, "group-by", "g", "`group` snapshots by host, paths and/or tags, separated by comma (disable grouping with '')") + f.BoolVarP(&opts.Force, "force", "f", false, `force re-reading the source files/directories (overrides the "parent" flag)`) + + opts.ExcludePatternOptions.Add(f) + + f.BoolVarP(&opts.ExcludeOtherFS, "one-file-system", "x", false, "exclude other file systems, don't cross filesystem boundaries and subvolumes") + f.StringArrayVar(&opts.ExcludeIfPresent, "exclude-if-present", nil, "takes `filename[:header]`, exclude contents of directories containing filename (except filename itself) if header of that file is as provided (can be specified multiple times)") + f.BoolVar(&opts.ExcludeCaches, "exclude-caches", false, `excludes cache directories that are marked with a CACHEDIR.TAG file. See https://bford.info/cachedir/ for the Cache Directory Tagging Standard`) + f.StringVar(&opts.ExcludeLargerThan, "exclude-larger-than", "", "max `size` of the files to be backed up (allowed suffixes: k/K, m/M, g/G, t/T)") + f.BoolVar(&opts.Stdin, "stdin", false, "read backup from stdin") + f.StringVar(&opts.StdinFilename, "stdin-filename", "stdin", "`filename` to use when reading from stdin") + f.BoolVar(&opts.StdinCommand, "stdin-from-command", false, "interpret arguments as command to execute and store its stdout") + f.Var(&opts.Tags, "tag", "add `tags` for the new snapshot in the format `tag[,tag,...]` (can be specified multiple times)") + f.UintVar(&opts.ReadConcurrency, "read-concurrency", 0, "read `n` files concurrently (default: $RESTIC_READ_CONCURRENCY or 2)") + f.StringVarP(&opts.Host, "host", "H", "", "set the `hostname` for the snapshot manually (default: $RESTIC_HOST). To prevent an expensive rescan use the \"parent\" flag") + f.StringVar(&opts.Host, "hostname", "", "set the `hostname` for the snapshot manually") + err := f.MarkDeprecated("hostname", "use --host") + if err != nil { + // MarkDeprecated only returns an error when the flag could not be found + panic(err) + } + f.StringArrayVar(&opts.FilesFrom, "files-from", nil, "read the files to backup from `file` (can be combined with file args; can be specified multiple times)") + f.StringArrayVar(&opts.FilesFromVerbatim, "files-from-verbatim", nil, "read the files to backup from `file` (can be combined with file args; can be specified multiple times)") + f.StringArrayVar(&opts.FilesFromRaw, "files-from-raw", nil, "read the files to backup from `file` (can be combined with file args; can be specified multiple times)") + f.StringVar(&opts.TimeStamp, "time", "", "`time` of the backup (ex. '2012-11-01 22:08:41') (default: now)") + f.BoolVar(&opts.WithAtime, "with-atime", false, "store the atime for all files and directories") + f.BoolVar(&opts.IgnoreInode, "ignore-inode", false, "ignore inode number and ctime changes when checking for modified files") + f.BoolVar(&opts.IgnoreCtime, "ignore-ctime", false, "ignore ctime changes when checking for modified files") + f.BoolVarP(&opts.DryRun, "dry-run", "n", false, "do not upload or write any data, just show what would be done") + f.BoolVar(&opts.NoScan, "no-scan", false, "do not run scanner to estimate size of backup") + if runtime.GOOS == "windows" { + f.BoolVar(&opts.UseFsSnapshot, "use-fs-snapshot", false, "use filesystem snapshot where possible (currently only Windows VSS)") + f.BoolVar(&opts.ExcludeCloudFiles, "exclude-cloud-files", false, "excludes online-only cloud files (such as OneDrive Files On-Demand)") + } + f.BoolVar(&opts.SkipIfUnchanged, "skip-if-unchanged", false, "skip snapshot creation if identical to parent snapshot") + + // parse read concurrency from env, on error the default value will be used + readConcurrency, _ := strconv.ParseUint(os.Getenv("RESTIC_READ_CONCURRENCY"), 10, 32) + opts.ReadConcurrency = uint(readConcurrency) + + // parse host from env, if not exists or empty the default value will be used + if host := os.Getenv("RESTIC_HOST"); host != "" { + opts.Host = host + } +} + var backupOptions BackupOptions var backupFSTestHook func(fs fs.FS) fs.FS @@ -105,54 +155,7 @@ var ErrInvalidSourceData = errors.New("at least one source file could not be rea func init() { cmdRoot.AddCommand(cmdBackup) - - f := cmdBackup.Flags() - f.StringVar(&backupOptions.Parent, "parent", "", "use this parent `snapshot` (default: latest snapshot in the group determined by --group-by and not newer than the timestamp determined by --time)") - backupOptions.GroupBy = restic.SnapshotGroupByOptions{Host: true, Path: true} - f.VarP(&backupOptions.GroupBy, "group-by", "g", "`group` snapshots by host, paths and/or tags, separated by comma (disable grouping with '')") - f.BoolVarP(&backupOptions.Force, "force", "f", false, `force re-reading the source files/directories (overrides the "parent" flag)`) - - backupOptions.ExcludePatternOptions.Add(f) - - f.BoolVarP(&backupOptions.ExcludeOtherFS, "one-file-system", "x", false, "exclude other file systems, don't cross filesystem boundaries and subvolumes") - f.StringArrayVar(&backupOptions.ExcludeIfPresent, "exclude-if-present", nil, "takes `filename[:header]`, exclude contents of directories containing filename (except filename itself) if header of that file is as provided (can be specified multiple times)") - f.BoolVar(&backupOptions.ExcludeCaches, "exclude-caches", false, `excludes cache directories that are marked with a CACHEDIR.TAG file. See https://bford.info/cachedir/ for the Cache Directory Tagging Standard`) - f.StringVar(&backupOptions.ExcludeLargerThan, "exclude-larger-than", "", "max `size` of the files to be backed up (allowed suffixes: k/K, m/M, g/G, t/T)") - f.BoolVar(&backupOptions.Stdin, "stdin", false, "read backup from stdin") - f.StringVar(&backupOptions.StdinFilename, "stdin-filename", "stdin", "`filename` to use when reading from stdin") - f.BoolVar(&backupOptions.StdinCommand, "stdin-from-command", false, "interpret arguments as command to execute and store its stdout") - f.Var(&backupOptions.Tags, "tag", "add `tags` for the new snapshot in the format `tag[,tag,...]` (can be specified multiple times)") - f.UintVar(&backupOptions.ReadConcurrency, "read-concurrency", 0, "read `n` files concurrently (default: $RESTIC_READ_CONCURRENCY or 2)") - f.StringVarP(&backupOptions.Host, "host", "H", "", "set the `hostname` for the snapshot manually (default: $RESTIC_HOST). To prevent an expensive rescan use the \"parent\" flag") - f.StringVar(&backupOptions.Host, "hostname", "", "set the `hostname` for the snapshot manually") - err := f.MarkDeprecated("hostname", "use --host") - if err != nil { - // MarkDeprecated only returns an error when the flag could not be found - panic(err) - } - f.StringArrayVar(&backupOptions.FilesFrom, "files-from", nil, "read the files to backup from `file` (can be combined with file args; can be specified multiple times)") - f.StringArrayVar(&backupOptions.FilesFromVerbatim, "files-from-verbatim", nil, "read the files to backup from `file` (can be combined with file args; can be specified multiple times)") - f.StringArrayVar(&backupOptions.FilesFromRaw, "files-from-raw", nil, "read the files to backup from `file` (can be combined with file args; can be specified multiple times)") - f.StringVar(&backupOptions.TimeStamp, "time", "", "`time` of the backup (ex. '2012-11-01 22:08:41') (default: now)") - f.BoolVar(&backupOptions.WithAtime, "with-atime", false, "store the atime for all files and directories") - f.BoolVar(&backupOptions.IgnoreInode, "ignore-inode", false, "ignore inode number and ctime changes when checking for modified files") - f.BoolVar(&backupOptions.IgnoreCtime, "ignore-ctime", false, "ignore ctime changes when checking for modified files") - f.BoolVarP(&backupOptions.DryRun, "dry-run", "n", false, "do not upload or write any data, just show what would be done") - f.BoolVar(&backupOptions.NoScan, "no-scan", false, "do not run scanner to estimate size of backup") - if runtime.GOOS == "windows" { - f.BoolVar(&backupOptions.UseFsSnapshot, "use-fs-snapshot", false, "use filesystem snapshot where possible (currently only Windows VSS)") - f.BoolVar(&backupOptions.ExcludeCloudFiles, "exclude-cloud-files", false, "excludes online-only cloud files (such as OneDrive Files On-Demand)") - } - f.BoolVar(&backupOptions.SkipIfUnchanged, "skip-if-unchanged", false, "skip snapshot creation if identical to parent snapshot") - - // parse read concurrency from env, on error the default value will be used - readConcurrency, _ := strconv.ParseUint(os.Getenv("RESTIC_READ_CONCURRENCY"), 10, 32) - backupOptions.ReadConcurrency = uint(readConcurrency) - - // parse host from env, if not exists or empty the default value will be used - if host := os.Getenv("RESTIC_HOST"); host != "" { - backupOptions.Host = host - } + backupOptions.AddFlags(cmdBackup.Flags()) } // filterExisting returns a slice of all existing items, or an error if no diff --git a/cmd/restic/cmd_cache.go b/cmd/restic/cmd_cache.go index cd970b699..009026a9f 100644 --- a/cmd/restic/cmd_cache.go +++ b/cmd/restic/cmd_cache.go @@ -13,6 +13,7 @@ import ( "github.com/restic/restic/internal/ui" "github.com/restic/restic/internal/ui/table" "github.com/spf13/cobra" + "github.com/spf13/pflag" ) var cmdCache = &cobra.Command{ @@ -41,15 +42,17 @@ type CacheOptions struct { NoSize bool } +func (opts *CacheOptions) AddFlags(f *pflag.FlagSet) { + f.BoolVar(&opts.Cleanup, "cleanup", false, "remove old cache directories") + f.UintVar(&opts.MaxAge, "max-age", 30, "max age in `days` for cache directories to be considered old") + f.BoolVar(&opts.NoSize, "no-size", false, "do not output the size of the cache directories") +} + var cacheOptions CacheOptions func init() { cmdRoot.AddCommand(cmdCache) - - f := cmdCache.Flags() - f.BoolVar(&cacheOptions.Cleanup, "cleanup", false, "remove old cache directories") - f.UintVar(&cacheOptions.MaxAge, "max-age", 30, "max age in `days` for cache directories to be considered old") - f.BoolVar(&cacheOptions.NoSize, "no-size", false, "do not output the size of the cache directories") + cacheOptions.AddFlags(cmdCache.Flags()) } func runCache(opts CacheOptions, gopts GlobalOptions, args []string) error { diff --git a/cmd/restic/cmd_check.go b/cmd/restic/cmd_check.go index 99e933af2..2ec4385c8 100644 --- a/cmd/restic/cmd_check.go +++ b/cmd/restic/cmd_check.go @@ -11,6 +11,7 @@ import ( "time" "github.com/spf13/cobra" + "github.com/spf13/pflag" "github.com/restic/restic/internal/backend/cache" "github.com/restic/restic/internal/checker" @@ -68,14 +69,9 @@ type CheckOptions struct { WithCache bool } -var checkOptions CheckOptions - -func init() { - cmdRoot.AddCommand(cmdCheck) - - f := cmdCheck.Flags() - f.BoolVar(&checkOptions.ReadData, "read-data", false, "read all data blobs") - f.StringVar(&checkOptions.ReadDataSubset, "read-data-subset", "", "read a `subset` of data packs, specified as 'n/t' for specific part, or either 'x%' or 'x.y%' or a size in bytes with suffixes k/K, m/M, g/G, t/T for a random subset") +func (opts *CheckOptions) AddFlags(f *pflag.FlagSet) { + f.BoolVar(&opts.ReadData, "read-data", false, "read all data blobs") + f.StringVar(&opts.ReadDataSubset, "read-data-subset", "", "read a `subset` of data packs, specified as 'n/t' for specific part, or either 'x%' or 'x.y%' or a size in bytes with suffixes k/K, m/M, g/G, t/T for a random subset") var ignored bool f.BoolVar(&ignored, "check-unused", false, "find unused blobs") err := f.MarkDeprecated("check-unused", "`--check-unused` is deprecated and will be ignored") @@ -83,7 +79,14 @@ func init() { // MarkDeprecated only returns an error when the flag is not found panic(err) } - f.BoolVar(&checkOptions.WithCache, "with-cache", false, "use existing cache, only read uncached data from repository") + f.BoolVar(&opts.WithCache, "with-cache", false, "use existing cache, only read uncached data from repository") +} + +var checkOptions CheckOptions + +func init() { + cmdRoot.AddCommand(cmdCheck) + checkOptions.AddFlags(cmdCheck.Flags()) } func checkFlags(opts CheckOptions) error { diff --git a/cmd/restic/cmd_copy.go b/cmd/restic/cmd_copy.go index 301e0e180..5d43b71f0 100644 --- a/cmd/restic/cmd_copy.go +++ b/cmd/restic/cmd_copy.go @@ -11,6 +11,7 @@ import ( "golang.org/x/sync/errgroup" "github.com/spf13/cobra" + "github.com/spf13/pflag" ) var cmdCopy = &cobra.Command{ @@ -53,14 +54,16 @@ type CopyOptions struct { restic.SnapshotFilter } +func (opts *CopyOptions) AddFlags(f *pflag.FlagSet) { + initSecondaryRepoOptions(f, &opts.secondaryRepoOptions, "destination", "to copy snapshots from") + initMultiSnapshotFilter(f, &opts.SnapshotFilter, true) +} + var copyOptions CopyOptions func init() { cmdRoot.AddCommand(cmdCopy) - - f := cmdCopy.Flags() - initSecondaryRepoOptions(f, ©Options.secondaryRepoOptions, "destination", "to copy snapshots from") - initMultiSnapshotFilter(f, ©Options.SnapshotFilter, true) + copyOptions.AddFlags(cmdCopy.Flags()) } func runCopy(ctx context.Context, opts CopyOptions, gopts GlobalOptions, args []string) error { diff --git a/cmd/restic/cmd_debug.go b/cmd/restic/cmd_debug.go index 4ce17f899..89e2bebd4 100644 --- a/cmd/restic/cmd_debug.go +++ b/cmd/restic/cmd_debug.go @@ -18,6 +18,7 @@ import ( "github.com/klauspost/compress/zstd" "github.com/spf13/cobra" + "github.com/spf13/pflag" "golang.org/x/sync/errgroup" "github.com/restic/restic/internal/crypto" @@ -64,16 +65,20 @@ type DebugExamineOptions struct { ReuploadBlobs bool } +func (opts *DebugExamineOptions) AddFlags(f *pflag.FlagSet) { + f.BoolVar(&opts.ExtractPack, "extract-pack", false, "write blobs to the current directory") + f.BoolVar(&opts.ReuploadBlobs, "reupload-blobs", false, "reupload blobs to the repository") + f.BoolVar(&opts.TryRepair, "try-repair", false, "try to repair broken blobs with single bit flips") + f.BoolVar(&opts.RepairByte, "repair-byte", false, "try to repair broken blobs by trying bytes") +} + var debugExamineOpts DebugExamineOptions func init() { cmdRoot.AddCommand(cmdDebug) cmdDebug.AddCommand(cmdDebugDump) cmdDebug.AddCommand(cmdDebugExamine) - cmdDebugExamine.Flags().BoolVar(&debugExamineOpts.ExtractPack, "extract-pack", false, "write blobs to the current directory") - cmdDebugExamine.Flags().BoolVar(&debugExamineOpts.ReuploadBlobs, "reupload-blobs", false, "reupload blobs to the repository") - cmdDebugExamine.Flags().BoolVar(&debugExamineOpts.TryRepair, "try-repair", false, "try to repair broken blobs with single bit flips") - cmdDebugExamine.Flags().BoolVar(&debugExamineOpts.RepairByte, "repair-byte", false, "try to repair broken blobs by trying bytes") + debugExamineOpts.AddFlags(cmdDebugExamine.Flags()) } func prettyPrintJSON(wr io.Writer, item interface{}) error { diff --git a/cmd/restic/cmd_diff.go b/cmd/restic/cmd_diff.go index d1067b5ec..2b5bc67c6 100644 --- a/cmd/restic/cmd_diff.go +++ b/cmd/restic/cmd_diff.go @@ -12,6 +12,7 @@ import ( "github.com/restic/restic/internal/restic" "github.com/restic/restic/internal/ui" "github.com/spf13/cobra" + "github.com/spf13/pflag" ) var cmdDiff = &cobra.Command{ @@ -57,13 +58,15 @@ type DiffOptions struct { ShowMetadata bool } +func (opts *DiffOptions) AddFlags(f *pflag.FlagSet) { + f.BoolVar(&opts.ShowMetadata, "metadata", false, "print changes in metadata") +} + var diffOptions DiffOptions func init() { cmdRoot.AddCommand(cmdDiff) - - f := cmdDiff.Flags() - f.BoolVar(&diffOptions.ShowMetadata, "metadata", false, "print changes in metadata") + diffOptions.AddFlags(cmdDiff.Flags()) } func loadSnapshot(ctx context.Context, be restic.Lister, repo restic.LoaderUnpacked, desc string) (*restic.Snapshot, string, error) { diff --git a/cmd/restic/cmd_dump.go b/cmd/restic/cmd_dump.go index 6b7f8d012..4f4a44f9c 100644 --- a/cmd/restic/cmd_dump.go +++ b/cmd/restic/cmd_dump.go @@ -13,6 +13,7 @@ import ( "github.com/restic/restic/internal/restic" "github.com/spf13/cobra" + "github.com/spf13/pflag" ) var cmdDump = &cobra.Command{ @@ -54,15 +55,17 @@ type DumpOptions struct { Target string } +func (opts *DumpOptions) AddFlags(f *pflag.FlagSet) { + initSingleSnapshotFilter(f, &opts.SnapshotFilter) + f.StringVarP(&opts.Archive, "archive", "a", "tar", "set archive `format` as \"tar\" or \"zip\"") + f.StringVarP(&opts.Target, "target", "t", "", "write the output to target `path`") +} + var dumpOptions DumpOptions func init() { cmdRoot.AddCommand(cmdDump) - - flags := cmdDump.Flags() - initSingleSnapshotFilter(flags, &dumpOptions.SnapshotFilter) - flags.StringVarP(&dumpOptions.Archive, "archive", "a", "tar", "set archive `format` as \"tar\" or \"zip\"") - flags.StringVarP(&dumpOptions.Target, "target", "t", "", "write the output to target `path`") + dumpOptions.AddFlags(cmdDump.Flags()) } func splitPath(p string) []string { diff --git a/cmd/restic/cmd_find.go b/cmd/restic/cmd_find.go index 2fcef5741..04b16ef8b 100644 --- a/cmd/restic/cmd_find.go +++ b/cmd/restic/cmd_find.go @@ -8,6 +8,7 @@ import ( "time" "github.com/spf13/cobra" + "github.com/spf13/pflag" "github.com/restic/restic/internal/debug" "github.com/restic/restic/internal/errors" @@ -62,25 +63,27 @@ type FindOptions struct { restic.SnapshotFilter } +func (opts *FindOptions) AddFlags(f *pflag.FlagSet) { + f.StringVarP(&opts.Oldest, "oldest", "O", "", "oldest modification date/time") + f.StringVarP(&opts.Newest, "newest", "N", "", "newest modification date/time") + f.StringArrayVarP(&opts.Snapshots, "snapshot", "s", nil, "snapshot `id` to search in (can be given multiple times)") + f.BoolVar(&opts.BlobID, "blob", false, "pattern is a blob-ID") + f.BoolVar(&opts.TreeID, "tree", false, "pattern is a tree-ID") + f.BoolVar(&opts.PackID, "pack", false, "pattern is a pack-ID") + f.BoolVar(&opts.ShowPackID, "show-pack-id", false, "display the pack-ID the blobs belong to (with --blob or --tree)") + f.BoolVarP(&opts.CaseInsensitive, "ignore-case", "i", false, "ignore case for pattern") + f.BoolVarP(&opts.Reverse, "reverse", "R", false, "reverse sort order oldest to newest") + f.BoolVarP(&opts.ListLong, "long", "l", false, "use a long listing format showing size and mode") + f.BoolVar(&opts.HumanReadable, "human-readable", false, "print sizes in human readable format") + + initMultiSnapshotFilter(f, &opts.SnapshotFilter, true) +} + var findOptions FindOptions func init() { cmdRoot.AddCommand(cmdFind) - - f := cmdFind.Flags() - f.StringVarP(&findOptions.Oldest, "oldest", "O", "", "oldest modification date/time") - f.StringVarP(&findOptions.Newest, "newest", "N", "", "newest modification date/time") - f.StringArrayVarP(&findOptions.Snapshots, "snapshot", "s", nil, "snapshot `id` to search in (can be given multiple times)") - f.BoolVar(&findOptions.BlobID, "blob", false, "pattern is a blob-ID") - f.BoolVar(&findOptions.TreeID, "tree", false, "pattern is a tree-ID") - f.BoolVar(&findOptions.PackID, "pack", false, "pattern is a pack-ID") - f.BoolVar(&findOptions.ShowPackID, "show-pack-id", false, "display the pack-ID the blobs belong to (with --blob or --tree)") - f.BoolVarP(&findOptions.CaseInsensitive, "ignore-case", "i", false, "ignore case for pattern") - f.BoolVarP(&findOptions.Reverse, "reverse", "R", false, "reverse sort order oldest to newest") - f.BoolVarP(&findOptions.ListLong, "long", "l", false, "use a long listing format showing size and mode") - f.BoolVar(&findOptions.HumanReadable, "human-readable", false, "print sizes in human readable format") - - initMultiSnapshotFilter(f, &findOptions.SnapshotFilter, true) + findOptions.AddFlags(cmdFind.Flags()) } type findPattern struct { diff --git a/cmd/restic/cmd_forget.go b/cmd/restic/cmd_forget.go index f9ae85cd1..572caae42 100644 --- a/cmd/restic/cmd_forget.go +++ b/cmd/restic/cmd_forget.go @@ -11,6 +11,7 @@ import ( "github.com/restic/restic/internal/restic" "github.com/restic/restic/internal/ui/termstatus" "github.com/spf13/cobra" + "github.com/spf13/pflag" ) var cmdForget = &cobra.Command{ @@ -111,44 +112,46 @@ type ForgetOptions struct { Prune bool } -var forgetOptions ForgetOptions -var forgetPruneOptions PruneOptions +func (opts *ForgetOptions) AddFlags(f *pflag.FlagSet) { + f.VarP(&opts.Last, "keep-last", "l", "keep the last `n` snapshots (use 'unlimited' to keep all snapshots)") + f.VarP(&opts.Hourly, "keep-hourly", "H", "keep the last `n` hourly snapshots (use 'unlimited' to keep all hourly snapshots)") + f.VarP(&opts.Daily, "keep-daily", "d", "keep the last `n` daily snapshots (use 'unlimited' to keep all daily snapshots)") + f.VarP(&opts.Weekly, "keep-weekly", "w", "keep the last `n` weekly snapshots (use 'unlimited' to keep all weekly snapshots)") + f.VarP(&opts.Monthly, "keep-monthly", "m", "keep the last `n` monthly snapshots (use 'unlimited' to keep all monthly snapshots)") + f.VarP(&opts.Yearly, "keep-yearly", "y", "keep the last `n` yearly snapshots (use 'unlimited' to keep all yearly snapshots)") + f.VarP(&opts.Within, "keep-within", "", "keep snapshots that are newer than `duration` (eg. 1y5m7d2h) relative to the latest snapshot") + f.VarP(&opts.WithinHourly, "keep-within-hourly", "", "keep hourly snapshots that are newer than `duration` (eg. 1y5m7d2h) relative to the latest snapshot") + f.VarP(&opts.WithinDaily, "keep-within-daily", "", "keep daily snapshots that are newer than `duration` (eg. 1y5m7d2h) relative to the latest snapshot") + f.VarP(&opts.WithinWeekly, "keep-within-weekly", "", "keep weekly snapshots that are newer than `duration` (eg. 1y5m7d2h) relative to the latest snapshot") + f.VarP(&opts.WithinMonthly, "keep-within-monthly", "", "keep monthly snapshots that are newer than `duration` (eg. 1y5m7d2h) relative to the latest snapshot") + f.VarP(&opts.WithinYearly, "keep-within-yearly", "", "keep yearly snapshots that are newer than `duration` (eg. 1y5m7d2h) relative to the latest snapshot") + f.Var(&opts.KeepTags, "keep-tag", "keep snapshots with this `taglist` (can be specified multiple times)") + f.BoolVar(&opts.UnsafeAllowRemoveAll, "unsafe-allow-remove-all", false, "allow deleting all snapshots of a snapshot group") -func init() { - cmdRoot.AddCommand(cmdForget) - - f := cmdForget.Flags() - f.VarP(&forgetOptions.Last, "keep-last", "l", "keep the last `n` snapshots (use 'unlimited' to keep all snapshots)") - f.VarP(&forgetOptions.Hourly, "keep-hourly", "H", "keep the last `n` hourly snapshots (use 'unlimited' to keep all hourly snapshots)") - f.VarP(&forgetOptions.Daily, "keep-daily", "d", "keep the last `n` daily snapshots (use 'unlimited' to keep all daily snapshots)") - f.VarP(&forgetOptions.Weekly, "keep-weekly", "w", "keep the last `n` weekly snapshots (use 'unlimited' to keep all weekly snapshots)") - f.VarP(&forgetOptions.Monthly, "keep-monthly", "m", "keep the last `n` monthly snapshots (use 'unlimited' to keep all monthly snapshots)") - f.VarP(&forgetOptions.Yearly, "keep-yearly", "y", "keep the last `n` yearly snapshots (use 'unlimited' to keep all yearly snapshots)") - f.VarP(&forgetOptions.Within, "keep-within", "", "keep snapshots that are newer than `duration` (eg. 1y5m7d2h) relative to the latest snapshot") - f.VarP(&forgetOptions.WithinHourly, "keep-within-hourly", "", "keep hourly snapshots that are newer than `duration` (eg. 1y5m7d2h) relative to the latest snapshot") - f.VarP(&forgetOptions.WithinDaily, "keep-within-daily", "", "keep daily snapshots that are newer than `duration` (eg. 1y5m7d2h) relative to the latest snapshot") - f.VarP(&forgetOptions.WithinWeekly, "keep-within-weekly", "", "keep weekly snapshots that are newer than `duration` (eg. 1y5m7d2h) relative to the latest snapshot") - f.VarP(&forgetOptions.WithinMonthly, "keep-within-monthly", "", "keep monthly snapshots that are newer than `duration` (eg. 1y5m7d2h) relative to the latest snapshot") - f.VarP(&forgetOptions.WithinYearly, "keep-within-yearly", "", "keep yearly snapshots that are newer than `duration` (eg. 1y5m7d2h) relative to the latest snapshot") - f.Var(&forgetOptions.KeepTags, "keep-tag", "keep snapshots with this `taglist` (can be specified multiple times)") - f.BoolVar(&forgetOptions.UnsafeAllowRemoveAll, "unsafe-allow-remove-all", false, "allow deleting all snapshots of a snapshot group") - - initMultiSnapshotFilter(f, &forgetOptions.SnapshotFilter, false) - f.StringArrayVar(&forgetOptions.Hosts, "hostname", nil, "only consider snapshots with the given `hostname` (can be specified multiple times)") + initMultiSnapshotFilter(f, &opts.SnapshotFilter, false) + f.StringArrayVar(&opts.Hosts, "hostname", nil, "only consider snapshots with the given `hostname` (can be specified multiple times)") err := f.MarkDeprecated("hostname", "use --host") if err != nil { // MarkDeprecated only returns an error when the flag is not found panic(err) } - f.BoolVarP(&forgetOptions.Compact, "compact", "c", false, "use compact output format") - forgetOptions.GroupBy = restic.SnapshotGroupByOptions{Host: true, Path: true} - f.VarP(&forgetOptions.GroupBy, "group-by", "g", "`group` snapshots by host, paths and/or tags, separated by comma (disable grouping with '')") - f.BoolVarP(&forgetOptions.DryRun, "dry-run", "n", false, "do not delete anything, just print what would be done") - f.BoolVar(&forgetOptions.Prune, "prune", false, "automatically run the 'prune' command if snapshots have been removed") + f.BoolVarP(&opts.Compact, "compact", "c", false, "use compact output format") + opts.GroupBy = restic.SnapshotGroupByOptions{Host: true, Path: true} + f.VarP(&opts.GroupBy, "group-by", "g", "`group` snapshots by host, paths and/or tags, separated by comma (disable grouping with '')") + f.BoolVarP(&opts.DryRun, "dry-run", "n", false, "do not delete anything, just print what would be done") + f.BoolVar(&opts.Prune, "prune", false, "automatically run the 'prune' command if snapshots have been removed") f.SortFlags = false - addPruneOptions(cmdForget, &forgetPruneOptions) +} + +var forgetOptions ForgetOptions +var forgetPruneOptions PruneOptions + +func init() { + cmdRoot.AddCommand(cmdForget) + forgetOptions.AddFlags(cmdForget.Flags()) + forgetPruneOptions.AddLimitedFlags(cmdForget.Flags()) } func verifyForgetOptions(opts *ForgetOptions) error { diff --git a/cmd/restic/cmd_generate.go b/cmd/restic/cmd_generate.go index 66b3fa7c5..37d9724e0 100644 --- a/cmd/restic/cmd_generate.go +++ b/cmd/restic/cmd_generate.go @@ -8,6 +8,7 @@ import ( "github.com/restic/restic/internal/errors" "github.com/spf13/cobra" "github.com/spf13/cobra/doc" + "github.com/spf13/pflag" ) var cmdGenerate = &cobra.Command{ @@ -37,16 +38,19 @@ type generateOptions struct { PowerShellCompletionFile string } +func (opts *generateOptions) AddFlags(f *pflag.FlagSet) { + f.StringVar(&opts.ManDir, "man", "", "write man pages to `directory`") + f.StringVar(&opts.BashCompletionFile, "bash-completion", "", "write bash completion `file` (`-` for stdout)") + f.StringVar(&opts.FishCompletionFile, "fish-completion", "", "write fish completion `file` (`-` for stdout)") + f.StringVar(&opts.ZSHCompletionFile, "zsh-completion", "", "write zsh completion `file` (`-` for stdout)") + f.StringVar(&opts.PowerShellCompletionFile, "powershell-completion", "", "write powershell completion `file` (`-` for stdout)") +} + var genOpts generateOptions func init() { cmdRoot.AddCommand(cmdGenerate) - fs := cmdGenerate.Flags() - fs.StringVar(&genOpts.ManDir, "man", "", "write man pages to `directory`") - fs.StringVar(&genOpts.BashCompletionFile, "bash-completion", "", "write bash completion `file` (`-` for stdout)") - fs.StringVar(&genOpts.FishCompletionFile, "fish-completion", "", "write fish completion `file` (`-` for stdout)") - fs.StringVar(&genOpts.ZSHCompletionFile, "zsh-completion", "", "write zsh completion `file` (`-` for stdout)") - fs.StringVar(&genOpts.PowerShellCompletionFile, "powershell-completion", "", "write powershell completion `file` (`-` for stdout)") + genOpts.AddFlags(cmdGenerate.Flags()) } func writeManpages(dir string) error { diff --git a/cmd/restic/cmd_init.go b/cmd/restic/cmd_init.go index 2a2aae1dc..f3f22d1d7 100644 --- a/cmd/restic/cmd_init.go +++ b/cmd/restic/cmd_init.go @@ -12,6 +12,7 @@ import ( "github.com/restic/restic/internal/restic" "github.com/spf13/cobra" + "github.com/spf13/pflag" ) var cmdInit = &cobra.Command{ @@ -40,15 +41,17 @@ type InitOptions struct { RepositoryVersion string } +func (opts *InitOptions) AddFlags(f *pflag.FlagSet) { + initSecondaryRepoOptions(f, &opts.secondaryRepoOptions, "secondary", "to copy chunker parameters from") + f.BoolVar(&opts.CopyChunkerParameters, "copy-chunker-params", false, "copy chunker parameters from the secondary repository (useful with the copy command)") + f.StringVar(&opts.RepositoryVersion, "repository-version", "stable", "repository format version to use, allowed values are a format version, 'latest' and 'stable'") +} + var initOptions InitOptions func init() { cmdRoot.AddCommand(cmdInit) - - f := cmdInit.Flags() - initSecondaryRepoOptions(f, &initOptions.secondaryRepoOptions, "secondary", "to copy chunker parameters from") - f.BoolVar(&initOptions.CopyChunkerParameters, "copy-chunker-params", false, "copy chunker parameters from the secondary repository (useful with the copy command)") - f.StringVar(&initOptions.RepositoryVersion, "repository-version", "stable", "repository format version to use, allowed values are a format version, 'latest' and 'stable'") + initOptions.AddFlags(cmdInit.Flags()) } func runInit(ctx context.Context, opts InitOptions, gopts GlobalOptions, args []string) error { diff --git a/cmd/restic/cmd_ls.go b/cmd/restic/cmd_ls.go index 373a31a40..7fda0b043 100644 --- a/cmd/restic/cmd_ls.go +++ b/cmd/restic/cmd_ls.go @@ -13,6 +13,7 @@ import ( "time" "github.com/spf13/cobra" + "github.com/spf13/pflag" "github.com/restic/restic/internal/errors" "github.com/restic/restic/internal/fs" @@ -70,19 +71,21 @@ type LsOptions struct { Reverse bool } +func (opts *LsOptions) AddFlags(f *pflag.FlagSet) { + initSingleSnapshotFilter(f, &opts.SnapshotFilter) + f.BoolVarP(&opts.ListLong, "long", "l", false, "use a long listing format showing size and mode") + f.BoolVar(&opts.Recursive, "recursive", false, "include files in subfolders of the listed directories") + f.BoolVar(&opts.HumanReadable, "human-readable", false, "print sizes in human readable format") + f.BoolVar(&opts.Ncdu, "ncdu", false, "output NCDU export format (pipe into 'ncdu -f -')") + f.VarP(&opts.Sort, "sort", "s", "sort output by (name|size|time=mtime|atime|ctime|extension)") + f.BoolVar(&opts.Reverse, "reverse", false, "reverse sorted output") +} + var lsOptions LsOptions func init() { cmdRoot.AddCommand(cmdLs) - - flags := cmdLs.Flags() - initSingleSnapshotFilter(flags, &lsOptions.SnapshotFilter) - flags.BoolVarP(&lsOptions.ListLong, "long", "l", false, "use a long listing format showing size and mode") - flags.BoolVar(&lsOptions.Recursive, "recursive", false, "include files in subfolders of the listed directories") - flags.BoolVar(&lsOptions.HumanReadable, "human-readable", false, "print sizes in human readable format") - flags.BoolVar(&lsOptions.Ncdu, "ncdu", false, "output NCDU export format (pipe into 'ncdu -f -')") - flags.VarP(&lsOptions.Sort, "sort", "s", "sort output by (name|size|time=mtime|atime|ctime|extension)") - flags.BoolVar(&lsOptions.Reverse, "reverse", false, "reverse sorted output") + lsOptions.AddFlags(cmdLs.Flags()) } type lsPrinter interface { diff --git a/cmd/restic/cmd_migrate.go b/cmd/restic/cmd_migrate.go index f6c28e383..4a5ecf84b 100644 --- a/cmd/restic/cmd_migrate.go +++ b/cmd/restic/cmd_migrate.go @@ -9,6 +9,7 @@ import ( "github.com/restic/restic/internal/ui/termstatus" "github.com/spf13/cobra" + "github.com/spf13/pflag" ) var cmdMigrate = &cobra.Command{ @@ -42,12 +43,15 @@ type MigrateOptions struct { Force bool } +func (opts *MigrateOptions) AddFlags(f *pflag.FlagSet) { + f.BoolVarP(&opts.Force, "force", "f", false, `apply a migration a second time`) +} + var migrateOptions MigrateOptions func init() { cmdRoot.AddCommand(cmdMigrate) - f := cmdMigrate.Flags() - f.BoolVarP(&migrateOptions.Force, "force", "f", false, `apply a migration a second time`) + migrateOptions.AddFlags(cmdMigrate.Flags()) } func checkMigrations(ctx context.Context, repo restic.Repository, printer progress.Printer) error { diff --git a/cmd/restic/cmd_mount.go b/cmd/restic/cmd_mount.go index b8a66dc90..21c9d62f4 100644 --- a/cmd/restic/cmd_mount.go +++ b/cmd/restic/cmd_mount.go @@ -10,6 +10,7 @@ import ( "time" "github.com/spf13/cobra" + "github.com/spf13/pflag" "github.com/restic/restic/internal/debug" "github.com/restic/restic/internal/errors" @@ -86,22 +87,24 @@ type MountOptions struct { PathTemplates []string } +func (opts *MountOptions) AddFlags(f *pflag.FlagSet) { + f.BoolVar(&opts.OwnerRoot, "owner-root", false, "use 'root' as the owner of files and dirs") + f.BoolVar(&opts.AllowOther, "allow-other", false, "allow other users to access the data in the mounted directory") + f.BoolVar(&opts.NoDefaultPermissions, "no-default-permissions", false, "for 'allow-other', ignore Unix permissions and allow users to read all snapshot files") + + initMultiSnapshotFilter(f, &opts.SnapshotFilter, true) + + f.StringArrayVar(&opts.PathTemplates, "path-template", nil, "set `template` for path names (can be specified multiple times)") + f.StringVar(&opts.TimeTemplate, "snapshot-template", time.RFC3339, "set `template` to use for snapshot dirs") + f.StringVar(&opts.TimeTemplate, "time-template", time.RFC3339, "set `template` to use for times") + _ = f.MarkDeprecated("snapshot-template", "use --time-template") +} + var mountOptions MountOptions func init() { cmdRoot.AddCommand(cmdMount) - - mountFlags := cmdMount.Flags() - mountFlags.BoolVar(&mountOptions.OwnerRoot, "owner-root", false, "use 'root' as the owner of files and dirs") - mountFlags.BoolVar(&mountOptions.AllowOther, "allow-other", false, "allow other users to access the data in the mounted directory") - mountFlags.BoolVar(&mountOptions.NoDefaultPermissions, "no-default-permissions", false, "for 'allow-other', ignore Unix permissions and allow users to read all snapshot files") - - initMultiSnapshotFilter(mountFlags, &mountOptions.SnapshotFilter, true) - - mountFlags.StringArrayVar(&mountOptions.PathTemplates, "path-template", nil, "set `template` for path names (can be specified multiple times)") - mountFlags.StringVar(&mountOptions.TimeTemplate, "snapshot-template", time.RFC3339, "set `template` to use for snapshot dirs") - mountFlags.StringVar(&mountOptions.TimeTemplate, "time-template", time.RFC3339, "set `template` to use for times") - _ = mountFlags.MarkDeprecated("snapshot-template", "use --time-template") + mountOptions.AddFlags(cmdMount.Flags()) } func runMount(ctx context.Context, opts MountOptions, gopts GlobalOptions, args []string) error { diff --git a/cmd/restic/cmd_prune.go b/cmd/restic/cmd_prune.go index fce109bdd..99b69c093 100644 --- a/cmd/restic/cmd_prune.go +++ b/cmd/restic/cmd_prune.go @@ -16,6 +16,7 @@ import ( "github.com/restic/restic/internal/ui/termstatus" "github.com/spf13/cobra" + "github.com/spf13/pflag" ) var cmdPrune = &cobra.Command{ @@ -61,23 +62,25 @@ type PruneOptions struct { RepackUncompressed bool } +func (opts *PruneOptions) AddFlags(f *pflag.FlagSet) { + opts.AddLimitedFlags(f) + f.BoolVarP(&opts.DryRun, "dry-run", "n", false, "do not modify the repository, just print what would be done") + f.StringVarP(&opts.UnsafeNoSpaceRecovery, "unsafe-recover-no-free-space", "", "", "UNSAFE, READ THE DOCUMENTATION BEFORE USING! Try to recover a repository stuck with no free space. Do not use without trying out 'prune --max-repack-size 0' first.") +} + +func (opts *PruneOptions) AddLimitedFlags(f *pflag.FlagSet) { + f.StringVar(&opts.MaxUnused, "max-unused", "5%", "tolerate given `limit` of unused data (absolute value in bytes with suffixes k/K, m/M, g/G, t/T, a value in % or the word 'unlimited')") + f.StringVar(&opts.MaxRepackSize, "max-repack-size", "", "stop after repacking this much data in total (allowed suffixes for `size`: k/K, m/M, g/G, t/T)") + f.BoolVar(&opts.RepackCacheableOnly, "repack-cacheable-only", false, "only repack packs which are cacheable") + f.BoolVar(&opts.RepackSmall, "repack-small", false, "repack pack files below 80% of target pack size") + f.BoolVar(&opts.RepackUncompressed, "repack-uncompressed", false, "repack all uncompressed data") +} + var pruneOptions PruneOptions func init() { cmdRoot.AddCommand(cmdPrune) - f := cmdPrune.Flags() - f.BoolVarP(&pruneOptions.DryRun, "dry-run", "n", false, "do not modify the repository, just print what would be done") - f.StringVarP(&pruneOptions.UnsafeNoSpaceRecovery, "unsafe-recover-no-free-space", "", "", "UNSAFE, READ THE DOCUMENTATION BEFORE USING! Try to recover a repository stuck with no free space. Do not use without trying out 'prune --max-repack-size 0' first.") - addPruneOptions(cmdPrune, &pruneOptions) -} - -func addPruneOptions(c *cobra.Command, pruneOptions *PruneOptions) { - f := c.Flags() - f.StringVar(&pruneOptions.MaxUnused, "max-unused", "5%", "tolerate given `limit` of unused data (absolute value in bytes with suffixes k/K, m/M, g/G, t/T, a value in % or the word 'unlimited')") - f.StringVar(&pruneOptions.MaxRepackSize, "max-repack-size", "", "stop after repacking this much data in total (allowed suffixes for `size`: k/K, m/M, g/G, t/T)") - f.BoolVar(&pruneOptions.RepackCacheableOnly, "repack-cacheable-only", false, "only repack packs which are cacheable") - f.BoolVar(&pruneOptions.RepackSmall, "repack-small", false, "repack pack files below 80% of target pack size") - f.BoolVar(&pruneOptions.RepackUncompressed, "repack-uncompressed", false, "repack all uncompressed data") + pruneOptions.AddFlags(cmdPrune.Flags()) } func verifyPruneOptions(opts *PruneOptions) error { diff --git a/cmd/restic/cmd_repair_index.go b/cmd/restic/cmd_repair_index.go index 83c1bfa7f..46ec1db98 100644 --- a/cmd/restic/cmd_repair_index.go +++ b/cmd/restic/cmd_repair_index.go @@ -47,16 +47,18 @@ type RepairIndexOptions struct { ReadAllPacks bool } +func (opts *RepairIndexOptions) AddFlags(f *pflag.FlagSet) { + f.BoolVar(&opts.ReadAllPacks, "read-all-packs", false, "read all pack files to generate new index from scratch") +} + var repairIndexOptions RepairIndexOptions func init() { cmdRepair.AddCommand(cmdRepairIndex) + repairIndexOptions.AddFlags(cmdRepairIndex.Flags()) // add alias for old name cmdRoot.AddCommand(cmdRebuildIndex) - - for _, f := range []*pflag.FlagSet{cmdRepairIndex.Flags(), cmdRebuildIndex.Flags()} { - f.BoolVar(&repairIndexOptions.ReadAllPacks, "read-all-packs", false, "read all pack files to generate new index from scratch") - } + repairIndexOptions.AddFlags(cmdRebuildIndex.Flags()) } func runRebuildIndex(ctx context.Context, opts RepairIndexOptions, gopts GlobalOptions, term *termstatus.Terminal) error { diff --git a/cmd/restic/cmd_repair_snapshots.go b/cmd/restic/cmd_repair_snapshots.go index 34c02b3ff..397f8a600 100644 --- a/cmd/restic/cmd_repair_snapshots.go +++ b/cmd/restic/cmd_repair_snapshots.go @@ -8,6 +8,7 @@ import ( "github.com/restic/restic/internal/walker" "github.com/spf13/cobra" + "github.com/spf13/pflag" ) var cmdRepairSnapshots = &cobra.Command{ @@ -57,16 +58,18 @@ type RepairOptions struct { restic.SnapshotFilter } +func (opts *RepairOptions) AddFlags(f *pflag.FlagSet) { + f.BoolVarP(&opts.DryRun, "dry-run", "n", false, "do not do anything, just print what would be done") + f.BoolVarP(&opts.Forget, "forget", "", false, "remove original snapshots after creating new ones") + + initMultiSnapshotFilter(f, &opts.SnapshotFilter, true) +} + var repairSnapshotOptions RepairOptions func init() { cmdRepair.AddCommand(cmdRepairSnapshots) - flags := cmdRepairSnapshots.Flags() - - flags.BoolVarP(&repairSnapshotOptions.DryRun, "dry-run", "n", false, "do not do anything, just print what would be done") - flags.BoolVarP(&repairSnapshotOptions.Forget, "forget", "", false, "remove original snapshots after creating new ones") - - initMultiSnapshotFilter(flags, &repairSnapshotOptions.SnapshotFilter, true) + repairSnapshotOptions.AddFlags(cmdRepairSnapshots.Flags()) } func runRepairSnapshots(ctx context.Context, gopts GlobalOptions, opts RepairOptions, args []string) error { diff --git a/cmd/restic/cmd_restore.go b/cmd/restic/cmd_restore.go index c930abc31..0382b859f 100644 --- a/cmd/restic/cmd_restore.go +++ b/cmd/restic/cmd_restore.go @@ -15,6 +15,7 @@ import ( "github.com/restic/restic/internal/ui/termstatus" "github.com/spf13/cobra" + "github.com/spf13/pflag" ) var cmdRestore = &cobra.Command{ @@ -63,26 +64,28 @@ type RestoreOptions struct { IncludeXattrPattern []string } +func (opts *RestoreOptions) AddFlags(f *pflag.FlagSet) { + f.StringVarP(&opts.Target, "target", "t", "", "directory to extract data to") + + opts.ExcludePatternOptions.Add(f) + opts.IncludePatternOptions.Add(f) + + f.StringArrayVar(&opts.ExcludeXattrPattern, "exclude-xattr", nil, "exclude xattr by `pattern` (can be specified multiple times)") + f.StringArrayVar(&opts.IncludeXattrPattern, "include-xattr", nil, "include xattr by `pattern` (can be specified multiple times)") + + initSingleSnapshotFilter(f, &opts.SnapshotFilter) + f.BoolVar(&opts.DryRun, "dry-run", false, "do not write any data, just show what would be done") + f.BoolVar(&opts.Sparse, "sparse", false, "restore files as sparse") + f.BoolVar(&opts.Verify, "verify", false, "verify restored files content") + f.Var(&opts.Overwrite, "overwrite", "overwrite behavior, one of (always|if-changed|if-newer|never) (default: always)") + f.BoolVar(&opts.Delete, "delete", false, "delete files from target directory if they do not exist in snapshot. Use '--dry-run -vv' to check what would be deleted") +} + var restoreOptions RestoreOptions func init() { cmdRoot.AddCommand(cmdRestore) - - flags := cmdRestore.Flags() - flags.StringVarP(&restoreOptions.Target, "target", "t", "", "directory to extract data to") - - restoreOptions.ExcludePatternOptions.Add(flags) - restoreOptions.IncludePatternOptions.Add(flags) - - flags.StringArrayVar(&restoreOptions.ExcludeXattrPattern, "exclude-xattr", nil, "exclude xattr by `pattern` (can be specified multiple times)") - flags.StringArrayVar(&restoreOptions.IncludeXattrPattern, "include-xattr", nil, "include xattr by `pattern` (can be specified multiple times)") - - initSingleSnapshotFilter(flags, &restoreOptions.SnapshotFilter) - flags.BoolVar(&restoreOptions.DryRun, "dry-run", false, "do not write any data, just show what would be done") - flags.BoolVar(&restoreOptions.Sparse, "sparse", false, "restore files as sparse") - flags.BoolVar(&restoreOptions.Verify, "verify", false, "verify restored files content") - flags.Var(&restoreOptions.Overwrite, "overwrite", "overwrite behavior, one of (always|if-changed|if-newer|never) (default: always)") - flags.BoolVar(&restoreOptions.Delete, "delete", false, "delete files from target directory if they do not exist in snapshot. Use '--dry-run -vv' to check what would be deleted") + restoreOptions.AddFlags(cmdRestore.Flags()) } func runRestore(ctx context.Context, opts RestoreOptions, gopts GlobalOptions, diff --git a/cmd/restic/cmd_rewrite.go b/cmd/restic/cmd_rewrite.go index f847aa372..3200a1e52 100644 --- a/cmd/restic/cmd_rewrite.go +++ b/cmd/restic/cmd_rewrite.go @@ -5,6 +5,7 @@ import ( "time" "github.com/spf13/cobra" + "github.com/spf13/pflag" "golang.org/x/sync/errgroup" "github.com/restic/restic/internal/debug" @@ -99,20 +100,22 @@ type RewriteOptions struct { filter.ExcludePatternOptions } +func (opts *RewriteOptions) AddFlags(f *pflag.FlagSet) { + f.BoolVarP(&opts.Forget, "forget", "", false, "remove original snapshots after creating new ones") + f.BoolVarP(&opts.DryRun, "dry-run", "n", false, "do not do anything, just print what would be done") + f.StringVar(&opts.Metadata.Hostname, "new-host", "", "replace hostname") + f.StringVar(&opts.Metadata.Time, "new-time", "", "replace time of the backup") + f.BoolVarP(&opts.SnapshotSummary, "snapshot-summary", "s", false, "create snapshot summary record if it does not exist") + + initMultiSnapshotFilter(f, &opts.SnapshotFilter, true) + opts.ExcludePatternOptions.Add(f) +} + var rewriteOptions RewriteOptions func init() { cmdRoot.AddCommand(cmdRewrite) - - f := cmdRewrite.Flags() - f.BoolVarP(&rewriteOptions.Forget, "forget", "", false, "remove original snapshots after creating new ones") - f.BoolVarP(&rewriteOptions.DryRun, "dry-run", "n", false, "do not do anything, just print what would be done") - f.StringVar(&rewriteOptions.Metadata.Hostname, "new-host", "", "replace hostname") - f.StringVar(&rewriteOptions.Metadata.Time, "new-time", "", "replace time of the backup") - f.BoolVarP(&rewriteOptions.SnapshotSummary, "snapshot-summary", "s", false, "create snapshot summary record if it does not exist") - - initMultiSnapshotFilter(f, &rewriteOptions.SnapshotFilter, true) - rewriteOptions.ExcludePatternOptions.Add(f) + rewriteOptions.AddFlags(cmdRewrite.Flags()) } // rewriteFilterFunc returns the filtered tree ID or an error. If a snapshot summary is returned, the snapshot will diff --git a/cmd/restic/cmd_self_update.go b/cmd/restic/cmd_self_update.go index 09c86bf2c..3e218a408 100644 --- a/cmd/restic/cmd_self_update.go +++ b/cmd/restic/cmd_self_update.go @@ -10,6 +10,7 @@ import ( "github.com/restic/restic/internal/errors" "github.com/restic/restic/internal/selfupdate" "github.com/spf13/cobra" + "github.com/spf13/pflag" ) var cmdSelfUpdate = &cobra.Command{ @@ -41,13 +42,15 @@ type SelfUpdateOptions struct { Output string } +func (opts *SelfUpdateOptions) AddFlags(f *pflag.FlagSet) { + f.StringVar(&opts.Output, "output", "", "Save the downloaded file as `filename` (default: running binary itself)") +} + var selfUpdateOptions SelfUpdateOptions func init() { cmdRoot.AddCommand(cmdSelfUpdate) - - flags := cmdSelfUpdate.Flags() - flags.StringVar(&selfUpdateOptions.Output, "output", "", "Save the downloaded file as `filename` (default: running binary itself)") + selfUpdateOptions.AddFlags(cmdSelfUpdate.Flags()) } func runSelfUpdate(ctx context.Context, opts SelfUpdateOptions, gopts GlobalOptions, args []string) error { diff --git a/cmd/restic/cmd_snapshots.go b/cmd/restic/cmd_snapshots.go index f935cec86..b426484d3 100644 --- a/cmd/restic/cmd_snapshots.go +++ b/cmd/restic/cmd_snapshots.go @@ -12,6 +12,7 @@ import ( "github.com/restic/restic/internal/ui" "github.com/restic/restic/internal/ui/table" "github.com/spf13/cobra" + "github.com/spf13/pflag" ) var cmdSnapshots = &cobra.Command{ @@ -45,22 +46,24 @@ type SnapshotOptions struct { GroupBy restic.SnapshotGroupByOptions } -var snapshotOptions SnapshotOptions - -func init() { - cmdRoot.AddCommand(cmdSnapshots) - - f := cmdSnapshots.Flags() - initMultiSnapshotFilter(f, &snapshotOptions.SnapshotFilter, true) - f.BoolVarP(&snapshotOptions.Compact, "compact", "c", false, "use compact output format") - f.BoolVar(&snapshotOptions.Last, "last", false, "only show the last snapshot for each host and path") +func (opts *SnapshotOptions) AddFlags(f *pflag.FlagSet) { + initMultiSnapshotFilter(f, &opts.SnapshotFilter, true) + f.BoolVarP(&opts.Compact, "compact", "c", false, "use compact output format") + f.BoolVar(&opts.Last, "last", false, "only show the last snapshot for each host and path") err := f.MarkDeprecated("last", "use --latest 1") if err != nil { // MarkDeprecated only returns an error when the flag is not found panic(err) } - f.IntVar(&snapshotOptions.Latest, "latest", 0, "only show the last `n` snapshots for each host and path") - f.VarP(&snapshotOptions.GroupBy, "group-by", "g", "`group` snapshots by host, paths and/or tags, separated by comma") + f.IntVar(&opts.Latest, "latest", 0, "only show the last `n` snapshots for each host and path") + f.VarP(&opts.GroupBy, "group-by", "g", "`group` snapshots by host, paths and/or tags, separated by comma") +} + +var snapshotOptions SnapshotOptions + +func init() { + cmdRoot.AddCommand(cmdSnapshots) + snapshotOptions.AddFlags(cmdSnapshots.Flags()) } func runSnapshots(ctx context.Context, opts SnapshotOptions, gopts GlobalOptions, args []string) error { diff --git a/cmd/restic/cmd_stats.go b/cmd/restic/cmd_stats.go index e0b60a29e..c3512c2d5 100644 --- a/cmd/restic/cmd_stats.go +++ b/cmd/restic/cmd_stats.go @@ -18,6 +18,7 @@ import ( "github.com/restic/restic/internal/walker" "github.com/spf13/cobra" + "github.com/spf13/pflag" ) var cmdStats = &cobra.Command{ @@ -70,6 +71,11 @@ type StatsOptions struct { restic.SnapshotFilter } +func (opts *StatsOptions) AddFlags(f *pflag.FlagSet) { + f.StringVar(&opts.countMode, "mode", countModeRestoreSize, "counting mode: restore-size (default), files-by-contents, blobs-per-file or raw-data") + initMultiSnapshotFilter(f, &opts.SnapshotFilter, true) +} + var statsOptions StatsOptions func must(err error) { @@ -80,13 +86,10 @@ func must(err error) { func init() { cmdRoot.AddCommand(cmdStats) - f := cmdStats.Flags() - f.StringVar(&statsOptions.countMode, "mode", countModeRestoreSize, "counting mode: restore-size (default), files-by-contents, blobs-per-file or raw-data") + statsOptions.AddFlags(cmdStats.Flags()) must(cmdStats.RegisterFlagCompletionFunc("mode", func(_ *cobra.Command, _ []string, _ string) ([]string, cobra.ShellCompDirective) { return []string{countModeRestoreSize, countModeUniqueFilesByContents, countModeBlobsPerFile, countModeRawData}, cobra.ShellCompDirectiveDefault })) - - initMultiSnapshotFilter(f, &statsOptions.SnapshotFilter, true) } func runStats(ctx context.Context, opts StatsOptions, gopts GlobalOptions, args []string) error { diff --git a/cmd/restic/cmd_tag.go b/cmd/restic/cmd_tag.go index f71e2556c..ab90cbd4f 100644 --- a/cmd/restic/cmd_tag.go +++ b/cmd/restic/cmd_tag.go @@ -4,6 +4,7 @@ import ( "context" "github.com/spf13/cobra" + "github.com/spf13/pflag" "github.com/restic/restic/internal/debug" "github.com/restic/restic/internal/errors" @@ -50,16 +51,18 @@ type TagOptions struct { RemoveTags restic.TagLists } +func (opts *TagOptions) AddFlags(f *pflag.FlagSet) { + f.Var(&opts.SetTags, "set", "`tags` which will replace the existing tags in the format `tag[,tag,...]` (can be given multiple times)") + f.Var(&opts.AddTags, "add", "`tags` which will be added to the existing tags in the format `tag[,tag,...]` (can be given multiple times)") + f.Var(&opts.RemoveTags, "remove", "`tags` which will be removed from the existing tags in the format `tag[,tag,...]` (can be given multiple times)") + initMultiSnapshotFilter(f, &opts.SnapshotFilter, true) +} + var tagOptions TagOptions func init() { cmdRoot.AddCommand(cmdTag) - - tagFlags := cmdTag.Flags() - tagFlags.Var(&tagOptions.SetTags, "set", "`tags` which will replace the existing tags in the format `tag[,tag,...]` (can be given multiple times)") - tagFlags.Var(&tagOptions.AddTags, "add", "`tags` which will be added to the existing tags in the format `tag[,tag,...]` (can be given multiple times)") - tagFlags.Var(&tagOptions.RemoveTags, "remove", "`tags` which will be removed from the existing tags in the format `tag[,tag,...]` (can be given multiple times)") - initMultiSnapshotFilter(tagFlags, &tagOptions.SnapshotFilter, true) + tagOptions.AddFlags(cmdTag.Flags()) } type changedSnapshot struct { diff --git a/cmd/restic/cmd_unlock.go b/cmd/restic/cmd_unlock.go index 825eb815c..03066451c 100644 --- a/cmd/restic/cmd_unlock.go +++ b/cmd/restic/cmd_unlock.go @@ -5,6 +5,7 @@ import ( "github.com/restic/restic/internal/repository" "github.com/spf13/cobra" + "github.com/spf13/pflag" ) var unlockCmd = &cobra.Command{ @@ -31,12 +32,15 @@ type UnlockOptions struct { RemoveAll bool } +func (opts *UnlockOptions) AddFlags(f *pflag.FlagSet) { + f.BoolVar(&opts.RemoveAll, "remove-all", false, "remove all locks, even non-stale ones") +} + var unlockOptions UnlockOptions func init() { cmdRoot.AddCommand(unlockCmd) - - unlockCmd.Flags().BoolVar(&unlockOptions.RemoveAll, "remove-all", false, "remove all locks, even non-stale ones") + unlockOptions.AddFlags(unlockCmd.Flags()) } func runUnlock(ctx context.Context, opts UnlockOptions, gopts GlobalOptions) error { From aacd6a47e3b1a9f619abff17c002ac5f6c1869b3 Mon Sep 17 00:00:00 2001 From: Michael Eischer Date: Thu, 6 Feb 2025 23:02:00 +0100 Subject: [PATCH 2/8] refactor to use constructor functions to create cobra commands This allows getting rid of the global options variables --- cmd/restic/cmd_backup.go | 57 ++++++++++++----------- cmd/restic/cmd_cache.go | 36 ++++++++------- cmd/restic/cmd_cat.go | 25 ++++++----- cmd/restic/cmd_check.go | 57 ++++++++++++----------- cmd/restic/cmd_copy.go | 35 ++++++++------- cmd/restic/cmd_debug.go | 72 +++++++++++++++++------------- cmd/restic/cmd_diff.go | 36 ++++++++------- cmd/restic/cmd_dump.go | 35 ++++++++------- cmd/restic/cmd_features.go | 50 +++++++++++---------- cmd/restic/cmd_find.go | 38 +++++++++------- cmd/restic/cmd_forget.go | 44 +++++++++--------- cmd/restic/cmd_generate.go | 33 +++++++------- cmd/restic/cmd_init.go | 35 ++++++++------- cmd/restic/cmd_key_add.go | 28 +++++++----- cmd/restic/cmd_key_list.go | 21 +++++---- cmd/restic/cmd_key_passwd.go | 35 +++++++++------ cmd/restic/cmd_key_remove.go | 21 +++++---- cmd/restic/cmd_list.go | 31 +++++++------ cmd/restic/cmd_ls.go | 35 ++++++++------- cmd/restic/cmd_migrate.go | 40 +++++++++-------- cmd/restic/cmd_mount.go | 36 ++++++++------- cmd/restic/cmd_options.go | 39 ++++++++-------- cmd/restic/cmd_prune.go | 40 +++++++++-------- cmd/restic/cmd_recover.go | 23 +++++----- cmd/restic/cmd_repair_index.go | 64 +++++++++++++++++--------- cmd/restic/cmd_repair_packs.go | 25 ++++++----- cmd/restic/cmd_repair_snapshots.go | 34 +++++++------- cmd/restic/cmd_restore.go | 40 +++++++++-------- cmd/restic/cmd_rewrite.go | 36 ++++++++------- cmd/restic/cmd_self_update.go | 34 +++++++------- cmd/restic/cmd_snapshots.go | 36 ++++++++------- cmd/restic/cmd_stats.go | 42 +++++++++-------- cmd/restic/cmd_tag.go | 40 +++++++++-------- cmd/restic/cmd_unlock.go | 35 ++++++++------- cmd/restic/cmd_version.go | 69 ++++++++++++++-------------- 35 files changed, 755 insertions(+), 602 deletions(-) diff --git a/cmd/restic/cmd_backup.go b/cmd/restic/cmd_backup.go index b7f0d7af6..99d49f9e0 100644 --- a/cmd/restic/cmd_backup.go +++ b/cmd/restic/cmd_backup.go @@ -31,10 +31,13 @@ import ( "github.com/restic/restic/internal/ui/termstatus" ) -var cmdBackup = &cobra.Command{ - Use: "backup [flags] [FILE/DIR] ...", - Short: "Create a new backup of files and/or directories", - Long: ` +func newBackupCommand() *cobra.Command { + var opts BackupOptions + + cmd := &cobra.Command{ + Use: "backup [flags] [FILE/DIR] ...", + Short: "Create a new backup of files and/or directories", + Long: ` The "backup" command creates a new snapshot and saves the files and directories given as the arguments. @@ -48,23 +51,31 @@ Exit status is 10 if the repository does not exist. Exit status is 11 if the repository is already locked. Exit status is 12 if the password is incorrect. `, - PreRun: func(_ *cobra.Command, _ []string) { - if backupOptions.Host == "" { - hostname, err := os.Hostname() - if err != nil { - debug.Log("os.Hostname() returned err: %v", err) - return + PreRun: func(_ *cobra.Command, _ []string) { + if opts.Host == "" { + hostname, err := os.Hostname() + if err != nil { + debug.Log("os.Hostname() returned err: %v", err) + return + } + opts.Host = hostname } - backupOptions.Host = hostname - } - }, - GroupID: cmdGroupDefault, - DisableAutoGenTag: true, - RunE: func(cmd *cobra.Command, args []string) error { - term, cancel := setupTermstatus() - defer cancel() - return runBackup(cmd.Context(), backupOptions, globalOptions, term, args) - }, + }, + GroupID: cmdGroupDefault, + DisableAutoGenTag: true, + RunE: func(cmd *cobra.Command, args []string) error { + term, cancel := setupTermstatus() + defer cancel() + return runBackup(cmd.Context(), opts, globalOptions, term, args) + }, + } + + opts.AddFlags(cmd.Flags()) + return cmd +} + +func init() { + cmdRoot.AddCommand(newBackupCommand()) } // BackupOptions bundles all options for the backup command. @@ -147,17 +158,11 @@ func (opts *BackupOptions) AddFlags(f *pflag.FlagSet) { } } -var backupOptions BackupOptions var backupFSTestHook func(fs fs.FS) fs.FS // ErrInvalidSourceData is used to report an incomplete backup var ErrInvalidSourceData = errors.New("at least one source file could not be read") -func init() { - cmdRoot.AddCommand(cmdBackup) - backupOptions.AddFlags(cmdBackup.Flags()) -} - // filterExisting returns a slice of all existing items, or an error if no // items exist at all. func filterExisting(items []string) (result []string, err error) { diff --git a/cmd/restic/cmd_cache.go b/cmd/restic/cmd_cache.go index 009026a9f..f9c6c3063 100644 --- a/cmd/restic/cmd_cache.go +++ b/cmd/restic/cmd_cache.go @@ -16,10 +16,13 @@ import ( "github.com/spf13/pflag" ) -var cmdCache = &cobra.Command{ - Use: "cache", - Short: "Operate on local cache directories", - Long: ` +func newCacheCommand() *cobra.Command { + var opts CacheOptions + + cmd := &cobra.Command{ + Use: "cache", + Short: "Operate on local cache directories", + Long: ` The "cache" command allows listing and cleaning local cache directories. EXIT STATUS @@ -28,11 +31,19 @@ EXIT STATUS Exit status is 0 if the command was successful. Exit status is 1 if there was any error. `, - GroupID: cmdGroupDefault, - DisableAutoGenTag: true, - RunE: func(_ *cobra.Command, args []string) error { - return runCache(cacheOptions, globalOptions, args) - }, + GroupID: cmdGroupDefault, + DisableAutoGenTag: true, + RunE: func(_ *cobra.Command, args []string) error { + return runCache(opts, globalOptions, args) + }, + } + + opts.AddFlags(cmd.Flags()) + return cmd +} + +func init() { + cmdRoot.AddCommand(newCacheCommand()) } // CacheOptions bundles all options for the snapshots command. @@ -48,13 +59,6 @@ func (opts *CacheOptions) AddFlags(f *pflag.FlagSet) { f.BoolVar(&opts.NoSize, "no-size", false, "do not output the size of the cache directories") } -var cacheOptions CacheOptions - -func init() { - cmdRoot.AddCommand(cmdCache) - cacheOptions.AddFlags(cmdCache.Flags()) -} - func runCache(opts CacheOptions, gopts GlobalOptions, args []string) error { if len(args) > 0 { return errors.Fatal("the cache command expects no arguments, only options - please see `restic help cache` for usage and flags") diff --git a/cmd/restic/cmd_cat.go b/cmd/restic/cmd_cat.go index 6160c54df..6d7e68ac9 100644 --- a/cmd/restic/cmd_cat.go +++ b/cmd/restic/cmd_cat.go @@ -14,10 +14,11 @@ import ( var catAllowedCmds = []string{"config", "index", "snapshot", "key", "masterkey", "lock", "pack", "blob", "tree"} -var cmdCat = &cobra.Command{ - Use: "cat [flags] [masterkey|config|pack ID|blob ID|snapshot ID|index ID|key ID|lock ID|tree snapshot:subfolder]", - Short: "Print internal objects to stdout", - Long: ` +func newCatCommand() *cobra.Command { + cmd := &cobra.Command{ + Use: "cat [flags] [masterkey|config|pack ID|blob ID|snapshot ID|index ID|key ID|lock ID|tree snapshot:subfolder]", + Short: "Print internal objects to stdout", + Long: ` The "cat" command is used to print internal objects to stdout. EXIT STATUS @@ -29,16 +30,18 @@ Exit status is 10 if the repository does not exist. Exit status is 11 if the repository is already locked. Exit status is 12 if the password is incorrect. `, - GroupID: cmdGroupDefault, - DisableAutoGenTag: true, - RunE: func(cmd *cobra.Command, args []string) error { - return runCat(cmd.Context(), globalOptions, args) - }, - ValidArgs: catAllowedCmds, + GroupID: cmdGroupDefault, + DisableAutoGenTag: true, + RunE: func(cmd *cobra.Command, args []string) error { + return runCat(cmd.Context(), globalOptions, args) + }, + ValidArgs: catAllowedCmds, + } + return cmd } func init() { - cmdRoot.AddCommand(cmdCat) + cmdRoot.AddCommand(newCatCommand()) } func validateCatArgs(args []string) error { diff --git a/cmd/restic/cmd_check.go b/cmd/restic/cmd_check.go index 2ec4385c8..37d39ee68 100644 --- a/cmd/restic/cmd_check.go +++ b/cmd/restic/cmd_check.go @@ -23,10 +23,12 @@ import ( "github.com/restic/restic/internal/ui/termstatus" ) -var cmdCheck = &cobra.Command{ - Use: "check [flags]", - Short: "Check the repository for errors", - Long: ` +func newCheckCommand() *cobra.Command { + var opts CheckOptions + cmd := &cobra.Command{ + Use: "check [flags]", + Short: "Check the repository for errors", + Long: ` The "check" command tests the repository for errors and reports any errors it finds. It can also be used to read all data and therefore simulate a restore. @@ -42,23 +44,31 @@ Exit status is 10 if the repository does not exist. Exit status is 11 if the repository is already locked. Exit status is 12 if the password is incorrect. `, - GroupID: cmdGroupDefault, - DisableAutoGenTag: true, - RunE: func(cmd *cobra.Command, args []string) error { - term, cancel := setupTermstatus() - defer cancel() - summary, err := runCheck(cmd.Context(), checkOptions, globalOptions, args, term) - if globalOptions.JSON { - if err != nil && summary.NumErrors == 0 { - summary.NumErrors = 1 + GroupID: cmdGroupDefault, + DisableAutoGenTag: true, + RunE: func(cmd *cobra.Command, args []string) error { + term, cancel := setupTermstatus() + defer cancel() + summary, err := runCheck(cmd.Context(), opts, globalOptions, args, term) + if globalOptions.JSON { + if err != nil && summary.NumErrors == 0 { + summary.NumErrors = 1 + } + term.Print(ui.ToJSONString(summary)) } - term.Print(ui.ToJSONString(summary)) - } - return err - }, - PreRunE: func(_ *cobra.Command, _ []string) error { - return checkFlags(checkOptions) - }, + return err + }, + PreRunE: func(_ *cobra.Command, _ []string) error { + return checkFlags(opts) + }, + } + + opts.AddFlags(cmd.Flags()) + return cmd +} + +func init() { + cmdRoot.AddCommand(newCheckCommand()) } // CheckOptions bundles all options for the 'check' command. @@ -82,13 +92,6 @@ func (opts *CheckOptions) AddFlags(f *pflag.FlagSet) { f.BoolVar(&opts.WithCache, "with-cache", false, "use existing cache, only read uncached data from repository") } -var checkOptions CheckOptions - -func init() { - cmdRoot.AddCommand(cmdCheck) - checkOptions.AddFlags(cmdCheck.Flags()) -} - func checkFlags(opts CheckOptions) error { if opts.ReadData && opts.ReadDataSubset != "" { return errors.Fatal("check flags --read-data and --read-data-subset cannot be used together") diff --git a/cmd/restic/cmd_copy.go b/cmd/restic/cmd_copy.go index 5d43b71f0..8ca78a341 100644 --- a/cmd/restic/cmd_copy.go +++ b/cmd/restic/cmd_copy.go @@ -14,10 +14,12 @@ import ( "github.com/spf13/pflag" ) -var cmdCopy = &cobra.Command{ - Use: "copy [flags] [snapshotID ...]", - Short: "Copy snapshots from one repository to another", - Long: ` +func newCopyCommand() *cobra.Command { + var opts CopyOptions + cmd := &cobra.Command{ + Use: "copy [flags] [snapshotID ...]", + Short: "Copy snapshots from one repository to another", + Long: ` The "copy" command copies one or more snapshots from one repository to another. NOTE: This process will have to both download (read) and upload (write) the @@ -41,11 +43,19 @@ Exit status is 10 if the repository does not exist. Exit status is 11 if the repository is already locked. Exit status is 12 if the password is incorrect. `, - GroupID: cmdGroupDefault, - DisableAutoGenTag: true, - RunE: func(cmd *cobra.Command, args []string) error { - return runCopy(cmd.Context(), copyOptions, globalOptions, args) - }, + GroupID: cmdGroupDefault, + DisableAutoGenTag: true, + RunE: func(cmd *cobra.Command, args []string) error { + return runCopy(cmd.Context(), opts, globalOptions, args) + }, + } + + opts.AddFlags(cmd.Flags()) + return cmd +} + +func init() { + cmdRoot.AddCommand(newCopyCommand()) } // CopyOptions bundles all options for the copy command. @@ -59,13 +69,6 @@ func (opts *CopyOptions) AddFlags(f *pflag.FlagSet) { initMultiSnapshotFilter(f, &opts.SnapshotFilter, true) } -var copyOptions CopyOptions - -func init() { - cmdRoot.AddCommand(cmdCopy) - copyOptions.AddFlags(cmdCopy.Flags()) -} - func runCopy(ctx context.Context, opts CopyOptions, gopts GlobalOptions, args []string) error { secondaryGopts, isFromRepo, err := fillSecondaryGlobalOpts(ctx, opts.secondaryRepoOptions, gopts, "destination") if err != nil { diff --git a/cmd/restic/cmd_debug.go b/cmd/restic/cmd_debug.go index 89e2bebd4..9e7f1d38e 100644 --- a/cmd/restic/cmd_debug.go +++ b/cmd/restic/cmd_debug.go @@ -29,17 +29,27 @@ import ( "github.com/restic/restic/internal/restic" ) -var cmdDebug = &cobra.Command{ - Use: "debug", - Short: "Debug commands", - GroupID: cmdGroupDefault, - DisableAutoGenTag: true, +func newDebugCommand() *cobra.Command { + cmd := &cobra.Command{ + Use: "debug", + Short: "Debug commands", + GroupID: cmdGroupDefault, + DisableAutoGenTag: true, + } + cmd.AddCommand(newDebugDumpCommand()) + cmd.AddCommand(newDebugExamineCommand()) + return cmd } -var cmdDebugDump = &cobra.Command{ - Use: "dump [indexes|snapshots|all|packs]", - Short: "Dump data structures", - Long: ` +func init() { + cmdRoot.AddCommand(newDebugCommand()) +} + +func newDebugDumpCommand() *cobra.Command { + cmd := &cobra.Command{ + Use: "dump [indexes|snapshots|all|packs]", + Short: "Dump data structures", + Long: ` The "dump" command dumps data structures from the repository as JSON objects. It is used for debugging purposes only. @@ -52,10 +62,28 @@ Exit status is 10 if the repository does not exist. Exit status is 11 if the repository is already locked. Exit status is 12 if the password is incorrect. `, - DisableAutoGenTag: true, - RunE: func(cmd *cobra.Command, args []string) error { - return runDebugDump(cmd.Context(), globalOptions, args) - }, + DisableAutoGenTag: true, + RunE: func(cmd *cobra.Command, args []string) error { + return runDebugDump(cmd.Context(), globalOptions, args) + }, + } + return cmd +} + +func newDebugExamineCommand() *cobra.Command { + var opts DebugExamineOptions + + cmd := &cobra.Command{ + Use: "examine pack-ID...", + Short: "Examine a pack file", + DisableAutoGenTag: true, + RunE: func(cmd *cobra.Command, args []string) error { + return runDebugExamine(cmd.Context(), globalOptions, opts, args) + }, + } + + opts.AddFlags(cmd.Flags()) + return cmd } type DebugExamineOptions struct { @@ -72,15 +100,6 @@ func (opts *DebugExamineOptions) AddFlags(f *pflag.FlagSet) { f.BoolVar(&opts.RepairByte, "repair-byte", false, "try to repair broken blobs by trying bytes") } -var debugExamineOpts DebugExamineOptions - -func init() { - cmdRoot.AddCommand(cmdDebug) - cmdDebug.AddCommand(cmdDebugDump) - cmdDebug.AddCommand(cmdDebugExamine) - debugExamineOpts.AddFlags(cmdDebugExamine.Flags()) -} - func prettyPrintJSON(wr io.Writer, item interface{}) error { buf, err := json.MarshalIndent(item, "", " ") if err != nil { @@ -197,15 +216,6 @@ func runDebugDump(ctx context.Context, gopts GlobalOptions, args []string) error } } -var cmdDebugExamine = &cobra.Command{ - Use: "examine pack-ID...", - Short: "Examine a pack file", - DisableAutoGenTag: true, - RunE: func(cmd *cobra.Command, args []string) error { - return runDebugExamine(cmd.Context(), globalOptions, debugExamineOpts, args) - }, -} - func tryRepairWithBitflip(ctx context.Context, key *crypto.Key, input []byte, bytewise bool) []byte { if bytewise { Printf(" trying to repair blob by finding a broken byte\n") diff --git a/cmd/restic/cmd_diff.go b/cmd/restic/cmd_diff.go index 2b5bc67c6..d8711e90f 100644 --- a/cmd/restic/cmd_diff.go +++ b/cmd/restic/cmd_diff.go @@ -15,10 +15,13 @@ import ( "github.com/spf13/pflag" ) -var cmdDiff = &cobra.Command{ - Use: "diff [flags] snapshotID snapshotID", - Short: "Show differences between two snapshots", - Long: ` +func newDiffCommand() *cobra.Command { + var opts DiffOptions + + cmd := &cobra.Command{ + Use: "diff [flags] snapshotID snapshotID", + Short: "Show differences between two snapshots", + Long: ` The "diff" command shows differences from the first to the second snapshot. The first characters in each line display what has happened to a particular file or directory: @@ -46,11 +49,19 @@ Exit status is 10 if the repository does not exist. Exit status is 11 if the repository is already locked. Exit status is 12 if the password is incorrect. `, - GroupID: cmdGroupDefault, - DisableAutoGenTag: true, - RunE: func(cmd *cobra.Command, args []string) error { - return runDiff(cmd.Context(), diffOptions, globalOptions, args) - }, + GroupID: cmdGroupDefault, + DisableAutoGenTag: true, + RunE: func(cmd *cobra.Command, args []string) error { + return runDiff(cmd.Context(), opts, globalOptions, args) + }, + } + + opts.AddFlags(cmd.Flags()) + return cmd +} + +func init() { + cmdRoot.AddCommand(newDiffCommand()) } // DiffOptions collects all options for the diff command. @@ -62,13 +73,6 @@ func (opts *DiffOptions) AddFlags(f *pflag.FlagSet) { f.BoolVar(&opts.ShowMetadata, "metadata", false, "print changes in metadata") } -var diffOptions DiffOptions - -func init() { - cmdRoot.AddCommand(cmdDiff) - diffOptions.AddFlags(cmdDiff.Flags()) -} - func loadSnapshot(ctx context.Context, be restic.Lister, repo restic.LoaderUnpacked, desc string) (*restic.Snapshot, string, error) { sn, subfolder, err := restic.FindSnapshot(ctx, be, repo, desc) if err != nil { diff --git a/cmd/restic/cmd_dump.go b/cmd/restic/cmd_dump.go index 4f4a44f9c..9e78dfe82 100644 --- a/cmd/restic/cmd_dump.go +++ b/cmd/restic/cmd_dump.go @@ -16,10 +16,12 @@ import ( "github.com/spf13/pflag" ) -var cmdDump = &cobra.Command{ - Use: "dump [flags] snapshotID file", - Short: "Print a backed-up file to stdout", - Long: ` +func newDumpCommand() *cobra.Command { + var opts DumpOptions + cmd := &cobra.Command{ + Use: "dump [flags] snapshotID file", + Short: "Print a backed-up file to stdout", + Long: ` The "dump" command extracts files from a snapshot from the repository. If a single file is selected, it prints its contents to stdout. Folders are output as a tar (default) or zip file containing the contents of the specified folder. @@ -41,11 +43,19 @@ Exit status is 10 if the repository does not exist. Exit status is 11 if the repository is already locked. Exit status is 12 if the password is incorrect. `, - GroupID: cmdGroupDefault, - DisableAutoGenTag: true, - RunE: func(cmd *cobra.Command, args []string) error { - return runDump(cmd.Context(), dumpOptions, globalOptions, args) - }, + GroupID: cmdGroupDefault, + DisableAutoGenTag: true, + RunE: func(cmd *cobra.Command, args []string) error { + return runDump(cmd.Context(), opts, globalOptions, args) + }, + } + + opts.AddFlags(cmd.Flags()) + return cmd +} + +func init() { + cmdRoot.AddCommand(newDumpCommand()) } // DumpOptions collects all options for the dump command. @@ -61,13 +71,6 @@ func (opts *DumpOptions) AddFlags(f *pflag.FlagSet) { f.StringVarP(&opts.Target, "target", "t", "", "write the output to target `path`") } -var dumpOptions DumpOptions - -func init() { - cmdRoot.AddCommand(cmdDump) - dumpOptions.AddFlags(cmdDump.Flags()) -} - func splitPath(p string) []string { d, f := path.Split(p) if d == "" || d == "/" { diff --git a/cmd/restic/cmd_features.go b/cmd/restic/cmd_features.go index a2f04be31..edb84d3f9 100644 --- a/cmd/restic/cmd_features.go +++ b/cmd/restic/cmd_features.go @@ -10,10 +10,11 @@ import ( "github.com/spf13/cobra" ) -var featuresCmd = &cobra.Command{ - Use: "features", - Short: "Print list of feature flags", - Long: ` +func newFeaturesCommand() *cobra.Command { + cmd := &cobra.Command{ + Use: "features", + Short: "Print list of feature flags", + Long: ` The "features" command prints a list of supported feature flags. To pass feature flags to restic, set the RESTIC_FEATURES environment variable @@ -31,29 +32,32 @@ EXIT STATUS Exit status is 0 if the command was successful. Exit status is 1 if there was any error. `, - GroupID: cmdGroupAdvanced, - DisableAutoGenTag: true, - RunE: func(_ *cobra.Command, args []string) error { - if len(args) != 0 { - return errors.Fatal("the feature command expects no arguments") - } + GroupID: cmdGroupAdvanced, + DisableAutoGenTag: true, + RunE: func(_ *cobra.Command, args []string) error { + if len(args) != 0 { + return errors.Fatal("the feature command expects no arguments") + } - fmt.Printf("All Feature Flags:\n") - flags := feature.Flag.List() + fmt.Printf("All Feature Flags:\n") + flags := feature.Flag.List() - tab := table.New() - tab.AddColumn("Name", "{{ .Name }}") - tab.AddColumn("Type", "{{ .Type }}") - tab.AddColumn("Default", "{{ .Default }}") - tab.AddColumn("Description", "{{ .Description }}") + tab := table.New() + tab.AddColumn("Name", "{{ .Name }}") + tab.AddColumn("Type", "{{ .Type }}") + tab.AddColumn("Default", "{{ .Default }}") + tab.AddColumn("Description", "{{ .Description }}") - for _, flag := range flags { - tab.AddRow(flag) - } - return tab.Write(globalOptions.stdout) - }, + for _, flag := range flags { + tab.AddRow(flag) + } + return tab.Write(globalOptions.stdout) + }, + } + + return cmd } func init() { - cmdRoot.AddCommand(featuresCmd) + cmdRoot.AddCommand(newFeaturesCommand()) } diff --git a/cmd/restic/cmd_find.go b/cmd/restic/cmd_find.go index 04b16ef8b..580ef1c7a 100644 --- a/cmd/restic/cmd_find.go +++ b/cmd/restic/cmd_find.go @@ -17,16 +17,19 @@ import ( "github.com/restic/restic/internal/walker" ) -var cmdFind = &cobra.Command{ - Use: "find [flags] PATTERN...", - Short: "Find a file, a directory or restic IDs", - Long: ` +func newFindCommand() *cobra.Command { + var opts FindOptions + + cmd := &cobra.Command{ + Use: "find [flags] PATTERN...", + Short: "Find a file, a directory or restic IDs", + Long: ` The "find" command searches for files or directories in snapshots stored in the repo. It can also be used to search for restic blobs or trees for troubleshooting. The default sort option for the snapshots is youngest to oldest. To sort the output from oldest to youngest specify --reverse.`, - Example: `restic find config.json + Example: `restic find config.json restic find --json "*.yml" "*.json" restic find --json --blob 420f620f b46ebe8a ddd38656 restic find --show-pack-id --blob 420f620f @@ -42,11 +45,19 @@ Exit status is 10 if the repository does not exist. Exit status is 11 if the repository is already locked. Exit status is 12 if the password is incorrect. `, - GroupID: cmdGroupDefault, - DisableAutoGenTag: true, - RunE: func(cmd *cobra.Command, args []string) error { - return runFind(cmd.Context(), findOptions, globalOptions, args) - }, + GroupID: cmdGroupDefault, + DisableAutoGenTag: true, + RunE: func(cmd *cobra.Command, args []string) error { + return runFind(cmd.Context(), opts, globalOptions, args) + }, + } + + opts.AddFlags(cmd.Flags()) + return cmd +} + +func init() { + cmdRoot.AddCommand(newFindCommand()) } // FindOptions bundles all options for the find command. @@ -79,13 +90,6 @@ func (opts *FindOptions) AddFlags(f *pflag.FlagSet) { initMultiSnapshotFilter(f, &opts.SnapshotFilter, true) } -var findOptions FindOptions - -func init() { - cmdRoot.AddCommand(cmdFind) - findOptions.AddFlags(cmdFind.Flags()) -} - type findPattern struct { oldest, newest time.Time pattern []string diff --git a/cmd/restic/cmd_forget.go b/cmd/restic/cmd_forget.go index 572caae42..04bf92e47 100644 --- a/cmd/restic/cmd_forget.go +++ b/cmd/restic/cmd_forget.go @@ -14,10 +14,14 @@ import ( "github.com/spf13/pflag" ) -var cmdForget = &cobra.Command{ - Use: "forget [flags] [snapshot ID] [...]", - Short: "Remove snapshots from the repository", - Long: ` +func newForgetCommand() *cobra.Command { + var opts ForgetOptions + var pruneOpts PruneOptions + + cmd := &cobra.Command{ + Use: "forget [flags] [snapshot ID] [...]", + Short: "Remove snapshots from the repository", + Long: ` The "forget" command removes snapshots according to a policy. All snapshots are first divided into groups according to "--group-by", and after that the policy specified by the "--keep-*" options is applied to each group individually. @@ -41,13 +45,22 @@ Exit status is 10 if the repository does not exist. Exit status is 11 if the repository is already locked. Exit status is 12 if the password is incorrect. `, - GroupID: cmdGroupDefault, - DisableAutoGenTag: true, - RunE: func(cmd *cobra.Command, args []string) error { - term, cancel := setupTermstatus() - defer cancel() - return runForget(cmd.Context(), forgetOptions, forgetPruneOptions, globalOptions, term, args) - }, + GroupID: cmdGroupDefault, + DisableAutoGenTag: true, + RunE: func(cmd *cobra.Command, args []string) error { + term, cancel := setupTermstatus() + defer cancel() + return runForget(cmd.Context(), opts, pruneOpts, globalOptions, term, args) + }, + } + + opts.AddFlags(cmd.Flags()) + pruneOpts.AddLimitedFlags(cmd.Flags()) + return cmd +} + +func init() { + cmdRoot.AddCommand(newForgetCommand()) } type ForgetPolicyCount int @@ -145,15 +158,6 @@ func (opts *ForgetOptions) AddFlags(f *pflag.FlagSet) { f.SortFlags = false } -var forgetOptions ForgetOptions -var forgetPruneOptions PruneOptions - -func init() { - cmdRoot.AddCommand(cmdForget) - forgetOptions.AddFlags(cmdForget.Flags()) - forgetPruneOptions.AddLimitedFlags(cmdForget.Flags()) -} - func verifyForgetOptions(opts *ForgetOptions) error { if opts.Last < -1 || opts.Hourly < -1 || opts.Daily < -1 || opts.Weekly < -1 || opts.Monthly < -1 || opts.Yearly < -1 { diff --git a/cmd/restic/cmd_generate.go b/cmd/restic/cmd_generate.go index 37d9724e0..fb2d89270 100644 --- a/cmd/restic/cmd_generate.go +++ b/cmd/restic/cmd_generate.go @@ -11,10 +11,13 @@ import ( "github.com/spf13/pflag" ) -var cmdGenerate = &cobra.Command{ - Use: "generate [flags]", - Short: "Generate manual pages and auto-completion files (bash, fish, zsh, powershell)", - Long: ` +func newGenerateCommand() *cobra.Command { + var opts generateOptions + + cmd := &cobra.Command{ + Use: "generate [flags]", + Short: "Generate manual pages and auto-completion files (bash, fish, zsh, powershell)", + Long: ` The "generate" command writes automatically generated files (like the man pages and the auto-completion files for bash, fish and zsh). @@ -24,10 +27,17 @@ EXIT STATUS Exit status is 0 if the command was successful. Exit status is 1 if there was any error. `, - DisableAutoGenTag: true, - RunE: func(_ *cobra.Command, args []string) error { - return runGenerate(genOpts, args) - }, + DisableAutoGenTag: true, + RunE: func(_ *cobra.Command, args []string) error { + return runGenerate(opts, args) + }, + } + opts.AddFlags(cmd.Flags()) + return cmd +} + +func init() { + cmdRoot.AddCommand(newGenerateCommand()) } type generateOptions struct { @@ -46,13 +56,6 @@ func (opts *generateOptions) AddFlags(f *pflag.FlagSet) { f.StringVar(&opts.PowerShellCompletionFile, "powershell-completion", "", "write powershell completion `file` (`-` for stdout)") } -var genOpts generateOptions - -func init() { - cmdRoot.AddCommand(cmdGenerate) - genOpts.AddFlags(cmdGenerate.Flags()) -} - func writeManpages(dir string) error { // use a fixed date for the man pages so that generating them is deterministic date, err := time.Parse("Jan 2006", "Jan 2017") diff --git a/cmd/restic/cmd_init.go b/cmd/restic/cmd_init.go index f3f22d1d7..0e1da7a78 100644 --- a/cmd/restic/cmd_init.go +++ b/cmd/restic/cmd_init.go @@ -15,10 +15,13 @@ import ( "github.com/spf13/pflag" ) -var cmdInit = &cobra.Command{ - Use: "init", - Short: "Initialize a new repository", - Long: ` +func newInitCommand() *cobra.Command { + var opts InitOptions + + cmd := &cobra.Command{ + Use: "init", + Short: "Initialize a new repository", + Long: ` The "init" command initializes a new repository. EXIT STATUS @@ -27,11 +30,18 @@ EXIT STATUS Exit status is 0 if the command was successful. Exit status is 1 if there was any error. `, - GroupID: cmdGroupDefault, - DisableAutoGenTag: true, - RunE: func(cmd *cobra.Command, args []string) error { - return runInit(cmd.Context(), initOptions, globalOptions, args) - }, + GroupID: cmdGroupDefault, + DisableAutoGenTag: true, + RunE: func(cmd *cobra.Command, args []string) error { + return runInit(cmd.Context(), opts, globalOptions, args) + }, + } + opts.AddFlags(cmd.Flags()) + return cmd +} + +func init() { + cmdRoot.AddCommand(newInitCommand()) } // InitOptions bundles all options for the init command. @@ -47,13 +57,6 @@ func (opts *InitOptions) AddFlags(f *pflag.FlagSet) { f.StringVar(&opts.RepositoryVersion, "repository-version", "stable", "repository format version to use, allowed values are a format version, 'latest' and 'stable'") } -var initOptions InitOptions - -func init() { - cmdRoot.AddCommand(cmdInit) - initOptions.AddFlags(cmdInit.Flags()) -} - func runInit(ctx context.Context, opts InitOptions, gopts GlobalOptions, args []string) error { if len(args) > 0 { return errors.Fatal("the init command expects no arguments, only options - please see `restic help init` for usage and flags") diff --git a/cmd/restic/cmd_key_add.go b/cmd/restic/cmd_key_add.go index 2737410a0..1bcf50d79 100644 --- a/cmd/restic/cmd_key_add.go +++ b/cmd/restic/cmd_key_add.go @@ -10,10 +10,13 @@ import ( "github.com/spf13/pflag" ) -var cmdKeyAdd = &cobra.Command{ - Use: "add", - Short: "Add a new key (password) to the repository; returns the new key ID", - Long: ` +func newKeyAddCommand() *cobra.Command { + var opts KeyAddOptions + + cmd := &cobra.Command{ + Use: "add", + Short: "Add a new key (password) to the repository; returns the new key ID", + Long: ` The "add" sub-command creates a new key and validates the key. Returns the new key ID. EXIT STATUS @@ -25,7 +28,14 @@ Exit status is 10 if the repository does not exist. Exit status is 11 if the repository is already locked. Exit status is 12 if the password is incorrect. `, - DisableAutoGenTag: true, + DisableAutoGenTag: true, + RunE: func(cmd *cobra.Command, args []string) error { + return runKeyAdd(cmd.Context(), globalOptions, opts, args) + }, + } + + opts.Add(cmd.Flags()) + return cmd } type KeyAddOptions struct { @@ -43,13 +53,7 @@ func (opts *KeyAddOptions) Add(flags *pflag.FlagSet) { } func init() { - cmdKey.AddCommand(cmdKeyAdd) - - var keyAddOpts KeyAddOptions - keyAddOpts.Add(cmdKeyAdd.Flags()) - cmdKeyAdd.RunE = func(cmd *cobra.Command, args []string) error { - return runKeyAdd(cmd.Context(), globalOptions, keyAddOpts, args) - } + cmdKey.AddCommand(newKeyAddCommand()) } func runKeyAdd(ctx context.Context, gopts GlobalOptions, opts KeyAddOptions, args []string) error { diff --git a/cmd/restic/cmd_key_list.go b/cmd/restic/cmd_key_list.go index 1c70cce8a..c97955aee 100644 --- a/cmd/restic/cmd_key_list.go +++ b/cmd/restic/cmd_key_list.go @@ -12,10 +12,11 @@ import ( "github.com/spf13/cobra" ) -var cmdKeyList = &cobra.Command{ - Use: "list", - Short: "List keys (passwords)", - Long: ` +func newKeyListCommand() *cobra.Command { + cmd := &cobra.Command{ + Use: "list", + Short: "List keys (passwords)", + Long: ` The "list" sub-command lists all the keys (passwords) associated with the repository. Returns the key ID, username, hostname, created time and if it's the current key being used to access the repository. @@ -29,14 +30,16 @@ Exit status is 10 if the repository does not exist. Exit status is 11 if the repository is already locked. Exit status is 12 if the password is incorrect. `, - DisableAutoGenTag: true, - RunE: func(cmd *cobra.Command, args []string) error { - return runKeyList(cmd.Context(), globalOptions, args) - }, + DisableAutoGenTag: true, + RunE: func(cmd *cobra.Command, args []string) error { + return runKeyList(cmd.Context(), globalOptions, args) + }, + } + return cmd } func init() { - cmdKey.AddCommand(cmdKeyList) + cmdKey.AddCommand(newKeyListCommand()) } func runKeyList(ctx context.Context, gopts GlobalOptions, args []string) error { diff --git a/cmd/restic/cmd_key_passwd.go b/cmd/restic/cmd_key_passwd.go index 9bb141749..71f71955e 100644 --- a/cmd/restic/cmd_key_passwd.go +++ b/cmd/restic/cmd_key_passwd.go @@ -7,12 +7,16 @@ import ( "github.com/restic/restic/internal/errors" "github.com/restic/restic/internal/repository" "github.com/spf13/cobra" + "github.com/spf13/pflag" ) -var cmdKeyPasswd = &cobra.Command{ - Use: "passwd", - Short: "Change key (password); creates a new key ID and removes the old key ID, returns new key ID", - Long: ` +func newKeyPasswdCommand() *cobra.Command { + var opts KeyPasswdOptions + + cmd := &cobra.Command{ + Use: "passwd", + Short: "Change key (password); creates a new key ID and removes the old key ID, returns new key ID", + Long: ` The "passwd" sub-command creates a new key, validates the key and remove the old key ID. Returns the new key ID. @@ -25,21 +29,26 @@ Exit status is 10 if the repository does not exist. Exit status is 11 if the repository is already locked. Exit status is 12 if the password is incorrect. `, - DisableAutoGenTag: true, + DisableAutoGenTag: true, + RunE: func(cmd *cobra.Command, args []string) error { + return runKeyPasswd(cmd.Context(), globalOptions, opts, args) + }, + } + + opts.AddFlags(cmd.Flags()) + return cmd +} + +func init() { + cmdKey.AddCommand(newKeyPasswdCommand()) } type KeyPasswdOptions struct { KeyAddOptions } -func init() { - cmdKey.AddCommand(cmdKeyPasswd) - - var keyPasswdOpts KeyPasswdOptions - keyPasswdOpts.KeyAddOptions.Add(cmdKeyPasswd.Flags()) - cmdKeyPasswd.RunE = func(cmd *cobra.Command, args []string) error { - return runKeyPasswd(cmd.Context(), globalOptions, keyPasswdOpts, args) - } +func (opts *KeyPasswdOptions) AddFlags(flags *pflag.FlagSet) { + opts.KeyAddOptions.Add(flags) } func runKeyPasswd(ctx context.Context, gopts GlobalOptions, opts KeyPasswdOptions, args []string) error { diff --git a/cmd/restic/cmd_key_remove.go b/cmd/restic/cmd_key_remove.go index 3cb2e0bd7..e3849eb3d 100644 --- a/cmd/restic/cmd_key_remove.go +++ b/cmd/restic/cmd_key_remove.go @@ -10,10 +10,11 @@ import ( "github.com/spf13/cobra" ) -var cmdKeyRemove = &cobra.Command{ - Use: "remove [ID]", - Short: "Remove key ID (password) from the repository.", - Long: ` +func newKeyRemoveCommand() *cobra.Command { + cmd := &cobra.Command{ + Use: "remove [ID]", + Short: "Remove key ID (password) from the repository.", + Long: ` The "remove" sub-command removes the selected key ID. The "remove" command does not allow removing the current key being used to access the repository. @@ -26,14 +27,16 @@ Exit status is 10 if the repository does not exist. Exit status is 11 if the repository is already locked. Exit status is 12 if the password is incorrect. `, - DisableAutoGenTag: true, - RunE: func(cmd *cobra.Command, args []string) error { - return runKeyRemove(cmd.Context(), globalOptions, args) - }, + DisableAutoGenTag: true, + RunE: func(cmd *cobra.Command, args []string) error { + return runKeyRemove(cmd.Context(), globalOptions, args) + }, + } + return cmd } func init() { - cmdKey.AddCommand(cmdKeyRemove) + cmdKey.AddCommand(newKeyRemoveCommand()) } func runKeyRemove(ctx context.Context, gopts GlobalOptions, args []string) error { diff --git a/cmd/restic/cmd_list.go b/cmd/restic/cmd_list.go index d66cddc4f..db33506ff 100644 --- a/cmd/restic/cmd_list.go +++ b/cmd/restic/cmd_list.go @@ -11,13 +11,14 @@ import ( "github.com/spf13/cobra" ) -var listAllowedArgs = []string{"blobs", "packs", "index", "snapshots", "keys", "locks"} -var listAllowedArgsUseString = strings.Join(listAllowedArgs, "|") +func newListCommand() *cobra.Command { + var listAllowedArgs = []string{"blobs", "packs", "index", "snapshots", "keys", "locks"} + var listAllowedArgsUseString = strings.Join(listAllowedArgs, "|") -var cmdList = &cobra.Command{ - Use: "list [flags] [" + listAllowedArgsUseString + "]", - Short: "List objects in the repository", - Long: ` + cmd := &cobra.Command{ + Use: "list [flags] [" + listAllowedArgsUseString + "]", + Short: "List objects in the repository", + Long: ` The "list" command allows listing objects in the repository based on type. EXIT STATUS @@ -29,17 +30,19 @@ Exit status is 10 if the repository does not exist. Exit status is 11 if the repository is already locked. Exit status is 12 if the password is incorrect. `, - DisableAutoGenTag: true, - GroupID: cmdGroupDefault, - RunE: func(cmd *cobra.Command, args []string) error { - return runList(cmd.Context(), globalOptions, args) - }, - ValidArgs: listAllowedArgs, - Args: cobra.MatchAll(cobra.ExactArgs(1), cobra.OnlyValidArgs), + DisableAutoGenTag: true, + GroupID: cmdGroupDefault, + RunE: func(cmd *cobra.Command, args []string) error { + return runList(cmd.Context(), globalOptions, args) + }, + ValidArgs: listAllowedArgs, + Args: cobra.MatchAll(cobra.ExactArgs(1), cobra.OnlyValidArgs), + } + return cmd } func init() { - cmdRoot.AddCommand(cmdList) + cmdRoot.AddCommand(newListCommand()) } func runList(ctx context.Context, gopts GlobalOptions, args []string) error { diff --git a/cmd/restic/cmd_ls.go b/cmd/restic/cmd_ls.go index 7fda0b043..b9f625efd 100644 --- a/cmd/restic/cmd_ls.go +++ b/cmd/restic/cmd_ls.go @@ -21,10 +21,13 @@ import ( "github.com/restic/restic/internal/walker" ) -var cmdLs = &cobra.Command{ - Use: "ls [flags] snapshotID [dir...]", - Short: "List files in a snapshot", - Long: ` +func newLsCommand() *cobra.Command { + var opts LsOptions + + cmd := &cobra.Command{ + Use: "ls [flags] snapshotID [dir...]", + Short: "List files in a snapshot", + Long: ` The "ls" command lists files and directories in a snapshot. The special snapshot ID "latest" can be used to list files and @@ -53,11 +56,18 @@ Exit status is 10 if the repository does not exist. Exit status is 11 if the repository is already locked. Exit status is 12 if the password is incorrect. `, - DisableAutoGenTag: true, - GroupID: cmdGroupDefault, - RunE: func(cmd *cobra.Command, args []string) error { - return runLs(cmd.Context(), lsOptions, globalOptions, args) - }, + DisableAutoGenTag: true, + GroupID: cmdGroupDefault, + RunE: func(cmd *cobra.Command, args []string) error { + return runLs(cmd.Context(), opts, globalOptions, args) + }, + } + opts.AddFlags(cmd.Flags()) + return cmd +} + +func init() { + cmdRoot.AddCommand(newLsCommand()) } // LsOptions collects all options for the ls command. @@ -81,13 +91,6 @@ func (opts *LsOptions) AddFlags(f *pflag.FlagSet) { f.BoolVar(&opts.Reverse, "reverse", false, "reverse sorted output") } -var lsOptions LsOptions - -func init() { - cmdRoot.AddCommand(cmdLs) - lsOptions.AddFlags(cmdLs.Flags()) -} - type lsPrinter interface { Snapshot(sn *restic.Snapshot) error Node(path string, node *restic.Node, isPrefixDirectory bool) error diff --git a/cmd/restic/cmd_migrate.go b/cmd/restic/cmd_migrate.go index 4a5ecf84b..9f07705ce 100644 --- a/cmd/restic/cmd_migrate.go +++ b/cmd/restic/cmd_migrate.go @@ -12,10 +12,13 @@ import ( "github.com/spf13/pflag" ) -var cmdMigrate = &cobra.Command{ - Use: "migrate [flags] [migration name] [...]", - Short: "Apply migrations", - Long: ` +func newMigrateCommand() *cobra.Command { + var opts MigrateOptions + + cmd := &cobra.Command{ + Use: "migrate [flags] [migration name] [...]", + Short: "Apply migrations", + Long: ` The "migrate" command checks which migrations can be applied for a repository and prints a list with available migration names. If one or more migration names are specified, these migrations are applied. @@ -29,13 +32,21 @@ Exit status is 10 if the repository does not exist. Exit status is 11 if the repository is already locked. Exit status is 12 if the password is incorrect. `, - DisableAutoGenTag: true, - GroupID: cmdGroupDefault, - RunE: func(cmd *cobra.Command, args []string) error { - term, cancel := setupTermstatus() - defer cancel() - return runMigrate(cmd.Context(), migrateOptions, globalOptions, args, term) - }, + DisableAutoGenTag: true, + GroupID: cmdGroupDefault, + RunE: func(cmd *cobra.Command, args []string) error { + term, cancel := setupTermstatus() + defer cancel() + return runMigrate(cmd.Context(), opts, globalOptions, args, term) + }, + } + + opts.AddFlags(cmd.Flags()) + return cmd +} + +func init() { + cmdRoot.AddCommand(newMigrateCommand()) } // MigrateOptions bundles all options for the 'check' command. @@ -47,13 +58,6 @@ func (opts *MigrateOptions) AddFlags(f *pflag.FlagSet) { f.BoolVarP(&opts.Force, "force", "f", false, `apply a migration a second time`) } -var migrateOptions MigrateOptions - -func init() { - cmdRoot.AddCommand(cmdMigrate) - migrateOptions.AddFlags(cmdMigrate.Flags()) -} - func checkMigrations(ctx context.Context, repo restic.Repository, printer progress.Printer) error { printer.P("available migrations:\n") found := false diff --git a/cmd/restic/cmd_mount.go b/cmd/restic/cmd_mount.go index 21c9d62f4..1c48c97cd 100644 --- a/cmd/restic/cmd_mount.go +++ b/cmd/restic/cmd_mount.go @@ -22,10 +22,13 @@ import ( "github.com/anacrolix/fuse/fs" ) -var cmdMount = &cobra.Command{ - Use: "mount [flags] mountpoint", - Short: "Mount the repository", - Long: ` +func newMountCommand() *cobra.Command { + var opts MountOptions + + cmd := &cobra.Command{ + Use: "mount [flags] mountpoint", + Short: "Mount the repository", + Long: ` The "mount" command mounts the repository via fuse to a directory. This is a read-only mount. @@ -70,11 +73,19 @@ Exit status is 10 if the repository does not exist. Exit status is 11 if the repository is already locked. Exit status is 12 if the password is incorrect. `, - DisableAutoGenTag: true, - GroupID: cmdGroupDefault, - RunE: func(cmd *cobra.Command, args []string) error { - return runMount(cmd.Context(), mountOptions, globalOptions, args) - }, + DisableAutoGenTag: true, + GroupID: cmdGroupDefault, + RunE: func(cmd *cobra.Command, args []string) error { + return runMount(cmd.Context(), opts, globalOptions, args) + }, + } + + opts.AddFlags(cmd.Flags()) + return cmd +} + +func init() { + cmdRoot.AddCommand(newMountCommand()) } // MountOptions collects all options for the mount command. @@ -100,13 +111,6 @@ func (opts *MountOptions) AddFlags(f *pflag.FlagSet) { _ = f.MarkDeprecated("snapshot-template", "use --time-template") } -var mountOptions MountOptions - -func init() { - cmdRoot.AddCommand(cmdMount) - mountOptions.AddFlags(cmdMount.Flags()) -} - func runMount(ctx context.Context, opts MountOptions, gopts GlobalOptions, args []string) error { if opts.TimeTemplate == "" { return errors.Fatal("time template string cannot be empty") diff --git a/cmd/restic/cmd_options.go b/cmd/restic/cmd_options.go index 9c07b2626..f99ba9c04 100644 --- a/cmd/restic/cmd_options.go +++ b/cmd/restic/cmd_options.go @@ -8,10 +8,11 @@ import ( "github.com/spf13/cobra" ) -var optionsCmd = &cobra.Command{ - Use: "options", - Short: "Print list of extended options", - Long: ` +func newOptionsCommand() *cobra.Command { + cmd := &cobra.Command{ + Use: "options", + Short: "Print list of extended options", + Long: ` The "options" command prints a list of extended options. EXIT STATUS @@ -20,22 +21,24 @@ EXIT STATUS Exit status is 0 if the command was successful. Exit status is 1 if there was any error. `, - GroupID: cmdGroupAdvanced, - DisableAutoGenTag: true, - Run: func(_ *cobra.Command, _ []string) { - fmt.Printf("All Extended Options:\n") - var maxLen int - for _, opt := range options.List() { - if l := len(opt.Namespace + "." + opt.Name); l > maxLen { - maxLen = l + GroupID: cmdGroupAdvanced, + DisableAutoGenTag: true, + Run: func(_ *cobra.Command, _ []string) { + fmt.Printf("All Extended Options:\n") + var maxLen int + for _, opt := range options.List() { + if l := len(opt.Namespace + "." + opt.Name); l > maxLen { + maxLen = l + } } - } - for _, opt := range options.List() { - fmt.Printf(" %*s %s\n", -maxLen, opt.Namespace+"."+opt.Name, opt.Text) - } - }, + for _, opt := range options.List() { + fmt.Printf(" %*s %s\n", -maxLen, opt.Namespace+"."+opt.Name, opt.Text) + } + }, + } + return cmd } func init() { - cmdRoot.AddCommand(optionsCmd) + cmdRoot.AddCommand(newOptionsCommand()) } diff --git a/cmd/restic/cmd_prune.go b/cmd/restic/cmd_prune.go index 99b69c093..5184b8a75 100644 --- a/cmd/restic/cmd_prune.go +++ b/cmd/restic/cmd_prune.go @@ -19,10 +19,13 @@ import ( "github.com/spf13/pflag" ) -var cmdPrune = &cobra.Command{ - Use: "prune [flags]", - Short: "Remove unneeded data from the repository", - Long: ` +func newPruneCommand() *cobra.Command { + var opts PruneOptions + + cmd := &cobra.Command{ + Use: "prune [flags]", + Short: "Remove unneeded data from the repository", + Long: ` The "prune" command checks the repository and removes data that is not referenced and therefore not needed any more. @@ -35,13 +38,21 @@ Exit status is 10 if the repository does not exist. Exit status is 11 if the repository is already locked. Exit status is 12 if the password is incorrect. `, - GroupID: cmdGroupDefault, - DisableAutoGenTag: true, - RunE: func(cmd *cobra.Command, _ []string) error { - term, cancel := setupTermstatus() - defer cancel() - return runPrune(cmd.Context(), pruneOptions, globalOptions, term) - }, + GroupID: cmdGroupDefault, + DisableAutoGenTag: true, + RunE: func(cmd *cobra.Command, _ []string) error { + term, cancel := setupTermstatus() + defer cancel() + return runPrune(cmd.Context(), opts, globalOptions, term) + }, + } + + opts.AddFlags(cmd.Flags()) + return cmd +} + +func init() { + cmdRoot.AddCommand(newPruneCommand()) } // PruneOptions collects all options for the cleanup command. @@ -76,13 +87,6 @@ func (opts *PruneOptions) AddLimitedFlags(f *pflag.FlagSet) { f.BoolVar(&opts.RepackUncompressed, "repack-uncompressed", false, "repack all uncompressed data") } -var pruneOptions PruneOptions - -func init() { - cmdRoot.AddCommand(cmdPrune) - pruneOptions.AddFlags(cmdPrune.Flags()) -} - func verifyPruneOptions(opts *PruneOptions) error { opts.MaxRepackBytes = math.MaxUint64 if len(opts.MaxRepackSize) > 0 { diff --git a/cmd/restic/cmd_recover.go b/cmd/restic/cmd_recover.go index 78fc2d148..050fbd056 100644 --- a/cmd/restic/cmd_recover.go +++ b/cmd/restic/cmd_recover.go @@ -11,10 +11,11 @@ import ( "golang.org/x/sync/errgroup" ) -var cmdRecover = &cobra.Command{ - Use: "recover [flags]", - Short: "Recover data from the repository not referenced by snapshots", - Long: ` +func newRecoverCommand() *cobra.Command { + cmd := &cobra.Command{ + Use: "recover [flags]", + Short: "Recover data from the repository not referenced by snapshots", + Long: ` The "recover" command builds a new snapshot from all directories it can find in the raw data of the repository which are not referenced in an existing snapshot. It can be used if, for example, a snapshot has been removed by accident with "forget". @@ -28,15 +29,17 @@ Exit status is 10 if the repository does not exist. Exit status is 11 if the repository is already locked. Exit status is 12 if the password is incorrect. `, - GroupID: cmdGroupDefault, - DisableAutoGenTag: true, - RunE: func(cmd *cobra.Command, _ []string) error { - return runRecover(cmd.Context(), globalOptions) - }, + GroupID: cmdGroupDefault, + DisableAutoGenTag: true, + RunE: func(cmd *cobra.Command, _ []string) error { + return runRecover(cmd.Context(), globalOptions) + }, + } + return cmd } func init() { - cmdRoot.AddCommand(cmdRecover) + cmdRoot.AddCommand(newRecoverCommand()) } func runRecover(ctx context.Context, gopts GlobalOptions) error { diff --git a/cmd/restic/cmd_repair_index.go b/cmd/restic/cmd_repair_index.go index 46ec1db98..441232bcb 100644 --- a/cmd/restic/cmd_repair_index.go +++ b/cmd/restic/cmd_repair_index.go @@ -9,10 +9,13 @@ import ( "github.com/spf13/pflag" ) -var cmdRepairIndex = &cobra.Command{ - Use: "index [flags]", - Short: "Build a new index", - Long: ` +func newRepairIndexCommand() *cobra.Command { + var opts RepairIndexOptions + + cmd := &cobra.Command{ + Use: "index [flags]", + Short: "Build a new index", + Long: ` The "repair index" command creates a new index based on the pack files in the repository. @@ -25,21 +28,20 @@ Exit status is 10 if the repository does not exist. Exit status is 11 if the repository is already locked. Exit status is 12 if the password is incorrect. `, - DisableAutoGenTag: true, - RunE: func(cmd *cobra.Command, _ []string) error { - term, cancel := setupTermstatus() - defer cancel() - return runRebuildIndex(cmd.Context(), repairIndexOptions, globalOptions, term) - }, + DisableAutoGenTag: true, + RunE: func(cmd *cobra.Command, _ []string) error { + term, cancel := setupTermstatus() + defer cancel() + return runRebuildIndex(cmd.Context(), opts, globalOptions, term) + }, + } + + opts.AddFlags(cmd.Flags()) + return cmd } -var cmdRebuildIndex = &cobra.Command{ - Use: "rebuild-index [flags]", - Short: cmdRepairIndex.Short, - Long: cmdRepairIndex.Long, - Deprecated: `Use "repair index" instead`, - DisableAutoGenTag: true, - RunE: cmdRepairIndex.RunE, +func init() { + cmdRepair.AddCommand(newRepairIndexCommand()) } // RepairIndexOptions collects all options for the repair index command. @@ -51,14 +53,32 @@ func (opts *RepairIndexOptions) AddFlags(f *pflag.FlagSet) { f.BoolVar(&opts.ReadAllPacks, "read-all-packs", false, "read all pack files to generate new index from scratch") } -var repairIndexOptions RepairIndexOptions +func newRebuildIndexCommand() *cobra.Command { + var opts RepairIndexOptions + + replacement := newRepairIndexCommand() + cmd := &cobra.Command{ + Use: "rebuild-index [flags]", + Short: replacement.Short, + Long: replacement.Long, + Deprecated: `Use "repair index" instead`, + DisableAutoGenTag: true, + // must create a new instance of the run function as it captures opts + // by reference + RunE: func(cmd *cobra.Command, _ []string) error { + term, cancel := setupTermstatus() + defer cancel() + return runRebuildIndex(cmd.Context(), opts, globalOptions, term) + }, + } + + opts.AddFlags(cmd.Flags()) + return cmd +} func init() { - cmdRepair.AddCommand(cmdRepairIndex) - repairIndexOptions.AddFlags(cmdRepairIndex.Flags()) // add alias for old name - cmdRoot.AddCommand(cmdRebuildIndex) - repairIndexOptions.AddFlags(cmdRebuildIndex.Flags()) + cmdRoot.AddCommand(newRebuildIndexCommand()) } func runRebuildIndex(ctx context.Context, opts RepairIndexOptions, gopts GlobalOptions, term *termstatus.Terminal) error { diff --git a/cmd/restic/cmd_repair_packs.go b/cmd/restic/cmd_repair_packs.go index 290c3734e..4b787a2f9 100644 --- a/cmd/restic/cmd_repair_packs.go +++ b/cmd/restic/cmd_repair_packs.go @@ -13,10 +13,11 @@ import ( "github.com/spf13/cobra" ) -var cmdRepairPacks = &cobra.Command{ - Use: "packs [packIDs...]", - Short: "Salvage damaged pack files", - Long: ` +func newRepairPacksCommand() *cobra.Command { + cmd := &cobra.Command{ + Use: "packs [packIDs...]", + Short: "Salvage damaged pack files", + Long: ` The "repair packs" command extracts intact blobs from the specified pack files, rebuilds the index to remove the damaged pack files and removes the pack files from the repository. @@ -29,16 +30,18 @@ Exit status is 10 if the repository does not exist. Exit status is 11 if the repository is already locked. Exit status is 12 if the password is incorrect. `, - DisableAutoGenTag: true, - RunE: func(cmd *cobra.Command, args []string) error { - term, cancel := setupTermstatus() - defer cancel() - return runRepairPacks(cmd.Context(), globalOptions, term, args) - }, + DisableAutoGenTag: true, + RunE: func(cmd *cobra.Command, args []string) error { + term, cancel := setupTermstatus() + defer cancel() + return runRepairPacks(cmd.Context(), globalOptions, term, args) + }, + } + return cmd } func init() { - cmdRepair.AddCommand(cmdRepairPacks) + cmdRepair.AddCommand(newRepairPacksCommand()) } func runRepairPacks(ctx context.Context, gopts GlobalOptions, term *termstatus.Terminal, args []string) error { diff --git a/cmd/restic/cmd_repair_snapshots.go b/cmd/restic/cmd_repair_snapshots.go index 397f8a600..fb294b02f 100644 --- a/cmd/restic/cmd_repair_snapshots.go +++ b/cmd/restic/cmd_repair_snapshots.go @@ -11,10 +11,13 @@ import ( "github.com/spf13/pflag" ) -var cmdRepairSnapshots = &cobra.Command{ - Use: "snapshots [flags] [snapshot ID] [...]", - Short: "Repair snapshots", - Long: ` +func newRepairSnapshotsCommand() *cobra.Command { + var opts RepairOptions + + cmd := &cobra.Command{ + Use: "snapshots [flags] [snapshot ID] [...]", + Short: "Repair snapshots", + Long: ` The "repair snapshots" command repairs broken snapshots. It scans the given snapshots and generates new ones with damaged directories and file contents removed. If the broken snapshots are deleted, a prune run will be able to @@ -44,10 +47,18 @@ Exit status is 10 if the repository does not exist. Exit status is 11 if the repository is already locked. Exit status is 12 if the password is incorrect. `, - DisableAutoGenTag: true, - RunE: func(cmd *cobra.Command, args []string) error { - return runRepairSnapshots(cmd.Context(), globalOptions, repairSnapshotOptions, args) - }, + DisableAutoGenTag: true, + RunE: func(cmd *cobra.Command, args []string) error { + return runRepairSnapshots(cmd.Context(), globalOptions, opts, args) + }, + } + + opts.AddFlags(cmd.Flags()) + return cmd +} + +func init() { + cmdRepair.AddCommand(newRepairSnapshotsCommand()) } // RepairOptions collects all options for the repair command. @@ -65,13 +76,6 @@ func (opts *RepairOptions) AddFlags(f *pflag.FlagSet) { initMultiSnapshotFilter(f, &opts.SnapshotFilter, true) } -var repairSnapshotOptions RepairOptions - -func init() { - cmdRepair.AddCommand(cmdRepairSnapshots) - repairSnapshotOptions.AddFlags(cmdRepairSnapshots.Flags()) -} - func runRepairSnapshots(ctx context.Context, gopts GlobalOptions, opts RepairOptions, args []string) error { ctx, repo, unlock, err := openWithExclusiveLock(ctx, gopts, opts.DryRun) if err != nil { diff --git a/cmd/restic/cmd_restore.go b/cmd/restic/cmd_restore.go index 0382b859f..0452322bb 100644 --- a/cmd/restic/cmd_restore.go +++ b/cmd/restic/cmd_restore.go @@ -18,10 +18,13 @@ import ( "github.com/spf13/pflag" ) -var cmdRestore = &cobra.Command{ - Use: "restore [flags] snapshotID", - Short: "Extract the data from a snapshot", - Long: ` +func newRestoreCommand() *cobra.Command { + var opts RestoreOptions + + cmd := &cobra.Command{ + Use: "restore [flags] snapshotID", + Short: "Extract the data from a snapshot", + Long: ` The "restore" command extracts the data from a snapshot from the repository to a directory. @@ -40,13 +43,21 @@ Exit status is 10 if the repository does not exist. Exit status is 11 if the repository is already locked. Exit status is 12 if the password is incorrect. `, - GroupID: cmdGroupDefault, - DisableAutoGenTag: true, - RunE: func(cmd *cobra.Command, args []string) error { - term, cancel := setupTermstatus() - defer cancel() - return runRestore(cmd.Context(), restoreOptions, globalOptions, term, args) - }, + GroupID: cmdGroupDefault, + DisableAutoGenTag: true, + RunE: func(cmd *cobra.Command, args []string) error { + term, cancel := setupTermstatus() + defer cancel() + return runRestore(cmd.Context(), opts, globalOptions, term, args) + }, + } + + opts.AddFlags(cmd.Flags()) + return cmd +} + +func init() { + cmdRoot.AddCommand(newRestoreCommand()) } // RestoreOptions collects all options for the restore command. @@ -81,13 +92,6 @@ func (opts *RestoreOptions) AddFlags(f *pflag.FlagSet) { f.BoolVar(&opts.Delete, "delete", false, "delete files from target directory if they do not exist in snapshot. Use '--dry-run -vv' to check what would be deleted") } -var restoreOptions RestoreOptions - -func init() { - cmdRoot.AddCommand(cmdRestore) - restoreOptions.AddFlags(cmdRestore.Flags()) -} - func runRestore(ctx context.Context, opts RestoreOptions, gopts GlobalOptions, term *termstatus.Terminal, args []string) error { diff --git a/cmd/restic/cmd_rewrite.go b/cmd/restic/cmd_rewrite.go index 3200a1e52..7e295f3df 100644 --- a/cmd/restic/cmd_rewrite.go +++ b/cmd/restic/cmd_rewrite.go @@ -16,10 +16,13 @@ import ( "github.com/restic/restic/internal/walker" ) -var cmdRewrite = &cobra.Command{ - Use: "rewrite [flags] [snapshotID ...]", - Short: "Rewrite snapshots to exclude unwanted files", - Long: ` +func newRewriteCommand() *cobra.Command { + var opts RewriteOptions + + cmd := &cobra.Command{ + Use: "rewrite [flags] [snapshotID ...]", + Short: "Rewrite snapshots to exclude unwanted files", + Long: ` The "rewrite" command excludes files from existing snapshots. It creates new snapshots containing the same data as the original ones, but without the files you specify to exclude. All metadata (time, host, tags) will be preserved. @@ -52,11 +55,19 @@ Exit status is 10 if the repository does not exist. Exit status is 11 if the repository is already locked. Exit status is 12 if the password is incorrect. `, - GroupID: cmdGroupDefault, - DisableAutoGenTag: true, - RunE: func(cmd *cobra.Command, args []string) error { - return runRewrite(cmd.Context(), rewriteOptions, globalOptions, args) - }, + GroupID: cmdGroupDefault, + DisableAutoGenTag: true, + RunE: func(cmd *cobra.Command, args []string) error { + return runRewrite(cmd.Context(), opts, globalOptions, args) + }, + } + + opts.AddFlags(cmd.Flags()) + return cmd +} + +func init() { + cmdRoot.AddCommand(newRewriteCommand()) } type snapshotMetadata struct { @@ -111,13 +122,6 @@ func (opts *RewriteOptions) AddFlags(f *pflag.FlagSet) { opts.ExcludePatternOptions.Add(f) } -var rewriteOptions RewriteOptions - -func init() { - cmdRoot.AddCommand(cmdRewrite) - rewriteOptions.AddFlags(cmdRewrite.Flags()) -} - // rewriteFilterFunc returns the filtered tree ID or an error. If a snapshot summary is returned, the snapshot will // be updated accordingly. type rewriteFilterFunc func(ctx context.Context, sn *restic.Snapshot) (restic.ID, *restic.SnapshotSummary, error) diff --git a/cmd/restic/cmd_self_update.go b/cmd/restic/cmd_self_update.go index 3e218a408..b3a5dbdc7 100644 --- a/cmd/restic/cmd_self_update.go +++ b/cmd/restic/cmd_self_update.go @@ -13,10 +13,13 @@ import ( "github.com/spf13/pflag" ) -var cmdSelfUpdate = &cobra.Command{ - Use: "self-update [flags]", - Short: "Update the restic binary", - Long: ` +func newSelfUpdateCommand() *cobra.Command { + var opts SelfUpdateOptions + + cmd := &cobra.Command{ + Use: "self-update [flags]", + Short: "Update the restic binary", + Long: ` The command "self-update" downloads the latest stable release of restic from GitHub and replaces the currently running binary. After download, the authenticity of the binary is verified using the GPG signature on the release @@ -31,10 +34,18 @@ Exit status is 10 if the repository does not exist. Exit status is 11 if the repository is already locked. Exit status is 12 if the password is incorrect. `, - DisableAutoGenTag: true, - RunE: func(cmd *cobra.Command, args []string) error { - return runSelfUpdate(cmd.Context(), selfUpdateOptions, globalOptions, args) - }, + DisableAutoGenTag: true, + RunE: func(cmd *cobra.Command, args []string) error { + return runSelfUpdate(cmd.Context(), opts, globalOptions, args) + }, + } + + opts.AddFlags(cmd.Flags()) + return cmd +} + +func init() { + cmdRoot.AddCommand(newSelfUpdateCommand()) } // SelfUpdateOptions collects all options for the update-restic command. @@ -46,13 +57,6 @@ func (opts *SelfUpdateOptions) AddFlags(f *pflag.FlagSet) { f.StringVar(&opts.Output, "output", "", "Save the downloaded file as `filename` (default: running binary itself)") } -var selfUpdateOptions SelfUpdateOptions - -func init() { - cmdRoot.AddCommand(cmdSelfUpdate) - selfUpdateOptions.AddFlags(cmdSelfUpdate.Flags()) -} - func runSelfUpdate(ctx context.Context, opts SelfUpdateOptions, gopts GlobalOptions, args []string) error { if opts.Output == "" { file, err := os.Executable() diff --git a/cmd/restic/cmd_snapshots.go b/cmd/restic/cmd_snapshots.go index b426484d3..dae5cafe8 100644 --- a/cmd/restic/cmd_snapshots.go +++ b/cmd/restic/cmd_snapshots.go @@ -15,10 +15,13 @@ import ( "github.com/spf13/pflag" ) -var cmdSnapshots = &cobra.Command{ - Use: "snapshots [flags] [snapshotID ...]", - Short: "List all snapshots", - Long: ` +func newSnapshotsCommand() *cobra.Command { + var opts SnapshotOptions + + cmd := &cobra.Command{ + Use: "snapshots [flags] [snapshotID ...]", + Short: "List all snapshots", + Long: ` The "snapshots" command lists all snapshots stored in the repository. EXIT STATUS @@ -30,11 +33,19 @@ Exit status is 10 if the repository does not exist. Exit status is 11 if the repository is already locked. Exit status is 12 if the password is incorrect. `, - GroupID: cmdGroupDefault, - DisableAutoGenTag: true, - RunE: func(cmd *cobra.Command, args []string) error { - return runSnapshots(cmd.Context(), snapshotOptions, globalOptions, args) - }, + GroupID: cmdGroupDefault, + DisableAutoGenTag: true, + RunE: func(cmd *cobra.Command, args []string) error { + return runSnapshots(cmd.Context(), opts, globalOptions, args) + }, + } + + opts.AddFlags(cmd.Flags()) + return cmd +} + +func init() { + cmdRoot.AddCommand(newSnapshotsCommand()) } // SnapshotOptions bundles all options for the snapshots command. @@ -59,13 +70,6 @@ func (opts *SnapshotOptions) AddFlags(f *pflag.FlagSet) { f.VarP(&opts.GroupBy, "group-by", "g", "`group` snapshots by host, paths and/or tags, separated by comma") } -var snapshotOptions SnapshotOptions - -func init() { - cmdRoot.AddCommand(cmdSnapshots) - snapshotOptions.AddFlags(cmdSnapshots.Flags()) -} - func runSnapshots(ctx context.Context, opts SnapshotOptions, gopts GlobalOptions, args []string) error { ctx, repo, unlock, err := openWithReadLock(ctx, gopts, gopts.NoLock) if err != nil { diff --git a/cmd/restic/cmd_stats.go b/cmd/restic/cmd_stats.go index c3512c2d5..a906a9be0 100644 --- a/cmd/restic/cmd_stats.go +++ b/cmd/restic/cmd_stats.go @@ -21,10 +21,13 @@ import ( "github.com/spf13/pflag" ) -var cmdStats = &cobra.Command{ - Use: "stats [flags] [snapshot ID] [...]", - Short: "Scan the repository and show basic statistics", - Long: ` +func newStatsCommand() *cobra.Command { + var opts StatsOptions + + cmd := &cobra.Command{ + Use: "stats [flags] [snapshot ID] [...]", + Short: "Scan the repository and show basic statistics", + Long: ` The "stats" command walks one or multiple snapshots in a repository and accumulates statistics about the data stored therein. It reports on the number of unique files and their sizes, according to one of @@ -56,11 +59,22 @@ Exit status is 10 if the repository does not exist. Exit status is 11 if the repository is already locked. Exit status is 12 if the password is incorrect. `, - GroupID: cmdGroupDefault, - DisableAutoGenTag: true, - RunE: func(cmd *cobra.Command, args []string) error { - return runStats(cmd.Context(), statsOptions, globalOptions, args) - }, + GroupID: cmdGroupDefault, + DisableAutoGenTag: true, + RunE: func(cmd *cobra.Command, args []string) error { + return runStats(cmd.Context(), opts, globalOptions, args) + }, + } + + opts.AddFlags(cmd.Flags()) + must(cmd.RegisterFlagCompletionFunc("mode", func(_ *cobra.Command, _ []string, _ string) ([]string, cobra.ShellCompDirective) { + return []string{countModeRestoreSize, countModeUniqueFilesByContents, countModeBlobsPerFile, countModeRawData}, cobra.ShellCompDirectiveDefault + })) + return cmd +} + +func init() { + cmdRoot.AddCommand(newStatsCommand()) } // StatsOptions collects all options for the stats command. @@ -76,22 +90,12 @@ func (opts *StatsOptions) AddFlags(f *pflag.FlagSet) { initMultiSnapshotFilter(f, &opts.SnapshotFilter, true) } -var statsOptions StatsOptions - func must(err error) { if err != nil { panic(fmt.Sprintf("error during setup: %v", err)) } } -func init() { - cmdRoot.AddCommand(cmdStats) - statsOptions.AddFlags(cmdStats.Flags()) - must(cmdStats.RegisterFlagCompletionFunc("mode", func(_ *cobra.Command, _ []string, _ string) ([]string, cobra.ShellCompDirective) { - return []string{countModeRestoreSize, countModeUniqueFilesByContents, countModeBlobsPerFile, countModeRawData}, cobra.ShellCompDirectiveDefault - })) -} - func runStats(ctx context.Context, opts StatsOptions, gopts GlobalOptions, args []string) error { err := verifyStatsInput(opts) if err != nil { diff --git a/cmd/restic/cmd_tag.go b/cmd/restic/cmd_tag.go index ab90cbd4f..f99e1ee9b 100644 --- a/cmd/restic/cmd_tag.go +++ b/cmd/restic/cmd_tag.go @@ -14,10 +14,13 @@ import ( "github.com/restic/restic/internal/ui/termstatus" ) -var cmdTag = &cobra.Command{ - Use: "tag [flags] [snapshotID ...]", - Short: "Modify tags on snapshots", - Long: ` +func newTagCommand() *cobra.Command { + var opts TagOptions + + cmd := &cobra.Command{ + Use: "tag [flags] [snapshotID ...]", + Short: "Modify tags on snapshots", + Long: ` The "tag" command allows you to modify tags on exiting snapshots. You can either set/replace the entire set of tags on a snapshot, or @@ -34,13 +37,21 @@ Exit status is 10 if the repository does not exist. Exit status is 11 if the repository is already locked. Exit status is 12 if the password is incorrect. `, - GroupID: cmdGroupDefault, - DisableAutoGenTag: true, - RunE: func(cmd *cobra.Command, args []string) error { - term, cancel := setupTermstatus() - defer cancel() - return runTag(cmd.Context(), tagOptions, globalOptions, term, args) - }, + GroupID: cmdGroupDefault, + DisableAutoGenTag: true, + RunE: func(cmd *cobra.Command, args []string) error { + term, cancel := setupTermstatus() + defer cancel() + return runTag(cmd.Context(), opts, globalOptions, term, args) + }, + } + + opts.AddFlags(cmd.Flags()) + return cmd +} + +func init() { + cmdRoot.AddCommand(newTagCommand()) } // TagOptions bundles all options for the 'tag' command. @@ -58,13 +69,6 @@ func (opts *TagOptions) AddFlags(f *pflag.FlagSet) { initMultiSnapshotFilter(f, &opts.SnapshotFilter, true) } -var tagOptions TagOptions - -func init() { - cmdRoot.AddCommand(cmdTag) - tagOptions.AddFlags(cmdTag.Flags()) -} - type changedSnapshot struct { MessageType string `json:"message_type"` // changed OldSnapshotID restic.ID `json:"old_snapshot_id"` diff --git a/cmd/restic/cmd_unlock.go b/cmd/restic/cmd_unlock.go index 03066451c..fc5755cb7 100644 --- a/cmd/restic/cmd_unlock.go +++ b/cmd/restic/cmd_unlock.go @@ -8,10 +8,13 @@ import ( "github.com/spf13/pflag" ) -var unlockCmd = &cobra.Command{ - Use: "unlock", - Short: "Remove locks other processes created", - Long: ` +func newUnlockCommand() *cobra.Command { + var opts UnlockOptions + + cmd := &cobra.Command{ + Use: "unlock", + Short: "Remove locks other processes created", + Long: ` The "unlock" command removes stale locks that have been created by other restic processes. EXIT STATUS @@ -20,11 +23,18 @@ EXIT STATUS Exit status is 0 if the command was successful. Exit status is 1 if there was any error. `, - GroupID: cmdGroupDefault, - DisableAutoGenTag: true, - RunE: func(cmd *cobra.Command, _ []string) error { - return runUnlock(cmd.Context(), unlockOptions, globalOptions) - }, + GroupID: cmdGroupDefault, + DisableAutoGenTag: true, + RunE: func(cmd *cobra.Command, _ []string) error { + return runUnlock(cmd.Context(), opts, globalOptions) + }, + } + opts.AddFlags(cmd.Flags()) + return cmd +} + +func init() { + cmdRoot.AddCommand(newUnlockCommand()) } // UnlockOptions collects all options for the unlock command. @@ -36,13 +46,6 @@ func (opts *UnlockOptions) AddFlags(f *pflag.FlagSet) { f.BoolVar(&opts.RemoveAll, "remove-all", false, "remove all locks, even non-stale ones") } -var unlockOptions UnlockOptions - -func init() { - cmdRoot.AddCommand(unlockCmd) - unlockOptions.AddFlags(unlockCmd.Flags()) -} - func runUnlock(ctx context.Context, opts UnlockOptions, gopts GlobalOptions) error { repo, err := OpenRepository(ctx, gopts) if err != nil { diff --git a/cmd/restic/cmd_version.go b/cmd/restic/cmd_version.go index daf984a85..57fce5628 100644 --- a/cmd/restic/cmd_version.go +++ b/cmd/restic/cmd_version.go @@ -8,10 +8,11 @@ import ( "github.com/spf13/cobra" ) -var versionCmd = &cobra.Command{ - Use: "version", - Short: "Print version information", - Long: ` +func newVersionCommand() *cobra.Command { + cmd := &cobra.Command{ + Use: "version", + Short: "Print version information", + Long: ` The "version" command prints detailed information about the build environment and the version of this software. @@ -21,38 +22,40 @@ EXIT STATUS Exit status is 0 if the command was successful. Exit status is 1 if there was any error. `, - DisableAutoGenTag: true, - Run: func(_ *cobra.Command, _ []string) { - if globalOptions.JSON { - type jsonVersion struct { - MessageType string `json:"message_type"` // version - Version string `json:"version"` - GoVersion string `json:"go_version"` - GoOS string `json:"go_os"` - GoArch string `json:"go_arch"` + DisableAutoGenTag: true, + Run: func(_ *cobra.Command, _ []string) { + if globalOptions.JSON { + type jsonVersion struct { + MessageType string `json:"message_type"` // version + Version string `json:"version"` + GoVersion string `json:"go_version"` + GoOS string `json:"go_os"` + GoArch string `json:"go_arch"` + } + + jsonS := jsonVersion{ + MessageType: "version", + Version: version, + GoVersion: runtime.Version(), + GoOS: runtime.GOOS, + GoArch: runtime.GOARCH, + } + + err := json.NewEncoder(globalOptions.stdout).Encode(jsonS) + if err != nil { + Warnf("JSON encode failed: %v\n", err) + return + } + } else { + fmt.Printf("restic %s compiled with %v on %v/%v\n", + version, runtime.Version(), runtime.GOOS, runtime.GOARCH) } - jsonS := jsonVersion{ - MessageType: "version", - Version: version, - GoVersion: runtime.Version(), - GoOS: runtime.GOOS, - GoArch: runtime.GOARCH, - } - - err := json.NewEncoder(globalOptions.stdout).Encode(jsonS) - if err != nil { - Warnf("JSON encode failed: %v\n", err) - return - } - } else { - fmt.Printf("restic %s compiled with %v on %v/%v\n", - version, runtime.Version(), runtime.GOOS, runtime.GOARCH) - } - - }, + }, + } + return cmd } func init() { - cmdRoot.AddCommand(versionCmd) + cmdRoot.AddCommand(newVersionCommand()) } From aa9cdf93cf6041b1e08f3faee03dab7548175fbf Mon Sep 17 00:00:00 2001 From: Michael Eischer Date: Fri, 7 Feb 2025 18:54:26 +0100 Subject: [PATCH 3/8] refactor persistent options to be applied via functions --- cmd/restic/global.go | 104 +++++++++++++++++------------------ cmd/restic/global_debug.go | 70 ++++++++++++++++------- cmd/restic/global_release.go | 8 +-- cmd/restic/main.go | 15 ++--- 4 files changed, 113 insertions(+), 84 deletions(-) diff --git a/cmd/restic/global.go b/cmd/restic/global.go index bea09837f..88502e18c 100644 --- a/cmd/restic/global.go +++ b/cmd/restic/global.go @@ -34,6 +34,7 @@ import ( "github.com/restic/restic/internal/restic" "github.com/restic/restic/internal/textfile" "github.com/restic/restic/internal/ui/termstatus" + "github.com/spf13/pflag" "github.com/restic/restic/internal/errors" @@ -95,6 +96,57 @@ type GlobalOptions struct { extended options.Options } +func (opts *GlobalOptions) AddFlags(f *pflag.FlagSet) { + f.StringVarP(&opts.Repo, "repo", "r", "", "`repository` to backup to or restore from (default: $RESTIC_REPOSITORY)") + f.StringVarP(&opts.RepositoryFile, "repository-file", "", "", "`file` to read the repository location from (default: $RESTIC_REPOSITORY_FILE)") + f.StringVarP(&opts.PasswordFile, "password-file", "p", "", "`file` to read the repository password from (default: $RESTIC_PASSWORD_FILE)") + f.StringVarP(&opts.KeyHint, "key-hint", "", "", "`key` ID of key to try decrypting first (default: $RESTIC_KEY_HINT)") + f.StringVarP(&opts.PasswordCommand, "password-command", "", "", "shell `command` to obtain the repository password from (default: $RESTIC_PASSWORD_COMMAND)") + f.BoolVarP(&opts.Quiet, "quiet", "q", false, "do not output comprehensive progress report") + // use empty parameter name as `-v, --verbose n` instead of the correct `--verbose=n` is confusing + f.CountVarP(&opts.Verbose, "verbose", "v", "be verbose (specify multiple times or a level using --verbose=n``, max level/times is 2)") + f.BoolVar(&opts.NoLock, "no-lock", false, "do not lock the repository, this allows some operations on read-only repositories") + f.DurationVar(&opts.RetryLock, "retry-lock", 0, "retry to lock the repository if it is already locked, takes a value like 5m or 2h (default: no retries)") + f.BoolVarP(&opts.JSON, "json", "", false, "set output mode to JSON for commands that support it") + f.StringVar(&opts.CacheDir, "cache-dir", "", "set the cache `directory`. (default: use system default cache directory)") + f.BoolVar(&opts.NoCache, "no-cache", false, "do not use a local cache") + f.StringSliceVar(&opts.RootCertFilenames, "cacert", nil, "`file` to load root certificates from (default: use system certificates or $RESTIC_CACERT)") + f.StringVar(&opts.TLSClientCertKeyFilename, "tls-client-cert", "", "path to a `file` containing PEM encoded TLS client certificate and private key (default: $RESTIC_TLS_CLIENT_CERT)") + f.BoolVar(&opts.InsecureNoPassword, "insecure-no-password", false, "use an empty password for the repository, must be passed to every restic command (insecure)") + f.BoolVar(&opts.InsecureTLS, "insecure-tls", false, "skip TLS certificate verification when connecting to the repository (insecure)") + f.BoolVar(&opts.CleanupCache, "cleanup-cache", false, "auto remove old cache directories") + f.Var(&opts.Compression, "compression", "compression mode (only available for repository format version 2), one of (auto|off|max) (default: $RESTIC_COMPRESSION)") + f.BoolVar(&opts.NoExtraVerify, "no-extra-verify", false, "skip additional verification of data before upload (see documentation)") + f.IntVar(&opts.Limits.UploadKb, "limit-upload", 0, "limits uploads to a maximum `rate` in KiB/s. (default: unlimited)") + f.IntVar(&opts.Limits.DownloadKb, "limit-download", 0, "limits downloads to a maximum `rate` in KiB/s. (default: unlimited)") + f.UintVar(&opts.PackSize, "pack-size", 0, "set target pack `size` in MiB, created pack files may be larger (default: $RESTIC_PACK_SIZE)") + f.StringSliceVarP(&opts.Options, "option", "o", []string{}, "set extended option (`key=value`, can be specified multiple times)") + f.StringVar(&opts.HTTPUserAgent, "http-user-agent", "", "set a http user agent for outgoing http requests") + f.DurationVar(&opts.StuckRequestTimeout, "stuck-request-timeout", 5*time.Minute, "`duration` after which to retry stuck requests") + + opts.Repo = os.Getenv("RESTIC_REPOSITORY") + opts.RepositoryFile = os.Getenv("RESTIC_REPOSITORY_FILE") + opts.PasswordFile = os.Getenv("RESTIC_PASSWORD_FILE") + opts.KeyHint = os.Getenv("RESTIC_KEY_HINT") + opts.PasswordCommand = os.Getenv("RESTIC_PASSWORD_COMMAND") + if os.Getenv("RESTIC_CACERT") != "" { + opts.RootCertFilenames = strings.Split(os.Getenv("RESTIC_CACERT"), ",") + } + opts.TLSClientCertKeyFilename = os.Getenv("RESTIC_TLS_CLIENT_CERT") + comp := os.Getenv("RESTIC_COMPRESSION") + if comp != "" { + // ignore error as there's no good way to handle it + _ = opts.Compression.Set(comp) + } + // parse target pack size from env, on error the default value will be used + targetPackSize, _ := strconv.ParseUint(os.Getenv("RESTIC_PACK_SIZE"), 10, 32) + opts.PackSize = uint(targetPackSize) + + if os.Getenv("RESTIC_HTTP_USER_AGENT") != "" { + opts.HTTPUserAgent = os.Getenv("RESTIC_HTTP_USER_AGENT") + } +} + var globalOptions = GlobalOptions{ stdout: os.Stdout, stderr: os.Stderr, @@ -112,58 +164,6 @@ func init() { backends.Register(sftp.NewFactory()) backends.Register(swift.NewFactory()) globalOptions.backends = backends - - f := cmdRoot.PersistentFlags() - f.StringVarP(&globalOptions.Repo, "repo", "r", "", "`repository` to backup to or restore from (default: $RESTIC_REPOSITORY)") - f.StringVarP(&globalOptions.RepositoryFile, "repository-file", "", "", "`file` to read the repository location from (default: $RESTIC_REPOSITORY_FILE)") - f.StringVarP(&globalOptions.PasswordFile, "password-file", "p", "", "`file` to read the repository password from (default: $RESTIC_PASSWORD_FILE)") - f.StringVarP(&globalOptions.KeyHint, "key-hint", "", "", "`key` ID of key to try decrypting first (default: $RESTIC_KEY_HINT)") - f.StringVarP(&globalOptions.PasswordCommand, "password-command", "", "", "shell `command` to obtain the repository password from (default: $RESTIC_PASSWORD_COMMAND)") - f.BoolVarP(&globalOptions.Quiet, "quiet", "q", false, "do not output comprehensive progress report") - // use empty parameter name as `-v, --verbose n` instead of the correct `--verbose=n` is confusing - f.CountVarP(&globalOptions.Verbose, "verbose", "v", "be verbose (specify multiple times or a level using --verbose=n``, max level/times is 2)") - f.BoolVar(&globalOptions.NoLock, "no-lock", false, "do not lock the repository, this allows some operations on read-only repositories") - f.DurationVar(&globalOptions.RetryLock, "retry-lock", 0, "retry to lock the repository if it is already locked, takes a value like 5m or 2h (default: no retries)") - f.BoolVarP(&globalOptions.JSON, "json", "", false, "set output mode to JSON for commands that support it") - f.StringVar(&globalOptions.CacheDir, "cache-dir", "", "set the cache `directory`. (default: use system default cache directory)") - f.BoolVar(&globalOptions.NoCache, "no-cache", false, "do not use a local cache") - f.StringSliceVar(&globalOptions.RootCertFilenames, "cacert", nil, "`file` to load root certificates from (default: use system certificates or $RESTIC_CACERT)") - f.StringVar(&globalOptions.TLSClientCertKeyFilename, "tls-client-cert", "", "path to a `file` containing PEM encoded TLS client certificate and private key (default: $RESTIC_TLS_CLIENT_CERT)") - f.BoolVar(&globalOptions.InsecureNoPassword, "insecure-no-password", false, "use an empty password for the repository, must be passed to every restic command (insecure)") - f.BoolVar(&globalOptions.InsecureTLS, "insecure-tls", false, "skip TLS certificate verification when connecting to the repository (insecure)") - f.BoolVar(&globalOptions.CleanupCache, "cleanup-cache", false, "auto remove old cache directories") - f.Var(&globalOptions.Compression, "compression", "compression mode (only available for repository format version 2), one of (auto|off|max) (default: $RESTIC_COMPRESSION)") - f.BoolVar(&globalOptions.NoExtraVerify, "no-extra-verify", false, "skip additional verification of data before upload (see documentation)") - f.IntVar(&globalOptions.Limits.UploadKb, "limit-upload", 0, "limits uploads to a maximum `rate` in KiB/s. (default: unlimited)") - f.IntVar(&globalOptions.Limits.DownloadKb, "limit-download", 0, "limits downloads to a maximum `rate` in KiB/s. (default: unlimited)") - f.UintVar(&globalOptions.PackSize, "pack-size", 0, "set target pack `size` in MiB, created pack files may be larger (default: $RESTIC_PACK_SIZE)") - f.StringSliceVarP(&globalOptions.Options, "option", "o", []string{}, "set extended option (`key=value`, can be specified multiple times)") - f.StringVar(&globalOptions.HTTPUserAgent, "http-user-agent", "", "set a http user agent for outgoing http requests") - f.DurationVar(&globalOptions.StuckRequestTimeout, "stuck-request-timeout", 5*time.Minute, "`duration` after which to retry stuck requests") - // Use our "generate" command instead of the cobra provided "completion" command - cmdRoot.CompletionOptions.DisableDefaultCmd = true - - globalOptions.Repo = os.Getenv("RESTIC_REPOSITORY") - globalOptions.RepositoryFile = os.Getenv("RESTIC_REPOSITORY_FILE") - globalOptions.PasswordFile = os.Getenv("RESTIC_PASSWORD_FILE") - globalOptions.KeyHint = os.Getenv("RESTIC_KEY_HINT") - globalOptions.PasswordCommand = os.Getenv("RESTIC_PASSWORD_COMMAND") - if os.Getenv("RESTIC_CACERT") != "" { - globalOptions.RootCertFilenames = strings.Split(os.Getenv("RESTIC_CACERT"), ",") - } - globalOptions.TLSClientCertKeyFilename = os.Getenv("RESTIC_TLS_CLIENT_CERT") - comp := os.Getenv("RESTIC_COMPRESSION") - if comp != "" { - // ignore error as there's no good way to handle it - _ = globalOptions.Compression.Set(comp) - } - // parse target pack size from env, on error the default value will be used - targetPackSize, _ := strconv.ParseUint(os.Getenv("RESTIC_PACK_SIZE"), 10, 32) - globalOptions.PackSize = uint(targetPackSize) - - if os.Getenv("RESTIC_HTTP_USER_AGENT") != "" { - globalOptions.HTTPUserAgent = os.Getenv("RESTIC_HTTP_USER_AGENT") - } } func stdinIsTerminal() bool { diff --git a/cmd/restic/global_debug.go b/cmd/restic/global_debug.go index 502b2cf6e..1fe35146a 100644 --- a/cmd/restic/global_debug.go +++ b/cmd/restic/global_debug.go @@ -11,10 +11,44 @@ import ( "github.com/restic/restic/internal/errors" "github.com/restic/restic/internal/repository" + "github.com/spf13/cobra" + "github.com/spf13/pflag" "github.com/pkg/profile" ) +func registerProfiling(cmd *cobra.Command) { + var profiler profiler + + origPreRun := cmd.PersistentPreRunE + cmd.PersistentPreRunE = func(cmd *cobra.Command, args []string) error { + if origPreRun != nil { + if err := origPreRun(cmd, args); err != nil { + return err + } + } + return profiler.Start(profiler.opts) + } + + origPostRun := cmd.PersistentPostRunE + cmd.PersistentPostRunE = func(cmd *cobra.Command, args []string) error { + profiler.Stop() + if origPostRun != nil { + return origPostRun(cmd, args) + } + return nil + } + + profiler.opts.AddFlags(cmd.PersistentFlags()) +} + +type profiler struct { + opts ProfileOptions + stop interface { + Stop() + } +} + type ProfileOptions struct { listen string memPath string @@ -24,19 +58,13 @@ type ProfileOptions struct { insecure bool } -var profileOpts ProfileOptions -var prof interface { - Stop() -} - -func init() { - f := cmdRoot.PersistentFlags() - f.StringVar(&profileOpts.listen, "listen-profile", "", "listen on this `address:port` for memory profiling") - f.StringVar(&profileOpts.memPath, "mem-profile", "", "write memory profile to `dir`") - f.StringVar(&profileOpts.cpuPath, "cpu-profile", "", "write cpu profile to `dir`") - f.StringVar(&profileOpts.tracePath, "trace-profile", "", "write trace to `dir`") - f.StringVar(&profileOpts.blockPath, "block-profile", "", "write block profile to `dir`") - f.BoolVar(&profileOpts.insecure, "insecure-kdf", false, "use insecure KDF settings") +func (opts *ProfileOptions) AddFlags(f *pflag.FlagSet) { + f.StringVar(&opts.listen, "listen-profile", "", "listen on this `address:port` for memory profiling") + f.StringVar(&opts.memPath, "mem-profile", "", "write memory profile to `dir`") + f.StringVar(&opts.cpuPath, "cpu-profile", "", "write cpu profile to `dir`") + f.StringVar(&opts.tracePath, "trace-profile", "", "write trace to `dir`") + f.StringVar(&opts.blockPath, "block-profile", "", "write block profile to `dir`") + f.BoolVar(&opts.insecure, "insecure-kdf", false, "use insecure KDF settings") } type fakeTestingTB struct{} @@ -45,7 +73,7 @@ func (fakeTestingTB) Logf(msg string, args ...interface{}) { fmt.Fprintf(os.Stderr, msg, args...) } -func runDebug() error { +func (p *profiler) Start(profileOpts ProfileOptions) error { if profileOpts.listen != "" { fmt.Fprintf(os.Stderr, "running profile HTTP server on %v\n", profileOpts.listen) go func() { @@ -75,13 +103,13 @@ func runDebug() error { } if profileOpts.memPath != "" { - prof = profile.Start(profile.Quiet, profile.NoShutdownHook, profile.MemProfile, profile.ProfilePath(profileOpts.memPath)) + p.stop = profile.Start(profile.Quiet, profile.NoShutdownHook, profile.MemProfile, profile.ProfilePath(profileOpts.memPath)) } else if profileOpts.cpuPath != "" { - prof = profile.Start(profile.Quiet, profile.NoShutdownHook, profile.CPUProfile, profile.ProfilePath(profileOpts.cpuPath)) + p.stop = profile.Start(profile.Quiet, profile.NoShutdownHook, profile.CPUProfile, profile.ProfilePath(profileOpts.cpuPath)) } else if profileOpts.tracePath != "" { - prof = profile.Start(profile.Quiet, profile.NoShutdownHook, profile.TraceProfile, profile.ProfilePath(profileOpts.tracePath)) + p.stop = profile.Start(profile.Quiet, profile.NoShutdownHook, profile.TraceProfile, profile.ProfilePath(profileOpts.tracePath)) } else if profileOpts.blockPath != "" { - prof = profile.Start(profile.Quiet, profile.NoShutdownHook, profile.BlockProfile, profile.ProfilePath(profileOpts.blockPath)) + p.stop = profile.Start(profile.Quiet, profile.NoShutdownHook, profile.BlockProfile, profile.ProfilePath(profileOpts.blockPath)) } if profileOpts.insecure { @@ -91,8 +119,8 @@ func runDebug() error { return nil } -func stopDebug() { - if prof != nil { - prof.Stop() +func (p *profiler) Stop() { + if p.stop != nil { + p.stop.Stop() } } diff --git a/cmd/restic/global_release.go b/cmd/restic/global_release.go index 1dab5a293..63ad6e17d 100644 --- a/cmd/restic/global_release.go +++ b/cmd/restic/global_release.go @@ -3,8 +3,8 @@ package main -// runDebug is a noop without the debug tag. -func runDebug() error { return nil } +import "github.com/spf13/cobra" -// stopDebug is a noop without the debug tag. -func stopDebug() {} +func registerProfiling(cmd *cobra.Command) { + // No profiling in release mode +} diff --git a/cmd/restic/main.go b/cmd/restic/main.go index 096c5695c..49499a151 100644 --- a/cmd/restic/main.go +++ b/cmd/restic/main.go @@ -74,13 +74,7 @@ The full documentation can be found at https://restic.readthedocs.io/ . Exit(1) } globalOptions.password = pwd - - // run the debug functions for all subcommands (if build tag "debug" is - // enabled) - return runDebug() - }, - PersistentPostRun: func(_ *cobra.Command, _ []string) { - stopDebug() + return nil }, } @@ -98,6 +92,13 @@ func init() { Title: "Advanced Options:", }, ) + + globalOptions.AddFlags(cmdRoot.PersistentFlags()) + + // Use our "generate" command instead of the cobra provided "completion" command + cmdRoot.CompletionOptions.DisableDefaultCmd = true + + registerProfiling(cmdRoot) } // Distinguish commands that need the password from those that work without, From 412d6d9ec58b7b11cb93589029aed4e43d8463ab Mon Sep 17 00:00:00 2001 From: Michael Eischer Date: Fri, 7 Feb 2025 18:28:56 +0100 Subject: [PATCH 4/8] Create root command via function --- cmd/restic/cmd_backup.go | 4 - cmd/restic/cmd_cache.go | 4 - cmd/restic/cmd_cat.go | 4 - cmd/restic/cmd_check.go | 4 - cmd/restic/cmd_copy.go | 4 - cmd/restic/cmd_debug.go | 10 +- cmd/restic/cmd_debug_disabled.go | 9 ++ cmd/restic/cmd_diff.go | 4 - cmd/restic/cmd_dump.go | 4 - cmd/restic/cmd_features.go | 4 - cmd/restic/cmd_find.go | 4 - cmd/restic/cmd_forget.go | 4 - cmd/restic/cmd_generate.go | 12 +-- cmd/restic/cmd_init.go | 4 - cmd/restic/cmd_key.go | 24 +++-- cmd/restic/cmd_key_add.go | 4 - cmd/restic/cmd_key_list.go | 4 - cmd/restic/cmd_key_passwd.go | 4 - cmd/restic/cmd_key_remove.go | 4 - cmd/restic/cmd_list.go | 4 - cmd/restic/cmd_ls.go | 4 - cmd/restic/cmd_migrate.go | 4 - cmd/restic/cmd_mount.go | 8 +- cmd/restic/cmd_mount_disabled.go | 10 ++ cmd/restic/cmd_options.go | 4 - cmd/restic/cmd_prune.go | 4 - cmd/restic/cmd_recover.go | 4 - cmd/restic/cmd_repair.go | 21 ++-- cmd/restic/cmd_repair_index.go | 9 -- cmd/restic/cmd_repair_packs.go | 4 - cmd/restic/cmd_repair_snapshots.go | 4 - cmd/restic/cmd_restore.go | 4 - cmd/restic/cmd_rewrite.go | 4 - cmd/restic/cmd_self_update.go | 10 +- cmd/restic/cmd_self_update_disabled.go | 9 ++ cmd/restic/cmd_snapshots.go | 4 - cmd/restic/cmd_stats.go | 4 - cmd/restic/cmd_tag.go | 4 - cmd/restic/cmd_unlock.go | 4 - cmd/restic/cmd_version.go | 4 - cmd/restic/flags_test.go | 2 +- cmd/restic/global_release.go | 2 +- cmd/restic/main.go | 132 ++++++++++++++++--------- 43 files changed, 163 insertions(+), 215 deletions(-) create mode 100644 cmd/restic/cmd_debug_disabled.go create mode 100644 cmd/restic/cmd_mount_disabled.go create mode 100644 cmd/restic/cmd_self_update_disabled.go diff --git a/cmd/restic/cmd_backup.go b/cmd/restic/cmd_backup.go index 99d49f9e0..70c0d2fb9 100644 --- a/cmd/restic/cmd_backup.go +++ b/cmd/restic/cmd_backup.go @@ -74,10 +74,6 @@ Exit status is 12 if the password is incorrect. return cmd } -func init() { - cmdRoot.AddCommand(newBackupCommand()) -} - // BackupOptions bundles all options for the backup command. type BackupOptions struct { filter.ExcludePatternOptions diff --git a/cmd/restic/cmd_cache.go b/cmd/restic/cmd_cache.go index f9c6c3063..284c94cad 100644 --- a/cmd/restic/cmd_cache.go +++ b/cmd/restic/cmd_cache.go @@ -42,10 +42,6 @@ Exit status is 1 if there was any error. return cmd } -func init() { - cmdRoot.AddCommand(newCacheCommand()) -} - // CacheOptions bundles all options for the snapshots command. type CacheOptions struct { Cleanup bool diff --git a/cmd/restic/cmd_cat.go b/cmd/restic/cmd_cat.go index 6d7e68ac9..983e309dd 100644 --- a/cmd/restic/cmd_cat.go +++ b/cmd/restic/cmd_cat.go @@ -40,10 +40,6 @@ Exit status is 12 if the password is incorrect. return cmd } -func init() { - cmdRoot.AddCommand(newCatCommand()) -} - func validateCatArgs(args []string) error { if len(args) < 1 { return errors.Fatal("type not specified") diff --git a/cmd/restic/cmd_check.go b/cmd/restic/cmd_check.go index 37d39ee68..f87303933 100644 --- a/cmd/restic/cmd_check.go +++ b/cmd/restic/cmd_check.go @@ -67,10 +67,6 @@ Exit status is 12 if the password is incorrect. return cmd } -func init() { - cmdRoot.AddCommand(newCheckCommand()) -} - // CheckOptions bundles all options for the 'check' command. type CheckOptions struct { ReadData bool diff --git a/cmd/restic/cmd_copy.go b/cmd/restic/cmd_copy.go index 8ca78a341..cdf688dfa 100644 --- a/cmd/restic/cmd_copy.go +++ b/cmd/restic/cmd_copy.go @@ -54,10 +54,6 @@ Exit status is 12 if the password is incorrect. return cmd } -func init() { - cmdRoot.AddCommand(newCopyCommand()) -} - // CopyOptions bundles all options for the copy command. type CopyOptions struct { secondaryRepoOptions diff --git a/cmd/restic/cmd_debug.go b/cmd/restic/cmd_debug.go index 9e7f1d38e..7e356b6c5 100644 --- a/cmd/restic/cmd_debug.go +++ b/cmd/restic/cmd_debug.go @@ -29,6 +29,12 @@ import ( "github.com/restic/restic/internal/restic" ) +func registerDebugCommand(cmd *cobra.Command) { + cmd.AddCommand( + newDebugCommand(), + ) +} + func newDebugCommand() *cobra.Command { cmd := &cobra.Command{ Use: "debug", @@ -41,10 +47,6 @@ func newDebugCommand() *cobra.Command { return cmd } -func init() { - cmdRoot.AddCommand(newDebugCommand()) -} - func newDebugDumpCommand() *cobra.Command { cmd := &cobra.Command{ Use: "dump [indexes|snapshots|all|packs]", diff --git a/cmd/restic/cmd_debug_disabled.go b/cmd/restic/cmd_debug_disabled.go new file mode 100644 index 000000000..34d06a467 --- /dev/null +++ b/cmd/restic/cmd_debug_disabled.go @@ -0,0 +1,9 @@ +//go:build !debug + +package main + +import "github.com/spf13/cobra" + +func registerDebugCommand(_ *cobra.Command) { + // No commands to register in non-debug mode +} diff --git a/cmd/restic/cmd_diff.go b/cmd/restic/cmd_diff.go index d8711e90f..35fe97fbb 100644 --- a/cmd/restic/cmd_diff.go +++ b/cmd/restic/cmd_diff.go @@ -60,10 +60,6 @@ Exit status is 12 if the password is incorrect. return cmd } -func init() { - cmdRoot.AddCommand(newDiffCommand()) -} - // DiffOptions collects all options for the diff command. type DiffOptions struct { ShowMetadata bool diff --git a/cmd/restic/cmd_dump.go b/cmd/restic/cmd_dump.go index 9e78dfe82..978b64616 100644 --- a/cmd/restic/cmd_dump.go +++ b/cmd/restic/cmd_dump.go @@ -54,10 +54,6 @@ Exit status is 12 if the password is incorrect. return cmd } -func init() { - cmdRoot.AddCommand(newDumpCommand()) -} - // DumpOptions collects all options for the dump command. type DumpOptions struct { restic.SnapshotFilter diff --git a/cmd/restic/cmd_features.go b/cmd/restic/cmd_features.go index edb84d3f9..7770e6a9f 100644 --- a/cmd/restic/cmd_features.go +++ b/cmd/restic/cmd_features.go @@ -57,7 +57,3 @@ Exit status is 1 if there was any error. return cmd } - -func init() { - cmdRoot.AddCommand(newFeaturesCommand()) -} diff --git a/cmd/restic/cmd_find.go b/cmd/restic/cmd_find.go index 580ef1c7a..d2fa3619a 100644 --- a/cmd/restic/cmd_find.go +++ b/cmd/restic/cmd_find.go @@ -56,10 +56,6 @@ Exit status is 12 if the password is incorrect. return cmd } -func init() { - cmdRoot.AddCommand(newFindCommand()) -} - // FindOptions bundles all options for the find command. type FindOptions struct { Oldest string diff --git a/cmd/restic/cmd_forget.go b/cmd/restic/cmd_forget.go index 04bf92e47..2bfacc4de 100644 --- a/cmd/restic/cmd_forget.go +++ b/cmd/restic/cmd_forget.go @@ -59,10 +59,6 @@ Exit status is 12 if the password is incorrect. return cmd } -func init() { - cmdRoot.AddCommand(newForgetCommand()) -} - type ForgetPolicyCount int var ErrNegativePolicyCount = errors.New("negative values not allowed, use 'unlimited' instead") diff --git a/cmd/restic/cmd_generate.go b/cmd/restic/cmd_generate.go index fb2d89270..5ced5fe01 100644 --- a/cmd/restic/cmd_generate.go +++ b/cmd/restic/cmd_generate.go @@ -36,10 +36,6 @@ Exit status is 1 if there was any error. return cmd } -func init() { - cmdRoot.AddCommand(newGenerateCommand()) -} - type generateOptions struct { ManDir string BashCompletionFile string @@ -56,7 +52,7 @@ func (opts *generateOptions) AddFlags(f *pflag.FlagSet) { f.StringVar(&opts.PowerShellCompletionFile, "powershell-completion", "", "write powershell completion `file` (`-` for stdout)") } -func writeManpages(dir string) error { +func writeManpages(root *cobra.Command, dir string) error { // use a fixed date for the man pages so that generating them is deterministic date, err := time.Parse("Jan 2006", "Jan 2017") if err != nil { @@ -71,7 +67,7 @@ func writeManpages(dir string) error { } Verbosef("writing man pages to directory %v\n", dir) - return doc.GenManTree(cmdRoot, header, dir) + return doc.GenManTree(root, header, dir) } func writeCompletion(filename string, shell string, generate func(w io.Writer) error) (err error) { @@ -119,8 +115,10 @@ func runGenerate(opts generateOptions, args []string) error { return errors.Fatal("the generate command expects no arguments, only options - please see `restic help generate` for usage and flags") } + cmdRoot := newRootCommand() + if opts.ManDir != "" { - err := writeManpages(opts.ManDir) + err := writeManpages(cmdRoot, opts.ManDir) if err != nil { return err } diff --git a/cmd/restic/cmd_init.go b/cmd/restic/cmd_init.go index 0e1da7a78..db2e97c93 100644 --- a/cmd/restic/cmd_init.go +++ b/cmd/restic/cmd_init.go @@ -40,10 +40,6 @@ Exit status is 1 if there was any error. return cmd } -func init() { - cmdRoot.AddCommand(newInitCommand()) -} - // InitOptions bundles all options for the init command. type InitOptions struct { secondaryRepoOptions diff --git a/cmd/restic/cmd_key.go b/cmd/restic/cmd_key.go index a94caa0d8..29d38bdce 100644 --- a/cmd/restic/cmd_key.go +++ b/cmd/restic/cmd_key.go @@ -4,17 +4,23 @@ import ( "github.com/spf13/cobra" ) -var cmdKey = &cobra.Command{ - Use: "key", - Short: "Manage keys (passwords)", - Long: ` +func newKeyCommand() *cobra.Command { + cmd := &cobra.Command{ + Use: "key", + Short: "Manage keys (passwords)", + Long: ` The "key" command allows you to set multiple access keys or passwords per repository. `, - DisableAutoGenTag: true, - GroupID: cmdGroupDefault, -} + DisableAutoGenTag: true, + GroupID: cmdGroupDefault, + } -func init() { - cmdRoot.AddCommand(cmdKey) + cmd.AddCommand( + newKeyAddCommand(), + newKeyListCommand(), + newKeyPasswdCommand(), + newKeyRemoveCommand(), + ) + return cmd } diff --git a/cmd/restic/cmd_key_add.go b/cmd/restic/cmd_key_add.go index 1bcf50d79..a7670a842 100644 --- a/cmd/restic/cmd_key_add.go +++ b/cmd/restic/cmd_key_add.go @@ -52,10 +52,6 @@ func (opts *KeyAddOptions) Add(flags *pflag.FlagSet) { flags.StringVarP(&opts.Hostname, "host", "", "", "the hostname for new key") } -func init() { - cmdKey.AddCommand(newKeyAddCommand()) -} - func runKeyAdd(ctx context.Context, gopts GlobalOptions, opts KeyAddOptions, args []string) error { if len(args) > 0 { return fmt.Errorf("the key add command expects no arguments, only options - please see `restic help key add` for usage and flags") diff --git a/cmd/restic/cmd_key_list.go b/cmd/restic/cmd_key_list.go index c97955aee..6a0509f0f 100644 --- a/cmd/restic/cmd_key_list.go +++ b/cmd/restic/cmd_key_list.go @@ -38,10 +38,6 @@ Exit status is 12 if the password is incorrect. return cmd } -func init() { - cmdKey.AddCommand(newKeyListCommand()) -} - func runKeyList(ctx context.Context, gopts GlobalOptions, args []string) error { if len(args) > 0 { return fmt.Errorf("the key list command expects no arguments, only options - please see `restic help key list` for usage and flags") diff --git a/cmd/restic/cmd_key_passwd.go b/cmd/restic/cmd_key_passwd.go index 71f71955e..378325216 100644 --- a/cmd/restic/cmd_key_passwd.go +++ b/cmd/restic/cmd_key_passwd.go @@ -39,10 +39,6 @@ Exit status is 12 if the password is incorrect. return cmd } -func init() { - cmdKey.AddCommand(newKeyPasswdCommand()) -} - type KeyPasswdOptions struct { KeyAddOptions } diff --git a/cmd/restic/cmd_key_remove.go b/cmd/restic/cmd_key_remove.go index e3849eb3d..f6713e4c2 100644 --- a/cmd/restic/cmd_key_remove.go +++ b/cmd/restic/cmd_key_remove.go @@ -35,10 +35,6 @@ Exit status is 12 if the password is incorrect. return cmd } -func init() { - cmdKey.AddCommand(newKeyRemoveCommand()) -} - func runKeyRemove(ctx context.Context, gopts GlobalOptions, args []string) error { if len(args) != 1 { return fmt.Errorf("key remove expects one argument as the key id") diff --git a/cmd/restic/cmd_list.go b/cmd/restic/cmd_list.go index db33506ff..cf0cec414 100644 --- a/cmd/restic/cmd_list.go +++ b/cmd/restic/cmd_list.go @@ -41,10 +41,6 @@ Exit status is 12 if the password is incorrect. return cmd } -func init() { - cmdRoot.AddCommand(newListCommand()) -} - func runList(ctx context.Context, gopts GlobalOptions, args []string) error { if len(args) != 1 { return errors.Fatal("type not specified") diff --git a/cmd/restic/cmd_ls.go b/cmd/restic/cmd_ls.go index b9f625efd..7a24d4c9b 100644 --- a/cmd/restic/cmd_ls.go +++ b/cmd/restic/cmd_ls.go @@ -66,10 +66,6 @@ Exit status is 12 if the password is incorrect. return cmd } -func init() { - cmdRoot.AddCommand(newLsCommand()) -} - // LsOptions collects all options for the ls command. type LsOptions struct { ListLong bool diff --git a/cmd/restic/cmd_migrate.go b/cmd/restic/cmd_migrate.go index 9f07705ce..d4e7d0ac1 100644 --- a/cmd/restic/cmd_migrate.go +++ b/cmd/restic/cmd_migrate.go @@ -45,10 +45,6 @@ Exit status is 12 if the password is incorrect. return cmd } -func init() { - cmdRoot.AddCommand(newMigrateCommand()) -} - // MigrateOptions bundles all options for the 'check' command. type MigrateOptions struct { Force bool diff --git a/cmd/restic/cmd_mount.go b/cmd/restic/cmd_mount.go index 1c48c97cd..15760c6b7 100644 --- a/cmd/restic/cmd_mount.go +++ b/cmd/restic/cmd_mount.go @@ -22,6 +22,10 @@ import ( "github.com/anacrolix/fuse/fs" ) +func registerMountCommand(cmdRoot *cobra.Command) { + cmdRoot.AddCommand(newMountCommand()) +} + func newMountCommand() *cobra.Command { var opts MountOptions @@ -84,10 +88,6 @@ Exit status is 12 if the password is incorrect. return cmd } -func init() { - cmdRoot.AddCommand(newMountCommand()) -} - // MountOptions collects all options for the mount command. type MountOptions struct { OwnerRoot bool diff --git a/cmd/restic/cmd_mount_disabled.go b/cmd/restic/cmd_mount_disabled.go new file mode 100644 index 000000000..8b83905e7 --- /dev/null +++ b/cmd/restic/cmd_mount_disabled.go @@ -0,0 +1,10 @@ +//go:build !darwin && !freebsd && !linux +// +build !darwin,!freebsd,!linux + +package main + +import "github.com/spf13/cobra" + +func registerMountCommand(_ *cobra.Command) { + // Mount command not supported on these platforms +} diff --git a/cmd/restic/cmd_options.go b/cmd/restic/cmd_options.go index f99ba9c04..34cf8656e 100644 --- a/cmd/restic/cmd_options.go +++ b/cmd/restic/cmd_options.go @@ -38,7 +38,3 @@ Exit status is 1 if there was any error. } return cmd } - -func init() { - cmdRoot.AddCommand(newOptionsCommand()) -} diff --git a/cmd/restic/cmd_prune.go b/cmd/restic/cmd_prune.go index 5184b8a75..a613f2255 100644 --- a/cmd/restic/cmd_prune.go +++ b/cmd/restic/cmd_prune.go @@ -51,10 +51,6 @@ Exit status is 12 if the password is incorrect. return cmd } -func init() { - cmdRoot.AddCommand(newPruneCommand()) -} - // PruneOptions collects all options for the cleanup command. type PruneOptions struct { DryRun bool diff --git a/cmd/restic/cmd_recover.go b/cmd/restic/cmd_recover.go index 050fbd056..10284c111 100644 --- a/cmd/restic/cmd_recover.go +++ b/cmd/restic/cmd_recover.go @@ -38,10 +38,6 @@ Exit status is 12 if the password is incorrect. return cmd } -func init() { - cmdRoot.AddCommand(newRecoverCommand()) -} - func runRecover(ctx context.Context, gopts GlobalOptions) error { hostname, err := os.Hostname() if err != nil { diff --git a/cmd/restic/cmd_repair.go b/cmd/restic/cmd_repair.go index 6a1a1f9dc..bb1c98d27 100644 --- a/cmd/restic/cmd_repair.go +++ b/cmd/restic/cmd_repair.go @@ -4,13 +4,18 @@ import ( "github.com/spf13/cobra" ) -var cmdRepair = &cobra.Command{ - Use: "repair", - Short: "Repair the repository", - GroupID: cmdGroupDefault, - DisableAutoGenTag: true, -} +func newRepairCommand() *cobra.Command { + cmd := &cobra.Command{ + Use: "repair", + Short: "Repair the repository", + GroupID: cmdGroupDefault, + DisableAutoGenTag: true, + } -func init() { - cmdRoot.AddCommand(cmdRepair) + cmd.AddCommand( + newRepairIndexCommand(), + newRepairPacksCommand(), + newRepairSnapshotsCommand(), + ) + return cmd } diff --git a/cmd/restic/cmd_repair_index.go b/cmd/restic/cmd_repair_index.go index 441232bcb..079b322a9 100644 --- a/cmd/restic/cmd_repair_index.go +++ b/cmd/restic/cmd_repair_index.go @@ -40,10 +40,6 @@ Exit status is 12 if the password is incorrect. return cmd } -func init() { - cmdRepair.AddCommand(newRepairIndexCommand()) -} - // RepairIndexOptions collects all options for the repair index command. type RepairIndexOptions struct { ReadAllPacks bool @@ -76,11 +72,6 @@ func newRebuildIndexCommand() *cobra.Command { return cmd } -func init() { - // add alias for old name - cmdRoot.AddCommand(newRebuildIndexCommand()) -} - func runRebuildIndex(ctx context.Context, opts RepairIndexOptions, gopts GlobalOptions, term *termstatus.Terminal) error { ctx, repo, unlock, err := openWithExclusiveLock(ctx, gopts, false) if err != nil { diff --git a/cmd/restic/cmd_repair_packs.go b/cmd/restic/cmd_repair_packs.go index 4b787a2f9..aaaa2f08f 100644 --- a/cmd/restic/cmd_repair_packs.go +++ b/cmd/restic/cmd_repair_packs.go @@ -40,10 +40,6 @@ Exit status is 12 if the password is incorrect. return cmd } -func init() { - cmdRepair.AddCommand(newRepairPacksCommand()) -} - func runRepairPacks(ctx context.Context, gopts GlobalOptions, term *termstatus.Terminal, args []string) error { ids := restic.NewIDSet() for _, arg := range args { diff --git a/cmd/restic/cmd_repair_snapshots.go b/cmd/restic/cmd_repair_snapshots.go index fb294b02f..ecca7900e 100644 --- a/cmd/restic/cmd_repair_snapshots.go +++ b/cmd/restic/cmd_repair_snapshots.go @@ -57,10 +57,6 @@ Exit status is 12 if the password is incorrect. return cmd } -func init() { - cmdRepair.AddCommand(newRepairSnapshotsCommand()) -} - // RepairOptions collects all options for the repair command. type RepairOptions struct { DryRun bool diff --git a/cmd/restic/cmd_restore.go b/cmd/restic/cmd_restore.go index 0452322bb..a29b8a19e 100644 --- a/cmd/restic/cmd_restore.go +++ b/cmd/restic/cmd_restore.go @@ -56,10 +56,6 @@ Exit status is 12 if the password is incorrect. return cmd } -func init() { - cmdRoot.AddCommand(newRestoreCommand()) -} - // RestoreOptions collects all options for the restore command. type RestoreOptions struct { filter.ExcludePatternOptions diff --git a/cmd/restic/cmd_rewrite.go b/cmd/restic/cmd_rewrite.go index 7e295f3df..4e5b39932 100644 --- a/cmd/restic/cmd_rewrite.go +++ b/cmd/restic/cmd_rewrite.go @@ -66,10 +66,6 @@ Exit status is 12 if the password is incorrect. return cmd } -func init() { - cmdRoot.AddCommand(newRewriteCommand()) -} - type snapshotMetadata struct { Hostname string Time *time.Time diff --git a/cmd/restic/cmd_self_update.go b/cmd/restic/cmd_self_update.go index b3a5dbdc7..2247274ac 100644 --- a/cmd/restic/cmd_self_update.go +++ b/cmd/restic/cmd_self_update.go @@ -13,6 +13,12 @@ import ( "github.com/spf13/pflag" ) +func registerSelfUpdateCommand(cmd *cobra.Command) { + cmd.AddCommand( + newSelfUpdateCommand(), + ) +} + func newSelfUpdateCommand() *cobra.Command { var opts SelfUpdateOptions @@ -44,10 +50,6 @@ Exit status is 12 if the password is incorrect. return cmd } -func init() { - cmdRoot.AddCommand(newSelfUpdateCommand()) -} - // SelfUpdateOptions collects all options for the update-restic command. type SelfUpdateOptions struct { Output string diff --git a/cmd/restic/cmd_self_update_disabled.go b/cmd/restic/cmd_self_update_disabled.go new file mode 100644 index 000000000..ce9af10d2 --- /dev/null +++ b/cmd/restic/cmd_self_update_disabled.go @@ -0,0 +1,9 @@ +//go:build !selfupdate + +package main + +import "github.com/spf13/cobra" + +func registerSelfUpdateCommand(_ *cobra.Command) { + // No commands to register in non-selfupdate mode +} diff --git a/cmd/restic/cmd_snapshots.go b/cmd/restic/cmd_snapshots.go index dae5cafe8..65e5753c3 100644 --- a/cmd/restic/cmd_snapshots.go +++ b/cmd/restic/cmd_snapshots.go @@ -44,10 +44,6 @@ Exit status is 12 if the password is incorrect. return cmd } -func init() { - cmdRoot.AddCommand(newSnapshotsCommand()) -} - // SnapshotOptions bundles all options for the snapshots command. type SnapshotOptions struct { restic.SnapshotFilter diff --git a/cmd/restic/cmd_stats.go b/cmd/restic/cmd_stats.go index a906a9be0..1e7df11cb 100644 --- a/cmd/restic/cmd_stats.go +++ b/cmd/restic/cmd_stats.go @@ -73,10 +73,6 @@ Exit status is 12 if the password is incorrect. return cmd } -func init() { - cmdRoot.AddCommand(newStatsCommand()) -} - // StatsOptions collects all options for the stats command. type StatsOptions struct { // the mode of counting to perform (see consts for available modes) diff --git a/cmd/restic/cmd_tag.go b/cmd/restic/cmd_tag.go index f99e1ee9b..39e9a16b5 100644 --- a/cmd/restic/cmd_tag.go +++ b/cmd/restic/cmd_tag.go @@ -50,10 +50,6 @@ Exit status is 12 if the password is incorrect. return cmd } -func init() { - cmdRoot.AddCommand(newTagCommand()) -} - // TagOptions bundles all options for the 'tag' command. type TagOptions struct { restic.SnapshotFilter diff --git a/cmd/restic/cmd_unlock.go b/cmd/restic/cmd_unlock.go index fc5755cb7..5932ffcaa 100644 --- a/cmd/restic/cmd_unlock.go +++ b/cmd/restic/cmd_unlock.go @@ -33,10 +33,6 @@ Exit status is 1 if there was any error. return cmd } -func init() { - cmdRoot.AddCommand(newUnlockCommand()) -} - // UnlockOptions collects all options for the unlock command. type UnlockOptions struct { RemoveAll bool diff --git a/cmd/restic/cmd_version.go b/cmd/restic/cmd_version.go index 57fce5628..533098b9b 100644 --- a/cmd/restic/cmd_version.go +++ b/cmd/restic/cmd_version.go @@ -55,7 +55,3 @@ Exit status is 1 if there was any error. } return cmd } - -func init() { - cmdRoot.AddCommand(newVersionCommand()) -} diff --git a/cmd/restic/flags_test.go b/cmd/restic/flags_test.go index 1ae20119c..b1001b7c5 100644 --- a/cmd/restic/flags_test.go +++ b/cmd/restic/flags_test.go @@ -8,7 +8,7 @@ import ( // TestFlags checks for double defined flags, the commands will panic on // ParseFlags() when a shorthand flag is defined twice. func TestFlags(t *testing.T) { - for _, cmd := range cmdRoot.Commands() { + for _, cmd := range newRootCommand().Commands() { t.Run(cmd.Name(), func(t *testing.T) { cmd.Flags().SetOutput(io.Discard) err := cmd.ParseFlags([]string{"--help"}) diff --git a/cmd/restic/global_release.go b/cmd/restic/global_release.go index 63ad6e17d..2c4f28b13 100644 --- a/cmd/restic/global_release.go +++ b/cmd/restic/global_release.go @@ -5,6 +5,6 @@ package main import "github.com/spf13/cobra" -func registerProfiling(cmd *cobra.Command) { +func registerProfiling(_ *cobra.Command) { // No profiling in release mode } diff --git a/cmd/restic/main.go b/cmd/restic/main.go index 49499a151..72a726b0e 100644 --- a/cmd/restic/main.go +++ b/cmd/restic/main.go @@ -29,60 +29,60 @@ func init() { var ErrOK = errors.New("ok") -// cmdRoot is the base command when no other command has been specified. -var cmdRoot = &cobra.Command{ - Use: "restic", - Short: "Backup and restore files", - Long: ` +var cmdGroupDefault = "default" +var cmdGroupAdvanced = "advanced" + +func newRootCommand() *cobra.Command { + cmd := &cobra.Command{ + Use: "restic", + Short: "Backup and restore files", + Long: ` restic is a backup program which allows saving multiple revisions of files and directories in an encrypted repository stored on different backends. The full documentation can be found at https://restic.readthedocs.io/ . `, - SilenceErrors: true, - SilenceUsage: true, - DisableAutoGenTag: true, + SilenceErrors: true, + SilenceUsage: true, + DisableAutoGenTag: true, - PersistentPreRunE: func(c *cobra.Command, _ []string) error { - // set verbosity, default is one - globalOptions.verbosity = 1 - if globalOptions.Quiet && globalOptions.Verbose > 0 { - return errors.Fatal("--quiet and --verbose cannot be specified at the same time") - } + PersistentPreRunE: func(c *cobra.Command, _ []string) error { + // set verbosity, default is one + globalOptions.verbosity = 1 + if globalOptions.Quiet && globalOptions.Verbose > 0 { + return errors.Fatal("--quiet and --verbose cannot be specified at the same time") + } - switch { - case globalOptions.Verbose >= 2: - globalOptions.verbosity = 3 - case globalOptions.Verbose > 0: - globalOptions.verbosity = 2 - case globalOptions.Quiet: - globalOptions.verbosity = 0 - } + switch { + case globalOptions.Verbose >= 2: + globalOptions.verbosity = 3 + case globalOptions.Verbose > 0: + globalOptions.verbosity = 2 + case globalOptions.Quiet: + globalOptions.verbosity = 0 + } + + // parse extended options + opts, err := options.Parse(globalOptions.Options) + if err != nil { + return err + } + globalOptions.extended = opts + if !needsPassword(c.Name()) { + return nil + } + pwd, err := resolvePassword(globalOptions, "RESTIC_PASSWORD") + if err != nil { + fmt.Fprintf(os.Stderr, "Resolving password failed: %v\n", err) + Exit(1) + } + globalOptions.password = pwd - // parse extended options - opts, err := options.Parse(globalOptions.Options) - if err != nil { - return err - } - globalOptions.extended = opts - if !needsPassword(c.Name()) { return nil - } - pwd, err := resolvePassword(globalOptions, "RESTIC_PASSWORD") - if err != nil { - fmt.Fprintf(os.Stderr, "Resolving password failed: %v\n", err) - Exit(1) - } - globalOptions.password = pwd - return nil - }, -} + }, + } -var cmdGroupDefault = "default" -var cmdGroupAdvanced = "advanced" - -func init() { - cmdRoot.AddGroup( + cmd.AddGroup( &cobra.Group{ ID: cmdGroupDefault, Title: "Available Commands:", @@ -93,12 +93,48 @@ func init() { }, ) - globalOptions.AddFlags(cmdRoot.PersistentFlags()) + globalOptions.AddFlags(cmd.PersistentFlags()) // Use our "generate" command instead of the cobra provided "completion" command - cmdRoot.CompletionOptions.DisableDefaultCmd = true + cmd.CompletionOptions.DisableDefaultCmd = true - registerProfiling(cmdRoot) + cmd.AddCommand( + newBackupCommand(), + newCacheCommand(), + newCatCommand(), + newCheckCommand(), + newCopyCommand(), + newDiffCommand(), + newDumpCommand(), + newFeaturesCommand(), + newFindCommand(), + newForgetCommand(), + newGenerateCommand(), + newInitCommand(), + newKeyCommand(), + newListCommand(), + newLsCommand(), + newMigrateCommand(), + newOptionsCommand(), + newPruneCommand(), + newRebuildIndexCommand(), + newRecoverCommand(), + newRepairCommand(), + newRestoreCommand(), + newRewriteCommand(), + newSnapshotsCommand(), + newStatsCommand(), + newTagCommand(), + newUnlockCommand(), + newVersionCommand(), + ) + + registerDebugCommand(cmd) + registerMountCommand(cmd) + registerSelfUpdateCommand(cmd) + registerProfiling(cmd) + + return cmd } // Distinguish commands that need the password from those that work without, @@ -165,7 +201,7 @@ func main() { version, runtime.Version(), runtime.GOOS, runtime.GOARCH) ctx := createGlobalContext() - err = cmdRoot.ExecuteContext(ctx) + err = newRootCommand().ExecuteContext(ctx) if err == nil { err = ctx.Err() From c752867f0ae141e21fff80f84c5a518371381991 Mon Sep 17 00:00:00 2001 From: Michael Eischer Date: Fri, 7 Feb 2025 18:56:21 +0100 Subject: [PATCH 5/8] fix linter errors --- cmd/restic/cmd_debug.go | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/cmd/restic/cmd_debug.go b/cmd/restic/cmd_debug.go index 7e356b6c5..63ca86da3 100644 --- a/cmd/restic/cmd_debug.go +++ b/cmd/restic/cmd_debug.go @@ -118,7 +118,9 @@ func debugPrintSnapshots(ctx context.Context, repo *repository.Repository, wr io return err } - fmt.Fprintf(wr, "snapshot_id: %v\n", id) + if _, err := fmt.Fprintf(wr, "snapshot_id: %v\n", id); err != nil { + return err + } return prettyPrintJSON(wr, snapshot) }) @@ -218,7 +220,7 @@ func runDebugDump(ctx context.Context, gopts GlobalOptions, args []string) error } } -func tryRepairWithBitflip(ctx context.Context, key *crypto.Key, input []byte, bytewise bool) []byte { +func tryRepairWithBitflip(key *crypto.Key, input []byte, bytewise bool) []byte { if bytewise { Printf(" trying to repair blob by finding a broken byte\n") } else { @@ -317,7 +319,7 @@ func tryRepairWithBitflip(ctx context.Context, key *crypto.Key, input []byte, by return fixed } -func decryptUnsigned(ctx context.Context, k *crypto.Key, buf []byte) []byte { +func decryptUnsigned(k *crypto.Key, buf []byte) []byte { // strip signature at the end l := len(buf) nonce, ct := buf[:16], buf[16:l-16] @@ -368,13 +370,13 @@ func loadBlobs(ctx context.Context, opts DebugExamineOptions, repo restic.Reposi if err != nil { Warnf("error decrypting blob: %v\n", err) if opts.TryRepair || opts.RepairByte { - plaintext = tryRepairWithBitflip(ctx, key, buf, opts.RepairByte) + plaintext = tryRepairWithBitflip(key, buf, opts.RepairByte) } if plaintext != nil { outputPrefix = "repaired " filePrefix = "repaired-" } else { - plaintext = decryptUnsigned(ctx, key, buf) + plaintext = decryptUnsigned(key, buf) err = storePlainBlob(blob.ID, "damaged-", plaintext) if err != nil { return err From d378a171c8889460608689474272fec501839ec0 Mon Sep 17 00:00:00 2001 From: Michael Eischer Date: Fri, 7 Feb 2025 19:08:51 +0100 Subject: [PATCH 6/8] cleanup backend initialization --- cmd/restic/global.go | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/cmd/restic/global.go b/cmd/restic/global.go index 88502e18c..0be456d29 100644 --- a/cmd/restic/global.go +++ b/cmd/restic/global.go @@ -148,11 +148,12 @@ func (opts *GlobalOptions) AddFlags(f *pflag.FlagSet) { } var globalOptions = GlobalOptions{ - stdout: os.Stdout, - stderr: os.Stderr, + stdout: os.Stdout, + stderr: os.Stderr, + backends: collectBackends(), } -func init() { +func collectBackends() *location.Registry { backends := location.NewRegistry() backends.Register(azure.NewFactory()) backends.Register(b2.NewFactory()) @@ -163,7 +164,7 @@ func init() { backends.Register(s3.NewFactory()) backends.Register(sftp.NewFactory()) backends.Register(swift.NewFactory()) - globalOptions.backends = backends + return backends } func stdinIsTerminal() bool { From 120bd08c0d3c6431ab20d5962917703ca6492a4c Mon Sep 17 00:00:00 2001 From: Michael Eischer Date: Fri, 7 Feb 2025 19:14:55 +0100 Subject: [PATCH 7/8] move globalOptions initialization into method --- cmd/restic/global.go | 35 ++++++++++++++++++++++++++++++++++- cmd/restic/main.go | 34 +--------------------------------- cmd/restic/secondary_repo.go | 2 +- 3 files changed, 36 insertions(+), 35 deletions(-) diff --git a/cmd/restic/global.go b/cmd/restic/global.go index 0be456d29..066d0acef 100644 --- a/cmd/restic/global.go +++ b/cmd/restic/global.go @@ -147,6 +147,39 @@ func (opts *GlobalOptions) AddFlags(f *pflag.FlagSet) { } } +func (opts *GlobalOptions) PreRun(needsPassword bool) error { + // set verbosity, default is one + opts.verbosity = 1 + if opts.Quiet && opts.Verbose > 0 { + return errors.Fatal("--quiet and --verbose cannot be specified at the same time") + } + + switch { + case opts.Verbose >= 2: + opts.verbosity = 3 + case opts.Verbose > 0: + opts.verbosity = 2 + case opts.Quiet: + opts.verbosity = 0 + } + + // parse extended options + extendedOpts, err := options.Parse(opts.Options) + if err != nil { + return err + } + opts.extended = extendedOpts + if !needsPassword { + return nil + } + pwd, err := resolvePassword(opts, "RESTIC_PASSWORD") + if err != nil { + return errors.Fatal(fmt.Sprintf("Resolving password failed: %v\n", err)) + } + opts.password = pwd + return nil +} + var globalOptions = GlobalOptions{ stdout: os.Stdout, stderr: os.Stderr, @@ -255,7 +288,7 @@ func Warnf(format string, args ...interface{}) { } // resolvePassword determines the password to be used for opening the repository. -func resolvePassword(opts GlobalOptions, envStr string) (string, error) { +func resolvePassword(opts *GlobalOptions, envStr string) (string, error) { if opts.PasswordFile != "" && opts.PasswordCommand != "" { return "", errors.Fatalf("Password file and command are mutually exclusive options") } diff --git a/cmd/restic/main.go b/cmd/restic/main.go index 72a726b0e..80da200e3 100644 --- a/cmd/restic/main.go +++ b/cmd/restic/main.go @@ -17,7 +17,6 @@ import ( "github.com/restic/restic/internal/debug" "github.com/restic/restic/internal/errors" "github.com/restic/restic/internal/feature" - "github.com/restic/restic/internal/options" "github.com/restic/restic/internal/repository" "github.com/restic/restic/internal/restic" ) @@ -47,38 +46,7 @@ The full documentation can be found at https://restic.readthedocs.io/ . DisableAutoGenTag: true, PersistentPreRunE: func(c *cobra.Command, _ []string) error { - // set verbosity, default is one - globalOptions.verbosity = 1 - if globalOptions.Quiet && globalOptions.Verbose > 0 { - return errors.Fatal("--quiet and --verbose cannot be specified at the same time") - } - - switch { - case globalOptions.Verbose >= 2: - globalOptions.verbosity = 3 - case globalOptions.Verbose > 0: - globalOptions.verbosity = 2 - case globalOptions.Quiet: - globalOptions.verbosity = 0 - } - - // parse extended options - opts, err := options.Parse(globalOptions.Options) - if err != nil { - return err - } - globalOptions.extended = opts - if !needsPassword(c.Name()) { - return nil - } - pwd, err := resolvePassword(globalOptions, "RESTIC_PASSWORD") - if err != nil { - fmt.Fprintf(os.Stderr, "Resolving password failed: %v\n", err) - Exit(1) - } - globalOptions.password = pwd - - return nil + return globalOptions.PreRun(needsPassword(c.Name())) }, } diff --git a/cmd/restic/secondary_repo.go b/cmd/restic/secondary_repo.go index 44621afa1..66fd4b03b 100644 --- a/cmd/restic/secondary_repo.go +++ b/cmd/restic/secondary_repo.go @@ -110,7 +110,7 @@ func fillSecondaryGlobalOpts(ctx context.Context, opts secondaryRepoOptions, gop if opts.password != "" { dstGopts.password = opts.password } else { - dstGopts.password, err = resolvePassword(dstGopts, pwdEnv) + dstGopts.password, err = resolvePassword(&dstGopts, pwdEnv) if err != nil { return GlobalOptions{}, false, err } From 0c4e65228a25baad25eb3d5143868a5a66b226c6 Mon Sep 17 00:00:00 2001 From: Michael Eischer Date: Fri, 7 Feb 2025 20:40:03 +0100 Subject: [PATCH 8/8] refactor secondary options --- cmd/restic/cmd_copy.go | 2 +- cmd/restic/cmd_init.go | 2 +- cmd/restic/secondary_repo.go | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/cmd/restic/cmd_copy.go b/cmd/restic/cmd_copy.go index cdf688dfa..2ad5a464c 100644 --- a/cmd/restic/cmd_copy.go +++ b/cmd/restic/cmd_copy.go @@ -61,7 +61,7 @@ type CopyOptions struct { } func (opts *CopyOptions) AddFlags(f *pflag.FlagSet) { - initSecondaryRepoOptions(f, &opts.secondaryRepoOptions, "destination", "to copy snapshots from") + opts.secondaryRepoOptions.AddFlags(f, "destination", "to copy snapshots from") initMultiSnapshotFilter(f, &opts.SnapshotFilter, true) } diff --git a/cmd/restic/cmd_init.go b/cmd/restic/cmd_init.go index db2e97c93..d66163af1 100644 --- a/cmd/restic/cmd_init.go +++ b/cmd/restic/cmd_init.go @@ -48,7 +48,7 @@ type InitOptions struct { } func (opts *InitOptions) AddFlags(f *pflag.FlagSet) { - initSecondaryRepoOptions(f, &opts.secondaryRepoOptions, "secondary", "to copy chunker parameters from") + opts.secondaryRepoOptions.AddFlags(f, "secondary", "to copy chunker parameters from") f.BoolVar(&opts.CopyChunkerParameters, "copy-chunker-params", false, "copy chunker parameters from the secondary repository (useful with the copy command)") f.StringVar(&opts.RepositoryVersion, "repository-version", "stable", "repository format version to use, allowed values are a format version, 'latest' and 'stable'") } diff --git a/cmd/restic/secondary_repo.go b/cmd/restic/secondary_repo.go index 66fd4b03b..db4c93bad 100644 --- a/cmd/restic/secondary_repo.go +++ b/cmd/restic/secondary_repo.go @@ -25,7 +25,7 @@ type secondaryRepoOptions struct { LegacyKeyHint string } -func initSecondaryRepoOptions(f *pflag.FlagSet, opts *secondaryRepoOptions, repoPrefix string, repoUsage string) { +func (opts *secondaryRepoOptions) AddFlags(f *pflag.FlagSet, repoPrefix string, repoUsage string) { f.StringVarP(&opts.LegacyRepo, "repo2", "", "", repoPrefix+" `repository` "+repoUsage+" (default: $RESTIC_REPOSITORY2)") f.StringVarP(&opts.LegacyRepositoryFile, "repository-file2", "", "", "`file` from which to read the "+repoPrefix+" repository location "+repoUsage+" (default: $RESTIC_REPOSITORY_FILE2)") f.StringVarP(&opts.LegacyPasswordFile, "password-file2", "", "", "`file` to read the "+repoPrefix+" repository password from (default: $RESTIC_PASSWORD_FILE2)")