mirror of https://github.com/restic/restic.git
Merge pull request #5241 from MichaelEischer/cleanup-cli
Refactor CLI command initialization to use less global state
This commit is contained in:
commit
8c12291f56
|
@ -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"
|
||||
|
@ -30,7 +31,10 @@ import (
|
|||
"github.com/restic/restic/internal/ui/termstatus"
|
||||
)
|
||||
|
||||
var cmdBackup = &cobra.Command{
|
||||
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: `
|
||||
|
@ -48,13 +52,13 @@ 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 == "" {
|
||||
if opts.Host == "" {
|
||||
hostname, err := os.Hostname()
|
||||
if err != nil {
|
||||
debug.Log("os.Hostname() returned err: %v", err)
|
||||
return
|
||||
}
|
||||
backupOptions.Host = hostname
|
||||
opts.Host = hostname
|
||||
}
|
||||
},
|
||||
GroupID: cmdGroupDefault,
|
||||
|
@ -62,8 +66,12 @@ Exit status is 12 if the password is incorrect.
|
|||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
term, cancel := setupTermstatus()
|
||||
defer cancel()
|
||||
return runBackup(cmd.Context(), backupOptions, globalOptions, term, args)
|
||||
return runBackup(cmd.Context(), opts, globalOptions, term, args)
|
||||
},
|
||||
}
|
||||
|
||||
opts.AddFlags(cmd.Flags())
|
||||
return cmd
|
||||
}
|
||||
|
||||
// BackupOptions bundles all options for the backup command.
|
||||
|
@ -97,64 +105,60 @@ type BackupOptions struct {
|
|||
SkipIfUnchanged bool
|
||||
}
|
||||
|
||||
var backupOptions BackupOptions
|
||||
var backupFSTestHook func(fs fs.FS) fs.FS
|
||||
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)`)
|
||||
|
||||
// ErrInvalidSourceData is used to report an incomplete backup
|
||||
var ErrInvalidSourceData = errors.New("at least one source file could not be read")
|
||||
opts.ExcludePatternOptions.Add(f)
|
||||
|
||||
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")
|
||||
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(&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")
|
||||
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(&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(&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(&backupOptions.SkipIfUnchanged, "skip-if-unchanged", false, "skip snapshot creation if identical to parent snapshot")
|
||||
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)
|
||||
backupOptions.ReadConcurrency = uint(readConcurrency)
|
||||
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 != "" {
|
||||
backupOptions.Host = host
|
||||
opts.Host = host
|
||||
}
|
||||
}
|
||||
|
||||
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")
|
||||
|
||||
// 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) {
|
||||
|
|
|
@ -13,9 +13,13 @@ 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{
|
||||
func newCacheCommand() *cobra.Command {
|
||||
var opts CacheOptions
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "cache",
|
||||
Short: "Operate on local cache directories",
|
||||
Long: `
|
||||
|
@ -30,8 +34,12 @@ 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)
|
||||
return runCache(opts, globalOptions, args)
|
||||
},
|
||||
}
|
||||
|
||||
opts.AddFlags(cmd.Flags())
|
||||
return cmd
|
||||
}
|
||||
|
||||
// CacheOptions bundles all options for the snapshots command.
|
||||
|
@ -41,15 +49,10 @@ type CacheOptions struct {
|
|||
NoSize bool
|
||||
}
|
||||
|
||||
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")
|
||||
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")
|
||||
}
|
||||
|
||||
func runCache(opts CacheOptions, gopts GlobalOptions, args []string) error {
|
||||
|
|
|
@ -14,7 +14,8 @@ import (
|
|||
|
||||
var catAllowedCmds = []string{"config", "index", "snapshot", "key", "masterkey", "lock", "pack", "blob", "tree"}
|
||||
|
||||
var cmdCat = &cobra.Command{
|
||||
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: `
|
||||
|
@ -35,10 +36,8 @@ Exit status is 12 if the password is incorrect.
|
|||
return runCat(cmd.Context(), globalOptions, args)
|
||||
},
|
||||
ValidArgs: catAllowedCmds,
|
||||
}
|
||||
|
||||
func init() {
|
||||
cmdRoot.AddCommand(cmdCat)
|
||||
}
|
||||
return cmd
|
||||
}
|
||||
|
||||
func validateCatArgs(args []string) error {
|
||||
|
|
|
@ -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"
|
||||
|
@ -22,7 +23,9 @@ import (
|
|||
"github.com/restic/restic/internal/ui/termstatus"
|
||||
)
|
||||
|
||||
var cmdCheck = &cobra.Command{
|
||||
func newCheckCommand() *cobra.Command {
|
||||
var opts CheckOptions
|
||||
cmd := &cobra.Command{
|
||||
Use: "check [flags]",
|
||||
Short: "Check the repository for errors",
|
||||
Long: `
|
||||
|
@ -46,7 +49,7 @@ Exit status is 12 if the password is incorrect.
|
|||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
term, cancel := setupTermstatus()
|
||||
defer cancel()
|
||||
summary, err := runCheck(cmd.Context(), checkOptions, globalOptions, args, term)
|
||||
summary, err := runCheck(cmd.Context(), opts, globalOptions, args, term)
|
||||
if globalOptions.JSON {
|
||||
if err != nil && summary.NumErrors == 0 {
|
||||
summary.NumErrors = 1
|
||||
|
@ -56,8 +59,12 @@ Exit status is 12 if the password is incorrect.
|
|||
return err
|
||||
},
|
||||
PreRunE: func(_ *cobra.Command, _ []string) error {
|
||||
return checkFlags(checkOptions)
|
||||
return checkFlags(opts)
|
||||
},
|
||||
}
|
||||
|
||||
opts.AddFlags(cmd.Flags())
|
||||
return cmd
|
||||
}
|
||||
|
||||
// CheckOptions bundles all options for the 'check' command.
|
||||
|
@ -68,14 +75,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 +85,7 @@ 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")
|
||||
}
|
||||
|
||||
func checkFlags(opts CheckOptions) error {
|
||||
|
|
|
@ -11,9 +11,12 @@ import (
|
|||
"golang.org/x/sync/errgroup"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
)
|
||||
|
||||
var cmdCopy = &cobra.Command{
|
||||
func newCopyCommand() *cobra.Command {
|
||||
var opts CopyOptions
|
||||
cmd := &cobra.Command{
|
||||
Use: "copy [flags] [snapshotID ...]",
|
||||
Short: "Copy snapshots from one repository to another",
|
||||
Long: `
|
||||
|
@ -43,8 +46,12 @@ 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)
|
||||
return runCopy(cmd.Context(), opts, globalOptions, args)
|
||||
},
|
||||
}
|
||||
|
||||
opts.AddFlags(cmd.Flags())
|
||||
return cmd
|
||||
}
|
||||
|
||||
// CopyOptions bundles all options for the copy command.
|
||||
|
@ -53,14 +60,9 @@ type CopyOptions struct {
|
|||
restic.SnapshotFilter
|
||||
}
|
||||
|
||||
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)
|
||||
func (opts *CopyOptions) AddFlags(f *pflag.FlagSet) {
|
||||
opts.secondaryRepoOptions.AddFlags(f, "destination", "to copy snapshots from")
|
||||
initMultiSnapshotFilter(f, &opts.SnapshotFilter, true)
|
||||
}
|
||||
|
||||
func runCopy(ctx context.Context, opts CopyOptions, gopts GlobalOptions, args []string) error {
|
||||
|
|
|
@ -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"
|
||||
|
@ -28,14 +29,26 @@ import (
|
|||
"github.com/restic/restic/internal/restic"
|
||||
)
|
||||
|
||||
var cmdDebug = &cobra.Command{
|
||||
func registerDebugCommand(cmd *cobra.Command) {
|
||||
cmd.AddCommand(
|
||||
newDebugCommand(),
|
||||
)
|
||||
}
|
||||
|
||||
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{
|
||||
func newDebugDumpCommand() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "dump [indexes|snapshots|all|packs]",
|
||||
Short: "Dump data structures",
|
||||
Long: `
|
||||
|
@ -55,6 +68,24 @@ Exit status is 12 if the password is incorrect.
|
|||
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 {
|
||||
|
@ -64,16 +95,11 @@ type DebugExamineOptions struct {
|
|||
ReuploadBlobs bool
|
||||
}
|
||||
|
||||
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")
|
||||
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")
|
||||
}
|
||||
|
||||
func prettyPrintJSON(wr io.Writer, item interface{}) error {
|
||||
|
@ -92,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)
|
||||
})
|
||||
|
@ -192,16 +220,7 @@ 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 {
|
||||
func tryRepairWithBitflip(key *crypto.Key, input []byte, bytewise bool) []byte {
|
||||
if bytewise {
|
||||
Printf(" trying to repair blob by finding a broken byte\n")
|
||||
} else {
|
||||
|
@ -300,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]
|
||||
|
@ -351,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
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -12,9 +12,13 @@ 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{
|
||||
func newDiffCommand() *cobra.Command {
|
||||
var opts DiffOptions
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "diff [flags] snapshotID snapshotID",
|
||||
Short: "Show differences between two snapshots",
|
||||
Long: `
|
||||
|
@ -48,8 +52,12 @@ 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)
|
||||
return runDiff(cmd.Context(), opts, globalOptions, args)
|
||||
},
|
||||
}
|
||||
|
||||
opts.AddFlags(cmd.Flags())
|
||||
return cmd
|
||||
}
|
||||
|
||||
// DiffOptions collects all options for the diff command.
|
||||
|
@ -57,13 +65,8 @@ type DiffOptions struct {
|
|||
ShowMetadata bool
|
||||
}
|
||||
|
||||
var diffOptions DiffOptions
|
||||
|
||||
func init() {
|
||||
cmdRoot.AddCommand(cmdDiff)
|
||||
|
||||
f := cmdDiff.Flags()
|
||||
f.BoolVar(&diffOptions.ShowMetadata, "metadata", false, "print changes in metadata")
|
||||
func (opts *DiffOptions) AddFlags(f *pflag.FlagSet) {
|
||||
f.BoolVar(&opts.ShowMetadata, "metadata", false, "print changes in metadata")
|
||||
}
|
||||
|
||||
func loadSnapshot(ctx context.Context, be restic.Lister, repo restic.LoaderUnpacked, desc string) (*restic.Snapshot, string, error) {
|
||||
|
|
|
@ -13,9 +13,12 @@ import (
|
|||
"github.com/restic/restic/internal/restic"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
)
|
||||
|
||||
var cmdDump = &cobra.Command{
|
||||
func newDumpCommand() *cobra.Command {
|
||||
var opts DumpOptions
|
||||
cmd := &cobra.Command{
|
||||
Use: "dump [flags] snapshotID file",
|
||||
Short: "Print a backed-up file to stdout",
|
||||
Long: `
|
||||
|
@ -43,8 +46,12 @@ 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)
|
||||
return runDump(cmd.Context(), opts, globalOptions, args)
|
||||
},
|
||||
}
|
||||
|
||||
opts.AddFlags(cmd.Flags())
|
||||
return cmd
|
||||
}
|
||||
|
||||
// DumpOptions collects all options for the dump command.
|
||||
|
@ -54,15 +61,10 @@ type DumpOptions struct {
|
|||
Target string
|
||||
}
|
||||
|
||||
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`")
|
||||
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`")
|
||||
}
|
||||
|
||||
func splitPath(p string) []string {
|
||||
|
|
|
@ -10,7 +10,8 @@ import (
|
|||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var featuresCmd = &cobra.Command{
|
||||
func newFeaturesCommand() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "features",
|
||||
Short: "Print list of feature flags",
|
||||
Long: `
|
||||
|
@ -52,8 +53,7 @@ Exit status is 1 if there was any error.
|
|||
}
|
||||
return tab.Write(globalOptions.stdout)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func init() {
|
||||
cmdRoot.AddCommand(featuresCmd)
|
||||
return cmd
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
|
@ -16,7 +17,10 @@ import (
|
|||
"github.com/restic/restic/internal/walker"
|
||||
)
|
||||
|
||||
var cmdFind = &cobra.Command{
|
||||
func newFindCommand() *cobra.Command {
|
||||
var opts FindOptions
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "find [flags] PATTERN...",
|
||||
Short: "Find a file, a directory or restic IDs",
|
||||
Long: `
|
||||
|
@ -44,8 +48,12 @@ 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)
|
||||
return runFind(cmd.Context(), opts, globalOptions, args)
|
||||
},
|
||||
}
|
||||
|
||||
opts.AddFlags(cmd.Flags())
|
||||
return cmd
|
||||
}
|
||||
|
||||
// FindOptions bundles all options for the find command.
|
||||
|
@ -62,25 +70,20 @@ type FindOptions struct {
|
|||
restic.SnapshotFilter
|
||||
}
|
||||
|
||||
var findOptions FindOptions
|
||||
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")
|
||||
|
||||
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)
|
||||
initMultiSnapshotFilter(f, &opts.SnapshotFilter, true)
|
||||
}
|
||||
|
||||
type findPattern struct {
|
||||
|
|
|
@ -11,9 +11,14 @@ 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{
|
||||
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: `
|
||||
|
@ -45,8 +50,13 @@ Exit status is 12 if the password is incorrect.
|
|||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
term, cancel := setupTermstatus()
|
||||
defer cancel()
|
||||
return runForget(cmd.Context(), forgetOptions, forgetPruneOptions, globalOptions, term, args)
|
||||
return runForget(cmd.Context(), opts, pruneOpts, globalOptions, term, args)
|
||||
},
|
||||
}
|
||||
|
||||
opts.AddFlags(cmd.Flags())
|
||||
pruneOpts.AddLimitedFlags(cmd.Flags())
|
||||
return cmd
|
||||
}
|
||||
|
||||
type ForgetPolicyCount int
|
||||
|
@ -111,44 +121,37 @@ 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)
|
||||
}
|
||||
|
||||
func verifyForgetOptions(opts *ForgetOptions) error {
|
||||
|
|
|
@ -8,9 +8,13 @@ import (
|
|||
"github.com/restic/restic/internal/errors"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/cobra/doc"
|
||||
"github.com/spf13/pflag"
|
||||
)
|
||||
|
||||
var cmdGenerate = &cobra.Command{
|
||||
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: `
|
||||
|
@ -25,8 +29,11 @@ Exit status is 1 if there was any error.
|
|||
`,
|
||||
DisableAutoGenTag: true,
|
||||
RunE: func(_ *cobra.Command, args []string) error {
|
||||
return runGenerate(genOpts, args)
|
||||
return runGenerate(opts, args)
|
||||
},
|
||||
}
|
||||
opts.AddFlags(cmd.Flags())
|
||||
return cmd
|
||||
}
|
||||
|
||||
type generateOptions struct {
|
||||
|
@ -37,19 +44,15 @@ type generateOptions struct {
|
|||
PowerShellCompletionFile string
|
||||
}
|
||||
|
||||
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)")
|
||||
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)")
|
||||
}
|
||||
|
||||
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 {
|
||||
|
@ -64,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) {
|
||||
|
@ -112,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
|
||||
}
|
||||
|
|
|
@ -12,9 +12,13 @@ import (
|
|||
"github.com/restic/restic/internal/restic"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
)
|
||||
|
||||
var cmdInit = &cobra.Command{
|
||||
func newInitCommand() *cobra.Command {
|
||||
var opts InitOptions
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "init",
|
||||
Short: "Initialize a new repository",
|
||||
Long: `
|
||||
|
@ -29,8 +33,11 @@ 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)
|
||||
return runInit(cmd.Context(), opts, globalOptions, args)
|
||||
},
|
||||
}
|
||||
opts.AddFlags(cmd.Flags())
|
||||
return cmd
|
||||
}
|
||||
|
||||
// InitOptions bundles all options for the init command.
|
||||
|
@ -40,15 +47,10 @@ type InitOptions struct {
|
|||
RepositoryVersion string
|
||||
}
|
||||
|
||||
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'")
|
||||
func (opts *InitOptions) AddFlags(f *pflag.FlagSet) {
|
||||
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'")
|
||||
}
|
||||
|
||||
func runInit(ctx context.Context, opts InitOptions, gopts GlobalOptions, args []string) error {
|
||||
|
|
|
@ -4,7 +4,8 @@ import (
|
|||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var cmdKey = &cobra.Command{
|
||||
func newKeyCommand() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "key",
|
||||
Short: "Manage keys (passwords)",
|
||||
Long: `
|
||||
|
@ -13,8 +14,13 @@ per repository.
|
|||
`,
|
||||
DisableAutoGenTag: true,
|
||||
GroupID: cmdGroupDefault,
|
||||
}
|
||||
}
|
||||
|
||||
func init() {
|
||||
cmdRoot.AddCommand(cmdKey)
|
||||
cmd.AddCommand(
|
||||
newKeyAddCommand(),
|
||||
newKeyListCommand(),
|
||||
newKeyPasswdCommand(),
|
||||
newKeyRemoveCommand(),
|
||||
)
|
||||
return cmd
|
||||
}
|
||||
|
|
|
@ -10,7 +10,10 @@ import (
|
|||
"github.com/spf13/pflag"
|
||||
)
|
||||
|
||||
var cmdKeyAdd = &cobra.Command{
|
||||
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: `
|
||||
|
@ -26,6 +29,13 @@ 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 runKeyAdd(cmd.Context(), globalOptions, opts, args)
|
||||
},
|
||||
}
|
||||
|
||||
opts.Add(cmd.Flags())
|
||||
return cmd
|
||||
}
|
||||
|
||||
type KeyAddOptions struct {
|
||||
|
@ -42,16 +52,6 @@ func (opts *KeyAddOptions) Add(flags *pflag.FlagSet) {
|
|||
flags.StringVarP(&opts.Hostname, "host", "", "", "the hostname for new key")
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
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")
|
||||
|
|
|
@ -12,7 +12,8 @@ import (
|
|||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var cmdKeyList = &cobra.Command{
|
||||
func newKeyListCommand() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "list",
|
||||
Short: "List keys (passwords)",
|
||||
Long: `
|
||||
|
@ -33,10 +34,8 @@ Exit status is 12 if the password is incorrect.
|
|||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
return runKeyList(cmd.Context(), globalOptions, args)
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
cmdKey.AddCommand(cmdKeyList)
|
||||
}
|
||||
return cmd
|
||||
}
|
||||
|
||||
func runKeyList(ctx context.Context, gopts GlobalOptions, args []string) error {
|
||||
|
|
|
@ -7,9 +7,13 @@ 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{
|
||||
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: `
|
||||
|
@ -26,20 +30,21 @@ 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 runKeyPasswd(cmd.Context(), globalOptions, opts, args)
|
||||
},
|
||||
}
|
||||
|
||||
opts.AddFlags(cmd.Flags())
|
||||
return cmd
|
||||
}
|
||||
|
||||
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 {
|
||||
|
|
|
@ -10,7 +10,8 @@ import (
|
|||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var cmdKeyRemove = &cobra.Command{
|
||||
func newKeyRemoveCommand() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "remove [ID]",
|
||||
Short: "Remove key ID (password) from the repository.",
|
||||
Long: `
|
||||
|
@ -30,10 +31,8 @@ Exit status is 12 if the password is incorrect.
|
|||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
return runKeyRemove(cmd.Context(), globalOptions, args)
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
cmdKey.AddCommand(cmdKeyRemove)
|
||||
}
|
||||
return cmd
|
||||
}
|
||||
|
||||
func runKeyRemove(ctx context.Context, gopts GlobalOptions, args []string) error {
|
||||
|
|
|
@ -11,10 +11,11 @@ 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{
|
||||
cmd := &cobra.Command{
|
||||
Use: "list [flags] [" + listAllowedArgsUseString + "]",
|
||||
Short: "List objects in the repository",
|
||||
Long: `
|
||||
|
@ -36,10 +37,8 @@ Exit status is 12 if the password is incorrect.
|
|||
},
|
||||
ValidArgs: listAllowedArgs,
|
||||
Args: cobra.MatchAll(cobra.ExactArgs(1), cobra.OnlyValidArgs),
|
||||
}
|
||||
|
||||
func init() {
|
||||
cmdRoot.AddCommand(cmdList)
|
||||
}
|
||||
return cmd
|
||||
}
|
||||
|
||||
func runList(ctx context.Context, gopts GlobalOptions, args []string) error {
|
||||
|
|
|
@ -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"
|
||||
|
@ -20,7 +21,10 @@ import (
|
|||
"github.com/restic/restic/internal/walker"
|
||||
)
|
||||
|
||||
var cmdLs = &cobra.Command{
|
||||
func newLsCommand() *cobra.Command {
|
||||
var opts LsOptions
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "ls [flags] snapshotID [dir...]",
|
||||
Short: "List files in a snapshot",
|
||||
Long: `
|
||||
|
@ -55,8 +59,11 @@ 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)
|
||||
return runLs(cmd.Context(), opts, globalOptions, args)
|
||||
},
|
||||
}
|
||||
opts.AddFlags(cmd.Flags())
|
||||
return cmd
|
||||
}
|
||||
|
||||
// LsOptions collects all options for the ls command.
|
||||
|
@ -70,19 +77,14 @@ type LsOptions struct {
|
|||
Reverse bool
|
||||
}
|
||||
|
||||
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")
|
||||
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")
|
||||
}
|
||||
|
||||
type lsPrinter interface {
|
||||
|
|
|
@ -9,9 +9,13 @@ import (
|
|||
"github.com/restic/restic/internal/ui/termstatus"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
)
|
||||
|
||||
var cmdMigrate = &cobra.Command{
|
||||
func newMigrateCommand() *cobra.Command {
|
||||
var opts MigrateOptions
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "migrate [flags] [migration name] [...]",
|
||||
Short: "Apply migrations",
|
||||
Long: `
|
||||
|
@ -33,8 +37,12 @@ Exit status is 12 if the password is incorrect.
|
|||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
term, cancel := setupTermstatus()
|
||||
defer cancel()
|
||||
return runMigrate(cmd.Context(), migrateOptions, globalOptions, args, term)
|
||||
return runMigrate(cmd.Context(), opts, globalOptions, args, term)
|
||||
},
|
||||
}
|
||||
|
||||
opts.AddFlags(cmd.Flags())
|
||||
return cmd
|
||||
}
|
||||
|
||||
// MigrateOptions bundles all options for the 'check' command.
|
||||
|
@ -42,12 +50,8 @@ type MigrateOptions struct {
|
|||
Force bool
|
||||
}
|
||||
|
||||
var migrateOptions MigrateOptions
|
||||
|
||||
func init() {
|
||||
cmdRoot.AddCommand(cmdMigrate)
|
||||
f := cmdMigrate.Flags()
|
||||
f.BoolVarP(&migrateOptions.Force, "force", "f", false, `apply a migration a second time`)
|
||||
func (opts *MigrateOptions) AddFlags(f *pflag.FlagSet) {
|
||||
f.BoolVarP(&opts.Force, "force", "f", false, `apply a migration a second time`)
|
||||
}
|
||||
|
||||
func checkMigrations(ctx context.Context, repo restic.Repository, printer progress.Printer) error {
|
||||
|
|
|
@ -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"
|
||||
|
@ -21,7 +22,14 @@ import (
|
|||
"github.com/anacrolix/fuse/fs"
|
||||
)
|
||||
|
||||
var cmdMount = &cobra.Command{
|
||||
func registerMountCommand(cmdRoot *cobra.Command) {
|
||||
cmdRoot.AddCommand(newMountCommand())
|
||||
}
|
||||
|
||||
func newMountCommand() *cobra.Command {
|
||||
var opts MountOptions
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "mount [flags] mountpoint",
|
||||
Short: "Mount the repository",
|
||||
Long: `
|
||||
|
@ -72,8 +80,12 @@ 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)
|
||||
return runMount(cmd.Context(), opts, globalOptions, args)
|
||||
},
|
||||
}
|
||||
|
||||
opts.AddFlags(cmd.Flags())
|
||||
return cmd
|
||||
}
|
||||
|
||||
// MountOptions collects all options for the mount command.
|
||||
|
@ -86,22 +98,17 @@ type MountOptions struct {
|
|||
PathTemplates []string
|
||||
}
|
||||
|
||||
var mountOptions MountOptions
|
||||
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")
|
||||
|
||||
func init() {
|
||||
cmdRoot.AddCommand(cmdMount)
|
||||
initMultiSnapshotFilter(f, &opts.SnapshotFilter, true)
|
||||
|
||||
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")
|
||||
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")
|
||||
}
|
||||
|
||||
func runMount(ctx context.Context, opts MountOptions, gopts GlobalOptions, args []string) error {
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -8,7 +8,8 @@ import (
|
|||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var optionsCmd = &cobra.Command{
|
||||
func newOptionsCommand() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "options",
|
||||
Short: "Print list of extended options",
|
||||
Long: `
|
||||
|
@ -34,8 +35,6 @@ Exit status is 1 if there was any error.
|
|||
fmt.Printf(" %*s %s\n", -maxLen, opt.Namespace+"."+opt.Name, opt.Text)
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
cmdRoot.AddCommand(optionsCmd)
|
||||
}
|
||||
return cmd
|
||||
}
|
||||
|
|
|
@ -16,9 +16,13 @@ import (
|
|||
"github.com/restic/restic/internal/ui/termstatus"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
)
|
||||
|
||||
var cmdPrune = &cobra.Command{
|
||||
func newPruneCommand() *cobra.Command {
|
||||
var opts PruneOptions
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "prune [flags]",
|
||||
Short: "Remove unneeded data from the repository",
|
||||
Long: `
|
||||
|
@ -39,8 +43,12 @@ Exit status is 12 if the password is incorrect.
|
|||
RunE: func(cmd *cobra.Command, _ []string) error {
|
||||
term, cancel := setupTermstatus()
|
||||
defer cancel()
|
||||
return runPrune(cmd.Context(), pruneOptions, globalOptions, term)
|
||||
return runPrune(cmd.Context(), opts, globalOptions, term)
|
||||
},
|
||||
}
|
||||
|
||||
opts.AddFlags(cmd.Flags())
|
||||
return cmd
|
||||
}
|
||||
|
||||
// PruneOptions collects all options for the cleanup command.
|
||||
|
@ -61,23 +69,18 @@ type PruneOptions struct {
|
|||
RepackUncompressed bool
|
||||
}
|
||||
|
||||
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 (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 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")
|
||||
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")
|
||||
}
|
||||
|
||||
func verifyPruneOptions(opts *PruneOptions) error {
|
||||
|
|
|
@ -11,7 +11,8 @@ import (
|
|||
"golang.org/x/sync/errgroup"
|
||||
)
|
||||
|
||||
var cmdRecover = &cobra.Command{
|
||||
func newRecoverCommand() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "recover [flags]",
|
||||
Short: "Recover data from the repository not referenced by snapshots",
|
||||
Long: `
|
||||
|
@ -33,10 +34,8 @@ Exit status is 12 if the password is incorrect.
|
|||
RunE: func(cmd *cobra.Command, _ []string) error {
|
||||
return runRecover(cmd.Context(), globalOptions)
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
cmdRoot.AddCommand(cmdRecover)
|
||||
}
|
||||
return cmd
|
||||
}
|
||||
|
||||
func runRecover(ctx context.Context, gopts GlobalOptions) error {
|
||||
|
|
|
@ -4,13 +4,18 @@ import (
|
|||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var cmdRepair = &cobra.Command{
|
||||
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
|
||||
}
|
||||
|
|
|
@ -9,7 +9,10 @@ import (
|
|||
"github.com/spf13/pflag"
|
||||
)
|
||||
|
||||
var cmdRepairIndex = &cobra.Command{
|
||||
func newRepairIndexCommand() *cobra.Command {
|
||||
var opts RepairIndexOptions
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "index [flags]",
|
||||
Short: "Build a new index",
|
||||
Long: `
|
||||
|
@ -29,17 +32,12 @@ Exit status is 12 if the password is incorrect.
|
|||
RunE: func(cmd *cobra.Command, _ []string) error {
|
||||
term, cancel := setupTermstatus()
|
||||
defer cancel()
|
||||
return runRebuildIndex(cmd.Context(), repairIndexOptions, globalOptions, term)
|
||||
return runRebuildIndex(cmd.Context(), opts, globalOptions, term)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
var cmdRebuildIndex = &cobra.Command{
|
||||
Use: "rebuild-index [flags]",
|
||||
Short: cmdRepairIndex.Short,
|
||||
Long: cmdRepairIndex.Long,
|
||||
Deprecated: `Use "repair index" instead`,
|
||||
DisableAutoGenTag: true,
|
||||
RunE: cmdRepairIndex.RunE,
|
||||
opts.AddFlags(cmd.Flags())
|
||||
return cmd
|
||||
}
|
||||
|
||||
// RepairIndexOptions collects all options for the repair index command.
|
||||
|
@ -47,16 +45,31 @@ type RepairIndexOptions struct {
|
|||
ReadAllPacks bool
|
||||
}
|
||||
|
||||
var repairIndexOptions RepairIndexOptions
|
||||
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")
|
||||
}
|
||||
|
||||
func init() {
|
||||
cmdRepair.AddCommand(cmdRepairIndex)
|
||||
// add alias for old name
|
||||
cmdRoot.AddCommand(cmdRebuildIndex)
|
||||
func newRebuildIndexCommand() *cobra.Command {
|
||||
var opts RepairIndexOptions
|
||||
|
||||
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")
|
||||
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 runRebuildIndex(ctx context.Context, opts RepairIndexOptions, gopts GlobalOptions, term *termstatus.Terminal) error {
|
||||
|
|
|
@ -13,7 +13,8 @@ import (
|
|||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var cmdRepairPacks = &cobra.Command{
|
||||
func newRepairPacksCommand() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "packs [packIDs...]",
|
||||
Short: "Salvage damaged pack files",
|
||||
Long: `
|
||||
|
@ -35,10 +36,8 @@ Exit status is 12 if the password is incorrect.
|
|||
defer cancel()
|
||||
return runRepairPacks(cmd.Context(), globalOptions, term, args)
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
cmdRepair.AddCommand(cmdRepairPacks)
|
||||
}
|
||||
return cmd
|
||||
}
|
||||
|
||||
func runRepairPacks(ctx context.Context, gopts GlobalOptions, term *termstatus.Terminal, args []string) error {
|
||||
|
|
|
@ -8,9 +8,13 @@ import (
|
|||
"github.com/restic/restic/internal/walker"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
)
|
||||
|
||||
var cmdRepairSnapshots = &cobra.Command{
|
||||
func newRepairSnapshotsCommand() *cobra.Command {
|
||||
var opts RepairOptions
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "snapshots [flags] [snapshot ID] [...]",
|
||||
Short: "Repair snapshots",
|
||||
Long: `
|
||||
|
@ -45,8 +49,12 @@ 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)
|
||||
return runRepairSnapshots(cmd.Context(), globalOptions, opts, args)
|
||||
},
|
||||
}
|
||||
|
||||
opts.AddFlags(cmd.Flags())
|
||||
return cmd
|
||||
}
|
||||
|
||||
// RepairOptions collects all options for the repair command.
|
||||
|
@ -57,16 +65,11 @@ type RepairOptions struct {
|
|||
restic.SnapshotFilter
|
||||
}
|
||||
|
||||
var repairSnapshotOptions RepairOptions
|
||||
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")
|
||||
|
||||
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)
|
||||
initMultiSnapshotFilter(f, &opts.SnapshotFilter, true)
|
||||
}
|
||||
|
||||
func runRepairSnapshots(ctx context.Context, gopts GlobalOptions, opts RepairOptions, args []string) error {
|
||||
|
|
|
@ -15,9 +15,13 @@ import (
|
|||
"github.com/restic/restic/internal/ui/termstatus"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
)
|
||||
|
||||
var cmdRestore = &cobra.Command{
|
||||
func newRestoreCommand() *cobra.Command {
|
||||
var opts RestoreOptions
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "restore [flags] snapshotID",
|
||||
Short: "Extract the data from a snapshot",
|
||||
Long: `
|
||||
|
@ -44,8 +48,12 @@ Exit status is 12 if the password is incorrect.
|
|||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
term, cancel := setupTermstatus()
|
||||
defer cancel()
|
||||
return runRestore(cmd.Context(), restoreOptions, globalOptions, term, args)
|
||||
return runRestore(cmd.Context(), opts, globalOptions, term, args)
|
||||
},
|
||||
}
|
||||
|
||||
opts.AddFlags(cmd.Flags())
|
||||
return cmd
|
||||
}
|
||||
|
||||
// RestoreOptions collects all options for the restore command.
|
||||
|
@ -63,26 +71,21 @@ type RestoreOptions struct {
|
|||
IncludeXattrPattern []string
|
||||
}
|
||||
|
||||
var restoreOptions RestoreOptions
|
||||
func (opts *RestoreOptions) AddFlags(f *pflag.FlagSet) {
|
||||
f.StringVarP(&opts.Target, "target", "t", "", "directory to extract data to")
|
||||
|
||||
func init() {
|
||||
cmdRoot.AddCommand(cmdRestore)
|
||||
opts.ExcludePatternOptions.Add(f)
|
||||
opts.IncludePatternOptions.Add(f)
|
||||
|
||||
flags := cmdRestore.Flags()
|
||||
flags.StringVarP(&restoreOptions.Target, "target", "t", "", "directory to extract data to")
|
||||
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)")
|
||||
|
||||
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")
|
||||
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")
|
||||
}
|
||||
|
||||
func runRestore(ctx context.Context, opts RestoreOptions, gopts GlobalOptions,
|
||||
|
|
|
@ -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"
|
||||
|
@ -15,7 +16,10 @@ import (
|
|||
"github.com/restic/restic/internal/walker"
|
||||
)
|
||||
|
||||
var cmdRewrite = &cobra.Command{
|
||||
func newRewriteCommand() *cobra.Command {
|
||||
var opts RewriteOptions
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "rewrite [flags] [snapshotID ...]",
|
||||
Short: "Rewrite snapshots to exclude unwanted files",
|
||||
Long: `
|
||||
|
@ -54,8 +58,12 @@ 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)
|
||||
return runRewrite(cmd.Context(), opts, globalOptions, args)
|
||||
},
|
||||
}
|
||||
|
||||
opts.AddFlags(cmd.Flags())
|
||||
return cmd
|
||||
}
|
||||
|
||||
type snapshotMetadata struct {
|
||||
|
@ -99,20 +107,15 @@ type RewriteOptions struct {
|
|||
filter.ExcludePatternOptions
|
||||
}
|
||||
|
||||
var rewriteOptions RewriteOptions
|
||||
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")
|
||||
|
||||
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)
|
||||
initMultiSnapshotFilter(f, &opts.SnapshotFilter, true)
|
||||
opts.ExcludePatternOptions.Add(f)
|
||||
}
|
||||
|
||||
// rewriteFilterFunc returns the filtered tree ID or an error. If a snapshot summary is returned, the snapshot will
|
||||
|
|
|
@ -10,9 +10,19 @@ 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{
|
||||
func registerSelfUpdateCommand(cmd *cobra.Command) {
|
||||
cmd.AddCommand(
|
||||
newSelfUpdateCommand(),
|
||||
)
|
||||
}
|
||||
|
||||
func newSelfUpdateCommand() *cobra.Command {
|
||||
var opts SelfUpdateOptions
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "self-update [flags]",
|
||||
Short: "Update the restic binary",
|
||||
Long: `
|
||||
|
@ -32,8 +42,12 @@ 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)
|
||||
return runSelfUpdate(cmd.Context(), opts, globalOptions, args)
|
||||
},
|
||||
}
|
||||
|
||||
opts.AddFlags(cmd.Flags())
|
||||
return cmd
|
||||
}
|
||||
|
||||
// SelfUpdateOptions collects all options for the update-restic command.
|
||||
|
@ -41,13 +55,8 @@ type SelfUpdateOptions struct {
|
|||
Output string
|
||||
}
|
||||
|
||||
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)")
|
||||
func (opts *SelfUpdateOptions) AddFlags(f *pflag.FlagSet) {
|
||||
f.StringVar(&opts.Output, "output", "", "Save the downloaded file as `filename` (default: running binary itself)")
|
||||
}
|
||||
|
||||
func runSelfUpdate(ctx context.Context, opts SelfUpdateOptions, gopts GlobalOptions, args []string) error {
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -12,9 +12,13 @@ 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{
|
||||
func newSnapshotsCommand() *cobra.Command {
|
||||
var opts SnapshotOptions
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "snapshots [flags] [snapshotID ...]",
|
||||
Short: "List all snapshots",
|
||||
Long: `
|
||||
|
@ -32,8 +36,12 @@ 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)
|
||||
return runSnapshots(cmd.Context(), opts, globalOptions, args)
|
||||
},
|
||||
}
|
||||
|
||||
opts.AddFlags(cmd.Flags())
|
||||
return cmd
|
||||
}
|
||||
|
||||
// SnapshotOptions bundles all options for the snapshots command.
|
||||
|
@ -45,22 +53,17 @@ 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")
|
||||
}
|
||||
|
||||
func runSnapshots(ctx context.Context, opts SnapshotOptions, gopts GlobalOptions, args []string) error {
|
||||
|
|
|
@ -18,9 +18,13 @@ import (
|
|||
"github.com/restic/restic/internal/walker"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
)
|
||||
|
||||
var cmdStats = &cobra.Command{
|
||||
func newStatsCommand() *cobra.Command {
|
||||
var opts StatsOptions
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "stats [flags] [snapshot ID] [...]",
|
||||
Short: "Scan the repository and show basic statistics",
|
||||
Long: `
|
||||
|
@ -58,8 +62,15 @@ 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)
|
||||
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
|
||||
}
|
||||
|
||||
// StatsOptions collects all options for the stats command.
|
||||
|
@ -70,7 +81,10 @@ type StatsOptions struct {
|
|||
restic.SnapshotFilter
|
||||
}
|
||||
|
||||
var statsOptions StatsOptions
|
||||
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)
|
||||
}
|
||||
|
||||
func must(err error) {
|
||||
if err != nil {
|
||||
|
@ -78,17 +92,6 @@ 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")
|
||||
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 {
|
||||
err := verifyStatsInput(opts)
|
||||
if err != nil {
|
||||
|
|
|
@ -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"
|
||||
|
@ -13,7 +14,10 @@ import (
|
|||
"github.com/restic/restic/internal/ui/termstatus"
|
||||
)
|
||||
|
||||
var cmdTag = &cobra.Command{
|
||||
func newTagCommand() *cobra.Command {
|
||||
var opts TagOptions
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "tag [flags] [snapshotID ...]",
|
||||
Short: "Modify tags on snapshots",
|
||||
Long: `
|
||||
|
@ -38,8 +42,12 @@ Exit status is 12 if the password is incorrect.
|
|||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
term, cancel := setupTermstatus()
|
||||
defer cancel()
|
||||
return runTag(cmd.Context(), tagOptions, globalOptions, term, args)
|
||||
return runTag(cmd.Context(), opts, globalOptions, term, args)
|
||||
},
|
||||
}
|
||||
|
||||
opts.AddFlags(cmd.Flags())
|
||||
return cmd
|
||||
}
|
||||
|
||||
// TagOptions bundles all options for the 'tag' command.
|
||||
|
@ -50,16 +58,11 @@ type TagOptions struct {
|
|||
RemoveTags restic.TagLists
|
||||
}
|
||||
|
||||
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)
|
||||
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)
|
||||
}
|
||||
|
||||
type changedSnapshot struct {
|
||||
|
|
|
@ -5,9 +5,13 @@ import (
|
|||
|
||||
"github.com/restic/restic/internal/repository"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
)
|
||||
|
||||
var unlockCmd = &cobra.Command{
|
||||
func newUnlockCommand() *cobra.Command {
|
||||
var opts UnlockOptions
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "unlock",
|
||||
Short: "Remove locks other processes created",
|
||||
Long: `
|
||||
|
@ -22,8 +26,11 @@ 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)
|
||||
return runUnlock(cmd.Context(), opts, globalOptions)
|
||||
},
|
||||
}
|
||||
opts.AddFlags(cmd.Flags())
|
||||
return cmd
|
||||
}
|
||||
|
||||
// UnlockOptions collects all options for the unlock command.
|
||||
|
@ -31,12 +38,8 @@ type UnlockOptions struct {
|
|||
RemoveAll bool
|
||||
}
|
||||
|
||||
var unlockOptions UnlockOptions
|
||||
|
||||
func init() {
|
||||
cmdRoot.AddCommand(unlockCmd)
|
||||
|
||||
unlockCmd.Flags().BoolVar(&unlockOptions.RemoveAll, "remove-all", false, "remove all locks, even non-stale ones")
|
||||
func (opts *UnlockOptions) AddFlags(f *pflag.FlagSet) {
|
||||
f.BoolVar(&opts.RemoveAll, "remove-all", false, "remove all locks, even non-stale ones")
|
||||
}
|
||||
|
||||
func runUnlock(ctx context.Context, opts UnlockOptions, gopts GlobalOptions) error {
|
||||
|
|
|
@ -8,7 +8,8 @@ import (
|
|||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var versionCmd = &cobra.Command{
|
||||
func newVersionCommand() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "version",
|
||||
Short: "Print version information",
|
||||
Long: `
|
||||
|
@ -51,8 +52,6 @@ Exit status is 1 if there was any error.
|
|||
}
|
||||
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
cmdRoot.AddCommand(versionCmd)
|
||||
}
|
||||
return cmd
|
||||
}
|
||||
|
|
|
@ -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"})
|
||||
|
|
|
@ -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,12 +96,97 @@ 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")
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
backends: collectBackends(),
|
||||
}
|
||||
|
||||
func init() {
|
||||
func collectBackends() *location.Registry {
|
||||
backends := location.NewRegistry()
|
||||
backends.Register(azure.NewFactory())
|
||||
backends.Register(b2.NewFactory())
|
||||
|
@ -111,59 +197,7 @@ func init() {
|
|||
backends.Register(s3.NewFactory())
|
||||
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")
|
||||
}
|
||||
return backends
|
||||
}
|
||||
|
||||
func stdinIsTerminal() bool {
|
||||
|
@ -254,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")
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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(_ *cobra.Command) {
|
||||
// No profiling in release mode
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
)
|
||||
|
@ -29,8 +28,11 @@ func init() {
|
|||
|
||||
var ErrOK = errors.New("ok")
|
||||
|
||||
// cmdRoot is the base command when no other command has been specified.
|
||||
var cmdRoot = &cobra.Command{
|
||||
var cmdGroupDefault = "default"
|
||||
var cmdGroupAdvanced = "advanced"
|
||||
|
||||
func newRootCommand() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "restic",
|
||||
Short: "Backup and restore files",
|
||||
Long: `
|
||||
|
@ -44,51 +46,11 @@ 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
|
||||
|
||||
// run the debug functions for all subcommands (if build tag "debug" is
|
||||
// enabled)
|
||||
return runDebug()
|
||||
return globalOptions.PreRun(needsPassword(c.Name()))
|
||||
},
|
||||
PersistentPostRun: func(_ *cobra.Command, _ []string) {
|
||||
stopDebug()
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
var cmdGroupDefault = "default"
|
||||
var cmdGroupAdvanced = "advanced"
|
||||
|
||||
func init() {
|
||||
cmdRoot.AddGroup(
|
||||
cmd.AddGroup(
|
||||
&cobra.Group{
|
||||
ID: cmdGroupDefault,
|
||||
Title: "Available Commands:",
|
||||
|
@ -98,6 +60,49 @@ func init() {
|
|||
Title: "Advanced Options:",
|
||||
},
|
||||
)
|
||||
|
||||
globalOptions.AddFlags(cmd.PersistentFlags())
|
||||
|
||||
// Use our "generate" command instead of the cobra provided "completion" command
|
||||
cmd.CompletionOptions.DisableDefaultCmd = true
|
||||
|
||||
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,
|
||||
|
@ -164,7 +169,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()
|
||||
|
|
|
@ -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)")
|
||||
|
@ -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
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue