mirror of
https://github.com/restic/restic.git
synced 2025-10-19 15:43:21 +00:00
Merge pull request #5555 from MichaelEischer/extract-globaloptions
Split globalOptions into separate package
This commit is contained in:
commit
71432c7f4b
75 changed files with 841 additions and 691 deletions
|
@ -24,6 +24,7 @@ import (
|
||||||
"github.com/restic/restic/internal/errors"
|
"github.com/restic/restic/internal/errors"
|
||||||
"github.com/restic/restic/internal/filter"
|
"github.com/restic/restic/internal/filter"
|
||||||
"github.com/restic/restic/internal/fs"
|
"github.com/restic/restic/internal/fs"
|
||||||
|
"github.com/restic/restic/internal/global"
|
||||||
"github.com/restic/restic/internal/repository"
|
"github.com/restic/restic/internal/repository"
|
||||||
"github.com/restic/restic/internal/restic"
|
"github.com/restic/restic/internal/restic"
|
||||||
"github.com/restic/restic/internal/textfile"
|
"github.com/restic/restic/internal/textfile"
|
||||||
|
@ -31,7 +32,7 @@ import (
|
||||||
"github.com/restic/restic/internal/ui/backup"
|
"github.com/restic/restic/internal/ui/backup"
|
||||||
)
|
)
|
||||||
|
|
||||||
func newBackupCommand(globalOptions *GlobalOptions) *cobra.Command {
|
func newBackupCommand(globalOptions *global.Options) *cobra.Command {
|
||||||
var opts BackupOptions
|
var opts BackupOptions
|
||||||
|
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
|
@ -64,7 +65,7 @@ Exit status is 12 if the password is incorrect.
|
||||||
GroupID: cmdGroupDefault,
|
GroupID: cmdGroupDefault,
|
||||||
DisableAutoGenTag: true,
|
DisableAutoGenTag: true,
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
return runBackup(cmd.Context(), opts, *globalOptions, globalOptions.term, args)
|
return runBackup(cmd.Context(), opts, *globalOptions, globalOptions.Term, args)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -274,8 +275,8 @@ func readFilenamesRaw(r io.Reader) (names []string, err error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check returns an error when an invalid combination of options was set.
|
// Check returns an error when an invalid combination of options was set.
|
||||||
func (opts BackupOptions) Check(gopts GlobalOptions, args []string) error {
|
func (opts BackupOptions) Check(gopts global.Options, args []string) error {
|
||||||
if gopts.password == "" && !gopts.InsecureNoPassword {
|
if gopts.Password == "" && !gopts.InsecureNoPassword {
|
||||||
if opts.Stdin {
|
if opts.Stdin {
|
||||||
return errors.Fatal("cannot read both password and data from stdin")
|
return errors.Fatal("cannot read both password and data from stdin")
|
||||||
}
|
}
|
||||||
|
@ -475,18 +476,18 @@ func findParentSnapshot(ctx context.Context, repo restic.ListerLoaderUnpacked, o
|
||||||
return sn, err
|
return sn, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func runBackup(ctx context.Context, opts BackupOptions, gopts GlobalOptions, term ui.Terminal, args []string) error {
|
func runBackup(ctx context.Context, opts BackupOptions, gopts global.Options, term ui.Terminal, args []string) error {
|
||||||
var vsscfg fs.VSSConfig
|
var vsscfg fs.VSSConfig
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
var printer backup.ProgressPrinter
|
var printer backup.ProgressPrinter
|
||||||
if gopts.JSON {
|
if gopts.JSON {
|
||||||
printer = backup.NewJSONProgress(term, gopts.verbosity)
|
printer = backup.NewJSONProgress(term, gopts.Verbosity)
|
||||||
} else {
|
} else {
|
||||||
printer = backup.NewTextProgress(term, gopts.verbosity)
|
printer = backup.NewTextProgress(term, gopts.Verbosity)
|
||||||
}
|
}
|
||||||
if runtime.GOOS == "windows" {
|
if runtime.GOOS == "windows" {
|
||||||
if vsscfg, err = fs.ParseVSSConfig(gopts.extended); err != nil {
|
if vsscfg, err = fs.ParseVSSConfig(gopts.Extended); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -509,13 +510,13 @@ func runBackup(ctx context.Context, opts BackupOptions, gopts GlobalOptions, ter
|
||||||
timeStamp := time.Now()
|
timeStamp := time.Now()
|
||||||
backupStart := timeStamp
|
backupStart := timeStamp
|
||||||
if opts.TimeStamp != "" {
|
if opts.TimeStamp != "" {
|
||||||
timeStamp, err = time.ParseInLocation(TimeFormat, opts.TimeStamp, time.Local)
|
timeStamp, err = time.ParseInLocation(global.TimeFormat, opts.TimeStamp, time.Local)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Fatalf("error in time option: %v", err)
|
return errors.Fatalf("error in time option: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if gopts.verbosity >= 2 && !gopts.JSON {
|
if gopts.Verbosity >= 2 && !gopts.JSON {
|
||||||
printer.P("open repository")
|
printer.P("open repository")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -668,7 +669,7 @@ func runBackup(ctx context.Context, opts BackupOptions, gopts GlobalOptions, ter
|
||||||
Time: timeStamp,
|
Time: timeStamp,
|
||||||
Hostname: opts.Host,
|
Hostname: opts.Host,
|
||||||
ParentSnapshot: parentSnapshot,
|
ParentSnapshot: parentSnapshot,
|
||||||
ProgramVersion: "restic " + version,
|
ProgramVersion: "restic " + global.Version,
|
||||||
SkipIfUnchanged: opts.SkipIfUnchanged,
|
SkipIfUnchanged: opts.SkipIfUnchanged,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -12,12 +12,13 @@ import (
|
||||||
"github.com/restic/restic/internal/data"
|
"github.com/restic/restic/internal/data"
|
||||||
"github.com/restic/restic/internal/errors"
|
"github.com/restic/restic/internal/errors"
|
||||||
"github.com/restic/restic/internal/fs"
|
"github.com/restic/restic/internal/fs"
|
||||||
|
"github.com/restic/restic/internal/global"
|
||||||
"github.com/restic/restic/internal/restic"
|
"github.com/restic/restic/internal/restic"
|
||||||
rtest "github.com/restic/restic/internal/test"
|
rtest "github.com/restic/restic/internal/test"
|
||||||
)
|
)
|
||||||
|
|
||||||
func testRunBackupAssumeFailure(t testing.TB, dir string, target []string, opts BackupOptions, gopts GlobalOptions) error {
|
func testRunBackupAssumeFailure(t testing.TB, dir string, target []string, opts BackupOptions, gopts global.Options) error {
|
||||||
return withTermStatus(t, gopts, func(ctx context.Context, gopts GlobalOptions) error {
|
return withTermStatus(t, gopts, func(ctx context.Context, gopts global.Options) error {
|
||||||
t.Logf("backing up %v in %v", target, dir)
|
t.Logf("backing up %v in %v", target, dir)
|
||||||
if dir != "" {
|
if dir != "" {
|
||||||
cleanup := rtest.Chdir(t, dir)
|
cleanup := rtest.Chdir(t, dir)
|
||||||
|
@ -25,11 +26,11 @@ func testRunBackupAssumeFailure(t testing.TB, dir string, target []string, opts
|
||||||
}
|
}
|
||||||
|
|
||||||
opts.GroupBy = data.SnapshotGroupByOptions{Host: true, Path: true}
|
opts.GroupBy = data.SnapshotGroupByOptions{Host: true, Path: true}
|
||||||
return runBackup(ctx, opts, gopts, gopts.term, target)
|
return runBackup(ctx, opts, gopts, gopts.Term, target)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func testRunBackup(t testing.TB, dir string, target []string, opts BackupOptions, gopts GlobalOptions) {
|
func testRunBackup(t testing.TB, dir string, target []string, opts BackupOptions, gopts global.Options) {
|
||||||
err := testRunBackupAssumeFailure(t, dir, target, opts, gopts)
|
err := testRunBackupAssumeFailure(t, dir, target, opts, gopts)
|
||||||
rtest.Assert(t, err == nil, "Error while backing up: %v", err)
|
rtest.Assert(t, err == nil, "Error while backing up: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -512,7 +513,7 @@ func TestBackupProgramVersion(t *testing.T) {
|
||||||
if newest == nil {
|
if newest == nil {
|
||||||
t.Fatal("expected a backup, got nil")
|
t.Fatal("expected a backup, got nil")
|
||||||
}
|
}
|
||||||
resticVersion := "restic " + version
|
resticVersion := "restic " + global.Version
|
||||||
rtest.Assert(t, newest.ProgramVersion == resticVersion,
|
rtest.Assert(t, newest.ProgramVersion == resticVersion,
|
||||||
"expected %v, got %v", resticVersion, newest.ProgramVersion)
|
"expected %v, got %v", resticVersion, newest.ProgramVersion)
|
||||||
}
|
}
|
||||||
|
@ -706,7 +707,7 @@ func TestBackupEmptyPassword(t *testing.T) {
|
||||||
env, cleanup := withTestEnvironment(t)
|
env, cleanup := withTestEnvironment(t)
|
||||||
defer cleanup()
|
defer cleanup()
|
||||||
|
|
||||||
env.gopts.password = ""
|
env.gopts.Password = ""
|
||||||
env.gopts.InsecureNoPassword = true
|
env.gopts.InsecureNoPassword = true
|
||||||
|
|
||||||
testSetupBackupData(t, env)
|
testSetupBackupData(t, env)
|
||||||
|
|
|
@ -10,13 +10,14 @@ import (
|
||||||
|
|
||||||
"github.com/restic/restic/internal/backend/cache"
|
"github.com/restic/restic/internal/backend/cache"
|
||||||
"github.com/restic/restic/internal/errors"
|
"github.com/restic/restic/internal/errors"
|
||||||
|
"github.com/restic/restic/internal/global"
|
||||||
"github.com/restic/restic/internal/ui"
|
"github.com/restic/restic/internal/ui"
|
||||||
"github.com/restic/restic/internal/ui/table"
|
"github.com/restic/restic/internal/ui/table"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"github.com/spf13/pflag"
|
"github.com/spf13/pflag"
|
||||||
)
|
)
|
||||||
|
|
||||||
func newCacheCommand(globalOptions *GlobalOptions) *cobra.Command {
|
func newCacheCommand(globalOptions *global.Options) *cobra.Command {
|
||||||
var opts CacheOptions
|
var opts CacheOptions
|
||||||
|
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
|
@ -34,7 +35,7 @@ Exit status is 1 if there was any error.
|
||||||
GroupID: cmdGroupDefault,
|
GroupID: cmdGroupDefault,
|
||||||
DisableAutoGenTag: true,
|
DisableAutoGenTag: true,
|
||||||
RunE: func(_ *cobra.Command, args []string) error {
|
RunE: func(_ *cobra.Command, args []string) error {
|
||||||
return runCache(opts, *globalOptions, args, globalOptions.term)
|
return runCache(opts, *globalOptions, args, globalOptions.Term)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -55,8 +56,8 @@ func (opts *CacheOptions) AddFlags(f *pflag.FlagSet) {
|
||||||
f.BoolVar(&opts.NoSize, "no-size", false, "do not output the size of the cache directories")
|
f.BoolVar(&opts.NoSize, "no-size", false, "do not output the size of the cache directories")
|
||||||
}
|
}
|
||||||
|
|
||||||
func runCache(opts CacheOptions, gopts GlobalOptions, args []string, term ui.Terminal) error {
|
func runCache(opts CacheOptions, gopts global.Options, args []string, term ui.Terminal) error {
|
||||||
printer := ui.NewProgressPrinter(false, gopts.verbosity, term)
|
printer := ui.NewProgressPrinter(false, gopts.Verbosity, term)
|
||||||
|
|
||||||
if len(args) > 0 {
|
if len(args) > 0 {
|
||||||
return errors.Fatal("the cache command expects no arguments, only options - please see `restic help cache` for usage and flags")
|
return errors.Fatal("the cache command expects no arguments, only options - please see `restic help cache` for usage and flags")
|
||||||
|
@ -161,7 +162,7 @@ func runCache(opts CacheOptions, gopts GlobalOptions, args []string, term ui.Ter
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
_ = tab.Write(gopts.term.OutputWriter())
|
_ = tab.Write(gopts.Term.OutputWriter())
|
||||||
printer.S("%d cache dirs in %s", len(dirs), cachedir)
|
printer.S("%d cache dirs in %s", len(dirs), cachedir)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|
|
@ -9,6 +9,7 @@ import (
|
||||||
|
|
||||||
"github.com/restic/restic/internal/data"
|
"github.com/restic/restic/internal/data"
|
||||||
"github.com/restic/restic/internal/errors"
|
"github.com/restic/restic/internal/errors"
|
||||||
|
"github.com/restic/restic/internal/global"
|
||||||
"github.com/restic/restic/internal/repository"
|
"github.com/restic/restic/internal/repository"
|
||||||
"github.com/restic/restic/internal/restic"
|
"github.com/restic/restic/internal/restic"
|
||||||
"github.com/restic/restic/internal/ui"
|
"github.com/restic/restic/internal/ui"
|
||||||
|
@ -16,7 +17,7 @@ import (
|
||||||
|
|
||||||
var catAllowedCmds = []string{"config", "index", "snapshot", "key", "masterkey", "lock", "pack", "blob", "tree"}
|
var catAllowedCmds = []string{"config", "index", "snapshot", "key", "masterkey", "lock", "pack", "blob", "tree"}
|
||||||
|
|
||||||
func newCatCommand(globalOptions *GlobalOptions) *cobra.Command {
|
func newCatCommand(globalOptions *global.Options) *cobra.Command {
|
||||||
cmd := &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]",
|
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",
|
Short: "Print internal objects to stdout",
|
||||||
|
@ -35,7 +36,7 @@ Exit status is 12 if the password is incorrect.
|
||||||
GroupID: cmdGroupDefault,
|
GroupID: cmdGroupDefault,
|
||||||
DisableAutoGenTag: true,
|
DisableAutoGenTag: true,
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
return runCat(cmd.Context(), *globalOptions, args, globalOptions.term)
|
return runCat(cmd.Context(), *globalOptions, args, globalOptions.Term)
|
||||||
},
|
},
|
||||||
ValidArgs: catAllowedCmds,
|
ValidArgs: catAllowedCmds,
|
||||||
}
|
}
|
||||||
|
@ -65,8 +66,8 @@ func validateCatArgs(args []string) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func runCat(ctx context.Context, gopts GlobalOptions, args []string, term ui.Terminal) error {
|
func runCat(ctx context.Context, gopts global.Options, args []string, term ui.Terminal) error {
|
||||||
printer := ui.NewProgressPrinter(gopts.JSON, gopts.verbosity, term)
|
printer := ui.NewProgressPrinter(gopts.JSON, gopts.Verbosity, term)
|
||||||
|
|
||||||
if err := validateCatArgs(args); err != nil {
|
if err := validateCatArgs(args); err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
|
@ -16,13 +16,14 @@ import (
|
||||||
"github.com/restic/restic/internal/backend/cache"
|
"github.com/restic/restic/internal/backend/cache"
|
||||||
"github.com/restic/restic/internal/checker"
|
"github.com/restic/restic/internal/checker"
|
||||||
"github.com/restic/restic/internal/errors"
|
"github.com/restic/restic/internal/errors"
|
||||||
|
"github.com/restic/restic/internal/global"
|
||||||
"github.com/restic/restic/internal/repository"
|
"github.com/restic/restic/internal/repository"
|
||||||
"github.com/restic/restic/internal/restic"
|
"github.com/restic/restic/internal/restic"
|
||||||
"github.com/restic/restic/internal/ui"
|
"github.com/restic/restic/internal/ui"
|
||||||
"github.com/restic/restic/internal/ui/progress"
|
"github.com/restic/restic/internal/ui/progress"
|
||||||
)
|
)
|
||||||
|
|
||||||
func newCheckCommand(globalOptions *GlobalOptions) *cobra.Command {
|
func newCheckCommand(globalOptions *global.Options) *cobra.Command {
|
||||||
var opts CheckOptions
|
var opts CheckOptions
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
Use: "check [flags]",
|
Use: "check [flags]",
|
||||||
|
@ -46,12 +47,12 @@ Exit status is 12 if the password is incorrect.
|
||||||
GroupID: cmdGroupDefault,
|
GroupID: cmdGroupDefault,
|
||||||
DisableAutoGenTag: true,
|
DisableAutoGenTag: true,
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
summary, err := runCheck(cmd.Context(), opts, *globalOptions, args, globalOptions.term)
|
summary, err := runCheck(cmd.Context(), opts, *globalOptions, args, globalOptions.Term)
|
||||||
if globalOptions.JSON {
|
if globalOptions.JSON {
|
||||||
if err != nil && summary.NumErrors == 0 {
|
if err != nil && summary.NumErrors == 0 {
|
||||||
summary.NumErrors = 1
|
summary.NumErrors = 1
|
||||||
}
|
}
|
||||||
globalOptions.term.Print(ui.ToJSONString(summary))
|
globalOptions.Term.Print(ui.ToJSONString(summary))
|
||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
},
|
},
|
||||||
|
@ -170,7 +171,7 @@ func parsePercentage(s string) (float64, error) {
|
||||||
// - if the user explicitly requested --no-cache, we don't use any cache
|
// - if the user explicitly requested --no-cache, we don't use any cache
|
||||||
// - if the user provides --cache-dir, we use a cache in a temporary sub-directory of the specified directory and the sub-directory is deleted after the check
|
// - if the user provides --cache-dir, we use a cache in a temporary sub-directory of the specified directory and the sub-directory is deleted after the check
|
||||||
// - by default, we use a cache in a temporary directory that is deleted after the check
|
// - by default, we use a cache in a temporary directory that is deleted after the check
|
||||||
func prepareCheckCache(opts CheckOptions, gopts *GlobalOptions, printer progress.Printer) (cleanup func()) {
|
func prepareCheckCache(opts CheckOptions, gopts *global.Options, printer progress.Printer) (cleanup func()) {
|
||||||
cleanup = func() {}
|
cleanup = func() {}
|
||||||
if opts.WithCache {
|
if opts.WithCache {
|
||||||
// use the default cache, no setup needed
|
// use the default cache, no setup needed
|
||||||
|
@ -217,7 +218,7 @@ func prepareCheckCache(opts CheckOptions, gopts *GlobalOptions, printer progress
|
||||||
return cleanup
|
return cleanup
|
||||||
}
|
}
|
||||||
|
|
||||||
func runCheck(ctx context.Context, opts CheckOptions, gopts GlobalOptions, args []string, term ui.Terminal) (checkSummary, error) {
|
func runCheck(ctx context.Context, opts CheckOptions, gopts global.Options, args []string, term ui.Terminal) (checkSummary, error) {
|
||||||
summary := checkSummary{MessageType: "summary"}
|
summary := checkSummary{MessageType: "summary"}
|
||||||
if len(args) != 0 {
|
if len(args) != 0 {
|
||||||
return summary, errors.Fatal("the check command expects no arguments, only options - please see `restic help check` for usage and flags")
|
return summary, errors.Fatal("the check command expects no arguments, only options - please see `restic help check` for usage and flags")
|
||||||
|
@ -225,7 +226,7 @@ func runCheck(ctx context.Context, opts CheckOptions, gopts GlobalOptions, args
|
||||||
|
|
||||||
var printer progress.Printer
|
var printer progress.Printer
|
||||||
if !gopts.JSON {
|
if !gopts.JSON {
|
||||||
printer = ui.NewProgressPrinter(gopts.JSON, gopts.verbosity, term)
|
printer = ui.NewProgressPrinter(gopts.JSON, gopts.Verbosity, term)
|
||||||
} else {
|
} else {
|
||||||
printer = newJSONErrorPrinter(term)
|
printer = newJSONErrorPrinter(term)
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,10 +4,11 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/restic/restic/internal/global"
|
||||||
rtest "github.com/restic/restic/internal/test"
|
rtest "github.com/restic/restic/internal/test"
|
||||||
)
|
)
|
||||||
|
|
||||||
func testRunCheck(t testing.TB, gopts GlobalOptions) {
|
func testRunCheck(t testing.TB, gopts global.Options) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
output, err := testRunCheckOutput(t, gopts, true)
|
output, err := testRunCheckOutput(t, gopts, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -16,19 +17,19 @@ func testRunCheck(t testing.TB, gopts GlobalOptions) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func testRunCheckMustFail(t testing.TB, gopts GlobalOptions) {
|
func testRunCheckMustFail(t testing.TB, gopts global.Options) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
_, err := testRunCheckOutput(t, gopts, false)
|
_, err := testRunCheckOutput(t, gopts, false)
|
||||||
rtest.Assert(t, err != nil, "expected non nil error after check of damaged repository")
|
rtest.Assert(t, err != nil, "expected non nil error after check of damaged repository")
|
||||||
}
|
}
|
||||||
|
|
||||||
func testRunCheckOutput(t testing.TB, gopts GlobalOptions, checkUnused bool) (string, error) {
|
func testRunCheckOutput(t testing.TB, gopts global.Options, checkUnused bool) (string, error) {
|
||||||
buf, err := withCaptureStdout(t, gopts, func(ctx context.Context, gopts GlobalOptions) error {
|
buf, err := withCaptureStdout(t, gopts, func(ctx context.Context, gopts global.Options) error {
|
||||||
opts := CheckOptions{
|
opts := CheckOptions{
|
||||||
ReadData: true,
|
ReadData: true,
|
||||||
CheckUnused: checkUnused,
|
CheckUnused: checkUnused,
|
||||||
}
|
}
|
||||||
_, err := runCheck(context.TODO(), opts, gopts, nil, gopts.term)
|
_, err := runCheck(context.TODO(), opts, gopts, nil, gopts.Term)
|
||||||
return err
|
return err
|
||||||
})
|
})
|
||||||
return buf.String(), err
|
return buf.String(), err
|
||||||
|
|
|
@ -9,6 +9,7 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/restic/restic/internal/errors"
|
"github.com/restic/restic/internal/errors"
|
||||||
|
"github.com/restic/restic/internal/global"
|
||||||
"github.com/restic/restic/internal/restic"
|
"github.com/restic/restic/internal/restic"
|
||||||
rtest "github.com/restic/restic/internal/test"
|
rtest "github.com/restic/restic/internal/test"
|
||||||
"github.com/restic/restic/internal/ui/progress"
|
"github.com/restic/restic/internal/ui/progress"
|
||||||
|
@ -202,7 +203,7 @@ func TestPrepareCheckCache(t *testing.T) {
|
||||||
err := os.Remove(tmpDirBase)
|
err := os.Remove(tmpDirBase)
|
||||||
rtest.OK(t, err)
|
rtest.OK(t, err)
|
||||||
}
|
}
|
||||||
gopts := GlobalOptions{CacheDir: tmpDirBase}
|
gopts := global.Options{CacheDir: tmpDirBase}
|
||||||
cleanup := prepareCheckCache(testCase.opts, &gopts, &progress.NoopPrinter{})
|
cleanup := prepareCheckCache(testCase.opts, &gopts, &progress.NoopPrinter{})
|
||||||
files, err := os.ReadDir(tmpDirBase)
|
files, err := os.ReadDir(tmpDirBase)
|
||||||
rtest.OK(t, err)
|
rtest.OK(t, err)
|
||||||
|
@ -232,7 +233,7 @@ func TestPrepareCheckCache(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPrepareDefaultCheckCache(t *testing.T) {
|
func TestPrepareDefaultCheckCache(t *testing.T) {
|
||||||
gopts := GlobalOptions{CacheDir: ""}
|
gopts := global.Options{CacheDir: ""}
|
||||||
cleanup := prepareCheckCache(CheckOptions{}, &gopts, &progress.NoopPrinter{})
|
cleanup := prepareCheckCache(CheckOptions{}, &gopts, &progress.NoopPrinter{})
|
||||||
_, err := os.ReadDir(gopts.CacheDir)
|
_, err := os.ReadDir(gopts.CacheDir)
|
||||||
rtest.OK(t, err)
|
rtest.OK(t, err)
|
||||||
|
|
|
@ -7,6 +7,7 @@ import (
|
||||||
"github.com/restic/restic/internal/data"
|
"github.com/restic/restic/internal/data"
|
||||||
"github.com/restic/restic/internal/debug"
|
"github.com/restic/restic/internal/debug"
|
||||||
"github.com/restic/restic/internal/errors"
|
"github.com/restic/restic/internal/errors"
|
||||||
|
"github.com/restic/restic/internal/global"
|
||||||
"github.com/restic/restic/internal/repository"
|
"github.com/restic/restic/internal/repository"
|
||||||
"github.com/restic/restic/internal/restic"
|
"github.com/restic/restic/internal/restic"
|
||||||
"github.com/restic/restic/internal/ui"
|
"github.com/restic/restic/internal/ui"
|
||||||
|
@ -17,7 +18,7 @@ import (
|
||||||
"github.com/spf13/pflag"
|
"github.com/spf13/pflag"
|
||||||
)
|
)
|
||||||
|
|
||||||
func newCopyCommand(globalOptions *GlobalOptions) *cobra.Command {
|
func newCopyCommand(globalOptions *global.Options) *cobra.Command {
|
||||||
var opts CopyOptions
|
var opts CopyOptions
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
Use: "copy [flags] [snapshotID ...]",
|
Use: "copy [flags] [snapshotID ...]",
|
||||||
|
@ -50,7 +51,7 @@ Exit status is 12 if the password is incorrect.
|
||||||
DisableAutoGenTag: true,
|
DisableAutoGenTag: true,
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
finalizeSnapshotFilter(&opts.SnapshotFilter)
|
finalizeSnapshotFilter(&opts.SnapshotFilter)
|
||||||
return runCopy(cmd.Context(), opts, *globalOptions, args, globalOptions.term)
|
return runCopy(cmd.Context(), opts, *globalOptions, args, globalOptions.Term)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -60,18 +61,18 @@ Exit status is 12 if the password is incorrect.
|
||||||
|
|
||||||
// CopyOptions bundles all options for the copy command.
|
// CopyOptions bundles all options for the copy command.
|
||||||
type CopyOptions struct {
|
type CopyOptions struct {
|
||||||
secondaryRepoOptions
|
global.SecondaryRepoOptions
|
||||||
data.SnapshotFilter
|
data.SnapshotFilter
|
||||||
}
|
}
|
||||||
|
|
||||||
func (opts *CopyOptions) AddFlags(f *pflag.FlagSet) {
|
func (opts *CopyOptions) AddFlags(f *pflag.FlagSet) {
|
||||||
opts.secondaryRepoOptions.AddFlags(f, "destination", "to copy snapshots from")
|
opts.SecondaryRepoOptions.AddFlags(f, "destination", "to copy snapshots from")
|
||||||
initMultiSnapshotFilter(f, &opts.SnapshotFilter, true)
|
initMultiSnapshotFilter(f, &opts.SnapshotFilter, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
func runCopy(ctx context.Context, opts CopyOptions, gopts GlobalOptions, args []string, term ui.Terminal) error {
|
func runCopy(ctx context.Context, opts CopyOptions, gopts global.Options, args []string, term ui.Terminal) error {
|
||||||
printer := ui.NewProgressPrinter(false, gopts.verbosity, term)
|
printer := ui.NewProgressPrinter(false, gopts.Verbosity, term)
|
||||||
secondaryGopts, isFromRepo, err := fillSecondaryGlobalOpts(ctx, opts.secondaryRepoOptions, gopts, "destination")
|
secondaryGopts, isFromRepo, err := opts.SecondaryRepoOptions.FillGlobalOpts(ctx, gopts, "destination")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,24 +6,25 @@ import (
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/restic/restic/internal/global"
|
||||||
rtest "github.com/restic/restic/internal/test"
|
rtest "github.com/restic/restic/internal/test"
|
||||||
)
|
)
|
||||||
|
|
||||||
func testRunCopy(t testing.TB, srcGopts GlobalOptions, dstGopts GlobalOptions) {
|
func testRunCopy(t testing.TB, srcGopts global.Options, dstGopts global.Options) {
|
||||||
gopts := srcGopts
|
gopts := srcGopts
|
||||||
gopts.Repo = dstGopts.Repo
|
gopts.Repo = dstGopts.Repo
|
||||||
gopts.password = dstGopts.password
|
gopts.Password = dstGopts.Password
|
||||||
gopts.InsecureNoPassword = dstGopts.InsecureNoPassword
|
gopts.InsecureNoPassword = dstGopts.InsecureNoPassword
|
||||||
copyOpts := CopyOptions{
|
copyOpts := CopyOptions{
|
||||||
secondaryRepoOptions: secondaryRepoOptions{
|
SecondaryRepoOptions: global.SecondaryRepoOptions{
|
||||||
Repo: srcGopts.Repo,
|
Repo: srcGopts.Repo,
|
||||||
password: srcGopts.password,
|
Password: srcGopts.Password,
|
||||||
InsecureNoPassword: srcGopts.InsecureNoPassword,
|
InsecureNoPassword: srcGopts.InsecureNoPassword,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
rtest.OK(t, withTermStatus(t, gopts, func(ctx context.Context, gopts GlobalOptions) error {
|
rtest.OK(t, withTermStatus(t, gopts, func(ctx context.Context, gopts global.Options) error {
|
||||||
return runCopy(context.TODO(), copyOpts, gopts, nil, gopts.term)
|
return runCopy(context.TODO(), copyOpts, gopts, nil, gopts.Term)
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -144,7 +145,7 @@ func TestCopyToEmptyPassword(t *testing.T) {
|
||||||
defer cleanup()
|
defer cleanup()
|
||||||
env2, cleanup2 := withTestEnvironment(t)
|
env2, cleanup2 := withTestEnvironment(t)
|
||||||
defer cleanup2()
|
defer cleanup2()
|
||||||
env2.gopts.password = ""
|
env2.gopts.Password = ""
|
||||||
env2.gopts.InsecureNoPassword = true
|
env2.gopts.InsecureNoPassword = true
|
||||||
|
|
||||||
testSetupBackupData(t, env)
|
testSetupBackupData(t, env)
|
||||||
|
|
|
@ -24,6 +24,7 @@ import (
|
||||||
"github.com/restic/restic/internal/crypto"
|
"github.com/restic/restic/internal/crypto"
|
||||||
"github.com/restic/restic/internal/data"
|
"github.com/restic/restic/internal/data"
|
||||||
"github.com/restic/restic/internal/errors"
|
"github.com/restic/restic/internal/errors"
|
||||||
|
"github.com/restic/restic/internal/global"
|
||||||
"github.com/restic/restic/internal/repository"
|
"github.com/restic/restic/internal/repository"
|
||||||
"github.com/restic/restic/internal/repository/index"
|
"github.com/restic/restic/internal/repository/index"
|
||||||
"github.com/restic/restic/internal/repository/pack"
|
"github.com/restic/restic/internal/repository/pack"
|
||||||
|
@ -32,13 +33,13 @@ import (
|
||||||
"github.com/restic/restic/internal/ui/progress"
|
"github.com/restic/restic/internal/ui/progress"
|
||||||
)
|
)
|
||||||
|
|
||||||
func registerDebugCommand(cmd *cobra.Command, globalOptions *GlobalOptions) {
|
func registerDebugCommand(cmd *cobra.Command, globalOptions *global.Options) {
|
||||||
cmd.AddCommand(
|
cmd.AddCommand(
|
||||||
newDebugCommand(globalOptions),
|
newDebugCommand(globalOptions),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
func newDebugCommand(globalOptions *GlobalOptions) *cobra.Command {
|
func newDebugCommand(globalOptions *global.Options) *cobra.Command {
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
Use: "debug",
|
Use: "debug",
|
||||||
Short: "Debug commands",
|
Short: "Debug commands",
|
||||||
|
@ -50,7 +51,7 @@ func newDebugCommand(globalOptions *GlobalOptions) *cobra.Command {
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
func newDebugDumpCommand(globalOptions *GlobalOptions) *cobra.Command {
|
func newDebugDumpCommand(globalOptions *global.Options) *cobra.Command {
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
Use: "dump [indexes|snapshots|all|packs]",
|
Use: "dump [indexes|snapshots|all|packs]",
|
||||||
Short: "Dump data structures",
|
Short: "Dump data structures",
|
||||||
|
@ -69,13 +70,13 @@ Exit status is 12 if the password is incorrect.
|
||||||
`,
|
`,
|
||||||
DisableAutoGenTag: true,
|
DisableAutoGenTag: true,
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
return runDebugDump(cmd.Context(), *globalOptions, args, globalOptions.term)
|
return runDebugDump(cmd.Context(), *globalOptions, args, globalOptions.Term)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
func newDebugExamineCommand(globalOptions *GlobalOptions) *cobra.Command {
|
func newDebugExamineCommand(globalOptions *global.Options) *cobra.Command {
|
||||||
var opts DebugExamineOptions
|
var opts DebugExamineOptions
|
||||||
|
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
|
@ -83,7 +84,7 @@ func newDebugExamineCommand(globalOptions *GlobalOptions) *cobra.Command {
|
||||||
Short: "Examine a pack file",
|
Short: "Examine a pack file",
|
||||||
DisableAutoGenTag: true,
|
DisableAutoGenTag: true,
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
return runDebugExamine(cmd.Context(), *globalOptions, opts, args, globalOptions.term)
|
return runDebugExamine(cmd.Context(), *globalOptions, opts, args, globalOptions.Term)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -184,8 +185,8 @@ func dumpIndexes(ctx context.Context, repo restic.ListerLoaderUnpacked, wr io.Wr
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func runDebugDump(ctx context.Context, gopts GlobalOptions, args []string, term ui.Terminal) error {
|
func runDebugDump(ctx context.Context, gopts global.Options, args []string, term ui.Terminal) error {
|
||||||
printer := ui.NewProgressPrinter(false, gopts.verbosity, term)
|
printer := ui.NewProgressPrinter(false, gopts.Verbosity, term)
|
||||||
|
|
||||||
if len(args) != 1 {
|
if len(args) != 1 {
|
||||||
return errors.Fatal("type not specified")
|
return errors.Fatal("type not specified")
|
||||||
|
@ -201,20 +202,20 @@ func runDebugDump(ctx context.Context, gopts GlobalOptions, args []string, term
|
||||||
|
|
||||||
switch tpe {
|
switch tpe {
|
||||||
case "indexes":
|
case "indexes":
|
||||||
return dumpIndexes(ctx, repo, gopts.term.OutputWriter(), printer)
|
return dumpIndexes(ctx, repo, gopts.Term.OutputWriter(), printer)
|
||||||
case "snapshots":
|
case "snapshots":
|
||||||
return debugPrintSnapshots(ctx, repo, gopts.term.OutputWriter())
|
return debugPrintSnapshots(ctx, repo, gopts.Term.OutputWriter())
|
||||||
case "packs":
|
case "packs":
|
||||||
return printPacks(ctx, repo, gopts.term.OutputWriter(), printer)
|
return printPacks(ctx, repo, gopts.Term.OutputWriter(), printer)
|
||||||
case "all":
|
case "all":
|
||||||
printer.S("snapshots:")
|
printer.S("snapshots:")
|
||||||
err := debugPrintSnapshots(ctx, repo, gopts.term.OutputWriter())
|
err := debugPrintSnapshots(ctx, repo, gopts.Term.OutputWriter())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
printer.S("indexes:")
|
printer.S("indexes:")
|
||||||
err = dumpIndexes(ctx, repo, gopts.term.OutputWriter(), printer)
|
err = dumpIndexes(ctx, repo, gopts.Term.OutputWriter(), printer)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -455,8 +456,8 @@ func storePlainBlob(id restic.ID, prefix string, plain []byte, printer progress.
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func runDebugExamine(ctx context.Context, gopts GlobalOptions, opts DebugExamineOptions, args []string, term ui.Terminal) error {
|
func runDebugExamine(ctx context.Context, gopts global.Options, opts DebugExamineOptions, args []string, term ui.Terminal) error {
|
||||||
printer := ui.NewProgressPrinter(false, gopts.verbosity, term)
|
printer := ui.NewProgressPrinter(false, gopts.Verbosity, term)
|
||||||
|
|
||||||
if opts.ExtractPack && gopts.NoLock {
|
if opts.ExtractPack && gopts.NoLock {
|
||||||
return fmt.Errorf("--extract-pack and --no-lock are mutually exclusive")
|
return fmt.Errorf("--extract-pack and --no-lock are mutually exclusive")
|
||||||
|
|
|
@ -2,8 +2,11 @@
|
||||||
|
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import "github.com/spf13/cobra"
|
import (
|
||||||
|
"github.com/restic/restic/internal/global"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
func registerDebugCommand(_ *cobra.Command, _ *GlobalOptions) {
|
func registerDebugCommand(_ *cobra.Command, _ *global.Options) {
|
||||||
// No commands to register in non-debug mode
|
// No commands to register in non-debug mode
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,13 +10,14 @@ import (
|
||||||
"github.com/restic/restic/internal/data"
|
"github.com/restic/restic/internal/data"
|
||||||
"github.com/restic/restic/internal/debug"
|
"github.com/restic/restic/internal/debug"
|
||||||
"github.com/restic/restic/internal/errors"
|
"github.com/restic/restic/internal/errors"
|
||||||
|
"github.com/restic/restic/internal/global"
|
||||||
"github.com/restic/restic/internal/restic"
|
"github.com/restic/restic/internal/restic"
|
||||||
"github.com/restic/restic/internal/ui"
|
"github.com/restic/restic/internal/ui"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"github.com/spf13/pflag"
|
"github.com/spf13/pflag"
|
||||||
)
|
)
|
||||||
|
|
||||||
func newDiffCommand(globalOptions *GlobalOptions) *cobra.Command {
|
func newDiffCommand(globalOptions *global.Options) *cobra.Command {
|
||||||
var opts DiffOptions
|
var opts DiffOptions
|
||||||
|
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
|
@ -53,7 +54,7 @@ Exit status is 12 if the password is incorrect.
|
||||||
GroupID: cmdGroupDefault,
|
GroupID: cmdGroupDefault,
|
||||||
DisableAutoGenTag: true,
|
DisableAutoGenTag: true,
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
return runDiff(cmd.Context(), opts, *globalOptions, args, globalOptions.term)
|
return runDiff(cmd.Context(), opts, *globalOptions, args, globalOptions.Term)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -361,12 +362,12 @@ func (c *Comparer) diffTree(ctx context.Context, stats *DiffStatsContainer, pref
|
||||||
return ctx.Err()
|
return ctx.Err()
|
||||||
}
|
}
|
||||||
|
|
||||||
func runDiff(ctx context.Context, opts DiffOptions, gopts GlobalOptions, args []string, term ui.Terminal) error {
|
func runDiff(ctx context.Context, opts DiffOptions, gopts global.Options, args []string, term ui.Terminal) error {
|
||||||
if len(args) != 2 {
|
if len(args) != 2 {
|
||||||
return errors.Fatalf("specify two snapshot IDs")
|
return errors.Fatalf("specify two snapshot IDs")
|
||||||
}
|
}
|
||||||
|
|
||||||
printer := ui.NewProgressPrinter(gopts.JSON, gopts.verbosity, term)
|
printer := ui.NewProgressPrinter(gopts.JSON, gopts.Verbosity, term)
|
||||||
|
|
||||||
ctx, repo, unlock, err := openWithReadLock(ctx, gopts, gopts.NoLock, printer)
|
ctx, repo, unlock, err := openWithReadLock(ctx, gopts, gopts.NoLock, printer)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -424,7 +425,7 @@ func runDiff(ctx context.Context, opts DiffOptions, gopts GlobalOptions, args []
|
||||||
}
|
}
|
||||||
|
|
||||||
if gopts.JSON {
|
if gopts.JSON {
|
||||||
enc := json.NewEncoder(gopts.term.OutputWriter())
|
enc := json.NewEncoder(gopts.Term.OutputWriter())
|
||||||
c.printChange = func(change *Change) {
|
c.printChange = func(change *Change) {
|
||||||
err := enc.Encode(change)
|
err := enc.Encode(change)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -458,7 +459,7 @@ func runDiff(ctx context.Context, opts DiffOptions, gopts GlobalOptions, args []
|
||||||
updateBlobs(repo, stats.BlobsAfter.Sub(both).Sub(stats.BlobsCommon), &stats.Added, printer.E)
|
updateBlobs(repo, stats.BlobsAfter.Sub(both).Sub(stats.BlobsCommon), &stats.Added, printer.E)
|
||||||
|
|
||||||
if gopts.JSON {
|
if gopts.JSON {
|
||||||
err := json.NewEncoder(gopts.term.OutputWriter()).Encode(stats)
|
err := json.NewEncoder(gopts.Term.OutputWriter()).Encode(stats)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
printer.E("JSON encode failed: %v", err)
|
printer.E("JSON encode failed: %v", err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,15 +11,16 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/restic/restic/internal/global"
|
||||||
rtest "github.com/restic/restic/internal/test"
|
rtest "github.com/restic/restic/internal/test"
|
||||||
)
|
)
|
||||||
|
|
||||||
func testRunDiffOutput(t testing.TB, gopts GlobalOptions, firstSnapshotID string, secondSnapshotID string) (string, error) {
|
func testRunDiffOutput(t testing.TB, gopts global.Options, firstSnapshotID string, secondSnapshotID string) (string, error) {
|
||||||
buf, err := withCaptureStdout(t, gopts, func(ctx context.Context, gopts GlobalOptions) error {
|
buf, err := withCaptureStdout(t, gopts, func(ctx context.Context, gopts global.Options) error {
|
||||||
opts := DiffOptions{
|
opts := DiffOptions{
|
||||||
ShowMetadata: false,
|
ShowMetadata: false,
|
||||||
}
|
}
|
||||||
return runDiff(ctx, opts, gopts, []string{firstSnapshotID, secondSnapshotID}, gopts.term)
|
return runDiff(ctx, opts, gopts, []string{firstSnapshotID, secondSnapshotID}, gopts.Term)
|
||||||
})
|
})
|
||||||
return buf.String(), err
|
return buf.String(), err
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,6 +11,7 @@ import (
|
||||||
"github.com/restic/restic/internal/debug"
|
"github.com/restic/restic/internal/debug"
|
||||||
"github.com/restic/restic/internal/dump"
|
"github.com/restic/restic/internal/dump"
|
||||||
"github.com/restic/restic/internal/errors"
|
"github.com/restic/restic/internal/errors"
|
||||||
|
"github.com/restic/restic/internal/global"
|
||||||
"github.com/restic/restic/internal/restic"
|
"github.com/restic/restic/internal/restic"
|
||||||
"github.com/restic/restic/internal/ui"
|
"github.com/restic/restic/internal/ui"
|
||||||
|
|
||||||
|
@ -18,7 +19,7 @@ import (
|
||||||
"github.com/spf13/pflag"
|
"github.com/spf13/pflag"
|
||||||
)
|
)
|
||||||
|
|
||||||
func newDumpCommand(globalOptions *GlobalOptions) *cobra.Command {
|
func newDumpCommand(globalOptions *global.Options) *cobra.Command {
|
||||||
var opts DumpOptions
|
var opts DumpOptions
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
Use: "dump [flags] snapshotID file",
|
Use: "dump [flags] snapshotID file",
|
||||||
|
@ -49,7 +50,7 @@ Exit status is 12 if the password is incorrect.
|
||||||
DisableAutoGenTag: true,
|
DisableAutoGenTag: true,
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
finalizeSnapshotFilter(&opts.SnapshotFilter)
|
finalizeSnapshotFilter(&opts.SnapshotFilter)
|
||||||
return runDump(cmd.Context(), opts, *globalOptions, args, globalOptions.term)
|
return runDump(cmd.Context(), opts, *globalOptions, args, globalOptions.Term)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -127,12 +128,12 @@ func printFromTree(ctx context.Context, tree *data.Tree, repo restic.BlobLoader,
|
||||||
return fmt.Errorf("path %q not found in snapshot", item)
|
return fmt.Errorf("path %q not found in snapshot", item)
|
||||||
}
|
}
|
||||||
|
|
||||||
func runDump(ctx context.Context, opts DumpOptions, gopts GlobalOptions, args []string, term ui.Terminal) error {
|
func runDump(ctx context.Context, opts DumpOptions, gopts global.Options, args []string, term ui.Terminal) error {
|
||||||
if len(args) != 2 {
|
if len(args) != 2 {
|
||||||
return errors.Fatal("no file and no snapshot ID specified")
|
return errors.Fatal("no file and no snapshot ID specified")
|
||||||
}
|
}
|
||||||
|
|
||||||
printer := ui.NewProgressPrinter(gopts.JSON, gopts.verbosity, term)
|
printer := ui.NewProgressPrinter(gopts.JSON, gopts.Verbosity, term)
|
||||||
|
|
||||||
switch opts.Archive {
|
switch opts.Archive {
|
||||||
case "tar", "zip":
|
case "tar", "zip":
|
||||||
|
|
|
@ -3,12 +3,13 @@ package main
|
||||||
import (
|
import (
|
||||||
"github.com/restic/restic/internal/errors"
|
"github.com/restic/restic/internal/errors"
|
||||||
"github.com/restic/restic/internal/feature"
|
"github.com/restic/restic/internal/feature"
|
||||||
|
"github.com/restic/restic/internal/global"
|
||||||
"github.com/restic/restic/internal/ui/table"
|
"github.com/restic/restic/internal/ui/table"
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
func newFeaturesCommand(globalOptions *GlobalOptions) *cobra.Command {
|
func newFeaturesCommand(globalOptions *global.Options) *cobra.Command {
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
Use: "features",
|
Use: "features",
|
||||||
Short: "Print list of feature flags",
|
Short: "Print list of feature flags",
|
||||||
|
@ -37,7 +38,7 @@ Exit status is 1 if there was any error.
|
||||||
return errors.Fatal("the feature command expects no arguments")
|
return errors.Fatal("the feature command expects no arguments")
|
||||||
}
|
}
|
||||||
|
|
||||||
globalOptions.term.Print("All Feature Flags:\n")
|
globalOptions.Term.Print("All Feature Flags:\n")
|
||||||
flags := feature.Flag.List()
|
flags := feature.Flag.List()
|
||||||
|
|
||||||
tab := table.New()
|
tab := table.New()
|
||||||
|
@ -49,7 +50,7 @@ Exit status is 1 if there was any error.
|
||||||
for _, flag := range flags {
|
for _, flag := range flags {
|
||||||
tab.AddRow(flag)
|
tab.AddRow(flag)
|
||||||
}
|
}
|
||||||
return tab.Write(globalOptions.term.OutputWriter())
|
return tab.Write(globalOptions.Term.OutputWriter())
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -16,12 +16,13 @@ import (
|
||||||
"github.com/restic/restic/internal/debug"
|
"github.com/restic/restic/internal/debug"
|
||||||
"github.com/restic/restic/internal/errors"
|
"github.com/restic/restic/internal/errors"
|
||||||
"github.com/restic/restic/internal/filter"
|
"github.com/restic/restic/internal/filter"
|
||||||
|
"github.com/restic/restic/internal/global"
|
||||||
"github.com/restic/restic/internal/restic"
|
"github.com/restic/restic/internal/restic"
|
||||||
"github.com/restic/restic/internal/ui"
|
"github.com/restic/restic/internal/ui"
|
||||||
"github.com/restic/restic/internal/walker"
|
"github.com/restic/restic/internal/walker"
|
||||||
)
|
)
|
||||||
|
|
||||||
func newFindCommand(globalOptions *GlobalOptions) *cobra.Command {
|
func newFindCommand(globalOptions *global.Options) *cobra.Command {
|
||||||
var opts FindOptions
|
var opts FindOptions
|
||||||
|
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
|
@ -53,7 +54,7 @@ Exit status is 12 if the password is incorrect.
|
||||||
DisableAutoGenTag: true,
|
DisableAutoGenTag: true,
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
finalizeSnapshotFilter(&opts.SnapshotFilter)
|
finalizeSnapshotFilter(&opts.SnapshotFilter)
|
||||||
return runFind(cmd.Context(), opts, *globalOptions, args, globalOptions.term)
|
return runFind(cmd.Context(), opts, *globalOptions, args, globalOptions.Term)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -187,7 +188,7 @@ func (s *statefulOutput) PrintPatternNormal(path string, node *data.Node) {
|
||||||
s.printer.P("")
|
s.printer.P("")
|
||||||
}
|
}
|
||||||
s.oldsn = s.newsn
|
s.oldsn = s.newsn
|
||||||
s.printer.P("Found matching entries in snapshot %s from %s", s.oldsn.ID().Str(), s.oldsn.Time.Local().Format(TimeFormat))
|
s.printer.P("Found matching entries in snapshot %s from %s", s.oldsn.ID().Str(), s.oldsn.Time.Local().Format(global.TimeFormat))
|
||||||
}
|
}
|
||||||
s.printer.S(formatNode(path, node, s.ListLong, s.HumanReadable))
|
s.printer.S(formatNode(path, node, s.ListLong, s.HumanReadable))
|
||||||
}
|
}
|
||||||
|
@ -240,7 +241,7 @@ func (s *statefulOutput) PrintObjectNormal(kind, id, nodepath, treeID string, sn
|
||||||
} else {
|
} else {
|
||||||
s.printer.S(" ... path %s", nodepath)
|
s.printer.S(" ... path %s", nodepath)
|
||||||
}
|
}
|
||||||
s.printer.S(" ... in snapshot %s (%s)", sn.ID().Str(), sn.Time.Local().Format(TimeFormat))
|
s.printer.S(" ... in snapshot %s (%s)", sn.ID().Str(), sn.Time.Local().Format(global.TimeFormat))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *statefulOutput) PrintObject(kind, id, nodepath, treeID string, sn *data.Snapshot) {
|
func (s *statefulOutput) PrintObject(kind, id, nodepath, treeID string, sn *data.Snapshot) {
|
||||||
|
@ -579,12 +580,12 @@ func (f *Finder) findObjectsPacks() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func runFind(ctx context.Context, opts FindOptions, gopts GlobalOptions, args []string, term ui.Terminal) error {
|
func runFind(ctx context.Context, opts FindOptions, gopts global.Options, args []string, term ui.Terminal) error {
|
||||||
if len(args) == 0 {
|
if len(args) == 0 {
|
||||||
return errors.Fatal("wrong number of arguments")
|
return errors.Fatal("wrong number of arguments")
|
||||||
}
|
}
|
||||||
|
|
||||||
printer := ui.NewProgressPrinter(gopts.JSON, gopts.verbosity, term)
|
printer := ui.NewProgressPrinter(gopts.JSON, gopts.Verbosity, term)
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
pat := findPattern{pattern: args}
|
pat := findPattern{pattern: args}
|
||||||
|
|
|
@ -7,14 +7,15 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/restic/restic/internal/global"
|
||||||
rtest "github.com/restic/restic/internal/test"
|
rtest "github.com/restic/restic/internal/test"
|
||||||
)
|
)
|
||||||
|
|
||||||
func testRunFind(t testing.TB, wantJSON bool, opts FindOptions, gopts GlobalOptions, pattern string) []byte {
|
func testRunFind(t testing.TB, wantJSON bool, opts FindOptions, gopts global.Options, pattern string) []byte {
|
||||||
buf, err := withCaptureStdout(t, gopts, func(ctx context.Context, gopts GlobalOptions) error {
|
buf, err := withCaptureStdout(t, gopts, func(ctx context.Context, gopts global.Options) error {
|
||||||
gopts.JSON = wantJSON
|
gopts.JSON = wantJSON
|
||||||
|
|
||||||
return runFind(ctx, opts, gopts, []string{pattern}, gopts.term)
|
return runFind(ctx, opts, gopts, []string{pattern}, gopts.Term)
|
||||||
})
|
})
|
||||||
rtest.OK(t, err)
|
rtest.OK(t, err)
|
||||||
return buf.Bytes()
|
return buf.Bytes()
|
||||||
|
|
|
@ -9,13 +9,14 @@ import (
|
||||||
|
|
||||||
"github.com/restic/restic/internal/data"
|
"github.com/restic/restic/internal/data"
|
||||||
"github.com/restic/restic/internal/errors"
|
"github.com/restic/restic/internal/errors"
|
||||||
|
"github.com/restic/restic/internal/global"
|
||||||
"github.com/restic/restic/internal/restic"
|
"github.com/restic/restic/internal/restic"
|
||||||
"github.com/restic/restic/internal/ui"
|
"github.com/restic/restic/internal/ui"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"github.com/spf13/pflag"
|
"github.com/spf13/pflag"
|
||||||
)
|
)
|
||||||
|
|
||||||
func newForgetCommand(globalOptions *GlobalOptions) *cobra.Command {
|
func newForgetCommand(globalOptions *global.Options) *cobra.Command {
|
||||||
var opts ForgetOptions
|
var opts ForgetOptions
|
||||||
var pruneOpts PruneOptions
|
var pruneOpts PruneOptions
|
||||||
|
|
||||||
|
@ -51,7 +52,7 @@ Exit status is 12 if the password is incorrect.
|
||||||
DisableAutoGenTag: true,
|
DisableAutoGenTag: true,
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
finalizeSnapshotFilter(&opts.SnapshotFilter)
|
finalizeSnapshotFilter(&opts.SnapshotFilter)
|
||||||
return runForget(cmd.Context(), opts, pruneOpts, *globalOptions, globalOptions.term, args)
|
return runForget(cmd.Context(), opts, pruneOpts, *globalOptions, globalOptions.Term, args)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -173,7 +174,7 @@ func verifyForgetOptions(opts *ForgetOptions) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func runForget(ctx context.Context, opts ForgetOptions, pruneOptions PruneOptions, gopts GlobalOptions, term ui.Terminal, args []string) error {
|
func runForget(ctx context.Context, opts ForgetOptions, pruneOptions PruneOptions, gopts global.Options, term ui.Terminal, args []string) error {
|
||||||
err := verifyForgetOptions(&opts)
|
err := verifyForgetOptions(&opts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -188,7 +189,7 @@ func runForget(ctx context.Context, opts ForgetOptions, pruneOptions PruneOption
|
||||||
return errors.Fatal("--no-lock is only applicable in combination with --dry-run for forget command")
|
return errors.Fatal("--no-lock is only applicable in combination with --dry-run for forget command")
|
||||||
}
|
}
|
||||||
|
|
||||||
printer := ui.NewProgressPrinter(gopts.JSON, gopts.verbosity, term)
|
printer := ui.NewProgressPrinter(gopts.JSON, gopts.Verbosity, term)
|
||||||
ctx, repo, unlock, err := openWithExclusiveLock(ctx, gopts, opts.DryRun && gopts.NoLock, printer)
|
ctx, repo, unlock, err := openWithExclusiveLock(ctx, gopts, opts.DryRun && gopts.NoLock, printer)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -253,7 +254,7 @@ func runForget(ctx context.Context, opts ForgetOptions, pruneOptions PruneOption
|
||||||
}
|
}
|
||||||
|
|
||||||
if gopts.Verbose >= 1 && !gopts.JSON {
|
if gopts.Verbose >= 1 && !gopts.JSON {
|
||||||
err = PrintSnapshotGroupHeader(gopts.term.OutputWriter(), k)
|
err = PrintSnapshotGroupHeader(gopts.Term.OutputWriter(), k)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -276,7 +277,7 @@ func runForget(ctx context.Context, opts ForgetOptions, pruneOptions PruneOption
|
||||||
}
|
}
|
||||||
if len(keep) != 0 && !gopts.Quiet && !gopts.JSON {
|
if len(keep) != 0 && !gopts.Quiet && !gopts.JSON {
|
||||||
printer.P("keep %d snapshots:\n", len(keep))
|
printer.P("keep %d snapshots:\n", len(keep))
|
||||||
if err := PrintSnapshots(gopts.term.OutputWriter(), keep, reasons, opts.Compact); err != nil {
|
if err := PrintSnapshots(gopts.Term.OutputWriter(), keep, reasons, opts.Compact); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
printer.P("\n")
|
printer.P("\n")
|
||||||
|
@ -285,7 +286,7 @@ func runForget(ctx context.Context, opts ForgetOptions, pruneOptions PruneOption
|
||||||
|
|
||||||
if len(remove) != 0 && !gopts.Quiet && !gopts.JSON {
|
if len(remove) != 0 && !gopts.Quiet && !gopts.JSON {
|
||||||
printer.P("remove %d snapshots:\n", len(remove))
|
printer.P("remove %d snapshots:\n", len(remove))
|
||||||
if err := PrintSnapshots(gopts.term.OutputWriter(), remove, nil, opts.Compact); err != nil {
|
if err := PrintSnapshots(gopts.Term.OutputWriter(), remove, nil, opts.Compact); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
printer.P("\n")
|
printer.P("\n")
|
||||||
|
@ -330,7 +331,7 @@ func runForget(ctx context.Context, opts ForgetOptions, pruneOptions PruneOption
|
||||||
}
|
}
|
||||||
|
|
||||||
if gopts.JSON && len(jsonGroups) > 0 {
|
if gopts.JSON && len(jsonGroups) > 0 {
|
||||||
err = printJSONForget(gopts.term.OutputWriter(), jsonGroups)
|
err = printJSONForget(gopts.Term.OutputWriter(), jsonGroups)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,19 +7,20 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/restic/restic/internal/data"
|
"github.com/restic/restic/internal/data"
|
||||||
|
"github.com/restic/restic/internal/global"
|
||||||
rtest "github.com/restic/restic/internal/test"
|
rtest "github.com/restic/restic/internal/test"
|
||||||
)
|
)
|
||||||
|
|
||||||
func testRunForgetMayFail(t testing.TB, gopts GlobalOptions, opts ForgetOptions, args ...string) error {
|
func testRunForgetMayFail(t testing.TB, gopts global.Options, opts ForgetOptions, args ...string) error {
|
||||||
pruneOpts := PruneOptions{
|
pruneOpts := PruneOptions{
|
||||||
MaxUnused: "5%",
|
MaxUnused: "5%",
|
||||||
}
|
}
|
||||||
return withTermStatus(t, gopts, func(ctx context.Context, gopts GlobalOptions) error {
|
return withTermStatus(t, gopts, func(ctx context.Context, gopts global.Options) error {
|
||||||
return runForget(context.TODO(), opts, pruneOpts, gopts, gopts.term, args)
|
return runForget(context.TODO(), opts, pruneOpts, gopts, gopts.Term, args)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func testRunForget(t testing.TB, gopts GlobalOptions, opts ForgetOptions, args ...string) {
|
func testRunForget(t testing.TB, gopts global.Options, opts ForgetOptions, args ...string) {
|
||||||
rtest.OK(t, testRunForgetMayFail(t, gopts, opts, args...))
|
rtest.OK(t, testRunForgetMayFail(t, gopts, opts, args...))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,6 +6,7 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/restic/restic/internal/errors"
|
"github.com/restic/restic/internal/errors"
|
||||||
|
"github.com/restic/restic/internal/global"
|
||||||
"github.com/restic/restic/internal/ui"
|
"github.com/restic/restic/internal/ui"
|
||||||
"github.com/restic/restic/internal/ui/progress"
|
"github.com/restic/restic/internal/ui/progress"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
@ -13,7 +14,7 @@ import (
|
||||||
"github.com/spf13/pflag"
|
"github.com/spf13/pflag"
|
||||||
)
|
)
|
||||||
|
|
||||||
func newGenerateCommand(globalOptions *GlobalOptions) *cobra.Command {
|
func newGenerateCommand(globalOptions *global.Options) *cobra.Command {
|
||||||
var opts generateOptions
|
var opts generateOptions
|
||||||
|
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
|
@ -31,7 +32,7 @@ Exit status is 1 if there was any error.
|
||||||
`,
|
`,
|
||||||
DisableAutoGenTag: true,
|
DisableAutoGenTag: true,
|
||||||
RunE: func(_ *cobra.Command, args []string) error {
|
RunE: func(_ *cobra.Command, args []string) error {
|
||||||
return runGenerate(opts, *globalOptions, args, globalOptions.term)
|
return runGenerate(opts, *globalOptions, args, globalOptions.Term)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
opts.AddFlags(cmd.Flags())
|
opts.AddFlags(cmd.Flags())
|
||||||
|
@ -72,7 +73,7 @@ func writeManpages(root *cobra.Command, dir string, printer progress.Printer) er
|
||||||
return doc.GenManTree(root, header, dir)
|
return doc.GenManTree(root, header, dir)
|
||||||
}
|
}
|
||||||
|
|
||||||
func writeCompletion(filename string, shell string, generate func(w io.Writer) error, printer progress.Printer, gopts GlobalOptions) (err error) {
|
func writeCompletion(filename string, shell string, generate func(w io.Writer) error, printer progress.Printer, gopts global.Options) (err error) {
|
||||||
printer.PT("writing %s completion file to %v", shell, filename)
|
printer.PT("writing %s completion file to %v", shell, filename)
|
||||||
var outWriter io.Writer
|
var outWriter io.Writer
|
||||||
if filename != "-" {
|
if filename != "-" {
|
||||||
|
@ -84,7 +85,7 @@ func writeCompletion(filename string, shell string, generate func(w io.Writer) e
|
||||||
defer func() { err = outFile.Close() }()
|
defer func() { err = outFile.Close() }()
|
||||||
outWriter = outFile
|
outWriter = outFile
|
||||||
} else {
|
} else {
|
||||||
outWriter = gopts.term.OutputWriter()
|
outWriter = gopts.Term.OutputWriter()
|
||||||
}
|
}
|
||||||
|
|
||||||
err = generate(outWriter)
|
err = generate(outWriter)
|
||||||
|
@ -110,13 +111,13 @@ func checkStdoutForSingleShell(opts generateOptions) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func runGenerate(opts generateOptions, gopts GlobalOptions, args []string, term ui.Terminal) error {
|
func runGenerate(opts generateOptions, gopts global.Options, args []string, term ui.Terminal) error {
|
||||||
if len(args) > 0 {
|
if len(args) > 0 {
|
||||||
return errors.Fatal("the generate command expects no arguments, only options - please see `restic help generate` for usage and flags")
|
return errors.Fatal("the generate command expects no arguments, only options - please see `restic help generate` for usage and flags")
|
||||||
}
|
}
|
||||||
|
|
||||||
printer := ui.NewProgressPrinter(gopts.JSON, gopts.verbosity, term)
|
printer := ui.NewProgressPrinter(gopts.JSON, gopts.Verbosity, term)
|
||||||
cmdRoot := newRootCommand(&GlobalOptions{})
|
cmdRoot := newRootCommand(&global.Options{})
|
||||||
|
|
||||||
if opts.ManDir != "" {
|
if opts.ManDir != "" {
|
||||||
err := writeManpages(cmdRoot, opts.ManDir, printer)
|
err := writeManpages(cmdRoot, opts.ManDir, printer)
|
||||||
|
|
|
@ -5,12 +5,13 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/restic/restic/internal/global"
|
||||||
rtest "github.com/restic/restic/internal/test"
|
rtest "github.com/restic/restic/internal/test"
|
||||||
)
|
)
|
||||||
|
|
||||||
func testRunGenerate(t testing.TB, gopts GlobalOptions, opts generateOptions) ([]byte, error) {
|
func testRunGenerate(t testing.TB, gopts global.Options, opts generateOptions) ([]byte, error) {
|
||||||
buf, err := withCaptureStdout(t, gopts, func(ctx context.Context, gopts GlobalOptions) error {
|
buf, err := withCaptureStdout(t, gopts, func(ctx context.Context, gopts global.Options) error {
|
||||||
return runGenerate(opts, gopts, []string{}, gopts.term)
|
return runGenerate(opts, gopts, []string{}, gopts.Term)
|
||||||
})
|
})
|
||||||
return buf.Bytes(), err
|
return buf.Bytes(), err
|
||||||
}
|
}
|
||||||
|
@ -28,14 +29,14 @@ func TestGenerateStdout(t *testing.T) {
|
||||||
|
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
output, err := testRunGenerate(t, GlobalOptions{}, tc.opts)
|
output, err := testRunGenerate(t, global.Options{}, tc.opts)
|
||||||
rtest.OK(t, err)
|
rtest.OK(t, err)
|
||||||
rtest.Assert(t, strings.Contains(string(output), "# "+tc.name+" completion for restic"), "has no expected completion header")
|
rtest.Assert(t, strings.Contains(string(output), "# "+tc.name+" completion for restic"), "has no expected completion header")
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
t.Run("Generate shell completions to stdout for two shells", func(t *testing.T) {
|
t.Run("Generate shell completions to stdout for two shells", func(t *testing.T) {
|
||||||
_, err := testRunGenerate(t, GlobalOptions{}, generateOptions{BashCompletionFile: "-", FishCompletionFile: "-"})
|
_, err := testRunGenerate(t, global.Options{}, generateOptions{BashCompletionFile: "-", FishCompletionFile: "-"})
|
||||||
rtest.Assert(t, err != nil, "generate shell completions to stdout for two shells fails")
|
rtest.Assert(t, err != nil, "generate shell completions to stdout for two shells fails")
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,7 +8,7 @@ import (
|
||||||
"github.com/restic/chunker"
|
"github.com/restic/chunker"
|
||||||
"github.com/restic/restic/internal/backend/location"
|
"github.com/restic/restic/internal/backend/location"
|
||||||
"github.com/restic/restic/internal/errors"
|
"github.com/restic/restic/internal/errors"
|
||||||
"github.com/restic/restic/internal/repository"
|
"github.com/restic/restic/internal/global"
|
||||||
"github.com/restic/restic/internal/restic"
|
"github.com/restic/restic/internal/restic"
|
||||||
"github.com/restic/restic/internal/ui"
|
"github.com/restic/restic/internal/ui"
|
||||||
"github.com/restic/restic/internal/ui/progress"
|
"github.com/restic/restic/internal/ui/progress"
|
||||||
|
@ -17,7 +17,7 @@ import (
|
||||||
"github.com/spf13/pflag"
|
"github.com/spf13/pflag"
|
||||||
)
|
)
|
||||||
|
|
||||||
func newInitCommand(globalOptions *GlobalOptions) *cobra.Command {
|
func newInitCommand(globalOptions *global.Options) *cobra.Command {
|
||||||
var opts InitOptions
|
var opts InitOptions
|
||||||
|
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
|
@ -35,7 +35,7 @@ Exit status is 1 if there was any error.
|
||||||
GroupID: cmdGroupDefault,
|
GroupID: cmdGroupDefault,
|
||||||
DisableAutoGenTag: true,
|
DisableAutoGenTag: true,
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
return runInit(cmd.Context(), opts, *globalOptions, args, globalOptions.term)
|
return runInit(cmd.Context(), opts, *globalOptions, args, globalOptions.Term)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
opts.AddFlags(cmd.Flags())
|
opts.AddFlags(cmd.Flags())
|
||||||
|
@ -44,23 +44,23 @@ Exit status is 1 if there was any error.
|
||||||
|
|
||||||
// InitOptions bundles all options for the init command.
|
// InitOptions bundles all options for the init command.
|
||||||
type InitOptions struct {
|
type InitOptions struct {
|
||||||
secondaryRepoOptions
|
global.SecondaryRepoOptions
|
||||||
CopyChunkerParameters bool
|
CopyChunkerParameters bool
|
||||||
RepositoryVersion string
|
RepositoryVersion string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (opts *InitOptions) AddFlags(f *pflag.FlagSet) {
|
func (opts *InitOptions) AddFlags(f *pflag.FlagSet) {
|
||||||
opts.secondaryRepoOptions.AddFlags(f, "secondary", "to copy chunker parameters from")
|
opts.SecondaryRepoOptions.AddFlags(f, "secondary", "to copy chunker parameters from")
|
||||||
f.BoolVar(&opts.CopyChunkerParameters, "copy-chunker-params", false, "copy chunker parameters from the secondary repository (useful with the copy command)")
|
f.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'")
|
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, term ui.Terminal) error {
|
func runInit(ctx context.Context, opts InitOptions, gopts global.Options, args []string, term ui.Terminal) error {
|
||||||
if len(args) > 0 {
|
if len(args) > 0 {
|
||||||
return errors.Fatal("the init command expects no arguments, only options - please see `restic help init` for usage and flags")
|
return errors.Fatal("the init command expects no arguments, only options - please see `restic help init` for usage and flags")
|
||||||
}
|
}
|
||||||
|
|
||||||
printer := ui.NewProgressPrinter(gopts.JSON, gopts.verbosity, term)
|
printer := ui.NewProgressPrinter(gopts.JSON, gopts.Verbosity, term)
|
||||||
|
|
||||||
var version uint
|
var version uint
|
||||||
switch opts.RepositoryVersion {
|
switch opts.RepositoryVersion {
|
||||||
|
@ -76,47 +76,18 @@ func runInit(ctx context.Context, opts InitOptions, gopts GlobalOptions, args []
|
||||||
version = uint(v)
|
version = uint(v)
|
||||||
}
|
}
|
||||||
|
|
||||||
if version < restic.MinRepoVersion || version > restic.MaxRepoVersion {
|
|
||||||
return errors.Fatalf("only repository versions between %v and %v are allowed", restic.MinRepoVersion, restic.MaxRepoVersion)
|
|
||||||
}
|
|
||||||
|
|
||||||
chunkerPolynomial, err := maybeReadChunkerPolynomial(ctx, opts, gopts, printer)
|
chunkerPolynomial, err := maybeReadChunkerPolynomial(ctx, opts, gopts, printer)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
gopts.Repo, err = ReadRepo(gopts)
|
s, err := global.CreateRepository(ctx, gopts, version, chunkerPolynomial, printer)
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
gopts.password, err = ReadPasswordTwice(ctx, gopts,
|
|
||||||
"enter password for new repository: ",
|
|
||||||
"enter password again: ")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
be, err := create(ctx, gopts.Repo, gopts, gopts.extended, printer)
|
|
||||||
if err != nil {
|
|
||||||
return errors.Fatalf("create repository at %s failed: %v", location.StripPassword(gopts.backends, gopts.Repo), err)
|
|
||||||
}
|
|
||||||
|
|
||||||
s, err := repository.New(be, repository.Options{
|
|
||||||
Compression: gopts.Compression,
|
|
||||||
PackSize: gopts.PackSize * 1024 * 1024,
|
|
||||||
})
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Fatalf("%s", err)
|
return errors.Fatalf("%s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = s.Init(ctx, version, gopts.password, chunkerPolynomial)
|
|
||||||
if err != nil {
|
|
||||||
return errors.Fatalf("create key in repository at %s failed: %v", location.StripPassword(gopts.backends, gopts.Repo), err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !gopts.JSON {
|
if !gopts.JSON {
|
||||||
printer.P("created restic repository %v at %s", s.Config().ID[:10], location.StripPassword(gopts.backends, gopts.Repo))
|
printer.P("created restic repository %v at %s", s.Config().ID[:10], location.StripPassword(gopts.Backends, gopts.Repo))
|
||||||
if opts.CopyChunkerParameters && chunkerPolynomial != nil {
|
if opts.CopyChunkerParameters && chunkerPolynomial != nil {
|
||||||
printer.P(" with chunker parameters copied from secondary repository")
|
printer.P(" with chunker parameters copied from secondary repository")
|
||||||
}
|
}
|
||||||
|
@ -129,22 +100,22 @@ func runInit(ctx context.Context, opts InitOptions, gopts GlobalOptions, args []
|
||||||
status := initSuccess{
|
status := initSuccess{
|
||||||
MessageType: "initialized",
|
MessageType: "initialized",
|
||||||
ID: s.Config().ID,
|
ID: s.Config().ID,
|
||||||
Repository: location.StripPassword(gopts.backends, gopts.Repo),
|
Repository: location.StripPassword(gopts.Backends, gopts.Repo),
|
||||||
}
|
}
|
||||||
return json.NewEncoder(gopts.term.OutputWriter()).Encode(status)
|
return json.NewEncoder(gopts.Term.OutputWriter()).Encode(status)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func maybeReadChunkerPolynomial(ctx context.Context, opts InitOptions, gopts GlobalOptions, printer progress.Printer) (*chunker.Pol, error) {
|
func maybeReadChunkerPolynomial(ctx context.Context, opts InitOptions, gopts global.Options, printer progress.Printer) (*chunker.Pol, error) {
|
||||||
if opts.CopyChunkerParameters {
|
if opts.CopyChunkerParameters {
|
||||||
otherGopts, _, err := fillSecondaryGlobalOpts(ctx, opts.secondaryRepoOptions, gopts, "secondary")
|
otherGopts, _, err := opts.SecondaryRepoOptions.FillGlobalOpts(ctx, gopts, "secondary")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
otherRepo, err := OpenRepository(ctx, otherGopts, printer)
|
otherRepo, err := global.OpenRepository(ctx, otherGopts, printer)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,19 +6,20 @@ import (
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/restic/restic/internal/global"
|
||||||
"github.com/restic/restic/internal/repository"
|
"github.com/restic/restic/internal/repository"
|
||||||
"github.com/restic/restic/internal/restic"
|
"github.com/restic/restic/internal/restic"
|
||||||
rtest "github.com/restic/restic/internal/test"
|
rtest "github.com/restic/restic/internal/test"
|
||||||
"github.com/restic/restic/internal/ui/progress"
|
"github.com/restic/restic/internal/ui/progress"
|
||||||
)
|
)
|
||||||
|
|
||||||
func testRunInit(t testing.TB, gopts GlobalOptions) {
|
func testRunInit(t testing.TB, gopts global.Options) {
|
||||||
repository.TestUseLowSecurityKDFParameters(t)
|
repository.TestUseLowSecurityKDFParameters(t)
|
||||||
restic.TestDisableCheckPolynomial(t)
|
restic.TestDisableCheckPolynomial(t)
|
||||||
restic.TestSetLockTimeout(t, 0)
|
restic.TestSetLockTimeout(t, 0)
|
||||||
|
|
||||||
err := withTermStatus(t, gopts, func(ctx context.Context, gopts GlobalOptions) error {
|
err := withTermStatus(t, gopts, func(ctx context.Context, gopts global.Options) error {
|
||||||
return runInit(ctx, InitOptions{}, gopts, nil, gopts.term)
|
return runInit(ctx, InitOptions{}, gopts, nil, gopts.Term)
|
||||||
})
|
})
|
||||||
rtest.OK(t, err)
|
rtest.OK(t, err)
|
||||||
t.Logf("repository initialized at %v", gopts.Repo)
|
t.Logf("repository initialized at %v", gopts.Repo)
|
||||||
|
@ -38,32 +39,32 @@ func TestInitCopyChunkerParams(t *testing.T) {
|
||||||
testRunInit(t, env2.gopts)
|
testRunInit(t, env2.gopts)
|
||||||
|
|
||||||
initOpts := InitOptions{
|
initOpts := InitOptions{
|
||||||
secondaryRepoOptions: secondaryRepoOptions{
|
SecondaryRepoOptions: global.SecondaryRepoOptions{
|
||||||
Repo: env2.gopts.Repo,
|
Repo: env2.gopts.Repo,
|
||||||
password: env2.gopts.password,
|
Password: env2.gopts.Password,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
err := withTermStatus(t, env.gopts, func(ctx context.Context, gopts GlobalOptions) error {
|
err := withTermStatus(t, env.gopts, func(ctx context.Context, gopts global.Options) error {
|
||||||
return runInit(ctx, initOpts, gopts, nil, gopts.term)
|
return runInit(ctx, initOpts, gopts, nil, gopts.Term)
|
||||||
})
|
})
|
||||||
rtest.Assert(t, err != nil, "expected invalid init options to fail")
|
rtest.Assert(t, err != nil, "expected invalid init options to fail")
|
||||||
|
|
||||||
initOpts.CopyChunkerParameters = true
|
initOpts.CopyChunkerParameters = true
|
||||||
err = withTermStatus(t, env.gopts, func(ctx context.Context, gopts GlobalOptions) error {
|
err = withTermStatus(t, env.gopts, func(ctx context.Context, gopts global.Options) error {
|
||||||
return runInit(ctx, initOpts, gopts, nil, gopts.term)
|
return runInit(ctx, initOpts, gopts, nil, gopts.Term)
|
||||||
})
|
})
|
||||||
rtest.OK(t, err)
|
rtest.OK(t, err)
|
||||||
|
|
||||||
var repo *repository.Repository
|
var repo *repository.Repository
|
||||||
err = withTermStatus(t, env.gopts, func(ctx context.Context, gopts GlobalOptions) error {
|
err = withTermStatus(t, env.gopts, func(ctx context.Context, gopts global.Options) error {
|
||||||
repo, err = OpenRepository(ctx, gopts, &progress.NoopPrinter{})
|
repo, err = global.OpenRepository(ctx, gopts, &progress.NoopPrinter{})
|
||||||
return err
|
return err
|
||||||
})
|
})
|
||||||
rtest.OK(t, err)
|
rtest.OK(t, err)
|
||||||
|
|
||||||
var otherRepo *repository.Repository
|
var otherRepo *repository.Repository
|
||||||
err = withTermStatus(t, env2.gopts, func(ctx context.Context, gopts GlobalOptions) error {
|
err = withTermStatus(t, env2.gopts, func(ctx context.Context, gopts global.Options) error {
|
||||||
otherRepo, err = OpenRepository(ctx, gopts, &progress.NoopPrinter{})
|
otherRepo, err = global.OpenRepository(ctx, gopts, &progress.NoopPrinter{})
|
||||||
return err
|
return err
|
||||||
})
|
})
|
||||||
rtest.OK(t, err)
|
rtest.OK(t, err)
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/restic/restic/internal/global"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
func newKeyCommand(globalOptions *GlobalOptions) *cobra.Command {
|
func newKeyCommand(globalOptions *global.Options) *cobra.Command {
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
Use: "key",
|
Use: "key",
|
||||||
Short: "Manage keys (passwords)",
|
Short: "Manage keys (passwords)",
|
||||||
|
|
|
@ -5,6 +5,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/restic/restic/internal/errors"
|
"github.com/restic/restic/internal/errors"
|
||||||
|
"github.com/restic/restic/internal/global"
|
||||||
"github.com/restic/restic/internal/repository"
|
"github.com/restic/restic/internal/repository"
|
||||||
"github.com/restic/restic/internal/ui"
|
"github.com/restic/restic/internal/ui"
|
||||||
"github.com/restic/restic/internal/ui/progress"
|
"github.com/restic/restic/internal/ui/progress"
|
||||||
|
@ -12,7 +13,7 @@ import (
|
||||||
"github.com/spf13/pflag"
|
"github.com/spf13/pflag"
|
||||||
)
|
)
|
||||||
|
|
||||||
func newKeyAddCommand(globalOptions *GlobalOptions) *cobra.Command {
|
func newKeyAddCommand(globalOptions *global.Options) *cobra.Command {
|
||||||
var opts KeyAddOptions
|
var opts KeyAddOptions
|
||||||
|
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
|
@ -32,7 +33,7 @@ Exit status is 12 if the password is incorrect.
|
||||||
`,
|
`,
|
||||||
DisableAutoGenTag: true,
|
DisableAutoGenTag: true,
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
return runKeyAdd(cmd.Context(), *globalOptions, opts, args, globalOptions.term)
|
return runKeyAdd(cmd.Context(), *globalOptions, opts, args, globalOptions.Term)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -54,12 +55,12 @@ func (opts *KeyAddOptions) Add(flags *pflag.FlagSet) {
|
||||||
flags.StringVarP(&opts.Hostname, "host", "", "", "the hostname for new key")
|
flags.StringVarP(&opts.Hostname, "host", "", "", "the hostname for new key")
|
||||||
}
|
}
|
||||||
|
|
||||||
func runKeyAdd(ctx context.Context, gopts GlobalOptions, opts KeyAddOptions, args []string, term ui.Terminal) error {
|
func runKeyAdd(ctx context.Context, gopts global.Options, opts KeyAddOptions, args []string, term ui.Terminal) error {
|
||||||
if len(args) > 0 {
|
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")
|
return fmt.Errorf("the key add command expects no arguments, only options - please see `restic help key add` for usage and flags")
|
||||||
}
|
}
|
||||||
|
|
||||||
printer := ui.NewProgressPrinter(false, gopts.verbosity, term)
|
printer := ui.NewProgressPrinter(false, gopts.Verbosity, term)
|
||||||
ctx, repo, unlock, err := openWithAppendLock(ctx, gopts, false, printer)
|
ctx, repo, unlock, err := openWithAppendLock(ctx, gopts, false, printer)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -69,7 +70,7 @@ func runKeyAdd(ctx context.Context, gopts GlobalOptions, opts KeyAddOptions, arg
|
||||||
return addKey(ctx, repo, gopts, opts, printer)
|
return addKey(ctx, repo, gopts, opts, printer)
|
||||||
}
|
}
|
||||||
|
|
||||||
func addKey(ctx context.Context, repo *repository.Repository, gopts GlobalOptions, opts KeyAddOptions, printer progress.Printer) error {
|
func addKey(ctx context.Context, repo *repository.Repository, gopts global.Options, opts KeyAddOptions, printer progress.Printer) error {
|
||||||
pw, err := getNewPassword(ctx, gopts, opts.NewPasswordFile, opts.InsecureNoPassword)
|
pw, err := getNewPassword(ctx, gopts, opts.NewPasswordFile, opts.InsecureNoPassword)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -93,7 +94,7 @@ func addKey(ctx context.Context, repo *repository.Repository, gopts GlobalOption
|
||||||
// testKeyNewPassword is used to set a new password during integration testing.
|
// testKeyNewPassword is used to set a new password during integration testing.
|
||||||
var testKeyNewPassword string
|
var testKeyNewPassword string
|
||||||
|
|
||||||
func getNewPassword(ctx context.Context, gopts GlobalOptions, newPasswordFile string, insecureNoPassword bool) (string, error) {
|
func getNewPassword(ctx context.Context, gopts global.Options, newPasswordFile string, insecureNoPassword bool) (string, error) {
|
||||||
if testKeyNewPassword != "" {
|
if testKeyNewPassword != "" {
|
||||||
return testKeyNewPassword, nil
|
return testKeyNewPassword, nil
|
||||||
}
|
}
|
||||||
|
@ -106,7 +107,7 @@ func getNewPassword(ctx context.Context, gopts GlobalOptions, newPasswordFile st
|
||||||
}
|
}
|
||||||
|
|
||||||
if newPasswordFile != "" {
|
if newPasswordFile != "" {
|
||||||
password, err := loadPasswordFromFile(newPasswordFile)
|
password, err := global.LoadPasswordFromFile(newPasswordFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
@ -119,11 +120,11 @@ func getNewPassword(ctx context.Context, gopts GlobalOptions, newPasswordFile st
|
||||||
// Since we already have an open repository, temporary remove the password
|
// Since we already have an open repository, temporary remove the password
|
||||||
// to prompt the user for the passwd.
|
// to prompt the user for the passwd.
|
||||||
newopts := gopts
|
newopts := gopts
|
||||||
newopts.password = ""
|
newopts.Password = ""
|
||||||
// empty passwords are already handled above
|
// empty passwords are already handled above
|
||||||
newopts.InsecureNoPassword = false
|
newopts.InsecureNoPassword = false
|
||||||
|
|
||||||
return ReadPasswordTwice(ctx, newopts,
|
return global.ReadPasswordTwice(ctx, newopts,
|
||||||
"enter new password: ",
|
"enter new password: ",
|
||||||
"enter password again: ")
|
"enter password again: ")
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,14 +10,15 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/restic/restic/internal/backend"
|
"github.com/restic/restic/internal/backend"
|
||||||
|
"github.com/restic/restic/internal/global"
|
||||||
"github.com/restic/restic/internal/repository"
|
"github.com/restic/restic/internal/repository"
|
||||||
rtest "github.com/restic/restic/internal/test"
|
rtest "github.com/restic/restic/internal/test"
|
||||||
"github.com/restic/restic/internal/ui/progress"
|
"github.com/restic/restic/internal/ui/progress"
|
||||||
)
|
)
|
||||||
|
|
||||||
func testRunKeyListOtherIDs(t testing.TB, gopts GlobalOptions) []string {
|
func testRunKeyListOtherIDs(t testing.TB, gopts global.Options) []string {
|
||||||
buf, err := withCaptureStdout(t, gopts, func(ctx context.Context, gopts GlobalOptions) error {
|
buf, err := withCaptureStdout(t, gopts, func(ctx context.Context, gopts global.Options) error {
|
||||||
return runKeyList(ctx, gopts, []string{}, gopts.term)
|
return runKeyList(ctx, gopts, []string{}, gopts.Term)
|
||||||
})
|
})
|
||||||
rtest.OK(t, err)
|
rtest.OK(t, err)
|
||||||
|
|
||||||
|
@ -34,35 +35,35 @@ func testRunKeyListOtherIDs(t testing.TB, gopts GlobalOptions) []string {
|
||||||
return IDs
|
return IDs
|
||||||
}
|
}
|
||||||
|
|
||||||
func testRunKeyAddNewKey(t testing.TB, newPassword string, gopts GlobalOptions) {
|
func testRunKeyAddNewKey(t testing.TB, newPassword string, gopts global.Options) {
|
||||||
testKeyNewPassword = newPassword
|
testKeyNewPassword = newPassword
|
||||||
defer func() {
|
defer func() {
|
||||||
testKeyNewPassword = ""
|
testKeyNewPassword = ""
|
||||||
}()
|
}()
|
||||||
|
|
||||||
err := withTermStatus(t, gopts, func(ctx context.Context, gopts GlobalOptions) error {
|
err := withTermStatus(t, gopts, func(ctx context.Context, gopts global.Options) error {
|
||||||
return runKeyAdd(ctx, gopts, KeyAddOptions{}, []string{}, gopts.term)
|
return runKeyAdd(ctx, gopts, KeyAddOptions{}, []string{}, gopts.Term)
|
||||||
})
|
})
|
||||||
rtest.OK(t, err)
|
rtest.OK(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func testRunKeyAddNewKeyUserHost(t testing.TB, gopts GlobalOptions) {
|
func testRunKeyAddNewKeyUserHost(t testing.TB, gopts global.Options) {
|
||||||
testKeyNewPassword = "john's geheimnis"
|
testKeyNewPassword = "john's geheimnis"
|
||||||
defer func() {
|
defer func() {
|
||||||
testKeyNewPassword = ""
|
testKeyNewPassword = ""
|
||||||
}()
|
}()
|
||||||
|
|
||||||
t.Log("adding key for john@example.com")
|
t.Log("adding key for john@example.com")
|
||||||
err := withTermStatus(t, gopts, func(ctx context.Context, gopts GlobalOptions) error {
|
err := withTermStatus(t, gopts, func(ctx context.Context, gopts global.Options) error {
|
||||||
return runKeyAdd(ctx, gopts, KeyAddOptions{
|
return runKeyAdd(ctx, gopts, KeyAddOptions{
|
||||||
Username: "john",
|
Username: "john",
|
||||||
Hostname: "example.com",
|
Hostname: "example.com",
|
||||||
}, []string{}, gopts.term)
|
}, []string{}, gopts.Term)
|
||||||
})
|
})
|
||||||
rtest.OK(t, err)
|
rtest.OK(t, err)
|
||||||
|
|
||||||
_ = withTermStatus(t, gopts, func(ctx context.Context, gopts GlobalOptions) error {
|
_ = withTermStatus(t, gopts, func(ctx context.Context, gopts global.Options) error {
|
||||||
repo, err := OpenRepository(ctx, gopts, &progress.NoopPrinter{})
|
repo, err := global.OpenRepository(ctx, gopts, &progress.NoopPrinter{})
|
||||||
rtest.OK(t, err)
|
rtest.OK(t, err)
|
||||||
key, err := repository.SearchKey(ctx, repo, testKeyNewPassword, 2, "")
|
key, err := repository.SearchKey(ctx, repo, testKeyNewPassword, 2, "")
|
||||||
rtest.OK(t, err)
|
rtest.OK(t, err)
|
||||||
|
@ -73,23 +74,23 @@ func testRunKeyAddNewKeyUserHost(t testing.TB, gopts GlobalOptions) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func testRunKeyPasswd(t testing.TB, newPassword string, gopts GlobalOptions) {
|
func testRunKeyPasswd(t testing.TB, newPassword string, gopts global.Options) {
|
||||||
testKeyNewPassword = newPassword
|
testKeyNewPassword = newPassword
|
||||||
defer func() {
|
defer func() {
|
||||||
testKeyNewPassword = ""
|
testKeyNewPassword = ""
|
||||||
}()
|
}()
|
||||||
|
|
||||||
err := withTermStatus(t, gopts, func(ctx context.Context, gopts GlobalOptions) error {
|
err := withTermStatus(t, gopts, func(ctx context.Context, gopts global.Options) error {
|
||||||
return runKeyPasswd(ctx, gopts, KeyPasswdOptions{}, []string{}, gopts.term)
|
return runKeyPasswd(ctx, gopts, KeyPasswdOptions{}, []string{}, gopts.Term)
|
||||||
})
|
})
|
||||||
rtest.OK(t, err)
|
rtest.OK(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func testRunKeyRemove(t testing.TB, gopts GlobalOptions, IDs []string) {
|
func testRunKeyRemove(t testing.TB, gopts global.Options, IDs []string) {
|
||||||
t.Logf("remove %d keys: %q\n", len(IDs), IDs)
|
t.Logf("remove %d keys: %q\n", len(IDs), IDs)
|
||||||
for _, id := range IDs {
|
for _, id := range IDs {
|
||||||
err := withTermStatus(t, gopts, func(ctx context.Context, gopts GlobalOptions) error {
|
err := withTermStatus(t, gopts, func(ctx context.Context, gopts global.Options) error {
|
||||||
return runKeyRemove(ctx, gopts, []string{id}, gopts.term)
|
return runKeyRemove(ctx, gopts, []string{id}, gopts.Term)
|
||||||
})
|
})
|
||||||
rtest.OK(t, err)
|
rtest.OK(t, err)
|
||||||
}
|
}
|
||||||
|
@ -103,26 +104,26 @@ func TestKeyAddRemove(t *testing.T) {
|
||||||
|
|
||||||
env, cleanup := withTestEnvironment(t)
|
env, cleanup := withTestEnvironment(t)
|
||||||
// must list keys more than once
|
// must list keys more than once
|
||||||
env.gopts.backendTestHook = nil
|
env.gopts.BackendTestHook = nil
|
||||||
defer cleanup()
|
defer cleanup()
|
||||||
|
|
||||||
testRunInit(t, env.gopts)
|
testRunInit(t, env.gopts)
|
||||||
|
|
||||||
testRunKeyPasswd(t, "geheim2", env.gopts)
|
testRunKeyPasswd(t, "geheim2", env.gopts)
|
||||||
env.gopts.password = "geheim2"
|
env.gopts.Password = "geheim2"
|
||||||
t.Logf("changed password to %q", env.gopts.password)
|
t.Logf("changed password to %q", env.gopts.Password)
|
||||||
|
|
||||||
for _, newPassword := range passwordList {
|
for _, newPassword := range passwordList {
|
||||||
testRunKeyAddNewKey(t, newPassword, env.gopts)
|
testRunKeyAddNewKey(t, newPassword, env.gopts)
|
||||||
t.Logf("added new password %q", newPassword)
|
t.Logf("added new password %q", newPassword)
|
||||||
env.gopts.password = newPassword
|
env.gopts.Password = newPassword
|
||||||
testRunKeyRemove(t, env.gopts, testRunKeyListOtherIDs(t, env.gopts))
|
testRunKeyRemove(t, env.gopts, testRunKeyListOtherIDs(t, env.gopts))
|
||||||
}
|
}
|
||||||
|
|
||||||
env.gopts.password = passwordList[len(passwordList)-1]
|
env.gopts.Password = passwordList[len(passwordList)-1]
|
||||||
t.Logf("testing access with last password %q\n", env.gopts.password)
|
t.Logf("testing access with last password %q\n", env.gopts.Password)
|
||||||
err := withTermStatus(t, env.gopts, func(ctx context.Context, gopts GlobalOptions) error {
|
err := withTermStatus(t, env.gopts, func(ctx context.Context, gopts global.Options) error {
|
||||||
return runKeyList(ctx, gopts, []string{}, gopts.term)
|
return runKeyList(ctx, gopts, []string{}, gopts.Term)
|
||||||
})
|
})
|
||||||
rtest.OK(t, err)
|
rtest.OK(t, err)
|
||||||
testRunCheck(t, env.gopts)
|
testRunCheck(t, env.gopts)
|
||||||
|
@ -135,21 +136,21 @@ func TestKeyAddInvalid(t *testing.T) {
|
||||||
defer cleanup()
|
defer cleanup()
|
||||||
testRunInit(t, env.gopts)
|
testRunInit(t, env.gopts)
|
||||||
|
|
||||||
err := withTermStatus(t, env.gopts, func(ctx context.Context, gopts GlobalOptions) error {
|
err := withTermStatus(t, env.gopts, func(ctx context.Context, gopts global.Options) error {
|
||||||
return runKeyAdd(ctx, gopts, KeyAddOptions{
|
return runKeyAdd(ctx, gopts, KeyAddOptions{
|
||||||
NewPasswordFile: "some-file",
|
NewPasswordFile: "some-file",
|
||||||
InsecureNoPassword: true,
|
InsecureNoPassword: true,
|
||||||
}, []string{}, gopts.term)
|
}, []string{}, gopts.Term)
|
||||||
})
|
})
|
||||||
rtest.Assert(t, strings.Contains(err.Error(), "only either"), "unexpected error message, got %q", err)
|
rtest.Assert(t, strings.Contains(err.Error(), "only either"), "unexpected error message, got %q", err)
|
||||||
|
|
||||||
pwfile := filepath.Join(t.TempDir(), "pwfile")
|
pwfile := filepath.Join(t.TempDir(), "pwfile")
|
||||||
rtest.OK(t, os.WriteFile(pwfile, []byte{}, 0o666))
|
rtest.OK(t, os.WriteFile(pwfile, []byte{}, 0o666))
|
||||||
|
|
||||||
err = withTermStatus(t, env.gopts, func(ctx context.Context, gopts GlobalOptions) error {
|
err = withTermStatus(t, env.gopts, func(ctx context.Context, gopts global.Options) error {
|
||||||
return runKeyAdd(ctx, gopts, KeyAddOptions{
|
return runKeyAdd(ctx, gopts, KeyAddOptions{
|
||||||
NewPasswordFile: pwfile,
|
NewPasswordFile: pwfile,
|
||||||
}, []string{}, gopts.term)
|
}, []string{}, gopts.Term)
|
||||||
})
|
})
|
||||||
rtest.Assert(t, strings.Contains(err.Error(), "an empty password is not allowed by default"), "unexpected error message, got %q", err)
|
rtest.Assert(t, strings.Contains(err.Error(), "an empty password is not allowed by default"), "unexpected error message, got %q", err)
|
||||||
}
|
}
|
||||||
|
@ -157,18 +158,18 @@ func TestKeyAddInvalid(t *testing.T) {
|
||||||
func TestKeyAddEmpty(t *testing.T) {
|
func TestKeyAddEmpty(t *testing.T) {
|
||||||
env, cleanup := withTestEnvironment(t)
|
env, cleanup := withTestEnvironment(t)
|
||||||
// must list keys more than once
|
// must list keys more than once
|
||||||
env.gopts.backendTestHook = nil
|
env.gopts.BackendTestHook = nil
|
||||||
defer cleanup()
|
defer cleanup()
|
||||||
testRunInit(t, env.gopts)
|
testRunInit(t, env.gopts)
|
||||||
|
|
||||||
err := withTermStatus(t, env.gopts, func(ctx context.Context, gopts GlobalOptions) error {
|
err := withTermStatus(t, env.gopts, func(ctx context.Context, gopts global.Options) error {
|
||||||
return runKeyAdd(ctx, gopts, KeyAddOptions{
|
return runKeyAdd(ctx, gopts, KeyAddOptions{
|
||||||
InsecureNoPassword: true,
|
InsecureNoPassword: true,
|
||||||
}, []string{}, gopts.term)
|
}, []string{}, gopts.Term)
|
||||||
})
|
})
|
||||||
rtest.OK(t, err)
|
rtest.OK(t, err)
|
||||||
|
|
||||||
env.gopts.password = ""
|
env.gopts.Password = ""
|
||||||
env.gopts.InsecureNoPassword = true
|
env.gopts.InsecureNoPassword = true
|
||||||
|
|
||||||
testRunCheck(t, env.gopts)
|
testRunCheck(t, env.gopts)
|
||||||
|
@ -187,7 +188,7 @@ func TestKeyProblems(t *testing.T) {
|
||||||
defer cleanup()
|
defer cleanup()
|
||||||
|
|
||||||
testRunInit(t, env.gopts)
|
testRunInit(t, env.gopts)
|
||||||
env.gopts.backendTestHook = func(r backend.Backend) (backend.Backend, error) {
|
env.gopts.BackendTestHook = func(r backend.Backend) (backend.Backend, error) {
|
||||||
return &emptySaveBackend{r}, nil
|
return &emptySaveBackend{r}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -196,21 +197,21 @@ func TestKeyProblems(t *testing.T) {
|
||||||
testKeyNewPassword = ""
|
testKeyNewPassword = ""
|
||||||
}()
|
}()
|
||||||
|
|
||||||
err := withTermStatus(t, env.gopts, func(ctx context.Context, gopts GlobalOptions) error {
|
err := withTermStatus(t, env.gopts, func(ctx context.Context, gopts global.Options) error {
|
||||||
return runKeyPasswd(ctx, gopts, KeyPasswdOptions{}, []string{}, gopts.term)
|
return runKeyPasswd(ctx, gopts, KeyPasswdOptions{}, []string{}, gopts.Term)
|
||||||
})
|
})
|
||||||
t.Log(err)
|
t.Log(err)
|
||||||
rtest.Assert(t, err != nil, "expected passwd change to fail")
|
rtest.Assert(t, err != nil, "expected passwd change to fail")
|
||||||
|
|
||||||
err = withTermStatus(t, env.gopts, func(ctx context.Context, gopts GlobalOptions) error {
|
err = withTermStatus(t, env.gopts, func(ctx context.Context, gopts global.Options) error {
|
||||||
return runKeyAdd(ctx, gopts, KeyAddOptions{}, []string{}, gopts.term)
|
return runKeyAdd(ctx, gopts, KeyAddOptions{}, []string{}, gopts.Term)
|
||||||
})
|
})
|
||||||
t.Log(err)
|
t.Log(err)
|
||||||
rtest.Assert(t, err != nil, "expected key adding to fail")
|
rtest.Assert(t, err != nil, "expected key adding to fail")
|
||||||
|
|
||||||
t.Logf("testing access with initial password %q\n", env.gopts.password)
|
t.Logf("testing access with initial password %q\n", env.gopts.Password)
|
||||||
err = withTermStatus(t, env.gopts, func(ctx context.Context, gopts GlobalOptions) error {
|
err = withTermStatus(t, env.gopts, func(ctx context.Context, gopts global.Options) error {
|
||||||
return runKeyList(ctx, gopts, []string{}, gopts.term)
|
return runKeyList(ctx, gopts, []string{}, gopts.Term)
|
||||||
})
|
})
|
||||||
rtest.OK(t, err)
|
rtest.OK(t, err)
|
||||||
testRunCheck(t, env.gopts)
|
testRunCheck(t, env.gopts)
|
||||||
|
@ -221,36 +222,36 @@ func TestKeyCommandInvalidArguments(t *testing.T) {
|
||||||
defer cleanup()
|
defer cleanup()
|
||||||
|
|
||||||
testRunInit(t, env.gopts)
|
testRunInit(t, env.gopts)
|
||||||
env.gopts.backendTestHook = func(r backend.Backend) (backend.Backend, error) {
|
env.gopts.BackendTestHook = func(r backend.Backend) (backend.Backend, error) {
|
||||||
return &emptySaveBackend{r}, nil
|
return &emptySaveBackend{r}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
err := withTermStatus(t, env.gopts, func(ctx context.Context, gopts GlobalOptions) error {
|
err := withTermStatus(t, env.gopts, func(ctx context.Context, gopts global.Options) error {
|
||||||
return runKeyAdd(ctx, gopts, KeyAddOptions{}, []string{"johndoe"}, gopts.term)
|
return runKeyAdd(ctx, gopts, KeyAddOptions{}, []string{"johndoe"}, gopts.Term)
|
||||||
})
|
})
|
||||||
t.Log(err)
|
t.Log(err)
|
||||||
rtest.Assert(t, err != nil && strings.Contains(err.Error(), "no arguments"), "unexpected error for key add: %v", err)
|
rtest.Assert(t, err != nil && strings.Contains(err.Error(), "no arguments"), "unexpected error for key add: %v", err)
|
||||||
|
|
||||||
err = withTermStatus(t, env.gopts, func(ctx context.Context, gopts GlobalOptions) error {
|
err = withTermStatus(t, env.gopts, func(ctx context.Context, gopts global.Options) error {
|
||||||
return runKeyPasswd(ctx, gopts, KeyPasswdOptions{}, []string{"johndoe"}, gopts.term)
|
return runKeyPasswd(ctx, gopts, KeyPasswdOptions{}, []string{"johndoe"}, gopts.Term)
|
||||||
})
|
})
|
||||||
t.Log(err)
|
t.Log(err)
|
||||||
rtest.Assert(t, err != nil && strings.Contains(err.Error(), "no arguments"), "unexpected error for key passwd: %v", err)
|
rtest.Assert(t, err != nil && strings.Contains(err.Error(), "no arguments"), "unexpected error for key passwd: %v", err)
|
||||||
|
|
||||||
err = withTermStatus(t, env.gopts, func(ctx context.Context, gopts GlobalOptions) error {
|
err = withTermStatus(t, env.gopts, func(ctx context.Context, gopts global.Options) error {
|
||||||
return runKeyList(ctx, gopts, []string{"johndoe"}, gopts.term)
|
return runKeyList(ctx, gopts, []string{"johndoe"}, gopts.Term)
|
||||||
})
|
})
|
||||||
t.Log(err)
|
t.Log(err)
|
||||||
rtest.Assert(t, err != nil && strings.Contains(err.Error(), "no arguments"), "unexpected error for key list: %v", err)
|
rtest.Assert(t, err != nil && strings.Contains(err.Error(), "no arguments"), "unexpected error for key list: %v", err)
|
||||||
|
|
||||||
err = withTermStatus(t, env.gopts, func(ctx context.Context, gopts GlobalOptions) error {
|
err = withTermStatus(t, env.gopts, func(ctx context.Context, gopts global.Options) error {
|
||||||
return runKeyRemove(ctx, gopts, []string{}, gopts.term)
|
return runKeyRemove(ctx, gopts, []string{}, gopts.Term)
|
||||||
})
|
})
|
||||||
t.Log(err)
|
t.Log(err)
|
||||||
rtest.Assert(t, err != nil && strings.Contains(err.Error(), "one argument"), "unexpected error for key remove: %v", err)
|
rtest.Assert(t, err != nil && strings.Contains(err.Error(), "one argument"), "unexpected error for key remove: %v", err)
|
||||||
|
|
||||||
err = withTermStatus(t, env.gopts, func(ctx context.Context, gopts GlobalOptions) error {
|
err = withTermStatus(t, env.gopts, func(ctx context.Context, gopts global.Options) error {
|
||||||
return runKeyRemove(ctx, gopts, []string{"john", "doe"}, gopts.term)
|
return runKeyRemove(ctx, gopts, []string{"john", "doe"}, gopts.Term)
|
||||||
})
|
})
|
||||||
t.Log(err)
|
t.Log(err)
|
||||||
rtest.Assert(t, err != nil && strings.Contains(err.Error(), "one argument"), "unexpected error for key remove: %v", err)
|
rtest.Assert(t, err != nil && strings.Contains(err.Error(), "one argument"), "unexpected error for key remove: %v", err)
|
||||||
|
|
|
@ -6,6 +6,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
|
"github.com/restic/restic/internal/global"
|
||||||
"github.com/restic/restic/internal/repository"
|
"github.com/restic/restic/internal/repository"
|
||||||
"github.com/restic/restic/internal/restic"
|
"github.com/restic/restic/internal/restic"
|
||||||
"github.com/restic/restic/internal/ui"
|
"github.com/restic/restic/internal/ui"
|
||||||
|
@ -14,7 +15,7 @@ import (
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
func newKeyListCommand(globalOptions *GlobalOptions) *cobra.Command {
|
func newKeyListCommand(globalOptions *global.Options) *cobra.Command {
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
Use: "list",
|
Use: "list",
|
||||||
Short: "List keys (passwords)",
|
Short: "List keys (passwords)",
|
||||||
|
@ -34,18 +35,18 @@ Exit status is 12 if the password is incorrect.
|
||||||
`,
|
`,
|
||||||
DisableAutoGenTag: true,
|
DisableAutoGenTag: true,
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
return runKeyList(cmd.Context(), *globalOptions, args, globalOptions.term)
|
return runKeyList(cmd.Context(), *globalOptions, args, globalOptions.Term)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
func runKeyList(ctx context.Context, gopts GlobalOptions, args []string, term ui.Terminal) error {
|
func runKeyList(ctx context.Context, gopts global.Options, args []string, term ui.Terminal) error {
|
||||||
if len(args) > 0 {
|
if len(args) > 0 {
|
||||||
return fmt.Errorf("the key list command expects no arguments, only options - please see `restic help key list` for usage and flags")
|
return fmt.Errorf("the key list command expects no arguments, only options - please see `restic help key list` for usage and flags")
|
||||||
}
|
}
|
||||||
|
|
||||||
printer := ui.NewProgressPrinter(gopts.JSON, gopts.verbosity, term)
|
printer := ui.NewProgressPrinter(gopts.JSON, gopts.Verbosity, term)
|
||||||
ctx, repo, unlock, err := openWithReadLock(ctx, gopts, gopts.NoLock, printer)
|
ctx, repo, unlock, err := openWithReadLock(ctx, gopts, gopts.NoLock, printer)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -55,7 +56,7 @@ func runKeyList(ctx context.Context, gopts GlobalOptions, args []string, term ui
|
||||||
return listKeys(ctx, repo, gopts, printer)
|
return listKeys(ctx, repo, gopts, printer)
|
||||||
}
|
}
|
||||||
|
|
||||||
func listKeys(ctx context.Context, s *repository.Repository, gopts GlobalOptions, printer progress.Printer) error {
|
func listKeys(ctx context.Context, s *repository.Repository, gopts global.Options, printer progress.Printer) error {
|
||||||
type keyInfo struct {
|
type keyInfo struct {
|
||||||
Current bool `json:"current"`
|
Current bool `json:"current"`
|
||||||
ID string `json:"id"`
|
ID string `json:"id"`
|
||||||
|
@ -81,7 +82,7 @@ func listKeys(ctx context.Context, s *repository.Repository, gopts GlobalOptions
|
||||||
ShortID: id.Str(),
|
ShortID: id.Str(),
|
||||||
UserName: k.Username,
|
UserName: k.Username,
|
||||||
HostName: k.Hostname,
|
HostName: k.Hostname,
|
||||||
Created: k.Created.Local().Format(TimeFormat),
|
Created: k.Created.Local().Format(global.TimeFormat),
|
||||||
}
|
}
|
||||||
|
|
||||||
m.Lock()
|
m.Lock()
|
||||||
|
@ -95,7 +96,7 @@ func listKeys(ctx context.Context, s *repository.Repository, gopts GlobalOptions
|
||||||
}
|
}
|
||||||
|
|
||||||
if gopts.JSON {
|
if gopts.JSON {
|
||||||
return json.NewEncoder(gopts.term.OutputWriter()).Encode(keys)
|
return json.NewEncoder(gopts.Term.OutputWriter()).Encode(keys)
|
||||||
}
|
}
|
||||||
|
|
||||||
tab := table.New()
|
tab := table.New()
|
||||||
|
@ -108,5 +109,5 @@ func listKeys(ctx context.Context, s *repository.Repository, gopts GlobalOptions
|
||||||
tab.AddRow(key)
|
tab.AddRow(key)
|
||||||
}
|
}
|
||||||
|
|
||||||
return tab.Write(gopts.term.OutputWriter())
|
return tab.Write(gopts.Term.OutputWriter())
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/restic/restic/internal/errors"
|
"github.com/restic/restic/internal/errors"
|
||||||
|
"github.com/restic/restic/internal/global"
|
||||||
"github.com/restic/restic/internal/repository"
|
"github.com/restic/restic/internal/repository"
|
||||||
"github.com/restic/restic/internal/ui"
|
"github.com/restic/restic/internal/ui"
|
||||||
"github.com/restic/restic/internal/ui/progress"
|
"github.com/restic/restic/internal/ui/progress"
|
||||||
|
@ -12,7 +13,7 @@ import (
|
||||||
"github.com/spf13/pflag"
|
"github.com/spf13/pflag"
|
||||||
)
|
)
|
||||||
|
|
||||||
func newKeyPasswdCommand(globalOptions *GlobalOptions) *cobra.Command {
|
func newKeyPasswdCommand(globalOptions *global.Options) *cobra.Command {
|
||||||
var opts KeyPasswdOptions
|
var opts KeyPasswdOptions
|
||||||
|
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
|
@ -33,7 +34,7 @@ Exit status is 12 if the password is incorrect.
|
||||||
`,
|
`,
|
||||||
DisableAutoGenTag: true,
|
DisableAutoGenTag: true,
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
return runKeyPasswd(cmd.Context(), *globalOptions, opts, args, globalOptions.term)
|
return runKeyPasswd(cmd.Context(), *globalOptions, opts, args, globalOptions.Term)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -49,12 +50,12 @@ func (opts *KeyPasswdOptions) AddFlags(flags *pflag.FlagSet) {
|
||||||
opts.KeyAddOptions.Add(flags)
|
opts.KeyAddOptions.Add(flags)
|
||||||
}
|
}
|
||||||
|
|
||||||
func runKeyPasswd(ctx context.Context, gopts GlobalOptions, opts KeyPasswdOptions, args []string, term ui.Terminal) error {
|
func runKeyPasswd(ctx context.Context, gopts global.Options, opts KeyPasswdOptions, args []string, term ui.Terminal) error {
|
||||||
if len(args) > 0 {
|
if len(args) > 0 {
|
||||||
return fmt.Errorf("the key passwd command expects no arguments, only options - please see `restic help key passwd` for usage and flags")
|
return fmt.Errorf("the key passwd command expects no arguments, only options - please see `restic help key passwd` for usage and flags")
|
||||||
}
|
}
|
||||||
|
|
||||||
printer := ui.NewProgressPrinter(false, gopts.verbosity, term)
|
printer := ui.NewProgressPrinter(false, gopts.Verbosity, term)
|
||||||
ctx, repo, unlock, err := openWithExclusiveLock(ctx, gopts, false, printer)
|
ctx, repo, unlock, err := openWithExclusiveLock(ctx, gopts, false, printer)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -64,7 +65,7 @@ func runKeyPasswd(ctx context.Context, gopts GlobalOptions, opts KeyPasswdOption
|
||||||
return changePassword(ctx, repo, gopts, opts, printer)
|
return changePassword(ctx, repo, gopts, opts, printer)
|
||||||
}
|
}
|
||||||
|
|
||||||
func changePassword(ctx context.Context, repo *repository.Repository, gopts GlobalOptions, opts KeyPasswdOptions, printer progress.Printer) error {
|
func changePassword(ctx context.Context, repo *repository.Repository, gopts global.Options, opts KeyPasswdOptions, printer progress.Printer) error {
|
||||||
pw, err := getNewPassword(ctx, gopts, opts.NewPasswordFile, opts.InsecureNoPassword)
|
pw, err := getNewPassword(ctx, gopts, opts.NewPasswordFile, opts.InsecureNoPassword)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
|
@ -5,6 +5,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/restic/restic/internal/errors"
|
"github.com/restic/restic/internal/errors"
|
||||||
|
"github.com/restic/restic/internal/global"
|
||||||
"github.com/restic/restic/internal/repository"
|
"github.com/restic/restic/internal/repository"
|
||||||
"github.com/restic/restic/internal/restic"
|
"github.com/restic/restic/internal/restic"
|
||||||
"github.com/restic/restic/internal/ui"
|
"github.com/restic/restic/internal/ui"
|
||||||
|
@ -12,7 +13,7 @@ import (
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
func newKeyRemoveCommand(globalOptions *GlobalOptions) *cobra.Command {
|
func newKeyRemoveCommand(globalOptions *global.Options) *cobra.Command {
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
Use: "remove [ID]",
|
Use: "remove [ID]",
|
||||||
Short: "Remove key ID (password) from the repository.",
|
Short: "Remove key ID (password) from the repository.",
|
||||||
|
@ -31,18 +32,18 @@ Exit status is 12 if the password is incorrect.
|
||||||
`,
|
`,
|
||||||
DisableAutoGenTag: true,
|
DisableAutoGenTag: true,
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
return runKeyRemove(cmd.Context(), *globalOptions, args, globalOptions.term)
|
return runKeyRemove(cmd.Context(), *globalOptions, args, globalOptions.Term)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
func runKeyRemove(ctx context.Context, gopts GlobalOptions, args []string, term ui.Terminal) error {
|
func runKeyRemove(ctx context.Context, gopts global.Options, args []string, term ui.Terminal) error {
|
||||||
if len(args) != 1 {
|
if len(args) != 1 {
|
||||||
return fmt.Errorf("key remove expects one argument as the key id")
|
return fmt.Errorf("key remove expects one argument as the key id")
|
||||||
}
|
}
|
||||||
|
|
||||||
printer := ui.NewProgressPrinter(gopts.JSON, gopts.verbosity, term)
|
printer := ui.NewProgressPrinter(gopts.JSON, gopts.Verbosity, term)
|
||||||
ctx, repo, unlock, err := openWithExclusiveLock(ctx, gopts, false, printer)
|
ctx, repo, unlock, err := openWithExclusiveLock(ctx, gopts, false, printer)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
|
@ -5,6 +5,7 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/restic/restic/internal/errors"
|
"github.com/restic/restic/internal/errors"
|
||||||
|
"github.com/restic/restic/internal/global"
|
||||||
"github.com/restic/restic/internal/repository/index"
|
"github.com/restic/restic/internal/repository/index"
|
||||||
"github.com/restic/restic/internal/restic"
|
"github.com/restic/restic/internal/restic"
|
||||||
"github.com/restic/restic/internal/ui"
|
"github.com/restic/restic/internal/ui"
|
||||||
|
@ -12,7 +13,7 @@ import (
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
func newListCommand(globalOptions *GlobalOptions) *cobra.Command {
|
func newListCommand(globalOptions *global.Options) *cobra.Command {
|
||||||
var listAllowedArgs = []string{"blobs", "packs", "index", "snapshots", "keys", "locks"}
|
var listAllowedArgs = []string{"blobs", "packs", "index", "snapshots", "keys", "locks"}
|
||||||
var listAllowedArgsUseString = strings.Join(listAllowedArgs, "|")
|
var listAllowedArgsUseString = strings.Join(listAllowedArgs, "|")
|
||||||
|
|
||||||
|
@ -34,7 +35,7 @@ Exit status is 12 if the password is incorrect.
|
||||||
DisableAutoGenTag: true,
|
DisableAutoGenTag: true,
|
||||||
GroupID: cmdGroupDefault,
|
GroupID: cmdGroupDefault,
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
return runList(cmd.Context(), *globalOptions, args, globalOptions.term)
|
return runList(cmd.Context(), *globalOptions, args, globalOptions.Term)
|
||||||
},
|
},
|
||||||
ValidArgs: listAllowedArgs,
|
ValidArgs: listAllowedArgs,
|
||||||
Args: cobra.MatchAll(cobra.ExactArgs(1), cobra.OnlyValidArgs),
|
Args: cobra.MatchAll(cobra.ExactArgs(1), cobra.OnlyValidArgs),
|
||||||
|
@ -42,8 +43,8 @@ Exit status is 12 if the password is incorrect.
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
func runList(ctx context.Context, gopts GlobalOptions, args []string, term ui.Terminal) error {
|
func runList(ctx context.Context, gopts global.Options, args []string, term ui.Terminal) error {
|
||||||
printer := ui.NewProgressPrinter(false, gopts.verbosity, term)
|
printer := ui.NewProgressPrinter(false, gopts.Verbosity, term)
|
||||||
|
|
||||||
if len(args) != 1 {
|
if len(args) != 1 {
|
||||||
return errors.Fatal("type not specified")
|
return errors.Fatal("type not specified")
|
||||||
|
|
|
@ -8,14 +8,15 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/restic/restic/internal/global"
|
||||||
"github.com/restic/restic/internal/restic"
|
"github.com/restic/restic/internal/restic"
|
||||||
rtest "github.com/restic/restic/internal/test"
|
rtest "github.com/restic/restic/internal/test"
|
||||||
"github.com/restic/restic/internal/ui"
|
"github.com/restic/restic/internal/ui"
|
||||||
)
|
)
|
||||||
|
|
||||||
func testRunList(t testing.TB, gopts GlobalOptions, tpe string) restic.IDs {
|
func testRunList(t testing.TB, gopts global.Options, tpe string) restic.IDs {
|
||||||
buf, err := withCaptureStdout(t, gopts, func(ctx context.Context, gopts GlobalOptions) error {
|
buf, err := withCaptureStdout(t, gopts, func(ctx context.Context, gopts global.Options) error {
|
||||||
return runList(ctx, gopts, []string{tpe}, gopts.term)
|
return runList(ctx, gopts, []string{tpe}, gopts.Term)
|
||||||
})
|
})
|
||||||
rtest.OK(t, err)
|
rtest.OK(t, err)
|
||||||
return parseIDsFromReader(t, buf)
|
return parseIDsFromReader(t, buf)
|
||||||
|
@ -50,7 +51,7 @@ func parseIDsFromReader(t testing.TB, rd io.Reader) restic.IDs {
|
||||||
return IDs
|
return IDs
|
||||||
}
|
}
|
||||||
|
|
||||||
func testListSnapshots(t testing.TB, gopts GlobalOptions, expected int) restic.IDs {
|
func testListSnapshots(t testing.TB, gopts global.Options, expected int) restic.IDs {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
snapshotIDs := testRunList(t, gopts, "snapshots")
|
snapshotIDs := testRunList(t, gopts, "snapshots")
|
||||||
rtest.Assert(t, len(snapshotIDs) == expected, "expected %v snapshot, got %v", expected, snapshotIDs)
|
rtest.Assert(t, len(snapshotIDs) == expected, "expected %v snapshot, got %v", expected, snapshotIDs)
|
||||||
|
@ -58,9 +59,9 @@ func testListSnapshots(t testing.TB, gopts GlobalOptions, expected int) restic.I
|
||||||
}
|
}
|
||||||
|
|
||||||
// extract blob set from repository index
|
// extract blob set from repository index
|
||||||
func testListBlobs(t testing.TB, gopts GlobalOptions) (blobSetFromIndex restic.IDSet) {
|
func testListBlobs(t testing.TB, gopts global.Options) (blobSetFromIndex restic.IDSet) {
|
||||||
err := withTermStatus(t, gopts, func(ctx context.Context, gopts GlobalOptions) error {
|
err := withTermStatus(t, gopts, func(ctx context.Context, gopts global.Options) error {
|
||||||
printer := ui.NewProgressPrinter(gopts.JSON, gopts.verbosity, gopts.term)
|
printer := ui.NewProgressPrinter(gopts.JSON, gopts.Verbosity, gopts.Term)
|
||||||
_, repo, unlock, err := openWithReadLock(ctx, gopts, false, printer)
|
_, repo, unlock, err := openWithReadLock(ctx, gopts, false, printer)
|
||||||
rtest.OK(t, err)
|
rtest.OK(t, err)
|
||||||
defer unlock()
|
defer unlock()
|
||||||
|
|
|
@ -18,12 +18,13 @@ import (
|
||||||
"github.com/restic/restic/internal/data"
|
"github.com/restic/restic/internal/data"
|
||||||
"github.com/restic/restic/internal/errors"
|
"github.com/restic/restic/internal/errors"
|
||||||
"github.com/restic/restic/internal/fs"
|
"github.com/restic/restic/internal/fs"
|
||||||
|
"github.com/restic/restic/internal/global"
|
||||||
"github.com/restic/restic/internal/restic"
|
"github.com/restic/restic/internal/restic"
|
||||||
"github.com/restic/restic/internal/ui"
|
"github.com/restic/restic/internal/ui"
|
||||||
"github.com/restic/restic/internal/walker"
|
"github.com/restic/restic/internal/walker"
|
||||||
)
|
)
|
||||||
|
|
||||||
func newLsCommand(globalOptions *GlobalOptions) *cobra.Command {
|
func newLsCommand(globalOptions *global.Options) *cobra.Command {
|
||||||
var opts LsOptions
|
var opts LsOptions
|
||||||
|
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
|
@ -62,7 +63,7 @@ Exit status is 12 if the password is incorrect.
|
||||||
GroupID: cmdGroupDefault,
|
GroupID: cmdGroupDefault,
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
finalizeSnapshotFilter(&opts.SnapshotFilter)
|
finalizeSnapshotFilter(&opts.SnapshotFilter)
|
||||||
return runLs(cmd.Context(), opts, *globalOptions, args, globalOptions.term)
|
return runLs(cmd.Context(), opts, *globalOptions, args, globalOptions.Term)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
opts.AddFlags(cmd.Flags())
|
opts.AddFlags(cmd.Flags())
|
||||||
|
@ -303,8 +304,8 @@ type toSortOutput struct {
|
||||||
node *data.Node
|
node *data.Node
|
||||||
}
|
}
|
||||||
|
|
||||||
func runLs(ctx context.Context, opts LsOptions, gopts GlobalOptions, args []string, term ui.Terminal) error {
|
func runLs(ctx context.Context, opts LsOptions, gopts global.Options, args []string, term ui.Terminal) error {
|
||||||
termPrinter := ui.NewProgressPrinter(gopts.JSON, gopts.verbosity, term)
|
termPrinter := ui.NewProgressPrinter(gopts.JSON, gopts.Verbosity, term)
|
||||||
|
|
||||||
if len(args) == 0 {
|
if len(args) == 0 {
|
||||||
return errors.Fatal("no snapshot ID specified, specify snapshot ID or use special ID 'latest'")
|
return errors.Fatal("no snapshot ID specified, specify snapshot ID or use special ID 'latest'")
|
||||||
|
@ -383,11 +384,11 @@ func runLs(ctx context.Context, opts LsOptions, gopts GlobalOptions, args []stri
|
||||||
|
|
||||||
if gopts.JSON {
|
if gopts.JSON {
|
||||||
printer = &jsonLsPrinter{
|
printer = &jsonLsPrinter{
|
||||||
enc: json.NewEncoder(gopts.term.OutputWriter()),
|
enc: json.NewEncoder(gopts.Term.OutputWriter()),
|
||||||
}
|
}
|
||||||
} else if opts.Ncdu {
|
} else if opts.Ncdu {
|
||||||
printer = &ncduLsPrinter{
|
printer = &ncduLsPrinter{
|
||||||
out: gopts.term.OutputWriter(),
|
out: gopts.Term.OutputWriter(),
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
printer = &textLsPrinter{
|
printer = &textLsPrinter{
|
||||||
|
|
|
@ -9,20 +9,21 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/restic/restic/internal/data"
|
"github.com/restic/restic/internal/data"
|
||||||
|
"github.com/restic/restic/internal/global"
|
||||||
"github.com/restic/restic/internal/restic"
|
"github.com/restic/restic/internal/restic"
|
||||||
rtest "github.com/restic/restic/internal/test"
|
rtest "github.com/restic/restic/internal/test"
|
||||||
)
|
)
|
||||||
|
|
||||||
func testRunLsWithOpts(t testing.TB, gopts GlobalOptions, opts LsOptions, args []string) []byte {
|
func testRunLsWithOpts(t testing.TB, gopts global.Options, opts LsOptions, args []string) []byte {
|
||||||
buf, err := withCaptureStdout(t, gopts, func(ctx context.Context, gopts GlobalOptions) error {
|
buf, err := withCaptureStdout(t, gopts, func(ctx context.Context, gopts global.Options) error {
|
||||||
gopts.Quiet = true
|
gopts.Quiet = true
|
||||||
return runLs(context.TODO(), opts, gopts, args, gopts.term)
|
return runLs(context.TODO(), opts, gopts, args, gopts.Term)
|
||||||
})
|
})
|
||||||
rtest.OK(t, err)
|
rtest.OK(t, err)
|
||||||
return buf.Bytes()
|
return buf.Bytes()
|
||||||
}
|
}
|
||||||
|
|
||||||
func testRunLs(t testing.TB, gopts GlobalOptions, snapshotID string) []string {
|
func testRunLs(t testing.TB, gopts global.Options, snapshotID string) []string {
|
||||||
out := testRunLsWithOpts(t, gopts, LsOptions{}, []string{snapshotID})
|
out := testRunLsWithOpts(t, gopts, LsOptions{}, []string{snapshotID})
|
||||||
return strings.Split(string(out), "\n")
|
return strings.Split(string(out), "\n")
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@ package main
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
|
"github.com/restic/restic/internal/global"
|
||||||
"github.com/restic/restic/internal/migrations"
|
"github.com/restic/restic/internal/migrations"
|
||||||
"github.com/restic/restic/internal/restic"
|
"github.com/restic/restic/internal/restic"
|
||||||
"github.com/restic/restic/internal/ui"
|
"github.com/restic/restic/internal/ui"
|
||||||
|
@ -12,7 +13,7 @@ import (
|
||||||
"github.com/spf13/pflag"
|
"github.com/spf13/pflag"
|
||||||
)
|
)
|
||||||
|
|
||||||
func newMigrateCommand(globalOptions *GlobalOptions) *cobra.Command {
|
func newMigrateCommand(globalOptions *global.Options) *cobra.Command {
|
||||||
var opts MigrateOptions
|
var opts MigrateOptions
|
||||||
|
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
|
@ -35,7 +36,7 @@ Exit status is 12 if the password is incorrect.
|
||||||
DisableAutoGenTag: true,
|
DisableAutoGenTag: true,
|
||||||
GroupID: cmdGroupDefault,
|
GroupID: cmdGroupDefault,
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
return runMigrate(cmd.Context(), opts, *globalOptions, args, globalOptions.term)
|
return runMigrate(cmd.Context(), opts, *globalOptions, args, globalOptions.Term)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -75,7 +76,7 @@ func checkMigrations(ctx context.Context, repo restic.Repository, printer progre
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func applyMigrations(ctx context.Context, opts MigrateOptions, gopts GlobalOptions, repo restic.Repository, args []string, term ui.Terminal, printer progress.Printer) error {
|
func applyMigrations(ctx context.Context, opts MigrateOptions, gopts global.Options, repo restic.Repository, args []string, term ui.Terminal, printer progress.Printer) error {
|
||||||
var firsterr error
|
var firsterr error
|
||||||
for _, name := range args {
|
for _, name := range args {
|
||||||
found := false
|
found := false
|
||||||
|
@ -133,8 +134,8 @@ func applyMigrations(ctx context.Context, opts MigrateOptions, gopts GlobalOptio
|
||||||
return firsterr
|
return firsterr
|
||||||
}
|
}
|
||||||
|
|
||||||
func runMigrate(ctx context.Context, opts MigrateOptions, gopts GlobalOptions, args []string, term ui.Terminal) error {
|
func runMigrate(ctx context.Context, opts MigrateOptions, gopts global.Options, args []string, term ui.Terminal) error {
|
||||||
printer := ui.NewProgressPrinter(false, gopts.verbosity, term)
|
printer := ui.NewProgressPrinter(false, gopts.Verbosity, term)
|
||||||
|
|
||||||
ctx, repo, unlock, err := openWithExclusiveLock(ctx, gopts, false, printer)
|
ctx, repo, unlock, err := openWithExclusiveLock(ctx, gopts, false, printer)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -16,6 +16,7 @@ import (
|
||||||
"github.com/restic/restic/internal/data"
|
"github.com/restic/restic/internal/data"
|
||||||
"github.com/restic/restic/internal/debug"
|
"github.com/restic/restic/internal/debug"
|
||||||
"github.com/restic/restic/internal/errors"
|
"github.com/restic/restic/internal/errors"
|
||||||
|
"github.com/restic/restic/internal/global"
|
||||||
"github.com/restic/restic/internal/ui"
|
"github.com/restic/restic/internal/ui"
|
||||||
|
|
||||||
"github.com/restic/restic/internal/fuse"
|
"github.com/restic/restic/internal/fuse"
|
||||||
|
@ -24,11 +25,11 @@ import (
|
||||||
"github.com/anacrolix/fuse/fs"
|
"github.com/anacrolix/fuse/fs"
|
||||||
)
|
)
|
||||||
|
|
||||||
func registerMountCommand(cmdRoot *cobra.Command, globalOptions *GlobalOptions) {
|
func registerMountCommand(cmdRoot *cobra.Command, globalOptions *global.Options) {
|
||||||
cmdRoot.AddCommand(newMountCommand(globalOptions))
|
cmdRoot.AddCommand(newMountCommand(globalOptions))
|
||||||
}
|
}
|
||||||
|
|
||||||
func newMountCommand(globalOptions *GlobalOptions) *cobra.Command {
|
func newMountCommand(globalOptions *global.Options) *cobra.Command {
|
||||||
var opts MountOptions
|
var opts MountOptions
|
||||||
|
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
|
@ -83,7 +84,7 @@ Exit status is 12 if the password is incorrect.
|
||||||
GroupID: cmdGroupDefault,
|
GroupID: cmdGroupDefault,
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
finalizeSnapshotFilter(&opts.SnapshotFilter)
|
finalizeSnapshotFilter(&opts.SnapshotFilter)
|
||||||
return runMount(cmd.Context(), opts, *globalOptions, args, globalOptions.term)
|
return runMount(cmd.Context(), opts, *globalOptions, args, globalOptions.Term)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -114,8 +115,8 @@ func (opts *MountOptions) AddFlags(f *pflag.FlagSet) {
|
||||||
_ = f.MarkDeprecated("snapshot-template", "use --time-template")
|
_ = f.MarkDeprecated("snapshot-template", "use --time-template")
|
||||||
}
|
}
|
||||||
|
|
||||||
func runMount(ctx context.Context, opts MountOptions, gopts GlobalOptions, args []string, term ui.Terminal) error {
|
func runMount(ctx context.Context, opts MountOptions, gopts global.Options, args []string, term ui.Terminal) error {
|
||||||
printer := ui.NewProgressPrinter(false, gopts.verbosity, term)
|
printer := ui.NewProgressPrinter(false, gopts.Verbosity, term)
|
||||||
|
|
||||||
if opts.TimeTemplate == "" {
|
if opts.TimeTemplate == "" {
|
||||||
return errors.Fatal("time template string cannot be empty")
|
return errors.Fatal("time template string cannot be empty")
|
||||||
|
|
|
@ -3,8 +3,11 @@
|
||||||
|
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import "github.com/spf13/cobra"
|
import (
|
||||||
|
"github.com/restic/restic/internal/global"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
func registerMountCommand(_ *cobra.Command, _ *GlobalOptions) {
|
func registerMountCommand(_ *cobra.Command, _ *global.Options) {
|
||||||
// Mount command not supported on these platforms
|
// Mount command not supported on these platforms
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,6 +15,7 @@ import (
|
||||||
systemFuse "github.com/anacrolix/fuse"
|
systemFuse "github.com/anacrolix/fuse"
|
||||||
"github.com/restic/restic/internal/data"
|
"github.com/restic/restic/internal/data"
|
||||||
"github.com/restic/restic/internal/debug"
|
"github.com/restic/restic/internal/debug"
|
||||||
|
"github.com/restic/restic/internal/global"
|
||||||
"github.com/restic/restic/internal/restic"
|
"github.com/restic/restic/internal/restic"
|
||||||
rtest "github.com/restic/restic/internal/test"
|
rtest "github.com/restic/restic/internal/test"
|
||||||
"github.com/restic/restic/internal/ui"
|
"github.com/restic/restic/internal/ui"
|
||||||
|
@ -58,13 +59,13 @@ func waitForMount(t testing.TB, dir string) {
|
||||||
t.Errorf("subdir %q of dir %s never appeared", mountTestSubdir, dir)
|
t.Errorf("subdir %q of dir %s never appeared", mountTestSubdir, dir)
|
||||||
}
|
}
|
||||||
|
|
||||||
func testRunMount(t testing.TB, gopts GlobalOptions, dir string, wg *sync.WaitGroup) {
|
func testRunMount(t testing.TB, gopts global.Options, dir string, wg *sync.WaitGroup) {
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
opts := MountOptions{
|
opts := MountOptions{
|
||||||
TimeTemplate: time.RFC3339,
|
TimeTemplate: time.RFC3339,
|
||||||
}
|
}
|
||||||
rtest.OK(t, withTermStatus(t, gopts, func(ctx context.Context, gopts GlobalOptions) error {
|
rtest.OK(t, withTermStatus(t, gopts, func(ctx context.Context, gopts global.Options) error {
|
||||||
return runMount(context.TODO(), opts, gopts, []string{dir}, gopts.term)
|
return runMount(context.TODO(), opts, gopts, []string{dir}, gopts.Term)
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -91,7 +92,7 @@ func listSnapshots(t testing.TB, dir string) []string {
|
||||||
return names
|
return names
|
||||||
}
|
}
|
||||||
|
|
||||||
func checkSnapshots(t testing.TB, gopts GlobalOptions, mountpoint string, snapshotIDs restic.IDs, expectedSnapshotsInFuseDir int) {
|
func checkSnapshots(t testing.TB, gopts global.Options, mountpoint string, snapshotIDs restic.IDs, expectedSnapshotsInFuseDir int) {
|
||||||
t.Logf("checking for %d snapshots: %v", len(snapshotIDs), snapshotIDs)
|
t.Logf("checking for %d snapshots: %v", len(snapshotIDs), snapshotIDs)
|
||||||
|
|
||||||
var wg sync.WaitGroup
|
var wg sync.WaitGroup
|
||||||
|
@ -129,8 +130,8 @@ func checkSnapshots(t testing.TB, gopts GlobalOptions, mountpoint string, snapsh
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
err := withTermStatus(t, gopts, func(ctx context.Context, gopts GlobalOptions) error {
|
err := withTermStatus(t, gopts, func(ctx context.Context, gopts global.Options) error {
|
||||||
printer := ui.NewProgressPrinter(gopts.JSON, gopts.verbosity, gopts.term)
|
printer := ui.NewProgressPrinter(gopts.JSON, gopts.Verbosity, gopts.Term)
|
||||||
_, repo, unlock, err := openWithReadLock(ctx, gopts, false, printer)
|
_, repo, unlock, err := openWithReadLock(ctx, gopts, false, printer)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -177,7 +178,7 @@ func TestMount(t *testing.T) {
|
||||||
|
|
||||||
env, cleanup := withTestEnvironment(t)
|
env, cleanup := withTestEnvironment(t)
|
||||||
// must list snapshots more than once
|
// must list snapshots more than once
|
||||||
env.gopts.backendTestHook = nil
|
env.gopts.BackendTestHook = nil
|
||||||
defer cleanup()
|
defer cleanup()
|
||||||
|
|
||||||
testRunInit(t, env.gopts)
|
testRunInit(t, env.gopts)
|
||||||
|
@ -224,7 +225,7 @@ func TestMountSameTimestamps(t *testing.T) {
|
||||||
|
|
||||||
env, cleanup := withTestEnvironment(t)
|
env, cleanup := withTestEnvironment(t)
|
||||||
// must list snapshots more than once
|
// must list snapshots more than once
|
||||||
env.gopts.backendTestHook = nil
|
env.gopts.BackendTestHook = nil
|
||||||
defer cleanup()
|
defer cleanup()
|
||||||
|
|
||||||
rtest.SetupTarTestFixture(t, env.base, filepath.Join("testdata", "repo-same-timestamps.tar.gz"))
|
rtest.SetupTarTestFixture(t, env.base, filepath.Join("testdata", "repo-same-timestamps.tar.gz"))
|
||||||
|
|
|
@ -3,12 +3,13 @@ package main
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/restic/restic/internal/global"
|
||||||
"github.com/restic/restic/internal/options"
|
"github.com/restic/restic/internal/options"
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
func newOptionsCommand(globalOptions *GlobalOptions) *cobra.Command {
|
func newOptionsCommand(globalOptions *global.Options) *cobra.Command {
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
Use: "options",
|
Use: "options",
|
||||||
Short: "Print list of extended options",
|
Short: "Print list of extended options",
|
||||||
|
@ -24,7 +25,7 @@ Exit status is 1 if there was any error.
|
||||||
GroupID: cmdGroupAdvanced,
|
GroupID: cmdGroupAdvanced,
|
||||||
DisableAutoGenTag: true,
|
DisableAutoGenTag: true,
|
||||||
Run: func(_ *cobra.Command, _ []string) {
|
Run: func(_ *cobra.Command, _ []string) {
|
||||||
globalOptions.term.Print("All Extended Options:")
|
globalOptions.Term.Print("All Extended Options:")
|
||||||
var maxLen int
|
var maxLen int
|
||||||
for _, opt := range options.List() {
|
for _, opt := range options.List() {
|
||||||
if l := len(opt.Namespace + "." + opt.Name); l > maxLen {
|
if l := len(opt.Namespace + "." + opt.Name); l > maxLen {
|
||||||
|
@ -32,7 +33,7 @@ Exit status is 1 if there was any error.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for _, opt := range options.List() {
|
for _, opt := range options.List() {
|
||||||
globalOptions.term.Print(fmt.Sprintf(" %*s %s", -maxLen, opt.Namespace+"."+opt.Name, opt.Text))
|
globalOptions.Term.Print(fmt.Sprintf(" %*s %s", -maxLen, opt.Namespace+"."+opt.Name, opt.Text))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,6 +10,7 @@ import (
|
||||||
"github.com/restic/restic/internal/data"
|
"github.com/restic/restic/internal/data"
|
||||||
"github.com/restic/restic/internal/debug"
|
"github.com/restic/restic/internal/debug"
|
||||||
"github.com/restic/restic/internal/errors"
|
"github.com/restic/restic/internal/errors"
|
||||||
|
"github.com/restic/restic/internal/global"
|
||||||
"github.com/restic/restic/internal/repository"
|
"github.com/restic/restic/internal/repository"
|
||||||
"github.com/restic/restic/internal/restic"
|
"github.com/restic/restic/internal/restic"
|
||||||
"github.com/restic/restic/internal/ui"
|
"github.com/restic/restic/internal/ui"
|
||||||
|
@ -19,7 +20,7 @@ import (
|
||||||
"github.com/spf13/pflag"
|
"github.com/spf13/pflag"
|
||||||
)
|
)
|
||||||
|
|
||||||
func newPruneCommand(globalOptions *GlobalOptions) *cobra.Command {
|
func newPruneCommand(globalOptions *global.Options) *cobra.Command {
|
||||||
var opts PruneOptions
|
var opts PruneOptions
|
||||||
|
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
|
@ -41,7 +42,7 @@ Exit status is 12 if the password is incorrect.
|
||||||
GroupID: cmdGroupDefault,
|
GroupID: cmdGroupDefault,
|
||||||
DisableAutoGenTag: true,
|
DisableAutoGenTag: true,
|
||||||
RunE: func(cmd *cobra.Command, _ []string) error {
|
RunE: func(cmd *cobra.Command, _ []string) error {
|
||||||
return runPrune(cmd.Context(), opts, *globalOptions, globalOptions.term)
|
return runPrune(cmd.Context(), opts, *globalOptions, globalOptions.Term)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -153,7 +154,7 @@ func verifyPruneOptions(opts *PruneOptions) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func runPrune(ctx context.Context, opts PruneOptions, gopts GlobalOptions, term ui.Terminal) error {
|
func runPrune(ctx context.Context, opts PruneOptions, gopts global.Options, term ui.Terminal) error {
|
||||||
err := verifyPruneOptions(&opts)
|
err := verifyPruneOptions(&opts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -167,7 +168,7 @@ func runPrune(ctx context.Context, opts PruneOptions, gopts GlobalOptions, term
|
||||||
return errors.Fatal("--no-lock is only applicable in combination with --dry-run for prune command")
|
return errors.Fatal("--no-lock is only applicable in combination with --dry-run for prune command")
|
||||||
}
|
}
|
||||||
|
|
||||||
printer := ui.NewProgressPrinter(false, gopts.verbosity, term)
|
printer := ui.NewProgressPrinter(false, gopts.Verbosity, term)
|
||||||
ctx, repo, unlock, err := openWithExclusiveLock(ctx, gopts, opts.DryRun && gopts.NoLock, printer)
|
ctx, repo, unlock, err := openWithExclusiveLock(ctx, gopts, opts.DryRun && gopts.NoLock, printer)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
|
@ -7,29 +7,30 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/restic/restic/internal/backend"
|
"github.com/restic/restic/internal/backend"
|
||||||
|
"github.com/restic/restic/internal/global"
|
||||||
"github.com/restic/restic/internal/repository"
|
"github.com/restic/restic/internal/repository"
|
||||||
rtest "github.com/restic/restic/internal/test"
|
rtest "github.com/restic/restic/internal/test"
|
||||||
)
|
)
|
||||||
|
|
||||||
func testRunPrune(t testing.TB, gopts GlobalOptions, opts PruneOptions) {
|
func testRunPrune(t testing.TB, gopts global.Options, opts PruneOptions) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
rtest.OK(t, testRunPruneOutput(t, gopts, opts))
|
rtest.OK(t, testRunPruneOutput(t, gopts, opts))
|
||||||
}
|
}
|
||||||
|
|
||||||
func testRunPruneMustFail(t testing.TB, gopts GlobalOptions, opts PruneOptions) {
|
func testRunPruneMustFail(t testing.TB, gopts global.Options, opts PruneOptions) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
err := testRunPruneOutput(t, gopts, opts)
|
err := testRunPruneOutput(t, gopts, opts)
|
||||||
rtest.Assert(t, err != nil, "expected non nil error")
|
rtest.Assert(t, err != nil, "expected non nil error")
|
||||||
}
|
}
|
||||||
|
|
||||||
func testRunPruneOutput(t testing.TB, gopts GlobalOptions, opts PruneOptions) error {
|
func testRunPruneOutput(t testing.TB, gopts global.Options, opts PruneOptions) error {
|
||||||
oldHook := gopts.backendTestHook
|
oldHook := gopts.BackendTestHook
|
||||||
gopts.backendTestHook = func(r backend.Backend) (backend.Backend, error) { return newListOnceBackend(r), nil }
|
gopts.BackendTestHook = func(r backend.Backend) (backend.Backend, error) { return newListOnceBackend(r), nil }
|
||||||
defer func() {
|
defer func() {
|
||||||
gopts.backendTestHook = oldHook
|
gopts.BackendTestHook = oldHook
|
||||||
}()
|
}()
|
||||||
return withTermStatus(t, gopts, func(ctx context.Context, gopts GlobalOptions) error {
|
return withTermStatus(t, gopts, func(ctx context.Context, gopts global.Options) error {
|
||||||
return runPrune(context.TODO(), opts, gopts, gopts.term)
|
return runPrune(context.TODO(), opts, gopts, gopts.Term)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -88,8 +89,8 @@ func createPrunableRepo(t *testing.T, env *testEnvironment) {
|
||||||
testRunForget(t, env.gopts, ForgetOptions{}, firstSnapshot.String())
|
testRunForget(t, env.gopts, ForgetOptions{}, firstSnapshot.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
func testRunForgetJSON(t testing.TB, gopts GlobalOptions, args ...string) {
|
func testRunForgetJSON(t testing.TB, gopts global.Options, args ...string) {
|
||||||
buf, err := withCaptureStdout(t, gopts, func(ctx context.Context, gopts GlobalOptions) error {
|
buf, err := withCaptureStdout(t, gopts, func(ctx context.Context, gopts global.Options) error {
|
||||||
gopts.JSON = true
|
gopts.JSON = true
|
||||||
opts := ForgetOptions{
|
opts := ForgetOptions{
|
||||||
DryRun: true,
|
DryRun: true,
|
||||||
|
@ -98,7 +99,7 @@ func testRunForgetJSON(t testing.TB, gopts GlobalOptions, args ...string) {
|
||||||
pruneOpts := PruneOptions{
|
pruneOpts := PruneOptions{
|
||||||
MaxUnused: "5%",
|
MaxUnused: "5%",
|
||||||
}
|
}
|
||||||
return runForget(context.TODO(), opts, pruneOpts, gopts, gopts.term, args)
|
return runForget(context.TODO(), opts, pruneOpts, gopts, gopts.Term, args)
|
||||||
})
|
})
|
||||||
rtest.OK(t, err)
|
rtest.OK(t, err)
|
||||||
|
|
||||||
|
@ -119,8 +120,8 @@ func testPrune(t *testing.T, pruneOpts PruneOptions, checkOpts CheckOptions) {
|
||||||
|
|
||||||
createPrunableRepo(t, env)
|
createPrunableRepo(t, env)
|
||||||
testRunPrune(t, env.gopts, pruneOpts)
|
testRunPrune(t, env.gopts, pruneOpts)
|
||||||
rtest.OK(t, withTermStatus(t, env.gopts, func(ctx context.Context, gopts GlobalOptions) error {
|
rtest.OK(t, withTermStatus(t, env.gopts, func(ctx context.Context, gopts global.Options) error {
|
||||||
_, err := runCheck(context.TODO(), checkOpts, gopts, nil, gopts.term)
|
_, err := runCheck(context.TODO(), checkOpts, gopts, nil, gopts.Term)
|
||||||
return err
|
return err
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
@ -149,14 +150,14 @@ func TestPruneWithDamagedRepository(t *testing.T) {
|
||||||
testListSnapshots(t, env.gopts, 1)
|
testListSnapshots(t, env.gopts, 1)
|
||||||
removePacksExcept(env.gopts, t, oldPacks, false)
|
removePacksExcept(env.gopts, t, oldPacks, false)
|
||||||
|
|
||||||
oldHook := env.gopts.backendTestHook
|
oldHook := env.gopts.BackendTestHook
|
||||||
env.gopts.backendTestHook = func(r backend.Backend) (backend.Backend, error) { return newListOnceBackend(r), nil }
|
env.gopts.BackendTestHook = func(r backend.Backend) (backend.Backend, error) { return newListOnceBackend(r), nil }
|
||||||
defer func() {
|
defer func() {
|
||||||
env.gopts.backendTestHook = oldHook
|
env.gopts.BackendTestHook = oldHook
|
||||||
}()
|
}()
|
||||||
// prune should fail
|
// prune should fail
|
||||||
rtest.Equals(t, repository.ErrPacksMissing, withTermStatus(t, env.gopts, func(ctx context.Context, gopts GlobalOptions) error {
|
rtest.Equals(t, repository.ErrPacksMissing, withTermStatus(t, env.gopts, func(ctx context.Context, gopts global.Options) error {
|
||||||
return runPrune(context.TODO(), pruneDefaultOptions, gopts, gopts.term)
|
return runPrune(context.TODO(), pruneDefaultOptions, gopts, gopts.Term)
|
||||||
}), "prune should have reported index not complete error")
|
}), "prune should have reported index not complete error")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -228,8 +229,8 @@ func testEdgeCaseRepo(t *testing.T, tarfile string, optionsCheck CheckOptions, o
|
||||||
if checkOK {
|
if checkOK {
|
||||||
testRunCheck(t, env.gopts)
|
testRunCheck(t, env.gopts)
|
||||||
} else {
|
} else {
|
||||||
rtest.Assert(t, withTermStatus(t, env.gopts, func(ctx context.Context, gopts GlobalOptions) error {
|
rtest.Assert(t, withTermStatus(t, env.gopts, func(ctx context.Context, gopts global.Options) error {
|
||||||
_, err := runCheck(context.TODO(), optionsCheck, gopts, nil, gopts.term)
|
_, err := runCheck(context.TODO(), optionsCheck, gopts, nil, gopts.Term)
|
||||||
return err
|
return err
|
||||||
}) != nil,
|
}) != nil,
|
||||||
"check should have reported an error")
|
"check should have reported an error")
|
||||||
|
@ -239,8 +240,8 @@ func testEdgeCaseRepo(t *testing.T, tarfile string, optionsCheck CheckOptions, o
|
||||||
testRunPrune(t, env.gopts, optionsPrune)
|
testRunPrune(t, env.gopts, optionsPrune)
|
||||||
testRunCheck(t, env.gopts)
|
testRunCheck(t, env.gopts)
|
||||||
} else {
|
} else {
|
||||||
rtest.Assert(t, withTermStatus(t, env.gopts, func(ctx context.Context, gopts GlobalOptions) error {
|
rtest.Assert(t, withTermStatus(t, env.gopts, func(ctx context.Context, gopts global.Options) error {
|
||||||
return runPrune(context.TODO(), optionsPrune, gopts, gopts.term)
|
return runPrune(context.TODO(), optionsPrune, gopts, gopts.Term)
|
||||||
}) != nil,
|
}) != nil,
|
||||||
"prune should have reported an error")
|
"prune should have reported an error")
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,7 @@ import (
|
||||||
|
|
||||||
"github.com/restic/restic/internal/data"
|
"github.com/restic/restic/internal/data"
|
||||||
"github.com/restic/restic/internal/errors"
|
"github.com/restic/restic/internal/errors"
|
||||||
|
"github.com/restic/restic/internal/global"
|
||||||
"github.com/restic/restic/internal/repository"
|
"github.com/restic/restic/internal/repository"
|
||||||
"github.com/restic/restic/internal/restic"
|
"github.com/restic/restic/internal/restic"
|
||||||
"github.com/restic/restic/internal/ui"
|
"github.com/restic/restic/internal/ui"
|
||||||
|
@ -15,7 +16,7 @@ import (
|
||||||
"golang.org/x/sync/errgroup"
|
"golang.org/x/sync/errgroup"
|
||||||
)
|
)
|
||||||
|
|
||||||
func newRecoverCommand(globalOptions *GlobalOptions) *cobra.Command {
|
func newRecoverCommand(globalOptions *global.Options) *cobra.Command {
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
Use: "recover [flags]",
|
Use: "recover [flags]",
|
||||||
Short: "Recover data from the repository not referenced by snapshots",
|
Short: "Recover data from the repository not referenced by snapshots",
|
||||||
|
@ -36,19 +37,19 @@ Exit status is 12 if the password is incorrect.
|
||||||
GroupID: cmdGroupDefault,
|
GroupID: cmdGroupDefault,
|
||||||
DisableAutoGenTag: true,
|
DisableAutoGenTag: true,
|
||||||
RunE: func(cmd *cobra.Command, _ []string) error {
|
RunE: func(cmd *cobra.Command, _ []string) error {
|
||||||
return runRecover(cmd.Context(), *globalOptions, globalOptions.term)
|
return runRecover(cmd.Context(), *globalOptions, globalOptions.Term)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
func runRecover(ctx context.Context, gopts GlobalOptions, term ui.Terminal) error {
|
func runRecover(ctx context.Context, gopts global.Options, term ui.Terminal) error {
|
||||||
hostname, err := os.Hostname()
|
hostname, err := os.Hostname()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
printer := ui.NewProgressPrinter(false, gopts.verbosity, term)
|
printer := ui.NewProgressPrinter(false, gopts.Verbosity, term)
|
||||||
ctx, repo, unlock, err := openWithExclusiveLock(ctx, gopts, false, printer)
|
ctx, repo, unlock, err := openWithExclusiveLock(ctx, gopts, false, printer)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
|
@ -4,19 +4,20 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/restic/restic/internal/global"
|
||||||
rtest "github.com/restic/restic/internal/test"
|
rtest "github.com/restic/restic/internal/test"
|
||||||
)
|
)
|
||||||
|
|
||||||
func testRunRecover(t testing.TB, gopts GlobalOptions) {
|
func testRunRecover(t testing.TB, gopts global.Options) {
|
||||||
rtest.OK(t, withTermStatus(t, gopts, func(ctx context.Context, gopts GlobalOptions) error {
|
rtest.OK(t, withTermStatus(t, gopts, func(ctx context.Context, gopts global.Options) error {
|
||||||
return runRecover(context.TODO(), gopts, gopts.term)
|
return runRecover(context.TODO(), gopts, gopts.Term)
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRecover(t *testing.T) {
|
func TestRecover(t *testing.T) {
|
||||||
env, cleanup := withTestEnvironment(t)
|
env, cleanup := withTestEnvironment(t)
|
||||||
// must list index more than once
|
// must list index more than once
|
||||||
env.gopts.backendTestHook = nil
|
env.gopts.BackendTestHook = nil
|
||||||
defer cleanup()
|
defer cleanup()
|
||||||
|
|
||||||
testSetupBackupData(t, env)
|
testSetupBackupData(t, env)
|
||||||
|
@ -32,7 +33,7 @@ func TestRecover(t *testing.T) {
|
||||||
ids = testListSnapshots(t, env.gopts, 1)
|
ids = testListSnapshots(t, env.gopts, 1)
|
||||||
testRunCheck(t, env.gopts)
|
testRunCheck(t, env.gopts)
|
||||||
// check that the root tree is included in the snapshot
|
// check that the root tree is included in the snapshot
|
||||||
rtest.OK(t, withTermStatus(t, env.gopts, func(ctx context.Context, gopts GlobalOptions) error {
|
rtest.OK(t, withTermStatus(t, env.gopts, func(ctx context.Context, gopts global.Options) error {
|
||||||
return runCat(context.TODO(), gopts, []string{"tree", ids[0].String() + ":" + sn.Tree.Str()}, gopts.term)
|
return runCat(context.TODO(), gopts, []string{"tree", ids[0].String() + ":" + sn.Tree.Str()}, gopts.Term)
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/restic/restic/internal/global"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
func newRepairCommand(globalOptions *GlobalOptions) *cobra.Command {
|
func newRepairCommand(globalOptions *global.Options) *cobra.Command {
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
Use: "repair",
|
Use: "repair",
|
||||||
Short: "Repair the repository",
|
Short: "Repair the repository",
|
||||||
|
|
|
@ -3,13 +3,14 @@ package main
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
|
"github.com/restic/restic/internal/global"
|
||||||
"github.com/restic/restic/internal/repository"
|
"github.com/restic/restic/internal/repository"
|
||||||
"github.com/restic/restic/internal/ui"
|
"github.com/restic/restic/internal/ui"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"github.com/spf13/pflag"
|
"github.com/spf13/pflag"
|
||||||
)
|
)
|
||||||
|
|
||||||
func newRepairIndexCommand(globalOptions *GlobalOptions) *cobra.Command {
|
func newRepairIndexCommand(globalOptions *global.Options) *cobra.Command {
|
||||||
var opts RepairIndexOptions
|
var opts RepairIndexOptions
|
||||||
|
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
|
@ -30,7 +31,7 @@ Exit status is 12 if the password is incorrect.
|
||||||
`,
|
`,
|
||||||
DisableAutoGenTag: true,
|
DisableAutoGenTag: true,
|
||||||
RunE: func(cmd *cobra.Command, _ []string) error {
|
RunE: func(cmd *cobra.Command, _ []string) error {
|
||||||
return runRebuildIndex(cmd.Context(), opts, *globalOptions, globalOptions.term)
|
return runRebuildIndex(cmd.Context(), opts, *globalOptions, globalOptions.Term)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -47,7 +48,7 @@ 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")
|
f.BoolVar(&opts.ReadAllPacks, "read-all-packs", false, "read all pack files to generate new index from scratch")
|
||||||
}
|
}
|
||||||
|
|
||||||
func newRebuildIndexCommand(globalOptions *GlobalOptions) *cobra.Command {
|
func newRebuildIndexCommand(globalOptions *global.Options) *cobra.Command {
|
||||||
var opts RepairIndexOptions
|
var opts RepairIndexOptions
|
||||||
|
|
||||||
replacement := newRepairIndexCommand(globalOptions)
|
replacement := newRepairIndexCommand(globalOptions)
|
||||||
|
@ -60,7 +61,7 @@ func newRebuildIndexCommand(globalOptions *GlobalOptions) *cobra.Command {
|
||||||
// must create a new instance of the run function as it captures opts
|
// must create a new instance of the run function as it captures opts
|
||||||
// by reference
|
// by reference
|
||||||
RunE: func(cmd *cobra.Command, _ []string) error {
|
RunE: func(cmd *cobra.Command, _ []string) error {
|
||||||
return runRebuildIndex(cmd.Context(), opts, *globalOptions, globalOptions.term)
|
return runRebuildIndex(cmd.Context(), opts, *globalOptions, globalOptions.Term)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -68,8 +69,8 @@ func newRebuildIndexCommand(globalOptions *GlobalOptions) *cobra.Command {
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
func runRebuildIndex(ctx context.Context, opts RepairIndexOptions, gopts GlobalOptions, term ui.Terminal) error {
|
func runRebuildIndex(ctx context.Context, opts RepairIndexOptions, gopts global.Options, term ui.Terminal) error {
|
||||||
printer := ui.NewProgressPrinter(false, gopts.verbosity, term)
|
printer := ui.NewProgressPrinter(false, gopts.Verbosity, term)
|
||||||
|
|
||||||
ctx, repo, unlock, err := openWithExclusiveLock(ctx, gopts, false, printer)
|
ctx, repo, unlock, err := openWithExclusiveLock(ctx, gopts, false, printer)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -10,19 +10,20 @@ import (
|
||||||
|
|
||||||
"github.com/restic/restic/internal/backend"
|
"github.com/restic/restic/internal/backend"
|
||||||
"github.com/restic/restic/internal/errors"
|
"github.com/restic/restic/internal/errors"
|
||||||
|
"github.com/restic/restic/internal/global"
|
||||||
"github.com/restic/restic/internal/repository/index"
|
"github.com/restic/restic/internal/repository/index"
|
||||||
"github.com/restic/restic/internal/restic"
|
"github.com/restic/restic/internal/restic"
|
||||||
rtest "github.com/restic/restic/internal/test"
|
rtest "github.com/restic/restic/internal/test"
|
||||||
)
|
)
|
||||||
|
|
||||||
func testRunRebuildIndex(t testing.TB, gopts GlobalOptions) {
|
func testRunRebuildIndex(t testing.TB, gopts global.Options) {
|
||||||
rtest.OK(t, withTermStatus(t, gopts, func(ctx context.Context, gopts GlobalOptions) error {
|
rtest.OK(t, withTermStatus(t, gopts, func(ctx context.Context, gopts global.Options) error {
|
||||||
gopts.Quiet = true
|
gopts.Quiet = true
|
||||||
return runRebuildIndex(context.TODO(), RepairIndexOptions{}, gopts, gopts.term)
|
return runRebuildIndex(context.TODO(), RepairIndexOptions{}, gopts, gopts.Term)
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
func testRebuildIndex(t *testing.T, backendTestHook backendWrapper) {
|
func testRebuildIndex(t *testing.T, backendTestHook global.BackendWrapper) {
|
||||||
env, cleanup := withTestEnvironment(t)
|
env, cleanup := withTestEnvironment(t)
|
||||||
defer cleanup()
|
defer cleanup()
|
||||||
|
|
||||||
|
@ -42,10 +43,10 @@ func testRebuildIndex(t *testing.T, backendTestHook backendWrapper) {
|
||||||
t.Fatalf("did not find hint for repair index command")
|
t.Fatalf("did not find hint for repair index command")
|
||||||
}
|
}
|
||||||
|
|
||||||
env.gopts.backendTestHook = backendTestHook
|
env.gopts.BackendTestHook = backendTestHook
|
||||||
testRunRebuildIndex(t, env.gopts)
|
testRunRebuildIndex(t, env.gopts)
|
||||||
|
|
||||||
env.gopts.backendTestHook = nil
|
env.gopts.BackendTestHook = nil
|
||||||
out, err = testRunCheckOutput(t, env.gopts, false)
|
out, err = testRunCheckOutput(t, env.gopts, false)
|
||||||
if len(out) != 0 {
|
if len(out) != 0 {
|
||||||
t.Fatalf("expected no output from the checker, got: %v", out)
|
t.Fatalf("expected no output from the checker, got: %v", out)
|
||||||
|
@ -125,12 +126,12 @@ func TestRebuildIndexFailsOnAppendOnly(t *testing.T) {
|
||||||
datafile := filepath.Join("..", "..", "internal", "checker", "testdata", "duplicate-packs-in-index-test-repo.tar.gz")
|
datafile := filepath.Join("..", "..", "internal", "checker", "testdata", "duplicate-packs-in-index-test-repo.tar.gz")
|
||||||
rtest.SetupTarTestFixture(t, env.base, datafile)
|
rtest.SetupTarTestFixture(t, env.base, datafile)
|
||||||
|
|
||||||
env.gopts.backendTestHook = func(r backend.Backend) (backend.Backend, error) {
|
env.gopts.BackendTestHook = func(r backend.Backend) (backend.Backend, error) {
|
||||||
return &appendOnlyBackend{r}, nil
|
return &appendOnlyBackend{r}, nil
|
||||||
}
|
}
|
||||||
err := withTermStatus(t, env.gopts, func(ctx context.Context, gopts GlobalOptions) error {
|
err := withTermStatus(t, env.gopts, func(ctx context.Context, gopts global.Options) error {
|
||||||
gopts.Quiet = true
|
gopts.Quiet = true
|
||||||
return runRebuildIndex(context.TODO(), RepairIndexOptions{}, gopts, gopts.term)
|
return runRebuildIndex(context.TODO(), RepairIndexOptions{}, gopts, gopts.Term)
|
||||||
})
|
})
|
||||||
|
|
||||||
if err == nil {
|
if err == nil {
|
||||||
|
|
|
@ -7,13 +7,14 @@ import (
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/restic/restic/internal/errors"
|
"github.com/restic/restic/internal/errors"
|
||||||
|
"github.com/restic/restic/internal/global"
|
||||||
"github.com/restic/restic/internal/repository"
|
"github.com/restic/restic/internal/repository"
|
||||||
"github.com/restic/restic/internal/restic"
|
"github.com/restic/restic/internal/restic"
|
||||||
"github.com/restic/restic/internal/ui"
|
"github.com/restic/restic/internal/ui"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
func newRepairPacksCommand(globalOptions *GlobalOptions) *cobra.Command {
|
func newRepairPacksCommand(globalOptions *global.Options) *cobra.Command {
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
Use: "packs [packIDs...]",
|
Use: "packs [packIDs...]",
|
||||||
Short: "Salvage damaged pack files",
|
Short: "Salvage damaged pack files",
|
||||||
|
@ -32,13 +33,13 @@ Exit status is 12 if the password is incorrect.
|
||||||
`,
|
`,
|
||||||
DisableAutoGenTag: true,
|
DisableAutoGenTag: true,
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
return runRepairPacks(cmd.Context(), *globalOptions, globalOptions.term, args)
|
return runRepairPacks(cmd.Context(), *globalOptions, globalOptions.Term, args)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
func runRepairPacks(ctx context.Context, gopts GlobalOptions, term ui.Terminal, args []string) error {
|
func runRepairPacks(ctx context.Context, gopts global.Options, term ui.Terminal, args []string) error {
|
||||||
ids := restic.NewIDSet()
|
ids := restic.NewIDSet()
|
||||||
for _, arg := range args {
|
for _, arg := range args {
|
||||||
id, err := restic.ParseID(arg)
|
id, err := restic.ParseID(arg)
|
||||||
|
@ -51,7 +52,7 @@ func runRepairPacks(ctx context.Context, gopts GlobalOptions, term ui.Terminal,
|
||||||
return errors.Fatal("no ids specified")
|
return errors.Fatal("no ids specified")
|
||||||
}
|
}
|
||||||
|
|
||||||
printer := ui.NewProgressPrinter(false, gopts.verbosity, term)
|
printer := ui.NewProgressPrinter(false, gopts.Verbosity, term)
|
||||||
|
|
||||||
ctx, repo, unlock, err := openWithExclusiveLock(ctx, gopts, false, printer)
|
ctx, repo, unlock, err := openWithExclusiveLock(ctx, gopts, false, printer)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -5,6 +5,7 @@ import (
|
||||||
|
|
||||||
"github.com/restic/restic/internal/data"
|
"github.com/restic/restic/internal/data"
|
||||||
"github.com/restic/restic/internal/errors"
|
"github.com/restic/restic/internal/errors"
|
||||||
|
"github.com/restic/restic/internal/global"
|
||||||
"github.com/restic/restic/internal/restic"
|
"github.com/restic/restic/internal/restic"
|
||||||
"github.com/restic/restic/internal/ui"
|
"github.com/restic/restic/internal/ui"
|
||||||
"github.com/restic/restic/internal/walker"
|
"github.com/restic/restic/internal/walker"
|
||||||
|
@ -13,7 +14,7 @@ import (
|
||||||
"github.com/spf13/pflag"
|
"github.com/spf13/pflag"
|
||||||
)
|
)
|
||||||
|
|
||||||
func newRepairSnapshotsCommand(globalOptions *GlobalOptions) *cobra.Command {
|
func newRepairSnapshotsCommand(globalOptions *global.Options) *cobra.Command {
|
||||||
var opts RepairOptions
|
var opts RepairOptions
|
||||||
|
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
|
@ -52,7 +53,7 @@ Exit status is 12 if the password is incorrect.
|
||||||
DisableAutoGenTag: true,
|
DisableAutoGenTag: true,
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
finalizeSnapshotFilter(&opts.SnapshotFilter)
|
finalizeSnapshotFilter(&opts.SnapshotFilter)
|
||||||
return runRepairSnapshots(cmd.Context(), *globalOptions, opts, args, globalOptions.term)
|
return runRepairSnapshots(cmd.Context(), *globalOptions, opts, args, globalOptions.Term)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -75,8 +76,8 @@ func (opts *RepairOptions) AddFlags(f *pflag.FlagSet) {
|
||||||
initMultiSnapshotFilter(f, &opts.SnapshotFilter, true)
|
initMultiSnapshotFilter(f, &opts.SnapshotFilter, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
func runRepairSnapshots(ctx context.Context, gopts GlobalOptions, opts RepairOptions, args []string, term ui.Terminal) error {
|
func runRepairSnapshots(ctx context.Context, gopts global.Options, opts RepairOptions, args []string, term ui.Terminal) error {
|
||||||
printer := ui.NewProgressPrinter(false, gopts.verbosity, term)
|
printer := ui.NewProgressPrinter(false, gopts.Verbosity, term)
|
||||||
|
|
||||||
ctx, repo, unlock, err := openWithExclusiveLock(ctx, gopts, opts.DryRun, printer)
|
ctx, repo, unlock, err := openWithExclusiveLock(ctx, gopts, opts.DryRun, printer)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -10,17 +10,18 @@ import (
|
||||||
"reflect"
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/restic/restic/internal/global"
|
||||||
"github.com/restic/restic/internal/restic"
|
"github.com/restic/restic/internal/restic"
|
||||||
rtest "github.com/restic/restic/internal/test"
|
rtest "github.com/restic/restic/internal/test"
|
||||||
)
|
)
|
||||||
|
|
||||||
func testRunRepairSnapshot(t testing.TB, gopts GlobalOptions, forget bool) {
|
func testRunRepairSnapshot(t testing.TB, gopts global.Options, forget bool) {
|
||||||
opts := RepairOptions{
|
opts := RepairOptions{
|
||||||
Forget: forget,
|
Forget: forget,
|
||||||
}
|
}
|
||||||
|
|
||||||
rtest.OK(t, withTermStatus(t, gopts, func(ctx context.Context, gopts GlobalOptions) error {
|
rtest.OK(t, withTermStatus(t, gopts, func(ctx context.Context, gopts global.Options) error {
|
||||||
return runRepairSnapshots(context.TODO(), gopts, opts, nil, gopts.term)
|
return runRepairSnapshots(context.TODO(), gopts, opts, nil, gopts.Term)
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -9,6 +9,7 @@ import (
|
||||||
"github.com/restic/restic/internal/debug"
|
"github.com/restic/restic/internal/debug"
|
||||||
"github.com/restic/restic/internal/errors"
|
"github.com/restic/restic/internal/errors"
|
||||||
"github.com/restic/restic/internal/filter"
|
"github.com/restic/restic/internal/filter"
|
||||||
|
"github.com/restic/restic/internal/global"
|
||||||
"github.com/restic/restic/internal/restorer"
|
"github.com/restic/restic/internal/restorer"
|
||||||
"github.com/restic/restic/internal/ui"
|
"github.com/restic/restic/internal/ui"
|
||||||
"github.com/restic/restic/internal/ui/progress"
|
"github.com/restic/restic/internal/ui/progress"
|
||||||
|
@ -18,7 +19,7 @@ import (
|
||||||
"github.com/spf13/pflag"
|
"github.com/spf13/pflag"
|
||||||
)
|
)
|
||||||
|
|
||||||
func newRestoreCommand(globalOptions *GlobalOptions) *cobra.Command {
|
func newRestoreCommand(globalOptions *global.Options) *cobra.Command {
|
||||||
var opts RestoreOptions
|
var opts RestoreOptions
|
||||||
|
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
|
@ -47,7 +48,7 @@ Exit status is 12 if the password is incorrect.
|
||||||
DisableAutoGenTag: true,
|
DisableAutoGenTag: true,
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
finalizeSnapshotFilter(&opts.SnapshotFilter)
|
finalizeSnapshotFilter(&opts.SnapshotFilter)
|
||||||
return runRestore(cmd.Context(), opts, *globalOptions, globalOptions.term, args)
|
return runRestore(cmd.Context(), opts, *globalOptions, globalOptions.Term, args)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -87,14 +88,14 @@ func (opts *RestoreOptions) AddFlags(f *pflag.FlagSet) {
|
||||||
f.BoolVar(&opts.Delete, "delete", false, "delete files from target directory if they do not exist in snapshot. Use '--dry-run -vv' to check what would be deleted")
|
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,
|
func runRestore(ctx context.Context, opts RestoreOptions, gopts global.Options,
|
||||||
term ui.Terminal, args []string) error {
|
term ui.Terminal, args []string) error {
|
||||||
|
|
||||||
var printer restoreui.ProgressPrinter
|
var printer restoreui.ProgressPrinter
|
||||||
if gopts.JSON {
|
if gopts.JSON {
|
||||||
printer = restoreui.NewJSONProgress(term, gopts.verbosity)
|
printer = restoreui.NewJSONProgress(term, gopts.Verbosity)
|
||||||
} else {
|
} else {
|
||||||
printer = restoreui.NewTextProgress(term, gopts.verbosity)
|
printer = restoreui.NewTextProgress(term, gopts.Verbosity)
|
||||||
}
|
}
|
||||||
|
|
||||||
excludePatternFns, err := opts.ExcludePatternOptions.CollectPatterns(printer.E)
|
excludePatternFns, err := opts.ExcludePatternOptions.CollectPatterns(printer.E)
|
||||||
|
|
|
@ -12,15 +12,16 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/restic/restic/internal/data"
|
"github.com/restic/restic/internal/data"
|
||||||
|
"github.com/restic/restic/internal/global"
|
||||||
"github.com/restic/restic/internal/restic"
|
"github.com/restic/restic/internal/restic"
|
||||||
rtest "github.com/restic/restic/internal/test"
|
rtest "github.com/restic/restic/internal/test"
|
||||||
)
|
)
|
||||||
|
|
||||||
func testRunRestore(t testing.TB, gopts GlobalOptions, dir string, snapshotID string) {
|
func testRunRestore(t testing.TB, gopts global.Options, dir string, snapshotID string) {
|
||||||
testRunRestoreExcludes(t, gopts, dir, snapshotID, nil)
|
testRunRestoreExcludes(t, gopts, dir, snapshotID, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func testRunRestoreExcludes(t testing.TB, gopts GlobalOptions, dir string, snapshotID string, excludes []string) {
|
func testRunRestoreExcludes(t testing.TB, gopts global.Options, dir string, snapshotID string, excludes []string) {
|
||||||
opts := RestoreOptions{
|
opts := RestoreOptions{
|
||||||
Target: dir,
|
Target: dir,
|
||||||
}
|
}
|
||||||
|
@ -29,13 +30,13 @@ func testRunRestoreExcludes(t testing.TB, gopts GlobalOptions, dir string, snaps
|
||||||
rtest.OK(t, testRunRestoreAssumeFailure(t, snapshotID, opts, gopts))
|
rtest.OK(t, testRunRestoreAssumeFailure(t, snapshotID, opts, gopts))
|
||||||
}
|
}
|
||||||
|
|
||||||
func testRunRestoreAssumeFailure(t testing.TB, snapshotID string, opts RestoreOptions, gopts GlobalOptions) error {
|
func testRunRestoreAssumeFailure(t testing.TB, snapshotID string, opts RestoreOptions, gopts global.Options) error {
|
||||||
return withTermStatus(t, gopts, func(ctx context.Context, gopts GlobalOptions) error {
|
return withTermStatus(t, gopts, func(ctx context.Context, gopts global.Options) error {
|
||||||
return runRestore(ctx, opts, gopts, gopts.term, []string{snapshotID})
|
return runRestore(ctx, opts, gopts, gopts.Term, []string{snapshotID})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func testRunRestoreLatest(t testing.TB, gopts GlobalOptions, dir string, paths []string, hosts []string) {
|
func testRunRestoreLatest(t testing.TB, gopts global.Options, dir string, paths []string, hosts []string) {
|
||||||
opts := RestoreOptions{
|
opts := RestoreOptions{
|
||||||
Target: dir,
|
Target: dir,
|
||||||
SnapshotFilter: data.SnapshotFilter{
|
SnapshotFilter: data.SnapshotFilter{
|
||||||
|
@ -47,7 +48,7 @@ func testRunRestoreLatest(t testing.TB, gopts GlobalOptions, dir string, paths [
|
||||||
rtest.OK(t, testRunRestoreAssumeFailure(t, "latest", opts, gopts))
|
rtest.OK(t, testRunRestoreAssumeFailure(t, "latest", opts, gopts))
|
||||||
}
|
}
|
||||||
|
|
||||||
func testRunRestoreIncludes(t testing.TB, gopts GlobalOptions, dir string, snapshotID restic.ID, includes []string) {
|
func testRunRestoreIncludes(t testing.TB, gopts global.Options, dir string, snapshotID restic.ID, includes []string) {
|
||||||
opts := RestoreOptions{
|
opts := RestoreOptions{
|
||||||
Target: dir,
|
Target: dir,
|
||||||
}
|
}
|
||||||
|
@ -56,7 +57,7 @@ func testRunRestoreIncludes(t testing.TB, gopts GlobalOptions, dir string, snaps
|
||||||
rtest.OK(t, testRunRestoreAssumeFailure(t, snapshotID.String(), opts, gopts))
|
rtest.OK(t, testRunRestoreAssumeFailure(t, snapshotID.String(), opts, gopts))
|
||||||
}
|
}
|
||||||
|
|
||||||
func testRunRestoreIncludesFromFile(t testing.TB, gopts GlobalOptions, dir string, snapshotID restic.ID, includesFile string) {
|
func testRunRestoreIncludesFromFile(t testing.TB, gopts global.Options, dir string, snapshotID restic.ID, includesFile string) {
|
||||||
opts := RestoreOptions{
|
opts := RestoreOptions{
|
||||||
Target: dir,
|
Target: dir,
|
||||||
}
|
}
|
||||||
|
@ -65,7 +66,7 @@ func testRunRestoreIncludesFromFile(t testing.TB, gopts GlobalOptions, dir strin
|
||||||
rtest.OK(t, testRunRestoreAssumeFailure(t, snapshotID.String(), opts, gopts))
|
rtest.OK(t, testRunRestoreAssumeFailure(t, snapshotID.String(), opts, gopts))
|
||||||
}
|
}
|
||||||
|
|
||||||
func testRunRestoreExcludesFromFile(t testing.TB, gopts GlobalOptions, dir string, snapshotID restic.ID, excludesFile string) {
|
func testRunRestoreExcludesFromFile(t testing.TB, gopts global.Options, dir string, snapshotID restic.ID, excludesFile string) {
|
||||||
opts := RestoreOptions{
|
opts := RestoreOptions{
|
||||||
Target: dir,
|
Target: dir,
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,6 +12,7 @@ import (
|
||||||
"github.com/restic/restic/internal/debug"
|
"github.com/restic/restic/internal/debug"
|
||||||
"github.com/restic/restic/internal/errors"
|
"github.com/restic/restic/internal/errors"
|
||||||
"github.com/restic/restic/internal/filter"
|
"github.com/restic/restic/internal/filter"
|
||||||
|
"github.com/restic/restic/internal/global"
|
||||||
"github.com/restic/restic/internal/repository"
|
"github.com/restic/restic/internal/repository"
|
||||||
"github.com/restic/restic/internal/restic"
|
"github.com/restic/restic/internal/restic"
|
||||||
"github.com/restic/restic/internal/ui"
|
"github.com/restic/restic/internal/ui"
|
||||||
|
@ -19,7 +20,7 @@ import (
|
||||||
"github.com/restic/restic/internal/walker"
|
"github.com/restic/restic/internal/walker"
|
||||||
)
|
)
|
||||||
|
|
||||||
func newRewriteCommand(globalOptions *GlobalOptions) *cobra.Command {
|
func newRewriteCommand(globalOptions *global.Options) *cobra.Command {
|
||||||
var opts RewriteOptions
|
var opts RewriteOptions
|
||||||
|
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
|
@ -62,7 +63,7 @@ Exit status is 12 if the password is incorrect.
|
||||||
DisableAutoGenTag: true,
|
DisableAutoGenTag: true,
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
finalizeSnapshotFilter(&opts.SnapshotFilter)
|
finalizeSnapshotFilter(&opts.SnapshotFilter)
|
||||||
return runRewrite(cmd.Context(), opts, *globalOptions, args, globalOptions.term)
|
return runRewrite(cmd.Context(), opts, *globalOptions, args, globalOptions.Term)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -91,7 +92,7 @@ func (sma snapshotMetadataArgs) convert() (*snapshotMetadata, error) {
|
||||||
|
|
||||||
var timeStamp *time.Time
|
var timeStamp *time.Time
|
||||||
if sma.Time != "" {
|
if sma.Time != "" {
|
||||||
t, err := time.ParseInLocation(TimeFormat, sma.Time, time.Local)
|
t, err := time.ParseInLocation(global.TimeFormat, sma.Time, time.Local)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Fatalf("error in time option: %v", err)
|
return nil, errors.Fatalf("error in time option: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -291,12 +292,12 @@ func filterAndReplaceSnapshot(ctx context.Context, repo restic.Repository, sn *d
|
||||||
return true, nil
|
return true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func runRewrite(ctx context.Context, opts RewriteOptions, gopts GlobalOptions, args []string, term ui.Terminal) error {
|
func runRewrite(ctx context.Context, opts RewriteOptions, gopts global.Options, args []string, term ui.Terminal) error {
|
||||||
if !opts.SnapshotSummary && opts.ExcludePatternOptions.Empty() && opts.Metadata.empty() {
|
if !opts.SnapshotSummary && opts.ExcludePatternOptions.Empty() && opts.Metadata.empty() {
|
||||||
return errors.Fatal("Nothing to do: no excludes provided and no new metadata provided")
|
return errors.Fatal("Nothing to do: no excludes provided and no new metadata provided")
|
||||||
}
|
}
|
||||||
|
|
||||||
printer := ui.NewProgressPrinter(false, gopts.verbosity, term)
|
printer := ui.NewProgressPrinter(false, gopts.Verbosity, term)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
repo *repository.Repository
|
repo *repository.Repository
|
||||||
|
|
|
@ -7,12 +7,13 @@ import (
|
||||||
|
|
||||||
"github.com/restic/restic/internal/data"
|
"github.com/restic/restic/internal/data"
|
||||||
"github.com/restic/restic/internal/filter"
|
"github.com/restic/restic/internal/filter"
|
||||||
|
"github.com/restic/restic/internal/global"
|
||||||
"github.com/restic/restic/internal/restic"
|
"github.com/restic/restic/internal/restic"
|
||||||
rtest "github.com/restic/restic/internal/test"
|
rtest "github.com/restic/restic/internal/test"
|
||||||
"github.com/restic/restic/internal/ui"
|
"github.com/restic/restic/internal/ui"
|
||||||
)
|
)
|
||||||
|
|
||||||
func testRunRewriteExclude(t testing.TB, gopts GlobalOptions, excludes []string, forget bool, metadata snapshotMetadataArgs) {
|
func testRunRewriteExclude(t testing.TB, gopts global.Options, excludes []string, forget bool, metadata snapshotMetadataArgs) {
|
||||||
opts := RewriteOptions{
|
opts := RewriteOptions{
|
||||||
ExcludePatternOptions: filter.ExcludePatternOptions{
|
ExcludePatternOptions: filter.ExcludePatternOptions{
|
||||||
Excludes: excludes,
|
Excludes: excludes,
|
||||||
|
@ -21,8 +22,8 @@ func testRunRewriteExclude(t testing.TB, gopts GlobalOptions, excludes []string,
|
||||||
Metadata: metadata,
|
Metadata: metadata,
|
||||||
}
|
}
|
||||||
|
|
||||||
rtest.OK(t, withTermStatus(t, gopts, func(ctx context.Context, gopts GlobalOptions) error {
|
rtest.OK(t, withTermStatus(t, gopts, func(ctx context.Context, gopts global.Options) error {
|
||||||
return runRewrite(context.TODO(), opts, gopts, nil, gopts.term)
|
return runRewrite(context.TODO(), opts, gopts, nil, gopts.Term)
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -42,8 +43,8 @@ func getSnapshot(t testing.TB, snapshotID restic.ID, env *testEnvironment) *data
|
||||||
t.Helper()
|
t.Helper()
|
||||||
|
|
||||||
var snapshots []*data.Snapshot
|
var snapshots []*data.Snapshot
|
||||||
err := withTermStatus(t, env.gopts, func(ctx context.Context, gopts GlobalOptions) error {
|
err := withTermStatus(t, env.gopts, func(ctx context.Context, gopts global.Options) error {
|
||||||
printer := ui.NewProgressPrinter(gopts.JSON, gopts.verbosity, gopts.term)
|
printer := ui.NewProgressPrinter(gopts.JSON, gopts.Verbosity, gopts.Term)
|
||||||
ctx, repo, unlock, err := openWithReadLock(ctx, gopts, false, printer)
|
ctx, repo, unlock, err := openWithReadLock(ctx, gopts, false, printer)
|
||||||
rtest.OK(t, err)
|
rtest.OK(t, err)
|
||||||
defer unlock()
|
defer unlock()
|
||||||
|
@ -118,8 +119,8 @@ func testRewriteMetadata(t *testing.T, metadata snapshotMetadataArgs) {
|
||||||
testRunRewriteExclude(t, env.gopts, []string{}, true, metadata)
|
testRunRewriteExclude(t, env.gopts, []string{}, true, metadata)
|
||||||
|
|
||||||
var snapshots []*data.Snapshot
|
var snapshots []*data.Snapshot
|
||||||
err := withTermStatus(t, env.gopts, func(ctx context.Context, gopts GlobalOptions) error {
|
err := withTermStatus(t, env.gopts, func(ctx context.Context, gopts global.Options) error {
|
||||||
printer := ui.NewProgressPrinter(gopts.JSON, gopts.verbosity, gopts.term)
|
printer := ui.NewProgressPrinter(gopts.JSON, gopts.Verbosity, gopts.Term)
|
||||||
ctx, repo, unlock, err := openWithReadLock(ctx, gopts, false, printer)
|
ctx, repo, unlock, err := openWithReadLock(ctx, gopts, false, printer)
|
||||||
rtest.OK(t, err)
|
rtest.OK(t, err)
|
||||||
defer unlock()
|
defer unlock()
|
||||||
|
@ -132,7 +133,7 @@ func testRewriteMetadata(t *testing.T, metadata snapshotMetadataArgs) {
|
||||||
newSnapshot := snapshots[0]
|
newSnapshot := snapshots[0]
|
||||||
|
|
||||||
if metadata.Time != "" {
|
if metadata.Time != "" {
|
||||||
rtest.Assert(t, newSnapshot.Time.Format(TimeFormat) == metadata.Time, "New snapshot should have time %s", metadata.Time)
|
rtest.Assert(t, newSnapshot.Time.Format(global.TimeFormat) == metadata.Time, "New snapshot should have time %s", metadata.Time)
|
||||||
}
|
}
|
||||||
|
|
||||||
if metadata.Hostname != "" {
|
if metadata.Hostname != "" {
|
||||||
|
@ -158,16 +159,16 @@ func TestRewriteSnaphotSummary(t *testing.T) {
|
||||||
defer cleanup()
|
defer cleanup()
|
||||||
createBasicRewriteRepo(t, env)
|
createBasicRewriteRepo(t, env)
|
||||||
|
|
||||||
rtest.OK(t, withTermStatus(t, env.gopts, func(ctx context.Context, gopts GlobalOptions) error {
|
rtest.OK(t, withTermStatus(t, env.gopts, func(ctx context.Context, gopts global.Options) error {
|
||||||
return runRewrite(context.TODO(), RewriteOptions{SnapshotSummary: true}, gopts, []string{}, gopts.term)
|
return runRewrite(context.TODO(), RewriteOptions{SnapshotSummary: true}, gopts, []string{}, gopts.Term)
|
||||||
}))
|
}))
|
||||||
// no new snapshot should be created as the snapshot already has a summary
|
// no new snapshot should be created as the snapshot already has a summary
|
||||||
snapshots := testListSnapshots(t, env.gopts, 1)
|
snapshots := testListSnapshots(t, env.gopts, 1)
|
||||||
|
|
||||||
// replace snapshot by one without a summary
|
// replace snapshot by one without a summary
|
||||||
var oldSummary *data.SnapshotSummary
|
var oldSummary *data.SnapshotSummary
|
||||||
err := withTermStatus(t, env.gopts, func(ctx context.Context, gopts GlobalOptions) error {
|
err := withTermStatus(t, env.gopts, func(ctx context.Context, gopts global.Options) error {
|
||||||
printer := ui.NewProgressPrinter(gopts.JSON, gopts.verbosity, gopts.term)
|
printer := ui.NewProgressPrinter(gopts.JSON, gopts.Verbosity, gopts.Term)
|
||||||
_, repo, unlock, err := openWithExclusiveLock(ctx, gopts, false, printer)
|
_, repo, unlock, err := openWithExclusiveLock(ctx, gopts, false, printer)
|
||||||
rtest.OK(t, err)
|
rtest.OK(t, err)
|
||||||
defer unlock()
|
defer unlock()
|
||||||
|
@ -183,8 +184,8 @@ func TestRewriteSnaphotSummary(t *testing.T) {
|
||||||
rtest.OK(t, err)
|
rtest.OK(t, err)
|
||||||
|
|
||||||
// rewrite snapshot and lookup ID of new snapshot
|
// rewrite snapshot and lookup ID of new snapshot
|
||||||
rtest.OK(t, withTermStatus(t, env.gopts, func(ctx context.Context, gopts GlobalOptions) error {
|
rtest.OK(t, withTermStatus(t, env.gopts, func(ctx context.Context, gopts global.Options) error {
|
||||||
return runRewrite(context.TODO(), RewriteOptions{SnapshotSummary: true}, gopts, []string{}, gopts.term)
|
return runRewrite(context.TODO(), RewriteOptions{SnapshotSummary: true}, gopts, []string{}, gopts.Term)
|
||||||
}))
|
}))
|
||||||
newSnapshots := testListSnapshots(t, env.gopts, 2)
|
newSnapshots := testListSnapshots(t, env.gopts, 2)
|
||||||
newSnapshot := restic.NewIDSet(newSnapshots...).Sub(restic.NewIDSet(snapshots...)).List()[0]
|
newSnapshot := restic.NewIDSet(newSnapshots...).Sub(restic.NewIDSet(snapshots...)).List()[0]
|
||||||
|
|
|
@ -8,19 +8,20 @@ import (
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
"github.com/restic/restic/internal/errors"
|
"github.com/restic/restic/internal/errors"
|
||||||
|
"github.com/restic/restic/internal/global"
|
||||||
"github.com/restic/restic/internal/selfupdate"
|
"github.com/restic/restic/internal/selfupdate"
|
||||||
"github.com/restic/restic/internal/ui"
|
"github.com/restic/restic/internal/ui"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"github.com/spf13/pflag"
|
"github.com/spf13/pflag"
|
||||||
)
|
)
|
||||||
|
|
||||||
func registerSelfUpdateCommand(cmd *cobra.Command, globalOptions *GlobalOptions) {
|
func registerSelfUpdateCommand(cmd *cobra.Command, globalOptions *global.Options) {
|
||||||
cmd.AddCommand(
|
cmd.AddCommand(
|
||||||
newSelfUpdateCommand(globalOptions),
|
newSelfUpdateCommand(globalOptions),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
func newSelfUpdateCommand(globalOptions *GlobalOptions) *cobra.Command {
|
func newSelfUpdateCommand(globalOptions *global.Options) *cobra.Command {
|
||||||
var opts SelfUpdateOptions
|
var opts SelfUpdateOptions
|
||||||
|
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
|
@ -43,7 +44,7 @@ Exit status is 12 if the password is incorrect.
|
||||||
`,
|
`,
|
||||||
DisableAutoGenTag: true,
|
DisableAutoGenTag: true,
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
return runSelfUpdate(cmd.Context(), opts, *globalOptions, args, globalOptions.term)
|
return runSelfUpdate(cmd.Context(), opts, *globalOptions, args, globalOptions.Term)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -60,7 +61,7 @@ func (opts *SelfUpdateOptions) AddFlags(f *pflag.FlagSet) {
|
||||||
f.StringVar(&opts.Output, "output", "", "Save the downloaded file as `filename` (default: running binary itself)")
|
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, term ui.Terminal) error {
|
func runSelfUpdate(ctx context.Context, opts SelfUpdateOptions, gopts global.Options, args []string, term ui.Terminal) error {
|
||||||
if opts.Output == "" {
|
if opts.Output == "" {
|
||||||
file, err := os.Executable()
|
file, err := os.Executable()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -86,15 +87,15 @@ func runSelfUpdate(ctx context.Context, opts SelfUpdateOptions, gopts GlobalOpti
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
printer := ui.NewProgressPrinter(false, gopts.verbosity, term)
|
printer := ui.NewProgressPrinter(false, gopts.Verbosity, term)
|
||||||
printer.P("writing restic to %v", opts.Output)
|
printer.P("writing restic to %v", opts.Output)
|
||||||
|
|
||||||
v, err := selfupdate.DownloadLatestStableRelease(ctx, opts.Output, version, printer.P)
|
v, err := selfupdate.DownloadLatestStableRelease(ctx, opts.Output, global.Version, printer.P)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Fatalf("unable to update restic: %v", err)
|
return errors.Fatalf("unable to update restic: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if v != version {
|
if v != global.Version {
|
||||||
printer.S("successfully updated restic to version %v", v)
|
printer.S("successfully updated restic to version %v", v)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,8 +2,11 @@
|
||||||
|
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import "github.com/spf13/cobra"
|
import (
|
||||||
|
"github.com/restic/restic/internal/global"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
func registerSelfUpdateCommand(_ *cobra.Command, _ *GlobalOptions) {
|
func registerSelfUpdateCommand(_ *cobra.Command, _ *global.Options) {
|
||||||
// No commands to register in non-selfupdate mode
|
// No commands to register in non-selfupdate mode
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,6 +9,7 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/restic/restic/internal/data"
|
"github.com/restic/restic/internal/data"
|
||||||
|
"github.com/restic/restic/internal/global"
|
||||||
"github.com/restic/restic/internal/restic"
|
"github.com/restic/restic/internal/restic"
|
||||||
"github.com/restic/restic/internal/ui"
|
"github.com/restic/restic/internal/ui"
|
||||||
"github.com/restic/restic/internal/ui/table"
|
"github.com/restic/restic/internal/ui/table"
|
||||||
|
@ -16,7 +17,7 @@ import (
|
||||||
"github.com/spf13/pflag"
|
"github.com/spf13/pflag"
|
||||||
)
|
)
|
||||||
|
|
||||||
func newSnapshotsCommand(globalOptions *GlobalOptions) *cobra.Command {
|
func newSnapshotsCommand(globalOptions *global.Options) *cobra.Command {
|
||||||
var opts SnapshotOptions
|
var opts SnapshotOptions
|
||||||
|
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
|
@ -38,7 +39,7 @@ Exit status is 12 if the password is incorrect.
|
||||||
DisableAutoGenTag: true,
|
DisableAutoGenTag: true,
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
finalizeSnapshotFilter(&opts.SnapshotFilter)
|
finalizeSnapshotFilter(&opts.SnapshotFilter)
|
||||||
return runSnapshots(cmd.Context(), opts, *globalOptions, args, globalOptions.term)
|
return runSnapshots(cmd.Context(), opts, *globalOptions, args, globalOptions.Term)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -68,8 +69,8 @@ func (opts *SnapshotOptions) AddFlags(f *pflag.FlagSet) {
|
||||||
f.VarP(&opts.GroupBy, "group-by", "g", "`group` snapshots by host, paths and/or tags, separated by comma")
|
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, term ui.Terminal) error {
|
func runSnapshots(ctx context.Context, opts SnapshotOptions, gopts global.Options, args []string, term ui.Terminal) error {
|
||||||
printer := ui.NewProgressPrinter(gopts.JSON, gopts.verbosity, term)
|
printer := ui.NewProgressPrinter(gopts.JSON, gopts.Verbosity, term)
|
||||||
ctx, repo, unlock, err := openWithReadLock(ctx, gopts, gopts.NoLock, printer)
|
ctx, repo, unlock, err := openWithReadLock(ctx, gopts, gopts.NoLock, printer)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -105,7 +106,7 @@ func runSnapshots(ctx context.Context, opts SnapshotOptions, gopts GlobalOptions
|
||||||
}
|
}
|
||||||
|
|
||||||
if gopts.JSON {
|
if gopts.JSON {
|
||||||
err := printSnapshotGroupJSON(gopts.term.OutputWriter(), snapshotGroups, grouped)
|
err := printSnapshotGroupJSON(gopts.Term.OutputWriter(), snapshotGroups, grouped)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
printer.E("error printing snapshots: %v", err)
|
printer.E("error printing snapshots: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -118,12 +119,12 @@ func runSnapshots(ctx context.Context, opts SnapshotOptions, gopts GlobalOptions
|
||||||
}
|
}
|
||||||
|
|
||||||
if grouped {
|
if grouped {
|
||||||
err := PrintSnapshotGroupHeader(gopts.term.OutputWriter(), k)
|
err := PrintSnapshotGroupHeader(gopts.Term.OutputWriter(), k)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
err := PrintSnapshots(gopts.term.OutputWriter(), list, nil, opts.Compact)
|
err := PrintSnapshots(gopts.Term.OutputWriter(), list, nil, opts.Compact)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -242,7 +243,7 @@ func PrintSnapshots(stdout io.Writer, list data.Snapshots, reasons []data.KeepRe
|
||||||
for _, sn := range list {
|
for _, sn := range list {
|
||||||
data := snapshot{
|
data := snapshot{
|
||||||
ID: sn.ID().Str(),
|
ID: sn.ID().Str(),
|
||||||
Timestamp: sn.Time.Local().Format(TimeFormat),
|
Timestamp: sn.Time.Local().Format(global.TimeFormat),
|
||||||
Hostname: sn.Hostname,
|
Hostname: sn.Hostname,
|
||||||
Tags: sn.Tags,
|
Tags: sn.Tags,
|
||||||
Paths: sn.Paths,
|
Paths: sn.Paths,
|
||||||
|
|
|
@ -5,16 +5,17 @@ import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/restic/restic/internal/global"
|
||||||
"github.com/restic/restic/internal/restic"
|
"github.com/restic/restic/internal/restic"
|
||||||
rtest "github.com/restic/restic/internal/test"
|
rtest "github.com/restic/restic/internal/test"
|
||||||
)
|
)
|
||||||
|
|
||||||
func testRunSnapshots(t testing.TB, gopts GlobalOptions) (newest *Snapshot, snapmap map[restic.ID]Snapshot) {
|
func testRunSnapshots(t testing.TB, gopts global.Options) (newest *Snapshot, snapmap map[restic.ID]Snapshot) {
|
||||||
buf, err := withCaptureStdout(t, gopts, func(ctx context.Context, gopts GlobalOptions) error {
|
buf, err := withCaptureStdout(t, gopts, func(ctx context.Context, gopts global.Options) error {
|
||||||
gopts.JSON = true
|
gopts.JSON = true
|
||||||
|
|
||||||
opts := SnapshotOptions{}
|
opts := SnapshotOptions{}
|
||||||
return runSnapshots(ctx, opts, gopts, []string{}, gopts.term)
|
return runSnapshots(ctx, opts, gopts, []string{}, gopts.Term)
|
||||||
})
|
})
|
||||||
rtest.OK(t, err)
|
rtest.OK(t, err)
|
||||||
|
|
||||||
|
|
|
@ -11,6 +11,7 @@ import (
|
||||||
"github.com/restic/chunker"
|
"github.com/restic/chunker"
|
||||||
"github.com/restic/restic/internal/crypto"
|
"github.com/restic/restic/internal/crypto"
|
||||||
"github.com/restic/restic/internal/data"
|
"github.com/restic/restic/internal/data"
|
||||||
|
"github.com/restic/restic/internal/global"
|
||||||
"github.com/restic/restic/internal/repository"
|
"github.com/restic/restic/internal/repository"
|
||||||
"github.com/restic/restic/internal/restic"
|
"github.com/restic/restic/internal/restic"
|
||||||
"github.com/restic/restic/internal/restorer"
|
"github.com/restic/restic/internal/restorer"
|
||||||
|
@ -23,7 +24,7 @@ import (
|
||||||
"github.com/spf13/pflag"
|
"github.com/spf13/pflag"
|
||||||
)
|
)
|
||||||
|
|
||||||
func newStatsCommand(globalOptions *GlobalOptions) *cobra.Command {
|
func newStatsCommand(globalOptions *global.Options) *cobra.Command {
|
||||||
var opts StatsOptions
|
var opts StatsOptions
|
||||||
|
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
|
@ -65,7 +66,7 @@ Exit status is 12 if the password is incorrect.
|
||||||
DisableAutoGenTag: true,
|
DisableAutoGenTag: true,
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
finalizeSnapshotFilter(&opts.SnapshotFilter)
|
finalizeSnapshotFilter(&opts.SnapshotFilter)
|
||||||
return runStats(cmd.Context(), opts, *globalOptions, args, globalOptions.term)
|
return runStats(cmd.Context(), opts, *globalOptions, args, globalOptions.Term)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -95,13 +96,13 @@ func must(err error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func runStats(ctx context.Context, opts StatsOptions, gopts GlobalOptions, args []string, term ui.Terminal) error {
|
func runStats(ctx context.Context, opts StatsOptions, gopts global.Options, args []string, term ui.Terminal) error {
|
||||||
err := verifyStatsInput(opts)
|
err := verifyStatsInput(opts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
printer := ui.NewProgressPrinter(gopts.JSON, gopts.verbosity, term)
|
printer := ui.NewProgressPrinter(gopts.JSON, gopts.Verbosity, term)
|
||||||
|
|
||||||
ctx, repo, unlock, err := openWithReadLock(ctx, gopts, gopts.NoLock, printer)
|
ctx, repo, unlock, err := openWithReadLock(ctx, gopts, gopts.NoLock, printer)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -170,7 +171,7 @@ func runStats(ctx context.Context, opts StatsOptions, gopts GlobalOptions, args
|
||||||
}
|
}
|
||||||
|
|
||||||
if gopts.JSON {
|
if gopts.JSON {
|
||||||
err = json.NewEncoder(gopts.term.OutputWriter()).Encode(stats)
|
err = json.NewEncoder(gopts.Term.OutputWriter()).Encode(stats)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("encoding output: %v", err)
|
return fmt.Errorf("encoding output: %v", err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,12 +9,13 @@ import (
|
||||||
"github.com/restic/restic/internal/data"
|
"github.com/restic/restic/internal/data"
|
||||||
"github.com/restic/restic/internal/debug"
|
"github.com/restic/restic/internal/debug"
|
||||||
"github.com/restic/restic/internal/errors"
|
"github.com/restic/restic/internal/errors"
|
||||||
|
"github.com/restic/restic/internal/global"
|
||||||
"github.com/restic/restic/internal/repository"
|
"github.com/restic/restic/internal/repository"
|
||||||
"github.com/restic/restic/internal/restic"
|
"github.com/restic/restic/internal/restic"
|
||||||
"github.com/restic/restic/internal/ui"
|
"github.com/restic/restic/internal/ui"
|
||||||
)
|
)
|
||||||
|
|
||||||
func newTagCommand(globalOptions *GlobalOptions) *cobra.Command {
|
func newTagCommand(globalOptions *global.Options) *cobra.Command {
|
||||||
var opts TagOptions
|
var opts TagOptions
|
||||||
|
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
|
@ -41,8 +42,7 @@ Exit status is 12 if the password is incorrect.
|
||||||
DisableAutoGenTag: true,
|
DisableAutoGenTag: true,
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
finalizeSnapshotFilter(&opts.SnapshotFilter)
|
finalizeSnapshotFilter(&opts.SnapshotFilter)
|
||||||
finalizeSnapshotFilter(&opts.SnapshotFilter)
|
return runTag(cmd.Context(), opts, *globalOptions, globalOptions.Term, args)
|
||||||
return runTag(cmd.Context(), opts, *globalOptions, globalOptions.term, args)
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -119,8 +119,8 @@ func changeTags(ctx context.Context, repo *repository.Repository, sn *data.Snaps
|
||||||
return changed, nil
|
return changed, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func runTag(ctx context.Context, opts TagOptions, gopts GlobalOptions, term ui.Terminal, args []string) error {
|
func runTag(ctx context.Context, opts TagOptions, gopts global.Options, term ui.Terminal, args []string) error {
|
||||||
printer := ui.NewProgressPrinter(gopts.JSON, gopts.verbosity, term)
|
printer := ui.NewProgressPrinter(gopts.JSON, gopts.Verbosity, term)
|
||||||
|
|
||||||
if len(opts.SetTags) == 0 && len(opts.AddTags) == 0 && len(opts.RemoveTags) == 0 {
|
if len(opts.SetTags) == 0 && len(opts.AddTags) == 0 && len(opts.RemoveTags) == 0 {
|
||||||
return errors.Fatal("nothing to do!")
|
return errors.Fatal("nothing to do!")
|
||||||
|
|
|
@ -5,12 +5,13 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/restic/restic/internal/data"
|
"github.com/restic/restic/internal/data"
|
||||||
|
"github.com/restic/restic/internal/global"
|
||||||
rtest "github.com/restic/restic/internal/test"
|
rtest "github.com/restic/restic/internal/test"
|
||||||
)
|
)
|
||||||
|
|
||||||
func testRunTag(t testing.TB, opts TagOptions, gopts GlobalOptions) {
|
func testRunTag(t testing.TB, opts TagOptions, gopts global.Options) {
|
||||||
rtest.OK(t, withTermStatus(t, gopts, func(ctx context.Context, gopts GlobalOptions) error {
|
rtest.OK(t, withTermStatus(t, gopts, func(ctx context.Context, gopts global.Options) error {
|
||||||
return runTag(context.TODO(), opts, gopts, gopts.term, []string{})
|
return runTag(context.TODO(), opts, gopts, gopts.Term, []string{})
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,13 +3,14 @@ package main
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
|
"github.com/restic/restic/internal/global"
|
||||||
"github.com/restic/restic/internal/repository"
|
"github.com/restic/restic/internal/repository"
|
||||||
"github.com/restic/restic/internal/ui"
|
"github.com/restic/restic/internal/ui"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"github.com/spf13/pflag"
|
"github.com/spf13/pflag"
|
||||||
)
|
)
|
||||||
|
|
||||||
func newUnlockCommand(globalOptions *GlobalOptions) *cobra.Command {
|
func newUnlockCommand(globalOptions *global.Options) *cobra.Command {
|
||||||
var opts UnlockOptions
|
var opts UnlockOptions
|
||||||
|
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
|
@ -27,7 +28,7 @@ Exit status is 1 if there was any error.
|
||||||
GroupID: cmdGroupDefault,
|
GroupID: cmdGroupDefault,
|
||||||
DisableAutoGenTag: true,
|
DisableAutoGenTag: true,
|
||||||
RunE: func(cmd *cobra.Command, _ []string) error {
|
RunE: func(cmd *cobra.Command, _ []string) error {
|
||||||
return runUnlock(cmd.Context(), opts, *globalOptions, globalOptions.term)
|
return runUnlock(cmd.Context(), opts, *globalOptions, globalOptions.Term)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
opts.AddFlags(cmd.Flags())
|
opts.AddFlags(cmd.Flags())
|
||||||
|
@ -43,9 +44,9 @@ func (opts *UnlockOptions) AddFlags(f *pflag.FlagSet) {
|
||||||
f.BoolVar(&opts.RemoveAll, "remove-all", false, "remove all locks, even non-stale ones")
|
f.BoolVar(&opts.RemoveAll, "remove-all", false, "remove all locks, even non-stale ones")
|
||||||
}
|
}
|
||||||
|
|
||||||
func runUnlock(ctx context.Context, opts UnlockOptions, gopts GlobalOptions, term ui.Terminal) error {
|
func runUnlock(ctx context.Context, opts UnlockOptions, gopts global.Options, term ui.Terminal) error {
|
||||||
printer := ui.NewProgressPrinter(gopts.JSON, gopts.verbosity, term)
|
printer := ui.NewProgressPrinter(gopts.JSON, gopts.Verbosity, term)
|
||||||
repo, err := OpenRepository(ctx, gopts, printer)
|
repo, err := global.OpenRepository(ctx, gopts, printer)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,11 +4,12 @@ import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
|
||||||
|
"github.com/restic/restic/internal/global"
|
||||||
"github.com/restic/restic/internal/ui"
|
"github.com/restic/restic/internal/ui"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
func newVersionCommand(globalOptions *GlobalOptions) *cobra.Command {
|
func newVersionCommand(globalOptions *global.Options) *cobra.Command {
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
Use: "version",
|
Use: "version",
|
||||||
Short: "Print version information",
|
Short: "Print version information",
|
||||||
|
@ -24,7 +25,7 @@ Exit status is 1 if there was any error.
|
||||||
`,
|
`,
|
||||||
DisableAutoGenTag: true,
|
DisableAutoGenTag: true,
|
||||||
Run: func(_ *cobra.Command, _ []string) {
|
Run: func(_ *cobra.Command, _ []string) {
|
||||||
printer := ui.NewProgressPrinter(globalOptions.JSON, globalOptions.verbosity, globalOptions.term)
|
printer := ui.NewProgressPrinter(globalOptions.JSON, globalOptions.Verbosity, globalOptions.Term)
|
||||||
|
|
||||||
if globalOptions.JSON {
|
if globalOptions.JSON {
|
||||||
type jsonVersion struct {
|
type jsonVersion struct {
|
||||||
|
@ -37,20 +38,20 @@ Exit status is 1 if there was any error.
|
||||||
|
|
||||||
jsonS := jsonVersion{
|
jsonS := jsonVersion{
|
||||||
MessageType: "version",
|
MessageType: "version",
|
||||||
Version: version,
|
Version: global.Version,
|
||||||
GoVersion: runtime.Version(),
|
GoVersion: runtime.Version(),
|
||||||
GoOS: runtime.GOOS,
|
GoOS: runtime.GOOS,
|
||||||
GoArch: runtime.GOARCH,
|
GoArch: runtime.GOARCH,
|
||||||
}
|
}
|
||||||
|
|
||||||
err := json.NewEncoder(globalOptions.term.OutputWriter()).Encode(jsonS)
|
err := json.NewEncoder(globalOptions.Term.OutputWriter()).Encode(jsonS)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
printer.E("JSON encode failed: %v\n", err)
|
printer.E("JSON encode failed: %v\n", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
printer.S("restic %s compiled with %v on %v/%v\n",
|
printer.S("restic %s compiled with %v on %v/%v\n",
|
||||||
version, runtime.Version(), runtime.GOOS, runtime.GOARCH)
|
global.Version, runtime.Version(), runtime.GOOS, runtime.GOARCH)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,12 +3,14 @@ package main
|
||||||
import (
|
import (
|
||||||
"io"
|
"io"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/restic/restic/internal/global"
|
||||||
)
|
)
|
||||||
|
|
||||||
// TestFlags checks for double defined flags, the commands will panic on
|
// TestFlags checks for double defined flags, the commands will panic on
|
||||||
// ParseFlags() when a shorthand flag is defined twice.
|
// ParseFlags() when a shorthand flag is defined twice.
|
||||||
func TestFlags(t *testing.T) {
|
func TestFlags(t *testing.T) {
|
||||||
for _, cmd := range newRootCommand(&GlobalOptions{}).Commands() {
|
for _, cmd := range newRootCommand(&global.Options{}).Commands() {
|
||||||
t.Run(cmd.Name(), func(t *testing.T) {
|
t.Run(cmd.Name(), func(t *testing.T) {
|
||||||
cmd.Flags().SetOutput(io.Discard)
|
cmd.Flags().SetOutput(io.Discard)
|
||||||
err := cmd.ParseFlags([]string{"--help"})
|
err := cmd.ParseFlags([]string{"--help"})
|
||||||
|
|
|
@ -5,6 +5,7 @@ import (
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/restic/restic/internal/data"
|
"github.com/restic/restic/internal/data"
|
||||||
|
"github.com/restic/restic/internal/global"
|
||||||
"github.com/restic/restic/internal/ui"
|
"github.com/restic/restic/internal/ui"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -43,6 +44,6 @@ func formatNode(path string, n *data.Node, long bool, human bool) string {
|
||||||
|
|
||||||
return fmt.Sprintf("%s %5d %5d %s %s %s%s",
|
return fmt.Sprintf("%s %5d %5d %s %s %s%s",
|
||||||
mode|n.Mode, n.UID, n.GID, size,
|
mode|n.Mode, n.UID, n.GID, size,
|
||||||
n.ModTime.Local().Format(TimeFormat), path,
|
n.ModTime.Local().Format(global.TimeFormat), path,
|
||||||
target)
|
target)
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,9 +14,11 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/restic/restic/internal/backend"
|
"github.com/restic/restic/internal/backend"
|
||||||
|
"github.com/restic/restic/internal/backend/all"
|
||||||
"github.com/restic/restic/internal/backend/retry"
|
"github.com/restic/restic/internal/backend/retry"
|
||||||
"github.com/restic/restic/internal/data"
|
"github.com/restic/restic/internal/data"
|
||||||
"github.com/restic/restic/internal/errors"
|
"github.com/restic/restic/internal/errors"
|
||||||
|
"github.com/restic/restic/internal/global"
|
||||||
"github.com/restic/restic/internal/options"
|
"github.com/restic/restic/internal/options"
|
||||||
"github.com/restic/restic/internal/repository"
|
"github.com/restic/restic/internal/repository"
|
||||||
"github.com/restic/restic/internal/restic"
|
"github.com/restic/restic/internal/restic"
|
||||||
|
@ -168,7 +170,7 @@ func dirStats(t testing.TB, dir string) (stat dirStat) {
|
||||||
|
|
||||||
type testEnvironment struct {
|
type testEnvironment struct {
|
||||||
base, cache, repo, mountpoint, testdata string
|
base, cache, repo, mountpoint, testdata string
|
||||||
gopts GlobalOptions
|
gopts global.Options
|
||||||
}
|
}
|
||||||
|
|
||||||
type logOutputter struct {
|
type logOutputter struct {
|
||||||
|
@ -208,17 +210,17 @@ func withTestEnvironment(t testing.TB) (env *testEnvironment, cleanup func()) {
|
||||||
rtest.OK(t, os.MkdirAll(env.cache, 0700))
|
rtest.OK(t, os.MkdirAll(env.cache, 0700))
|
||||||
rtest.OK(t, os.MkdirAll(env.repo, 0700))
|
rtest.OK(t, os.MkdirAll(env.repo, 0700))
|
||||||
|
|
||||||
env.gopts = GlobalOptions{
|
env.gopts = global.Options{
|
||||||
Repo: env.repo,
|
Repo: env.repo,
|
||||||
Quiet: true,
|
Quiet: true,
|
||||||
CacheDir: env.cache,
|
CacheDir: env.cache,
|
||||||
password: rtest.TestPassword,
|
Password: rtest.TestPassword,
|
||||||
extended: make(options.Options),
|
Extended: make(options.Options),
|
||||||
|
|
||||||
// replace this hook with "nil" if listing a filetype more than once is necessary
|
// replace this hook with "nil" if listing a filetype more than once is necessary
|
||||||
backendTestHook: func(r backend.Backend) (backend.Backend, error) { return newOrderedListOnceBackend(r), nil },
|
BackendTestHook: func(r backend.Backend) (backend.Backend, error) { return newOrderedListOnceBackend(r), nil },
|
||||||
// start with default set of backends
|
// start with default set of backends
|
||||||
backends: collectBackends(),
|
Backends: all.Backends(),
|
||||||
}
|
}
|
||||||
|
|
||||||
cleanup = func() {
|
cleanup = func() {
|
||||||
|
@ -239,10 +241,10 @@ func testSetupBackupData(t testing.TB, env *testEnvironment) string {
|
||||||
return datafile
|
return datafile
|
||||||
}
|
}
|
||||||
|
|
||||||
func listPacks(gopts GlobalOptions, t *testing.T) restic.IDSet {
|
func listPacks(gopts global.Options, t *testing.T) restic.IDSet {
|
||||||
var packs restic.IDSet
|
var packs restic.IDSet
|
||||||
err := withTermStatus(t, gopts, func(ctx context.Context, gopts GlobalOptions) error {
|
err := withTermStatus(t, gopts, func(ctx context.Context, gopts global.Options) error {
|
||||||
printer := ui.NewProgressPrinter(gopts.JSON, gopts.verbosity, gopts.term)
|
printer := ui.NewProgressPrinter(gopts.JSON, gopts.Verbosity, gopts.Term)
|
||||||
ctx, r, unlock, err := openWithReadLock(ctx, gopts, false, printer)
|
ctx, r, unlock, err := openWithReadLock(ctx, gopts, false, printer)
|
||||||
rtest.OK(t, err)
|
rtest.OK(t, err)
|
||||||
defer unlock()
|
defer unlock()
|
||||||
|
@ -258,10 +260,10 @@ func listPacks(gopts GlobalOptions, t *testing.T) restic.IDSet {
|
||||||
return packs
|
return packs
|
||||||
}
|
}
|
||||||
|
|
||||||
func listTreePacks(gopts GlobalOptions, t *testing.T) restic.IDSet {
|
func listTreePacks(gopts global.Options, t *testing.T) restic.IDSet {
|
||||||
var treePacks restic.IDSet
|
var treePacks restic.IDSet
|
||||||
err := withTermStatus(t, gopts, func(ctx context.Context, gopts GlobalOptions) error {
|
err := withTermStatus(t, gopts, func(ctx context.Context, gopts global.Options) error {
|
||||||
printer := ui.NewProgressPrinter(gopts.JSON, gopts.verbosity, gopts.term)
|
printer := ui.NewProgressPrinter(gopts.JSON, gopts.Verbosity, gopts.Term)
|
||||||
ctx, r, unlock, err := openWithReadLock(ctx, gopts, false, printer)
|
ctx, r, unlock, err := openWithReadLock(ctx, gopts, false, printer)
|
||||||
rtest.OK(t, err)
|
rtest.OK(t, err)
|
||||||
defer unlock()
|
defer unlock()
|
||||||
|
@ -278,9 +280,9 @@ func listTreePacks(gopts GlobalOptions, t *testing.T) restic.IDSet {
|
||||||
return treePacks
|
return treePacks
|
||||||
}
|
}
|
||||||
|
|
||||||
func captureBackend(gopts *GlobalOptions) func() backend.Backend {
|
func captureBackend(gopts *global.Options) func() backend.Backend {
|
||||||
var be backend.Backend
|
var be backend.Backend
|
||||||
gopts.backendTestHook = func(r backend.Backend) (backend.Backend, error) {
|
gopts.BackendTestHook = func(r backend.Backend) (backend.Backend, error) {
|
||||||
be = r
|
be = r
|
||||||
return r, nil
|
return r, nil
|
||||||
}
|
}
|
||||||
|
@ -289,10 +291,10 @@ func captureBackend(gopts *GlobalOptions) func() backend.Backend {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func removePacks(gopts GlobalOptions, t testing.TB, remove restic.IDSet) {
|
func removePacks(gopts global.Options, t testing.TB, remove restic.IDSet) {
|
||||||
be := captureBackend(&gopts)
|
be := captureBackend(&gopts)
|
||||||
err := withTermStatus(t, gopts, func(ctx context.Context, gopts GlobalOptions) error {
|
err := withTermStatus(t, gopts, func(ctx context.Context, gopts global.Options) error {
|
||||||
printer := ui.NewProgressPrinter(gopts.JSON, gopts.verbosity, gopts.term)
|
printer := ui.NewProgressPrinter(gopts.JSON, gopts.Verbosity, gopts.Term)
|
||||||
ctx, _, unlock, err := openWithExclusiveLock(ctx, gopts, false, printer)
|
ctx, _, unlock, err := openWithExclusiveLock(ctx, gopts, false, printer)
|
||||||
rtest.OK(t, err)
|
rtest.OK(t, err)
|
||||||
defer unlock()
|
defer unlock()
|
||||||
|
@ -305,10 +307,10 @@ func removePacks(gopts GlobalOptions, t testing.TB, remove restic.IDSet) {
|
||||||
rtest.OK(t, err)
|
rtest.OK(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func removePacksExcept(gopts GlobalOptions, t testing.TB, keep restic.IDSet, removeTreePacks bool) {
|
func removePacksExcept(gopts global.Options, t testing.TB, keep restic.IDSet, removeTreePacks bool) {
|
||||||
be := captureBackend(&gopts)
|
be := captureBackend(&gopts)
|
||||||
err := withTermStatus(t, gopts, func(ctx context.Context, gopts GlobalOptions) error {
|
err := withTermStatus(t, gopts, func(ctx context.Context, gopts global.Options) error {
|
||||||
printer := ui.NewProgressPrinter(gopts.JSON, gopts.verbosity, gopts.term)
|
printer := ui.NewProgressPrinter(gopts.JSON, gopts.Verbosity, gopts.Term)
|
||||||
ctx, r, unlock, err := openWithExclusiveLock(ctx, gopts, false, printer)
|
ctx, r, unlock, err := openWithExclusiveLock(ctx, gopts, false, printer)
|
||||||
rtest.OK(t, err)
|
rtest.OK(t, err)
|
||||||
defer unlock()
|
defer unlock()
|
||||||
|
@ -344,7 +346,7 @@ func includes(haystack []string, needle string) bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func loadSnapshotMap(t testing.TB, gopts GlobalOptions) map[string]struct{} {
|
func loadSnapshotMap(t testing.TB, gopts global.Options) map[string]struct{} {
|
||||||
snapshotIDs := testRunList(t, gopts, "snapshots")
|
snapshotIDs := testRunList(t, gopts, "snapshots")
|
||||||
|
|
||||||
m := make(map[string]struct{})
|
m := make(map[string]struct{})
|
||||||
|
@ -366,10 +368,10 @@ func lastSnapshot(old, new map[string]struct{}) (map[string]struct{}, string) {
|
||||||
return old, ""
|
return old, ""
|
||||||
}
|
}
|
||||||
|
|
||||||
func testLoadSnapshot(t testing.TB, gopts GlobalOptions, id restic.ID) *data.Snapshot {
|
func testLoadSnapshot(t testing.TB, gopts global.Options, id restic.ID) *data.Snapshot {
|
||||||
var snapshot *data.Snapshot
|
var snapshot *data.Snapshot
|
||||||
err := withTermStatus(t, gopts, func(ctx context.Context, gopts GlobalOptions) error {
|
err := withTermStatus(t, gopts, func(ctx context.Context, gopts global.Options) error {
|
||||||
printer := ui.NewProgressPrinter(gopts.JSON, gopts.verbosity, gopts.term)
|
printer := ui.NewProgressPrinter(gopts.JSON, gopts.Verbosity, gopts.Term)
|
||||||
_, repo, unlock, err := openWithReadLock(ctx, gopts, false, printer)
|
_, repo, unlock, err := openWithReadLock(ctx, gopts, false, printer)
|
||||||
rtest.OK(t, err)
|
rtest.OK(t, err)
|
||||||
defer unlock()
|
defer unlock()
|
||||||
|
@ -413,25 +415,25 @@ func testFileSize(filename string, size int64) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func withCaptureStdout(t testing.TB, gopts GlobalOptions, callback func(ctx context.Context, gopts GlobalOptions) error) (*bytes.Buffer, error) {
|
func withCaptureStdout(t testing.TB, gopts global.Options, callback func(ctx context.Context, gopts global.Options) error) (*bytes.Buffer, error) {
|
||||||
buf := bytes.NewBuffer(nil)
|
buf := bytes.NewBuffer(nil)
|
||||||
err := withTermStatusRaw(os.Stdin, buf, &logOutputter{t: t}, gopts, callback)
|
err := withTermStatusRaw(os.Stdin, buf, &logOutputter{t: t}, gopts, callback)
|
||||||
return buf, err
|
return buf, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func withTermStatus(t testing.TB, gopts GlobalOptions, callback func(ctx context.Context, gopts GlobalOptions) error) error {
|
func withTermStatus(t testing.TB, gopts global.Options, callback func(ctx context.Context, gopts global.Options) error) error {
|
||||||
// stdout and stderr are written to by printer functions etc. That is the written data
|
// stdout and stderr are written to by printer functions etc. That is the written data
|
||||||
// usually consists of one or multiple lines and therefore can be handled well
|
// usually consists of one or multiple lines and therefore can be handled well
|
||||||
// by t.Log.
|
// by t.Log.
|
||||||
return withTermStatusRaw(os.Stdin, &logOutputter{t: t}, &logOutputter{t: t}, gopts, callback)
|
return withTermStatusRaw(os.Stdin, &logOutputter{t: t}, &logOutputter{t: t}, gopts, callback)
|
||||||
}
|
}
|
||||||
|
|
||||||
func withTermStatusRaw(stdin io.ReadCloser, stdout, stderr io.Writer, gopts GlobalOptions, callback func(ctx context.Context, gopts GlobalOptions) error) error {
|
func withTermStatusRaw(stdin io.ReadCloser, stdout, stderr io.Writer, gopts global.Options, callback func(ctx context.Context, gopts global.Options) error) error {
|
||||||
ctx, cancel := context.WithCancel(context.TODO())
|
ctx, cancel := context.WithCancel(context.TODO())
|
||||||
var wg sync.WaitGroup
|
var wg sync.WaitGroup
|
||||||
|
|
||||||
term := termstatus.New(stdin, stdout, stderr, gopts.Quiet)
|
term := termstatus.New(stdin, stdout, stderr, gopts.Quiet)
|
||||||
gopts.term = term
|
gopts.Term = term
|
||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
go func() {
|
go func() {
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
|
|
|
@ -11,6 +11,7 @@ import (
|
||||||
"github.com/restic/restic/internal/backend"
|
"github.com/restic/restic/internal/backend"
|
||||||
"github.com/restic/restic/internal/data"
|
"github.com/restic/restic/internal/data"
|
||||||
"github.com/restic/restic/internal/errors"
|
"github.com/restic/restic/internal/errors"
|
||||||
|
"github.com/restic/restic/internal/global"
|
||||||
"github.com/restic/restic/internal/restic"
|
"github.com/restic/restic/internal/restic"
|
||||||
rtest "github.com/restic/restic/internal/test"
|
rtest "github.com/restic/restic/internal/test"
|
||||||
"github.com/restic/restic/internal/ui"
|
"github.com/restic/restic/internal/ui"
|
||||||
|
@ -80,7 +81,7 @@ func TestListOnce(t *testing.T) {
|
||||||
env, cleanup := withTestEnvironment(t)
|
env, cleanup := withTestEnvironment(t)
|
||||||
defer cleanup()
|
defer cleanup()
|
||||||
|
|
||||||
env.gopts.backendTestHook = func(r backend.Backend) (backend.Backend, error) {
|
env.gopts.BackendTestHook = func(r backend.Backend) (backend.Backend, error) {
|
||||||
return newOrderedListOnceBackend(r), nil
|
return newOrderedListOnceBackend(r), nil
|
||||||
}
|
}
|
||||||
pruneOpts := PruneOptions{MaxUnused: "0"}
|
pruneOpts := PruneOptions{MaxUnused: "0"}
|
||||||
|
@ -88,15 +89,15 @@ func TestListOnce(t *testing.T) {
|
||||||
|
|
||||||
createPrunableRepo(t, env)
|
createPrunableRepo(t, env)
|
||||||
testRunPrune(t, env.gopts, pruneOpts)
|
testRunPrune(t, env.gopts, pruneOpts)
|
||||||
rtest.OK(t, withTermStatus(t, env.gopts, func(ctx context.Context, gopts GlobalOptions) error {
|
rtest.OK(t, withTermStatus(t, env.gopts, func(ctx context.Context, gopts global.Options) error {
|
||||||
_, err := runCheck(context.TODO(), checkOpts, gopts, nil, gopts.term)
|
_, err := runCheck(context.TODO(), checkOpts, gopts, nil, gopts.Term)
|
||||||
return err
|
return err
|
||||||
}))
|
}))
|
||||||
rtest.OK(t, withTermStatus(t, env.gopts, func(ctx context.Context, gopts GlobalOptions) error {
|
rtest.OK(t, withTermStatus(t, env.gopts, func(ctx context.Context, gopts global.Options) error {
|
||||||
return runRebuildIndex(context.TODO(), RepairIndexOptions{}, gopts, gopts.term)
|
return runRebuildIndex(context.TODO(), RepairIndexOptions{}, gopts, gopts.Term)
|
||||||
}))
|
}))
|
||||||
rtest.OK(t, withTermStatus(t, env.gopts, func(ctx context.Context, gopts GlobalOptions) error {
|
rtest.OK(t, withTermStatus(t, env.gopts, func(ctx context.Context, gopts global.Options) error {
|
||||||
return runRebuildIndex(context.TODO(), RepairIndexOptions{ReadAllPacks: true}, gopts, gopts.term)
|
return runRebuildIndex(context.TODO(), RepairIndexOptions{ReadAllPacks: true}, gopts, gopts.Term)
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -129,7 +130,7 @@ func TestBackendLoadWriteTo(t *testing.T) {
|
||||||
defer cleanup()
|
defer cleanup()
|
||||||
|
|
||||||
// setup backend which only works if it's WriteTo method is correctly propagated upwards
|
// setup backend which only works if it's WriteTo method is correctly propagated upwards
|
||||||
env.gopts.backendInnerTestHook = func(r backend.Backend) (backend.Backend, error) {
|
env.gopts.BackendInnerTestHook = func(r backend.Backend) (backend.Backend, error) {
|
||||||
return &onlyLoadWithWriteToBackend{Backend: r}, nil
|
return &onlyLoadWithWriteToBackend{Backend: r}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -149,7 +150,7 @@ func TestFindListOnce(t *testing.T) {
|
||||||
env, cleanup := withTestEnvironment(t)
|
env, cleanup := withTestEnvironment(t)
|
||||||
defer cleanup()
|
defer cleanup()
|
||||||
|
|
||||||
env.gopts.backendTestHook = func(r backend.Backend) (backend.Backend, error) {
|
env.gopts.BackendTestHook = func(r backend.Backend) (backend.Backend, error) {
|
||||||
return newOrderedListOnceBackend(r), nil
|
return newOrderedListOnceBackend(r), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -163,8 +164,8 @@ func TestFindListOnce(t *testing.T) {
|
||||||
thirdSnapshot := restic.NewIDSet(testListSnapshots(t, env.gopts, 3)...)
|
thirdSnapshot := restic.NewIDSet(testListSnapshots(t, env.gopts, 3)...)
|
||||||
|
|
||||||
var snapshotIDs restic.IDSet
|
var snapshotIDs restic.IDSet
|
||||||
rtest.OK(t, withTermStatus(t, env.gopts, func(ctx context.Context, gopts GlobalOptions) error {
|
rtest.OK(t, withTermStatus(t, env.gopts, func(ctx context.Context, gopts global.Options) error {
|
||||||
printer := ui.NewProgressPrinter(gopts.JSON, gopts.verbosity, gopts.term)
|
printer := ui.NewProgressPrinter(gopts.JSON, gopts.Verbosity, gopts.Term)
|
||||||
ctx, repo, unlock, err := openWithReadLock(ctx, gopts, false, printer)
|
ctx, repo, unlock, err := openWithReadLock(ctx, gopts, false, printer)
|
||||||
rtest.OK(t, err)
|
rtest.OK(t, err)
|
||||||
defer unlock()
|
defer unlock()
|
||||||
|
@ -214,7 +215,7 @@ func TestBackendRetryConfig(t *testing.T) {
|
||||||
|
|
||||||
var wrappedBackend *failConfigOnceBackend
|
var wrappedBackend *failConfigOnceBackend
|
||||||
// cause config loading to fail once
|
// cause config loading to fail once
|
||||||
env.gopts.backendInnerTestHook = func(r backend.Backend) (backend.Backend, error) {
|
env.gopts.BackendInnerTestHook = func(r backend.Backend) (backend.Backend, error) {
|
||||||
wrappedBackend = &failConfigOnceBackend{Backend: r}
|
wrappedBackend = &failConfigOnceBackend{Backend: r}
|
||||||
return wrappedBackend, nil
|
return wrappedBackend, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,12 +3,13 @@ package main
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
|
"github.com/restic/restic/internal/global"
|
||||||
"github.com/restic/restic/internal/repository"
|
"github.com/restic/restic/internal/repository"
|
||||||
"github.com/restic/restic/internal/ui/progress"
|
"github.com/restic/restic/internal/ui/progress"
|
||||||
)
|
)
|
||||||
|
|
||||||
func internalOpenWithLocked(ctx context.Context, gopts GlobalOptions, dryRun bool, exclusive bool, printer progress.Printer) (context.Context, *repository.Repository, func(), error) {
|
func internalOpenWithLocked(ctx context.Context, gopts global.Options, dryRun bool, exclusive bool, printer progress.Printer) (context.Context, *repository.Repository, func(), error) {
|
||||||
repo, err := OpenRepository(ctx, gopts, printer)
|
repo, err := global.OpenRepository(ctx, gopts, printer)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, nil, err
|
return nil, nil, nil, err
|
||||||
}
|
}
|
||||||
|
@ -34,16 +35,16 @@ func internalOpenWithLocked(ctx context.Context, gopts GlobalOptions, dryRun boo
|
||||||
return ctx, repo, unlock, nil
|
return ctx, repo, unlock, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func openWithReadLock(ctx context.Context, gopts GlobalOptions, noLock bool, printer progress.Printer) (context.Context, *repository.Repository, func(), error) {
|
func openWithReadLock(ctx context.Context, gopts global.Options, noLock bool, printer progress.Printer) (context.Context, *repository.Repository, func(), error) {
|
||||||
// TODO enforce read-only operations once the locking code has moved to the repository
|
// TODO enforce read-only operations once the locking code has moved to the repository
|
||||||
return internalOpenWithLocked(ctx, gopts, noLock, false, printer)
|
return internalOpenWithLocked(ctx, gopts, noLock, false, printer)
|
||||||
}
|
}
|
||||||
|
|
||||||
func openWithAppendLock(ctx context.Context, gopts GlobalOptions, dryRun bool, printer progress.Printer) (context.Context, *repository.Repository, func(), error) {
|
func openWithAppendLock(ctx context.Context, gopts global.Options, dryRun bool, printer progress.Printer) (context.Context, *repository.Repository, func(), error) {
|
||||||
// TODO enforce non-exclusive operations once the locking code has moved to the repository
|
// TODO enforce non-exclusive operations once the locking code has moved to the repository
|
||||||
return internalOpenWithLocked(ctx, gopts, dryRun, false, printer)
|
return internalOpenWithLocked(ctx, gopts, dryRun, false, printer)
|
||||||
}
|
}
|
||||||
|
|
||||||
func openWithExclusiveLock(ctx context.Context, gopts GlobalOptions, dryRun bool, printer progress.Printer) (context.Context, *repository.Repository, func(), error) {
|
func openWithExclusiveLock(ctx context.Context, gopts global.Options, dryRun bool, printer progress.Printer) (context.Context, *repository.Repository, func(), error) {
|
||||||
return internalOpenWithLocked(ctx, gopts, dryRun, true, printer)
|
return internalOpenWithLocked(ctx, gopts, dryRun, true, printer)
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,9 +14,11 @@ import (
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"go.uber.org/automaxprocs/maxprocs"
|
"go.uber.org/automaxprocs/maxprocs"
|
||||||
|
|
||||||
|
"github.com/restic/restic/internal/backend/all"
|
||||||
"github.com/restic/restic/internal/debug"
|
"github.com/restic/restic/internal/debug"
|
||||||
"github.com/restic/restic/internal/errors"
|
"github.com/restic/restic/internal/errors"
|
||||||
"github.com/restic/restic/internal/feature"
|
"github.com/restic/restic/internal/feature"
|
||||||
|
"github.com/restic/restic/internal/global"
|
||||||
"github.com/restic/restic/internal/repository"
|
"github.com/restic/restic/internal/repository"
|
||||||
"github.com/restic/restic/internal/restic"
|
"github.com/restic/restic/internal/restic"
|
||||||
"github.com/restic/restic/internal/ui/termstatus"
|
"github.com/restic/restic/internal/ui/termstatus"
|
||||||
|
@ -32,7 +34,7 @@ var ErrOK = errors.New("ok")
|
||||||
var cmdGroupDefault = "default"
|
var cmdGroupDefault = "default"
|
||||||
var cmdGroupAdvanced = "advanced"
|
var cmdGroupAdvanced = "advanced"
|
||||||
|
|
||||||
func newRootCommand(globalOptions *GlobalOptions) *cobra.Command {
|
func newRootCommand(globalOptions *global.Options) *cobra.Command {
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
Use: "restic",
|
Use: "restic",
|
||||||
Short: "Backup and restore files",
|
Short: "Backup and restore files",
|
||||||
|
@ -102,7 +104,7 @@ The full documentation can be found at https://restic.readthedocs.io/ .
|
||||||
registerDebugCommand(cmd, globalOptions)
|
registerDebugCommand(cmd, globalOptions)
|
||||||
registerMountCommand(cmd, globalOptions)
|
registerMountCommand(cmd, globalOptions)
|
||||||
registerSelfUpdateCommand(cmd, globalOptions)
|
registerSelfUpdateCommand(cmd, globalOptions)
|
||||||
registerProfiling(cmd, os.Stderr)
|
global.RegisterProfiling(cmd, os.Stderr)
|
||||||
|
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
@ -127,7 +129,7 @@ func tweakGoGC() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func printExitError(globalOptions GlobalOptions, code int, message string) {
|
func printExitError(globalOptions global.Options, code int, message string) {
|
||||||
if globalOptions.JSON {
|
if globalOptions.JSON {
|
||||||
type jsonExitError struct {
|
type jsonExitError struct {
|
||||||
MessageType string `json:"message_type"` // exit_error
|
MessageType string `json:"message_type"` // exit_error
|
||||||
|
@ -170,15 +172,15 @@ func main() {
|
||||||
|
|
||||||
debug.Log("main %#v", os.Args)
|
debug.Log("main %#v", os.Args)
|
||||||
debug.Log("restic %s compiled with %v on %v/%v",
|
debug.Log("restic %s compiled with %v on %v/%v",
|
||||||
version, runtime.Version(), runtime.GOOS, runtime.GOARCH)
|
global.Version, runtime.Version(), runtime.GOOS, runtime.GOARCH)
|
||||||
|
|
||||||
globalOptions := GlobalOptions{
|
globalOptions := global.Options{
|
||||||
backends: collectBackends(),
|
Backends: all.Backends(),
|
||||||
}
|
}
|
||||||
func() {
|
func() {
|
||||||
term, cancel := termstatus.Setup(os.Stdin, os.Stdout, os.Stderr, globalOptions.Quiet)
|
term, cancel := termstatus.Setup(os.Stdin, os.Stdout, os.Stderr, globalOptions.Quiet)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
globalOptions.term = term
|
globalOptions.Term = term
|
||||||
ctx := createGlobalContext(os.Stderr)
|
ctx := createGlobalContext(os.Stderr)
|
||||||
err = newRootCommand(&globalOptions).ExecuteContext(ctx)
|
err = newRootCommand(&globalOptions).ExecuteContext(ctx)
|
||||||
switch err {
|
switch err {
|
||||||
|
@ -220,7 +222,7 @@ func main() {
|
||||||
exitCode = 3
|
exitCode = 3
|
||||||
case errors.Is(err, ErrFailedToRemoveOneOrMoreSnapshots):
|
case errors.Is(err, ErrFailedToRemoveOneOrMoreSnapshots):
|
||||||
exitCode = 3
|
exitCode = 3
|
||||||
case errors.Is(err, ErrNoRepository):
|
case errors.Is(err, global.ErrNoRepository):
|
||||||
exitCode = 10
|
exitCode = 10
|
||||||
case restic.IsAlreadyLocked(err):
|
case restic.IsAlreadyLocked(err):
|
||||||
exitCode = 11
|
exitCode = 11
|
||||||
|
|
|
@ -308,9 +308,9 @@ func generateFiles() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var versionPattern = `var version = ".*"`
|
var versionPattern = `const Version = ".*"`
|
||||||
|
|
||||||
const versionCodeFile = "cmd/restic/global.go"
|
const versionCodeFile = "internal/global/global.go"
|
||||||
|
|
||||||
func updateVersion() {
|
func updateVersion() {
|
||||||
err := os.WriteFile("VERSION", []byte(opts.Version+"\n"), 0644)
|
err := os.WriteFile("VERSION", []byte(opts.Version+"\n"), 0644)
|
||||||
|
@ -318,7 +318,7 @@ func updateVersion() {
|
||||||
die("unable to write version to file: %v", err)
|
die("unable to write version to file: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
newVersion := fmt.Sprintf("var version = %q", opts.Version)
|
newVersion := fmt.Sprintf("const Version = %q", opts.Version)
|
||||||
replace(versionCodeFile, versionPattern, newVersion)
|
replace(versionCodeFile, versionPattern, newVersion)
|
||||||
|
|
||||||
if len(uncommittedChanges("VERSION")) > 0 || len(uncommittedChanges(versionCodeFile)) > 0 {
|
if len(uncommittedChanges("VERSION")) > 0 || len(uncommittedChanges(versionCodeFile)) > 0 {
|
||||||
|
|
28
internal/backend/all/all.go
Normal file
28
internal/backend/all/all.go
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
package all
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/restic/restic/internal/backend/azure"
|
||||||
|
"github.com/restic/restic/internal/backend/b2"
|
||||||
|
"github.com/restic/restic/internal/backend/gs"
|
||||||
|
"github.com/restic/restic/internal/backend/local"
|
||||||
|
"github.com/restic/restic/internal/backend/location"
|
||||||
|
"github.com/restic/restic/internal/backend/rclone"
|
||||||
|
"github.com/restic/restic/internal/backend/rest"
|
||||||
|
"github.com/restic/restic/internal/backend/s3"
|
||||||
|
"github.com/restic/restic/internal/backend/sftp"
|
||||||
|
"github.com/restic/restic/internal/backend/swift"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Backends() *location.Registry {
|
||||||
|
backends := location.NewRegistry()
|
||||||
|
backends.Register(azure.NewFactory())
|
||||||
|
backends.Register(b2.NewFactory())
|
||||||
|
backends.Register(gs.NewFactory())
|
||||||
|
backends.Register(local.NewFactory())
|
||||||
|
backends.Register(rclone.NewFactory())
|
||||||
|
backends.Register(rest.NewFactory())
|
||||||
|
backends.Register(s3.NewFactory())
|
||||||
|
backends.Register(sftp.NewFactory())
|
||||||
|
backends.Register(swift.NewFactory())
|
||||||
|
return backends
|
||||||
|
}
|
|
@ -1,8 +1,9 @@
|
||||||
package main
|
package global
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
@ -10,22 +11,14 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/restic/chunker"
|
||||||
"github.com/restic/restic/internal/backend"
|
"github.com/restic/restic/internal/backend"
|
||||||
"github.com/restic/restic/internal/backend/azure"
|
|
||||||
"github.com/restic/restic/internal/backend/b2"
|
|
||||||
"github.com/restic/restic/internal/backend/cache"
|
"github.com/restic/restic/internal/backend/cache"
|
||||||
"github.com/restic/restic/internal/backend/gs"
|
|
||||||
"github.com/restic/restic/internal/backend/limiter"
|
"github.com/restic/restic/internal/backend/limiter"
|
||||||
"github.com/restic/restic/internal/backend/local"
|
|
||||||
"github.com/restic/restic/internal/backend/location"
|
"github.com/restic/restic/internal/backend/location"
|
||||||
"github.com/restic/restic/internal/backend/logger"
|
"github.com/restic/restic/internal/backend/logger"
|
||||||
"github.com/restic/restic/internal/backend/rclone"
|
|
||||||
"github.com/restic/restic/internal/backend/rest"
|
|
||||||
"github.com/restic/restic/internal/backend/retry"
|
"github.com/restic/restic/internal/backend/retry"
|
||||||
"github.com/restic/restic/internal/backend/s3"
|
|
||||||
"github.com/restic/restic/internal/backend/sema"
|
"github.com/restic/restic/internal/backend/sema"
|
||||||
"github.com/restic/restic/internal/backend/sftp"
|
|
||||||
"github.com/restic/restic/internal/backend/swift"
|
|
||||||
"github.com/restic/restic/internal/debug"
|
"github.com/restic/restic/internal/debug"
|
||||||
"github.com/restic/restic/internal/options"
|
"github.com/restic/restic/internal/options"
|
||||||
"github.com/restic/restic/internal/repository"
|
"github.com/restic/restic/internal/repository"
|
||||||
|
@ -42,15 +35,15 @@ import (
|
||||||
// to a missing backend storage location or config file
|
// to a missing backend storage location or config file
|
||||||
var ErrNoRepository = errors.New("repository does not exist")
|
var ErrNoRepository = errors.New("repository does not exist")
|
||||||
|
|
||||||
var version = "0.18.1-dev (compiled manually)"
|
const Version = "0.18.1-dev (compiled manually)"
|
||||||
|
|
||||||
// TimeFormat is the format used for all timestamps printed by restic.
|
// TimeFormat is the format used for all timestamps printed by restic.
|
||||||
const TimeFormat = "2006-01-02 15:04:05"
|
const TimeFormat = "2006-01-02 15:04:05"
|
||||||
|
|
||||||
type backendWrapper func(r backend.Backend) (backend.Backend, error)
|
type BackendWrapper func(r backend.Backend) (backend.Backend, error)
|
||||||
|
|
||||||
// GlobalOptions hold all global options for restic.
|
// Options hold all global options for restic.
|
||||||
type GlobalOptions struct {
|
type Options struct {
|
||||||
Repo string
|
Repo string
|
||||||
RepositoryFile string
|
RepositoryFile string
|
||||||
PasswordFile string
|
PasswordFile string
|
||||||
|
@ -72,25 +65,25 @@ type GlobalOptions struct {
|
||||||
backend.TransportOptions
|
backend.TransportOptions
|
||||||
limiter.Limits
|
limiter.Limits
|
||||||
|
|
||||||
password string
|
Password string
|
||||||
term ui.Terminal
|
Term ui.Terminal
|
||||||
|
|
||||||
backends *location.Registry
|
Backends *location.Registry
|
||||||
backendTestHook, backendInnerTestHook backendWrapper
|
BackendTestHook, BackendInnerTestHook BackendWrapper
|
||||||
|
|
||||||
// verbosity is set as follows:
|
// Verbosity is set as follows:
|
||||||
// 0 means: don't print any messages except errors, this is used when --quiet is specified
|
// 0 means: don't print any messages except errors, this is used when --quiet is specified
|
||||||
// 1 is the default: print essential messages
|
// 1 is the default: print essential messages
|
||||||
// 2 means: print more messages, report minor things, this is used when --verbose is specified
|
// 2 means: print more messages, report minor things, this is used when --verbose is specified
|
||||||
// 3 means: print very detailed debug messages, this is used when --verbose=2 is specified
|
// 3 means: print very detailed debug messages, this is used when --verbose=2 is specified
|
||||||
verbosity uint
|
Verbosity uint
|
||||||
|
|
||||||
Options []string
|
Options []string
|
||||||
|
|
||||||
extended options.Options
|
Extended options.Options
|
||||||
}
|
}
|
||||||
|
|
||||||
func (opts *GlobalOptions) AddFlags(f *pflag.FlagSet) {
|
func (opts *Options) AddFlags(f *pflag.FlagSet) {
|
||||||
f.StringVarP(&opts.Repo, "repo", "r", "", "`repository` to backup to or restore from (default: $RESTIC_REPOSITORY)")
|
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.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.PasswordFile, "password-file", "p", "", "`file` to read the repository password from (default: $RESTIC_PASSWORD_FILE)")
|
||||||
|
@ -141,20 +134,20 @@ func (opts *GlobalOptions) AddFlags(f *pflag.FlagSet) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (opts *GlobalOptions) PreRun(needsPassword bool) error {
|
func (opts *Options) PreRun(needsPassword bool) error {
|
||||||
// set verbosity, default is one
|
// set verbosity, default is one
|
||||||
opts.verbosity = 1
|
opts.Verbosity = 1
|
||||||
if opts.Quiet && opts.Verbose > 0 {
|
if opts.Quiet && opts.Verbose > 0 {
|
||||||
return errors.Fatal("--quiet and --verbose cannot be specified at the same time")
|
return errors.Fatal("--quiet and --verbose cannot be specified at the same time")
|
||||||
}
|
}
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
case opts.Verbose >= 2:
|
case opts.Verbose >= 2:
|
||||||
opts.verbosity = 3
|
opts.Verbosity = 3
|
||||||
case opts.Verbose > 0:
|
case opts.Verbose > 0:
|
||||||
opts.verbosity = 2
|
opts.Verbosity = 2
|
||||||
case opts.Quiet:
|
case opts.Quiet:
|
||||||
opts.verbosity = 0
|
opts.Verbosity = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
// parse extended options
|
// parse extended options
|
||||||
|
@ -162,7 +155,7 @@ func (opts *GlobalOptions) PreRun(needsPassword bool) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
opts.extended = extendedOpts
|
opts.Extended = extendedOpts
|
||||||
if !needsPassword {
|
if !needsPassword {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -170,26 +163,12 @@ func (opts *GlobalOptions) PreRun(needsPassword bool) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Fatalf("Resolving password failed: %v", err)
|
return errors.Fatalf("Resolving password failed: %v", err)
|
||||||
}
|
}
|
||||||
opts.password = pwd
|
opts.Password = pwd
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func collectBackends() *location.Registry {
|
|
||||||
backends := location.NewRegistry()
|
|
||||||
backends.Register(azure.NewFactory())
|
|
||||||
backends.Register(b2.NewFactory())
|
|
||||||
backends.Register(gs.NewFactory())
|
|
||||||
backends.Register(local.NewFactory())
|
|
||||||
backends.Register(rclone.NewFactory())
|
|
||||||
backends.Register(rest.NewFactory())
|
|
||||||
backends.Register(s3.NewFactory())
|
|
||||||
backends.Register(sftp.NewFactory())
|
|
||||||
backends.Register(swift.NewFactory())
|
|
||||||
return backends
|
|
||||||
}
|
|
||||||
|
|
||||||
// resolvePassword determines the password to be used for opening the repository.
|
// resolvePassword determines the password to be used for opening the repository.
|
||||||
func resolvePassword(opts *GlobalOptions, envStr string) (string, error) {
|
func resolvePassword(opts *Options, envStr string) (string, error) {
|
||||||
if opts.PasswordFile != "" && opts.PasswordCommand != "" {
|
if opts.PasswordFile != "" && opts.PasswordCommand != "" {
|
||||||
return "", errors.Fatalf("Password file and command are mutually exclusive options")
|
return "", errors.Fatalf("Password file and command are mutually exclusive options")
|
||||||
}
|
}
|
||||||
|
@ -207,7 +186,7 @@ func resolvePassword(opts *GlobalOptions, envStr string) (string, error) {
|
||||||
return strings.TrimSpace(string(output)), nil
|
return strings.TrimSpace(string(output)), nil
|
||||||
}
|
}
|
||||||
if opts.PasswordFile != "" {
|
if opts.PasswordFile != "" {
|
||||||
return loadPasswordFromFile(opts.PasswordFile)
|
return LoadPasswordFromFile(opts.PasswordFile)
|
||||||
}
|
}
|
||||||
|
|
||||||
if pwd := os.Getenv(envStr); pwd != "" {
|
if pwd := os.Getenv(envStr); pwd != "" {
|
||||||
|
@ -217,9 +196,9 @@ func resolvePassword(opts *GlobalOptions, envStr string) (string, error) {
|
||||||
return "", nil
|
return "", nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// loadPasswordFromFile loads a password from a file while stripping a BOM and
|
// LoadPasswordFromFile loads a password from a file while stripping a BOM and
|
||||||
// converting the password to UTF-8.
|
// converting the password to UTF-8.
|
||||||
func loadPasswordFromFile(pwdFile string) (string, error) {
|
func LoadPasswordFromFile(pwdFile string) (string, error) {
|
||||||
s, err := textfile.Read(pwdFile)
|
s, err := textfile.Read(pwdFile)
|
||||||
if errors.Is(err, os.ErrNotExist) {
|
if errors.Is(err, os.ErrNotExist) {
|
||||||
return "", errors.Fatalf("%s does not exist", pwdFile)
|
return "", errors.Fatalf("%s does not exist", pwdFile)
|
||||||
|
@ -227,22 +206,22 @@ func loadPasswordFromFile(pwdFile string) (string, error) {
|
||||||
return strings.TrimSpace(string(s)), errors.Wrap(err, "Readfile")
|
return strings.TrimSpace(string(s)), errors.Wrap(err, "Readfile")
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReadPassword reads the password from a password file, the environment
|
// readPassword reads the password from a password file, the environment
|
||||||
// variable RESTIC_PASSWORD or prompts the user. If the context is canceled,
|
// variable RESTIC_PASSWORD or prompts the user. If the context is canceled,
|
||||||
// the function leaks the password reading goroutine.
|
// the function leaks the password reading goroutine.
|
||||||
func ReadPassword(ctx context.Context, gopts GlobalOptions, prompt string) (string, error) {
|
func readPassword(ctx context.Context, gopts Options, prompt string) (string, error) {
|
||||||
if gopts.InsecureNoPassword {
|
if gopts.InsecureNoPassword {
|
||||||
if gopts.password != "" {
|
if gopts.Password != "" {
|
||||||
return "", errors.Fatal("--insecure-no-password must not be specified together with providing a password via a cli option or environment variable")
|
return "", errors.Fatal("--insecure-no-password must not be specified together with providing a password via a cli option or environment variable")
|
||||||
}
|
}
|
||||||
return "", nil
|
return "", nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if gopts.password != "" {
|
if gopts.Password != "" {
|
||||||
return gopts.password, nil
|
return gopts.Password, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
password, err := gopts.term.ReadPassword(ctx, prompt)
|
password, err := gopts.Term.ReadPassword(ctx, prompt)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", fmt.Errorf("unable to read password: %w", err)
|
return "", fmt.Errorf("unable to read password: %w", err)
|
||||||
}
|
}
|
||||||
|
@ -257,13 +236,13 @@ func ReadPassword(ctx context.Context, gopts GlobalOptions, prompt string) (stri
|
||||||
// ReadPasswordTwice calls ReadPassword two times and returns an error when the
|
// ReadPasswordTwice calls ReadPassword two times and returns an error when the
|
||||||
// passwords don't match. If the context is canceled, the function leaks the
|
// passwords don't match. If the context is canceled, the function leaks the
|
||||||
// password reading goroutine.
|
// password reading goroutine.
|
||||||
func ReadPasswordTwice(ctx context.Context, gopts GlobalOptions, prompt1, prompt2 string) (string, error) {
|
func ReadPasswordTwice(ctx context.Context, gopts Options, prompt1, prompt2 string) (string, error) {
|
||||||
pw1, err := ReadPassword(ctx, gopts, prompt1)
|
pw1, err := readPassword(ctx, gopts, prompt1)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
if gopts.term.InputIsTerminal() {
|
if gopts.Term.InputIsTerminal() {
|
||||||
pw2, err := ReadPassword(ctx, gopts, prompt2)
|
pw2, err := readPassword(ctx, gopts, prompt2)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
@ -276,7 +255,7 @@ func ReadPasswordTwice(ctx context.Context, gopts GlobalOptions, prompt1, prompt
|
||||||
return pw1, nil
|
return pw1, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func ReadRepo(gopts GlobalOptions) (string, error) {
|
func readRepo(gopts Options) (string, error) {
|
||||||
if gopts.Repo == "" && gopts.RepositoryFile == "" {
|
if gopts.Repo == "" && gopts.RepositoryFile == "" {
|
||||||
return "", errors.Fatal("Please specify repository location (-r or --repository-file)")
|
return "", errors.Fatal("Please specify repository location (-r or --repository-file)")
|
||||||
}
|
}
|
||||||
|
@ -304,17 +283,65 @@ func ReadRepo(gopts GlobalOptions) (string, error) {
|
||||||
const maxKeys = 20
|
const maxKeys = 20
|
||||||
|
|
||||||
// OpenRepository reads the password and opens the repository.
|
// OpenRepository reads the password and opens the repository.
|
||||||
func OpenRepository(ctx context.Context, gopts GlobalOptions, printer progress.Printer) (*repository.Repository, error) {
|
func OpenRepository(ctx context.Context, gopts Options, printer progress.Printer) (*repository.Repository, error) {
|
||||||
repo, err := ReadRepo(gopts)
|
repo, err := readRepo(gopts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
be, err := open(ctx, repo, gopts, gopts.extended, printer)
|
be, err := innerOpenBackend(ctx, repo, gopts, gopts.Extended, false, printer)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
err = hasRepositoryConfig(ctx, be, repo, gopts)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
s, err := createRepositoryInstance(be, gopts)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = decryptRepository(ctx, s, &gopts, printer)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
printRepositoryInfo(s, gopts, printer)
|
||||||
|
|
||||||
|
if gopts.NoCache {
|
||||||
|
return s, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
err = setupCache(s, gopts, printer)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return s, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// hasRepositoryConfig checks if the repository config file exists and is not empty.
|
||||||
|
func hasRepositoryConfig(ctx context.Context, be backend.Backend, repo string, gopts Options) error {
|
||||||
|
fi, err := be.Stat(ctx, backend.Handle{Type: restic.ConfigFile})
|
||||||
|
if be.IsNotExist(err) {
|
||||||
|
//nolint:staticcheck // capitalized error string is intentional
|
||||||
|
return fmt.Errorf("Fatal: %w: unable to open config file: %v\nIs there a repository at the following location?\n%v", ErrNoRepository, err, location.StripPassword(gopts.Backends, repo))
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return errors.Fatalf("unable to open config file: %v\n%v", err, location.StripPassword(gopts.Backends, repo))
|
||||||
|
}
|
||||||
|
|
||||||
|
if fi.Size == 0 {
|
||||||
|
return errors.New("config file has zero size, invalid repository?")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// createRepositoryInstance creates a new repository instance with the given options.
|
||||||
|
func createRepositoryInstance(be backend.Backend, gopts Options) (*repository.Repository, error) {
|
||||||
s, err := repository.New(be, repository.Options{
|
s, err := repository.New(be, repository.Options{
|
||||||
Compression: gopts.Compression,
|
Compression: gopts.Compression,
|
||||||
PackSize: gopts.PackSize * 1024 * 1024,
|
PackSize: gopts.PackSize * 1024 * 1024,
|
||||||
|
@ -323,38 +350,48 @@ func OpenRepository(ctx context.Context, gopts GlobalOptions, printer progress.P
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Fatalf("%s", err)
|
return nil, errors.Fatalf("%s", err)
|
||||||
}
|
}
|
||||||
|
return s, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// decryptRepository handles password reading and decrypts the repository.
|
||||||
|
func decryptRepository(ctx context.Context, s *repository.Repository, gopts *Options, printer progress.Printer) error {
|
||||||
passwordTriesLeft := 1
|
passwordTriesLeft := 1
|
||||||
if gopts.term.InputIsTerminal() && gopts.password == "" && !gopts.InsecureNoPassword {
|
if gopts.Term.InputIsTerminal() && gopts.Password == "" && !gopts.InsecureNoPassword {
|
||||||
passwordTriesLeft = 3
|
passwordTriesLeft = 3
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var err error
|
||||||
for ; passwordTriesLeft > 0; passwordTriesLeft-- {
|
for ; passwordTriesLeft > 0; passwordTriesLeft-- {
|
||||||
gopts.password, err = ReadPassword(ctx, gopts, "enter password for repository: ")
|
gopts.Password, err = readPassword(ctx, *gopts, "enter password for repository: ")
|
||||||
if ctx.Err() != nil {
|
if ctx.Err() != nil {
|
||||||
return nil, ctx.Err()
|
return ctx.Err()
|
||||||
}
|
}
|
||||||
if err != nil && passwordTriesLeft > 1 {
|
if err != nil && passwordTriesLeft > 1 {
|
||||||
gopts.password = ""
|
gopts.Password = ""
|
||||||
printer.E("%s. Try again", err)
|
printer.E("%s. Try again", err)
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
err = s.SearchKey(ctx, gopts.password, maxKeys, gopts.KeyHint)
|
err = s.SearchKey(ctx, gopts.Password, maxKeys, gopts.KeyHint)
|
||||||
if err != nil && passwordTriesLeft > 1 {
|
if err != nil && passwordTriesLeft > 1 {
|
||||||
gopts.password = ""
|
gopts.Password = ""
|
||||||
printer.E("%s. Try again", err)
|
printer.E("%s. Try again", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.IsFatal(err) || errors.Is(err, repository.ErrNoKeyFound) {
|
if errors.IsFatal(err) || errors.Is(err, repository.ErrNoKeyFound) {
|
||||||
return nil, err
|
return err
|
||||||
}
|
}
|
||||||
return nil, errors.Fatalf("%s", err)
|
return errors.Fatalf("%s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// printRepositoryInfo displays the repository ID, version and compression level.
|
||||||
|
func printRepositoryInfo(s *repository.Repository, gopts Options, printer progress.Printer) {
|
||||||
id := s.Config().ID
|
id := s.Config().ID
|
||||||
if len(id) > 8 {
|
if len(id) > 8 {
|
||||||
id = id[:8]
|
id = id[:8]
|
||||||
|
@ -364,15 +401,14 @@ func OpenRepository(ctx context.Context, gopts GlobalOptions, printer progress.P
|
||||||
extra = ", compression level " + gopts.Compression.String()
|
extra = ", compression level " + gopts.Compression.String()
|
||||||
}
|
}
|
||||||
printer.PT("repository %v opened (version %v%s)", id, s.Config().Version, extra)
|
printer.PT("repository %v opened (version %v%s)", id, s.Config().Version, extra)
|
||||||
|
}
|
||||||
|
|
||||||
if gopts.NoCache {
|
// setupCache creates a new cache and removes old cache directories if instructed to do so.
|
||||||
return s, nil
|
func setupCache(s *repository.Repository, gopts Options, printer progress.Printer) error {
|
||||||
}
|
|
||||||
|
|
||||||
c, err := cache.New(s.Config().ID, gopts.CacheDir)
|
c, err := cache.New(s.Config().ID, gopts.CacheDir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
printer.E("unable to open cache: %v", err)
|
printer.E("unable to open cache: %v", err)
|
||||||
return s, nil
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if c.Created {
|
if c.Created {
|
||||||
|
@ -389,7 +425,7 @@ func OpenRepository(ctx context.Context, gopts GlobalOptions, printer progress.P
|
||||||
|
|
||||||
// nothing more to do if no old cache dirs could be found
|
// nothing more to do if no old cache dirs could be found
|
||||||
if len(oldCacheDirs) == 0 {
|
if len(oldCacheDirs) == 0 {
|
||||||
return s, nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// cleanup old cache dirs if instructed to do so
|
// cleanup old cache dirs if instructed to do so
|
||||||
|
@ -406,11 +442,78 @@ func OpenRepository(ctx context.Context, gopts GlobalOptions, printer progress.P
|
||||||
printer.PT("found %d old cache directories in %v, run `restic cache --cleanup` to remove them",
|
printer.PT("found %d old cache directories in %v, run `restic cache --cleanup` to remove them",
|
||||||
len(oldCacheDirs), c.Base)
|
len(oldCacheDirs), c.Base)
|
||||||
}
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateRepository a repository with the given version and chunker polynomial.
|
||||||
|
func CreateRepository(ctx context.Context, gopts Options, version uint, chunkerPolynomial *chunker.Pol, printer progress.Printer) (*repository.Repository, error) {
|
||||||
|
if version < restic.MinRepoVersion || version > restic.MaxRepoVersion {
|
||||||
|
return nil, errors.Fatalf("only repository versions between %v and %v are allowed", restic.MinRepoVersion, restic.MaxRepoVersion)
|
||||||
|
}
|
||||||
|
|
||||||
|
repo, err := readRepo(gopts)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
gopts.Password, err = ReadPasswordTwice(ctx, gopts,
|
||||||
|
"enter password for new repository: ",
|
||||||
|
"enter password again: ")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
be, err := innerOpenBackend(ctx, repo, gopts, gopts.Extended, true, printer)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Fatalf("create repository at %s failed: %v", location.StripPassword(gopts.Backends, repo), err)
|
||||||
|
}
|
||||||
|
|
||||||
|
s, err := createRepositoryInstance(be, gopts)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = s.Init(ctx, version, gopts.Password, chunkerPolynomial)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Fatalf("create key in repository at %s failed: %v", location.StripPassword(gopts.Backends, repo), err)
|
||||||
|
}
|
||||||
|
|
||||||
return s, nil
|
return s, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseConfig(loc location.Location, opts options.Options) (interface{}, error) {
|
func innerOpenBackend(ctx context.Context, s string, gopts Options, opts options.Options, create bool, printer progress.Printer) (backend.Backend, error) {
|
||||||
|
debug.Log("parsing location %v", location.StripPassword(gopts.Backends, s))
|
||||||
|
|
||||||
|
scheme, cfg, err := parseConfig(gopts.Backends, s, opts)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
rt, lim, err := setupTransport(gopts)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
be, err := createOrOpenBackend(ctx, scheme, cfg, rt, lim, gopts, s, create, printer)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
be, err = wrapBackend(be, gopts, printer)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return be, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseConfig parses the repository location and extended options and returns the scheme and configuration.
|
||||||
|
func parseConfig(backends *location.Registry, s string, opts options.Options) (string, interface{}, error) {
|
||||||
|
loc, err := location.Parse(backends, s)
|
||||||
|
if err != nil {
|
||||||
|
return "", nil, errors.Fatalf("parsing repository location failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
cfg := loc.Config
|
cfg := loc.Config
|
||||||
if cfg, ok := cfg.(backend.ApplyEnvironmenter); ok {
|
if cfg, ok := cfg.(backend.ApplyEnvironmenter); ok {
|
||||||
cfg.ApplyEnvironment("")
|
cfg.ApplyEnvironment("")
|
||||||
|
@ -419,40 +522,36 @@ func parseConfig(loc location.Location, opts options.Options) (interface{}, erro
|
||||||
// only apply options for a particular backend here
|
// only apply options for a particular backend here
|
||||||
opts = opts.Extract(loc.Scheme)
|
opts = opts.Extract(loc.Scheme)
|
||||||
if err := opts.Apply(loc.Scheme, cfg); err != nil {
|
if err := opts.Apply(loc.Scheme, cfg); err != nil {
|
||||||
return nil, err
|
return "", nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
debug.Log("opening %v repository at %#v", loc.Scheme, cfg)
|
debug.Log("opening %v repository at %#v", loc.Scheme, cfg)
|
||||||
return cfg, nil
|
return loc.Scheme, cfg, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func innerOpen(ctx context.Context, s string, gopts GlobalOptions, opts options.Options, create bool, printer progress.Printer) (backend.Backend, error) {
|
// setupTransport creates and configures the transport with rate limiting.
|
||||||
debug.Log("parsing location %v", location.StripPassword(gopts.backends, s))
|
func setupTransport(gopts Options) (http.RoundTripper, limiter.Limiter, error) {
|
||||||
loc, err := location.Parse(gopts.backends, s)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Fatalf("parsing repository location failed: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
cfg, err := parseConfig(loc, opts)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
rt, err := backend.Transport(gopts.TransportOptions)
|
rt, err := backend.Transport(gopts.TransportOptions)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Fatalf("%s", err)
|
return nil, nil, errors.Fatalf("%s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// wrap the transport so that the throughput via HTTP is limited
|
// wrap the transport so that the throughput via HTTP is limited
|
||||||
lim := limiter.NewStaticLimiter(gopts.Limits)
|
lim := limiter.NewStaticLimiter(gopts.Limits)
|
||||||
rt = lim.Transport(rt)
|
rt = lim.Transport(rt)
|
||||||
|
|
||||||
factory := gopts.backends.Lookup(loc.Scheme)
|
return rt, lim, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// createOrOpenBackend creates or opens a backend using the appropriate factory method.
|
||||||
|
func createOrOpenBackend(ctx context.Context, scheme string, cfg interface{}, rt http.RoundTripper, lim limiter.Limiter, gopts Options, s string, create bool, printer progress.Printer) (backend.Backend, error) {
|
||||||
|
factory := gopts.Backends.Lookup(scheme)
|
||||||
if factory == nil {
|
if factory == nil {
|
||||||
return nil, errors.Fatalf("invalid backend: %q", loc.Scheme)
|
return nil, errors.Fatalf("invalid backend: %q", scheme)
|
||||||
}
|
}
|
||||||
|
|
||||||
var be backend.Backend
|
var be backend.Backend
|
||||||
|
var err error
|
||||||
if create {
|
if create {
|
||||||
be, err = factory.Create(ctx, cfg, rt, lim, printer.E)
|
be, err = factory.Create(ctx, cfg, rt, lim, printer.E)
|
||||||
} else {
|
} else {
|
||||||
|
@ -461,22 +560,28 @@ func innerOpen(ctx context.Context, s string, gopts GlobalOptions, opts options.
|
||||||
|
|
||||||
if errors.Is(err, backend.ErrNoRepository) {
|
if errors.Is(err, backend.ErrNoRepository) {
|
||||||
//nolint:staticcheck // capitalized error string is intentional
|
//nolint:staticcheck // capitalized error string is intentional
|
||||||
return nil, fmt.Errorf("Fatal: %w at %v: %v", ErrNoRepository, location.StripPassword(gopts.backends, s), err)
|
return nil, fmt.Errorf("Fatal: %w at %v: %v", ErrNoRepository, location.StripPassword(gopts.Backends, s), err)
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if create {
|
if create {
|
||||||
// init already wraps the error message
|
// init already wraps the error message
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return nil, errors.Fatalf("unable to open repository at %v: %v", location.StripPassword(gopts.backends, s), err)
|
return nil, errors.Fatalf("unable to open repository at %v: %v", location.StripPassword(gopts.Backends, s), err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return be, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// wrapBackend applies debug logging, test hooks, and retry wrapper to the backend.
|
||||||
|
func wrapBackend(be backend.Backend, gopts Options, printer progress.Printer) (backend.Backend, error) {
|
||||||
// wrap with debug logging and connection limiting
|
// wrap with debug logging and connection limiting
|
||||||
be = logger.New(sema.NewBackend(be))
|
be = logger.New(sema.NewBackend(be))
|
||||||
|
|
||||||
// wrap backend if a test specified an inner hook
|
// wrap backend if a test specified an inner hook
|
||||||
if gopts.backendInnerTestHook != nil {
|
if gopts.BackendInnerTestHook != nil {
|
||||||
be, err = gopts.backendInnerTestHook(be)
|
var err error
|
||||||
|
be, err = gopts.BackendInnerTestHook(be)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -495,8 +600,9 @@ func innerOpen(ctx context.Context, s string, gopts GlobalOptions, opts options.
|
||||||
be = retry.New(be, 15*time.Minute, report, success)
|
be = retry.New(be, 15*time.Minute, report, success)
|
||||||
|
|
||||||
// wrap backend if a test specified a hook
|
// wrap backend if a test specified a hook
|
||||||
if gopts.backendTestHook != nil {
|
if gopts.BackendTestHook != nil {
|
||||||
be, err = gopts.backendTestHook(be)
|
var err error
|
||||||
|
be, err = gopts.BackendTestHook(be)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -504,32 +610,3 @@ func innerOpen(ctx context.Context, s string, gopts GlobalOptions, opts options.
|
||||||
|
|
||||||
return be, nil
|
return be, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Open the backend specified by a location config.
|
|
||||||
func open(ctx context.Context, s string, gopts GlobalOptions, opts options.Options, printer progress.Printer) (backend.Backend, error) {
|
|
||||||
be, err := innerOpen(ctx, s, gopts, opts, false, printer)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// check if config is there
|
|
||||||
fi, err := be.Stat(ctx, backend.Handle{Type: restic.ConfigFile})
|
|
||||||
if be.IsNotExist(err) {
|
|
||||||
//nolint:staticcheck // capitalized error string is intentional
|
|
||||||
return nil, fmt.Errorf("Fatal: %w: unable to open config file: %v\nIs there a repository at the following location?\n%v", ErrNoRepository, err, location.StripPassword(gopts.backends, s))
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Fatalf("unable to open config file: %v\nIs there a repository at the following location?\n%v", err, location.StripPassword(gopts.backends, s))
|
|
||||||
}
|
|
||||||
|
|
||||||
if fi.Size == 0 {
|
|
||||||
return nil, errors.New("config file has zero size, invalid repository?")
|
|
||||||
}
|
|
||||||
|
|
||||||
return be, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create the backend specified by URI.
|
|
||||||
func create(ctx context.Context, s string, gopts GlobalOptions, opts options.Options, printer progress.Printer) (backend.Backend, error) {
|
|
||||||
return innerOpen(ctx, s, gopts, opts, true, printer)
|
|
||||||
}
|
|
|
@ -1,7 +1,7 @@
|
||||||
//go:build debug || profile
|
//go:build debug || profile
|
||||||
// +build debug profile
|
// +build debug profile
|
||||||
|
|
||||||
package main
|
package global
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
@ -17,8 +17,8 @@ import (
|
||||||
"github.com/pkg/profile"
|
"github.com/pkg/profile"
|
||||||
)
|
)
|
||||||
|
|
||||||
func registerProfiling(cmd *cobra.Command, stderr io.Writer) {
|
func RegisterProfiling(cmd *cobra.Command, stderr io.Writer) {
|
||||||
var profiler profiler
|
var profiler Profiler
|
||||||
|
|
||||||
origPreRun := cmd.PersistentPreRunE
|
origPreRun := cmd.PersistentPreRunE
|
||||||
cmd.PersistentPreRunE = func(cmd *cobra.Command, args []string) error {
|
cmd.PersistentPreRunE = func(cmd *cobra.Command, args []string) error {
|
||||||
|
@ -40,7 +40,7 @@ func registerProfiling(cmd *cobra.Command, stderr io.Writer) {
|
||||||
profiler.opts.AddFlags(cmd.PersistentFlags())
|
profiler.opts.AddFlags(cmd.PersistentFlags())
|
||||||
}
|
}
|
||||||
|
|
||||||
type profiler struct {
|
type Profiler struct {
|
||||||
opts ProfileOptions
|
opts ProfileOptions
|
||||||
stop interface {
|
stop interface {
|
||||||
Stop()
|
Stop()
|
||||||
|
@ -73,7 +73,7 @@ func (t fakeTestingTB) Logf(msg string, args ...interface{}) {
|
||||||
fmt.Fprintf(t.stderr, msg, args...)
|
fmt.Fprintf(t.stderr, msg, args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *profiler) Start(profileOpts ProfileOptions, stderr io.Writer) error {
|
func (p *Profiler) Start(profileOpts ProfileOptions, stderr io.Writer) error {
|
||||||
if profileOpts.listen != "" {
|
if profileOpts.listen != "" {
|
||||||
fmt.Fprintf(stderr, "running profile HTTP server on %v\n", profileOpts.listen)
|
fmt.Fprintf(stderr, "running profile HTTP server on %v\n", profileOpts.listen)
|
||||||
go func() {
|
go func() {
|
||||||
|
@ -119,7 +119,7 @@ func (p *profiler) Start(profileOpts ProfileOptions, stderr io.Writer) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *profiler) Stop() {
|
func (p *Profiler) Stop() {
|
||||||
if p.stop != nil {
|
if p.stop != nil {
|
||||||
p.stop.Stop()
|
p.stop.Stop()
|
||||||
}
|
}
|
|
@ -1,7 +1,7 @@
|
||||||
//go:build !debug && !profile
|
//go:build !debug && !profile
|
||||||
// +build !debug,!profile
|
// +build !debug,!profile
|
||||||
|
|
||||||
package main
|
package global
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"io"
|
"io"
|
||||||
|
@ -9,6 +9,6 @@ import (
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
func registerProfiling(_ *cobra.Command, _ io.Writer) {
|
func RegisterProfiling(_ *cobra.Command, _ io.Writer) {
|
||||||
// No profiling in release mode
|
// No profiling in release mode
|
||||||
}
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
package main
|
package global
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
@ -14,9 +14,9 @@ func TestReadRepo(t *testing.T) {
|
||||||
tempDir := rtest.TempDir(t)
|
tempDir := rtest.TempDir(t)
|
||||||
|
|
||||||
// test --repo option
|
// test --repo option
|
||||||
var gopts GlobalOptions
|
var gopts Options
|
||||||
gopts.Repo = tempDir
|
gopts.Repo = tempDir
|
||||||
repo, err := ReadRepo(gopts)
|
repo, err := readRepo(gopts)
|
||||||
rtest.OK(t, err)
|
rtest.OK(t, err)
|
||||||
rtest.Equals(t, tempDir, repo)
|
rtest.Equals(t, tempDir, repo)
|
||||||
|
|
||||||
|
@ -25,27 +25,27 @@ func TestReadRepo(t *testing.T) {
|
||||||
err = os.WriteFile(foo, []byte(tempDir+"\n"), 0666)
|
err = os.WriteFile(foo, []byte(tempDir+"\n"), 0666)
|
||||||
rtest.OK(t, err)
|
rtest.OK(t, err)
|
||||||
|
|
||||||
var gopts2 GlobalOptions
|
var gopts2 Options
|
||||||
gopts2.RepositoryFile = foo
|
gopts2.RepositoryFile = foo
|
||||||
repo, err = ReadRepo(gopts2)
|
repo, err = readRepo(gopts2)
|
||||||
rtest.OK(t, err)
|
rtest.OK(t, err)
|
||||||
rtest.Equals(t, tempDir, repo)
|
rtest.Equals(t, tempDir, repo)
|
||||||
|
|
||||||
var gopts3 GlobalOptions
|
var gopts3 Options
|
||||||
gopts3.RepositoryFile = foo + "-invalid"
|
gopts3.RepositoryFile = foo + "-invalid"
|
||||||
_, err = ReadRepo(gopts3)
|
_, err = readRepo(gopts3)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Fatal("must not read repository path from invalid file path")
|
t.Fatal("must not read repository path from invalid file path")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestReadEmptyPassword(t *testing.T) {
|
func TestReadEmptyPassword(t *testing.T) {
|
||||||
opts := GlobalOptions{InsecureNoPassword: true}
|
opts := Options{InsecureNoPassword: true}
|
||||||
password, err := ReadPassword(context.TODO(), opts, "test")
|
password, err := readPassword(context.TODO(), opts, "test")
|
||||||
rtest.OK(t, err)
|
rtest.OK(t, err)
|
||||||
rtest.Equals(t, "", password, "got unexpected password")
|
rtest.Equals(t, "", password, "got unexpected password")
|
||||||
|
|
||||||
opts.password = "invalid"
|
opts.Password = "invalid"
|
||||||
_, err = ReadPassword(context.TODO(), opts, "test")
|
_, err = readPassword(context.TODO(), opts, "test")
|
||||||
rtest.Assert(t, strings.Contains(err.Error(), "must not be specified together with providing a password via a cli option or environment variable"), "unexpected error message, got %v", err)
|
rtest.Assert(t, strings.Contains(err.Error(), "must not be specified together with providing a password via a cli option or environment variable"), "unexpected error message, got %v", err)
|
||||||
}
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
package main
|
package global
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
@ -8,8 +8,8 @@ import (
|
||||||
"github.com/spf13/pflag"
|
"github.com/spf13/pflag"
|
||||||
)
|
)
|
||||||
|
|
||||||
type secondaryRepoOptions struct {
|
type SecondaryRepoOptions struct {
|
||||||
password string
|
Password string
|
||||||
// from-repo options
|
// from-repo options
|
||||||
Repo string
|
Repo string
|
||||||
RepositoryFile string
|
RepositoryFile string
|
||||||
|
@ -25,7 +25,7 @@ type secondaryRepoOptions struct {
|
||||||
LegacyKeyHint string
|
LegacyKeyHint string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (opts *secondaryRepoOptions) AddFlags(f *pflag.FlagSet, 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.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.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)")
|
f.StringVarP(&opts.LegacyPasswordFile, "password-file2", "", "", "`file` to read the "+repoPrefix+" repository password from (default: $RESTIC_PASSWORD_FILE2)")
|
||||||
|
@ -59,9 +59,9 @@ func (opts *secondaryRepoOptions) AddFlags(f *pflag.FlagSet, repoPrefix string,
|
||||||
opts.PasswordCommand = os.Getenv("RESTIC_FROM_PASSWORD_COMMAND")
|
opts.PasswordCommand = os.Getenv("RESTIC_FROM_PASSWORD_COMMAND")
|
||||||
}
|
}
|
||||||
|
|
||||||
func fillSecondaryGlobalOpts(ctx context.Context, opts secondaryRepoOptions, gopts GlobalOptions, repoPrefix string) (GlobalOptions, bool, error) {
|
func (opts *SecondaryRepoOptions) FillGlobalOpts(ctx context.Context, gopts Options, repoPrefix string) (Options, bool, error) {
|
||||||
if opts.Repo == "" && opts.RepositoryFile == "" && opts.LegacyRepo == "" && opts.LegacyRepositoryFile == "" {
|
if opts.Repo == "" && opts.RepositoryFile == "" && opts.LegacyRepo == "" && opts.LegacyRepositoryFile == "" {
|
||||||
return GlobalOptions{}, false, errors.Fatal("Please specify a source repository location (--from-repo or --from-repository-file)")
|
return Options{}, false, errors.Fatal("Please specify a source repository location (--from-repo or --from-repository-file)")
|
||||||
}
|
}
|
||||||
|
|
||||||
hasFromRepo := opts.Repo != "" || opts.RepositoryFile != "" || opts.PasswordFile != "" ||
|
hasFromRepo := opts.Repo != "" || opts.RepositoryFile != "" || opts.PasswordFile != "" ||
|
||||||
|
@ -70,7 +70,7 @@ func fillSecondaryGlobalOpts(ctx context.Context, opts secondaryRepoOptions, gop
|
||||||
opts.LegacyKeyHint != "" || opts.LegacyPasswordCommand != ""
|
opts.LegacyKeyHint != "" || opts.LegacyPasswordCommand != ""
|
||||||
|
|
||||||
if hasFromRepo && hasRepo2 {
|
if hasFromRepo && hasRepo2 {
|
||||||
return GlobalOptions{}, false, errors.Fatal("Option groups repo2 and from-repo are mutually exclusive, please specify only one")
|
return Options{}, false, errors.Fatal("Option groups repo2 and from-repo are mutually exclusive, please specify only one")
|
||||||
}
|
}
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
|
@ -79,7 +79,7 @@ func fillSecondaryGlobalOpts(ctx context.Context, opts secondaryRepoOptions, gop
|
||||||
|
|
||||||
if hasFromRepo {
|
if hasFromRepo {
|
||||||
if opts.Repo != "" && opts.RepositoryFile != "" {
|
if opts.Repo != "" && opts.RepositoryFile != "" {
|
||||||
return GlobalOptions{}, false, errors.Fatal("Options --from-repo and --from-repository-file are mutually exclusive, please specify only one")
|
return Options{}, false, errors.Fatal("Options --from-repo and --from-repository-file are mutually exclusive, please specify only one")
|
||||||
}
|
}
|
||||||
|
|
||||||
dstGopts.Repo = opts.Repo
|
dstGopts.Repo = opts.Repo
|
||||||
|
@ -93,7 +93,7 @@ func fillSecondaryGlobalOpts(ctx context.Context, opts secondaryRepoOptions, gop
|
||||||
repoPrefix = "source"
|
repoPrefix = "source"
|
||||||
} else {
|
} else {
|
||||||
if opts.LegacyRepo != "" && opts.LegacyRepositoryFile != "" {
|
if opts.LegacyRepo != "" && opts.LegacyRepositoryFile != "" {
|
||||||
return GlobalOptions{}, false, errors.Fatal("Options --repo2 and --repository-file2 are mutually exclusive, please specify only one")
|
return Options{}, false, errors.Fatal("Options --repo2 and --repository-file2 are mutually exclusive, please specify only one")
|
||||||
}
|
}
|
||||||
|
|
||||||
dstGopts.Repo = opts.LegacyRepo
|
dstGopts.Repo = opts.LegacyRepo
|
||||||
|
@ -107,17 +107,17 @@ func fillSecondaryGlobalOpts(ctx context.Context, opts secondaryRepoOptions, gop
|
||||||
pwdEnv = "RESTIC_PASSWORD2"
|
pwdEnv = "RESTIC_PASSWORD2"
|
||||||
}
|
}
|
||||||
|
|
||||||
if opts.password != "" {
|
if opts.Password != "" {
|
||||||
dstGopts.password = opts.password
|
dstGopts.Password = opts.Password
|
||||||
} else {
|
} else {
|
||||||
dstGopts.password, err = resolvePassword(&dstGopts, pwdEnv)
|
dstGopts.Password, err = resolvePassword(&dstGopts, pwdEnv)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return GlobalOptions{}, false, err
|
return Options{}, false, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
dstGopts.password, err = ReadPassword(ctx, dstGopts, "enter password for "+repoPrefix+" repository: ")
|
dstGopts.Password, err = readPassword(ctx, dstGopts, "enter password for "+repoPrefix+" repository: ")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return GlobalOptions{}, false, err
|
return Options{}, false, err
|
||||||
}
|
}
|
||||||
return dstGopts, hasFromRepo, nil
|
return dstGopts, hasFromRepo, nil
|
||||||
}
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
package main
|
package global
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
@ -13,8 +13,8 @@ import (
|
||||||
func TestFillSecondaryGlobalOpts(t *testing.T) {
|
func TestFillSecondaryGlobalOpts(t *testing.T) {
|
||||||
//secondaryRepoTestCase defines a struct for test cases
|
//secondaryRepoTestCase defines a struct for test cases
|
||||||
type secondaryRepoTestCase struct {
|
type secondaryRepoTestCase struct {
|
||||||
Opts secondaryRepoOptions
|
Opts SecondaryRepoOptions
|
||||||
DstGOpts GlobalOptions
|
DstGOpts Options
|
||||||
FromRepo bool
|
FromRepo bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -22,74 +22,74 @@ func TestFillSecondaryGlobalOpts(t *testing.T) {
|
||||||
var validSecondaryRepoTestCases = []secondaryRepoTestCase{
|
var validSecondaryRepoTestCases = []secondaryRepoTestCase{
|
||||||
{
|
{
|
||||||
// Test if Repo and Password are parsed correctly.
|
// Test if Repo and Password are parsed correctly.
|
||||||
Opts: secondaryRepoOptions{
|
Opts: SecondaryRepoOptions{
|
||||||
Repo: "backupDst",
|
Repo: "backupDst",
|
||||||
password: "secretDst",
|
Password: "secretDst",
|
||||||
},
|
},
|
||||||
DstGOpts: GlobalOptions{
|
DstGOpts: Options{
|
||||||
Repo: "backupDst",
|
Repo: "backupDst",
|
||||||
password: "secretDst",
|
Password: "secretDst",
|
||||||
},
|
},
|
||||||
FromRepo: true,
|
FromRepo: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
// Test if RepositoryFile and PasswordFile are parsed correctly.
|
// Test if RepositoryFile and PasswordFile are parsed correctly.
|
||||||
Opts: secondaryRepoOptions{
|
Opts: SecondaryRepoOptions{
|
||||||
RepositoryFile: "backupDst",
|
RepositoryFile: "backupDst",
|
||||||
PasswordFile: "passwordFileDst",
|
PasswordFile: "passwordFileDst",
|
||||||
},
|
},
|
||||||
DstGOpts: GlobalOptions{
|
DstGOpts: Options{
|
||||||
RepositoryFile: "backupDst",
|
RepositoryFile: "backupDst",
|
||||||
password: "secretDst",
|
Password: "secretDst",
|
||||||
PasswordFile: "passwordFileDst",
|
PasswordFile: "passwordFileDst",
|
||||||
},
|
},
|
||||||
FromRepo: true,
|
FromRepo: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
// Test if RepositoryFile and PasswordCommand are parsed correctly.
|
// Test if RepositoryFile and PasswordCommand are parsed correctly.
|
||||||
Opts: secondaryRepoOptions{
|
Opts: SecondaryRepoOptions{
|
||||||
RepositoryFile: "backupDst",
|
RepositoryFile: "backupDst",
|
||||||
PasswordCommand: "echo secretDst",
|
PasswordCommand: "echo secretDst",
|
||||||
},
|
},
|
||||||
DstGOpts: GlobalOptions{
|
DstGOpts: Options{
|
||||||
RepositoryFile: "backupDst",
|
RepositoryFile: "backupDst",
|
||||||
password: "secretDst",
|
Password: "secretDst",
|
||||||
PasswordCommand: "echo secretDst",
|
PasswordCommand: "echo secretDst",
|
||||||
},
|
},
|
||||||
FromRepo: true,
|
FromRepo: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
// Test if LegacyRepo and Password are parsed correctly.
|
// Test if LegacyRepo and Password are parsed correctly.
|
||||||
Opts: secondaryRepoOptions{
|
Opts: SecondaryRepoOptions{
|
||||||
LegacyRepo: "backupDst",
|
LegacyRepo: "backupDst",
|
||||||
password: "secretDst",
|
Password: "secretDst",
|
||||||
},
|
},
|
||||||
DstGOpts: GlobalOptions{
|
DstGOpts: Options{
|
||||||
Repo: "backupDst",
|
Repo: "backupDst",
|
||||||
password: "secretDst",
|
Password: "secretDst",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
// Test if LegacyRepositoryFile and LegacyPasswordFile are parsed correctly.
|
// Test if LegacyRepositoryFile and LegacyPasswordFile are parsed correctly.
|
||||||
Opts: secondaryRepoOptions{
|
Opts: SecondaryRepoOptions{
|
||||||
LegacyRepositoryFile: "backupDst",
|
LegacyRepositoryFile: "backupDst",
|
||||||
LegacyPasswordFile: "passwordFileDst",
|
LegacyPasswordFile: "passwordFileDst",
|
||||||
},
|
},
|
||||||
DstGOpts: GlobalOptions{
|
DstGOpts: Options{
|
||||||
RepositoryFile: "backupDst",
|
RepositoryFile: "backupDst",
|
||||||
password: "secretDst",
|
Password: "secretDst",
|
||||||
PasswordFile: "passwordFileDst",
|
PasswordFile: "passwordFileDst",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
// Test if LegacyRepositoryFile and LegacyPasswordCommand are parsed correctly.
|
// Test if LegacyRepositoryFile and LegacyPasswordCommand are parsed correctly.
|
||||||
Opts: secondaryRepoOptions{
|
Opts: SecondaryRepoOptions{
|
||||||
LegacyRepositoryFile: "backupDst",
|
LegacyRepositoryFile: "backupDst",
|
||||||
LegacyPasswordCommand: "echo secretDst",
|
LegacyPasswordCommand: "echo secretDst",
|
||||||
},
|
},
|
||||||
DstGOpts: GlobalOptions{
|
DstGOpts: Options{
|
||||||
RepositoryFile: "backupDst",
|
RepositoryFile: "backupDst",
|
||||||
password: "secretDst",
|
Password: "secretDst",
|
||||||
PasswordCommand: "echo secretDst",
|
PasswordCommand: "echo secretDst",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -99,18 +99,18 @@ func TestFillSecondaryGlobalOpts(t *testing.T) {
|
||||||
var invalidSecondaryRepoTestCases = []secondaryRepoTestCase{
|
var invalidSecondaryRepoTestCases = []secondaryRepoTestCase{
|
||||||
{
|
{
|
||||||
// Test must fail on no repo given.
|
// Test must fail on no repo given.
|
||||||
Opts: secondaryRepoOptions{},
|
Opts: SecondaryRepoOptions{},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
// Test must fail as Repo and RepositoryFile are both given
|
// Test must fail as Repo and RepositoryFile are both given
|
||||||
Opts: secondaryRepoOptions{
|
Opts: SecondaryRepoOptions{
|
||||||
Repo: "backupDst",
|
Repo: "backupDst",
|
||||||
RepositoryFile: "backupDst",
|
RepositoryFile: "backupDst",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
// Test must fail as PasswordFile and PasswordCommand are both given
|
// Test must fail as PasswordFile and PasswordCommand are both given
|
||||||
Opts: secondaryRepoOptions{
|
Opts: SecondaryRepoOptions{
|
||||||
Repo: "backupDst",
|
Repo: "backupDst",
|
||||||
PasswordFile: "passwordFileDst",
|
PasswordFile: "passwordFileDst",
|
||||||
PasswordCommand: "notEmpty",
|
PasswordCommand: "notEmpty",
|
||||||
|
@ -118,28 +118,28 @@ func TestFillSecondaryGlobalOpts(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
// Test must fail as PasswordFile does not exist
|
// Test must fail as PasswordFile does not exist
|
||||||
Opts: secondaryRepoOptions{
|
Opts: SecondaryRepoOptions{
|
||||||
Repo: "backupDst",
|
Repo: "backupDst",
|
||||||
PasswordFile: "NonExistingFile",
|
PasswordFile: "NonExistingFile",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
// Test must fail as PasswordCommand does not exist
|
// Test must fail as PasswordCommand does not exist
|
||||||
Opts: secondaryRepoOptions{
|
Opts: SecondaryRepoOptions{
|
||||||
Repo: "backupDst",
|
Repo: "backupDst",
|
||||||
PasswordCommand: "notEmpty",
|
PasswordCommand: "notEmpty",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
// Test must fail as current and legacy options are mixed
|
// Test must fail as current and legacy options are mixed
|
||||||
Opts: secondaryRepoOptions{
|
Opts: SecondaryRepoOptions{
|
||||||
Repo: "backupDst",
|
Repo: "backupDst",
|
||||||
LegacyRepo: "backupDst",
|
LegacyRepo: "backupDst",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
// Test must fail as current and legacy options are mixed
|
// Test must fail as current and legacy options are mixed
|
||||||
Opts: secondaryRepoOptions{
|
Opts: SecondaryRepoOptions{
|
||||||
Repo: "backupDst",
|
Repo: "backupDst",
|
||||||
LegacyPasswordCommand: "notEmpty",
|
LegacyPasswordCommand: "notEmpty",
|
||||||
},
|
},
|
||||||
|
@ -147,10 +147,10 @@ func TestFillSecondaryGlobalOpts(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
//gOpts defines the Global options used in the secondary repository tests
|
//gOpts defines the Global options used in the secondary repository tests
|
||||||
var gOpts = GlobalOptions{
|
var gOpts = Options{
|
||||||
Repo: "backupSrc",
|
Repo: "backupSrc",
|
||||||
RepositoryFile: "backupSrc",
|
RepositoryFile: "backupSrc",
|
||||||
password: "secretSrc",
|
Password: "secretSrc",
|
||||||
PasswordFile: "passwordFileSrc",
|
PasswordFile: "passwordFileSrc",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -165,7 +165,7 @@ func TestFillSecondaryGlobalOpts(t *testing.T) {
|
||||||
|
|
||||||
// Test all valid cases
|
// Test all valid cases
|
||||||
for _, testCase := range validSecondaryRepoTestCases {
|
for _, testCase := range validSecondaryRepoTestCases {
|
||||||
DstGOpts, isFromRepo, err := fillSecondaryGlobalOpts(context.TODO(), testCase.Opts, gOpts, "destination")
|
DstGOpts, isFromRepo, err := testCase.Opts.FillGlobalOpts(context.TODO(), gOpts, "destination")
|
||||||
rtest.OK(t, err)
|
rtest.OK(t, err)
|
||||||
rtest.Equals(t, DstGOpts, testCase.DstGOpts)
|
rtest.Equals(t, DstGOpts, testCase.DstGOpts)
|
||||||
rtest.Equals(t, isFromRepo, testCase.FromRepo)
|
rtest.Equals(t, isFromRepo, testCase.FromRepo)
|
||||||
|
@ -173,7 +173,7 @@ func TestFillSecondaryGlobalOpts(t *testing.T) {
|
||||||
|
|
||||||
// Test all invalid cases
|
// Test all invalid cases
|
||||||
for _, testCase := range invalidSecondaryRepoTestCases {
|
for _, testCase := range invalidSecondaryRepoTestCases {
|
||||||
_, _, err := fillSecondaryGlobalOpts(context.TODO(), testCase.Opts, gOpts, "destination")
|
_, _, err := testCase.Opts.FillGlobalOpts(context.TODO(), gOpts, "destination")
|
||||||
rtest.Assert(t, err != nil, "Expected error, but function did not return an error")
|
rtest.Assert(t, err != nil, "Expected error, but function did not return an error")
|
||||||
}
|
}
|
||||||
}
|
}
|
Loading…
Add table
Add a link
Reference in a new issue