mirror of
https://github.com/restic/restic.git
synced 2026-02-03 04:20:45 -05:00
Merge pull request #5518 from MichaelEischer/termstatus-everywhere
Consolidate terminal input/output functionality in termstatus.Terminal
This commit is contained in:
commit
13e476e1eb
152 changed files with 1213 additions and 1213 deletions
|
|
@ -3,6 +3,7 @@ package main
|
|||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
|
|
@ -10,27 +11,27 @@ import (
|
|||
"github.com/restic/restic/internal/debug"
|
||||
)
|
||||
|
||||
func createGlobalContext() context.Context {
|
||||
func createGlobalContext(stderr io.Writer) context.Context {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
|
||||
ch := make(chan os.Signal, 1)
|
||||
go cleanupHandler(ch, cancel)
|
||||
go cleanupHandler(ch, cancel, stderr)
|
||||
signal.Notify(ch, syscall.SIGINT, syscall.SIGTERM)
|
||||
|
||||
return ctx
|
||||
}
|
||||
|
||||
// cleanupHandler handles the SIGINT and SIGTERM signals.
|
||||
func cleanupHandler(c <-chan os.Signal, cancel context.CancelFunc) {
|
||||
func cleanupHandler(c <-chan os.Signal, cancel context.CancelFunc, stderr io.Writer) {
|
||||
s := <-c
|
||||
debug.Log("signal %v received, cleaning up", s)
|
||||
// ignore error as there's no good way to handle it
|
||||
_, _ = fmt.Fprintf(os.Stderr, "\rsignal %v received, cleaning up \n", s)
|
||||
_, _ = fmt.Fprintf(stderr, "\rsignal %v received, cleaning up \n", s)
|
||||
|
||||
if val, _ := os.LookupEnv("RESTIC_DEBUG_STACKTRACE_SIGINT"); val != "" {
|
||||
_, _ = os.Stderr.WriteString("\n--- STACKTRACE START ---\n\n")
|
||||
_, _ = os.Stderr.WriteString(debug.DumpStacktrace())
|
||||
_, _ = os.Stderr.WriteString("\n--- STACKTRACE END ---\n")
|
||||
_, _ = stderr.Write([]byte("\n--- STACKTRACE START ---\n\n"))
|
||||
_, _ = stderr.Write([]byte(debug.DumpStacktrace()))
|
||||
_, _ = stderr.Write([]byte("\n--- STACKTRACE END ---\n"))
|
||||
}
|
||||
|
||||
cancel()
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ import (
|
|||
"github.com/restic/restic/internal/ui/backup"
|
||||
)
|
||||
|
||||
func newBackupCommand() *cobra.Command {
|
||||
func newBackupCommand(globalOptions *GlobalOptions) *cobra.Command {
|
||||
var opts BackupOptions
|
||||
|
||||
cmd := &cobra.Command{
|
||||
|
|
@ -63,9 +63,7 @@ Exit status is 12 if the password is incorrect.
|
|||
GroupID: cmdGroupDefault,
|
||||
DisableAutoGenTag: true,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
term, cancel := setupTermstatus()
|
||||
defer cancel()
|
||||
return runBackup(cmd.Context(), opts, globalOptions, term, args)
|
||||
return runBackup(cmd.Context(), opts, *globalOptions, globalOptions.term, args)
|
||||
},
|
||||
}
|
||||
|
||||
|
|
@ -184,7 +182,7 @@ func filterExisting(items []string, warnf func(msg string, args ...interface{}))
|
|||
// If filename is empty, readPatternsFromFile returns an empty slice.
|
||||
// If filename is a dash (-), readPatternsFromFile will read the lines from the
|
||||
// standard input.
|
||||
func readLines(filename string) ([]string, error) {
|
||||
func readLines(filename string, stdin io.ReadCloser) ([]string, error) {
|
||||
if filename == "" {
|
||||
return nil, nil
|
||||
}
|
||||
|
|
@ -195,7 +193,7 @@ func readLines(filename string) ([]string, error) {
|
|||
)
|
||||
|
||||
if filename == "-" {
|
||||
data, err = io.ReadAll(os.Stdin)
|
||||
data, err = io.ReadAll(stdin)
|
||||
} else {
|
||||
data, err = textfile.Read(filename)
|
||||
}
|
||||
|
|
@ -220,8 +218,8 @@ func readLines(filename string) ([]string, error) {
|
|||
// readFilenamesFromFileRaw reads a list of filenames from the given file,
|
||||
// or stdin if filename is "-". Each filename is terminated by a zero byte,
|
||||
// which is stripped off.
|
||||
func readFilenamesFromFileRaw(filename string) (names []string, err error) {
|
||||
f := os.Stdin
|
||||
func readFilenamesFromFileRaw(filename string, stdin io.ReadCloser) (names []string, err error) {
|
||||
f := stdin
|
||||
if filename != "-" {
|
||||
if f, err = os.Open(filename); err != nil {
|
||||
return nil, err
|
||||
|
|
@ -380,13 +378,13 @@ func collectRejectFuncs(opts BackupOptions, targets []string, fs fs.FS, warnf fu
|
|||
}
|
||||
|
||||
// collectTargets returns a list of target files/dirs from several sources.
|
||||
func collectTargets(opts BackupOptions, args []string, warnf func(msg string, args ...interface{})) (targets []string, err error) {
|
||||
func collectTargets(opts BackupOptions, args []string, warnf func(msg string, args ...interface{}), stdin io.ReadCloser) (targets []string, err error) {
|
||||
if opts.Stdin || opts.StdinCommand {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
for _, file := range opts.FilesFrom {
|
||||
fromfile, err := readLines(file)
|
||||
fromfile, err := readLines(file, stdin)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -411,7 +409,7 @@ func collectTargets(opts BackupOptions, args []string, warnf func(msg string, ar
|
|||
}
|
||||
|
||||
for _, file := range opts.FilesFromVerbatim {
|
||||
fromfile, err := readLines(file)
|
||||
fromfile, err := readLines(file, stdin)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -424,7 +422,7 @@ func collectTargets(opts BackupOptions, args []string, warnf func(msg string, ar
|
|||
}
|
||||
|
||||
for _, file := range opts.FilesFromRaw {
|
||||
fromfile, err := readFilenamesFromFileRaw(file)
|
||||
fromfile, err := readFilenamesFromFileRaw(file, stdin)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -480,7 +478,12 @@ func runBackup(ctx context.Context, opts BackupOptions, gopts GlobalOptions, ter
|
|||
var vsscfg fs.VSSConfig
|
||||
var err error
|
||||
|
||||
msg := newTerminalProgressPrinter(gopts.JSON, gopts.verbosity, term)
|
||||
var printer backup.ProgressPrinter
|
||||
if gopts.JSON {
|
||||
printer = backup.NewJSONProgress(term, gopts.verbosity)
|
||||
} else {
|
||||
printer = backup.NewTextProgress(term, gopts.verbosity)
|
||||
}
|
||||
if runtime.GOOS == "windows" {
|
||||
if vsscfg, err = fs.ParseVSSConfig(gopts.extended); err != nil {
|
||||
return err
|
||||
|
|
@ -492,7 +495,7 @@ func runBackup(ctx context.Context, opts BackupOptions, gopts GlobalOptions, ter
|
|||
return err
|
||||
}
|
||||
|
||||
targets, err := collectTargets(opts, args, msg.E)
|
||||
targets, err := collectTargets(opts, args, printer.E, term.InputRaw())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -507,27 +510,21 @@ func runBackup(ctx context.Context, opts BackupOptions, gopts GlobalOptions, ter
|
|||
}
|
||||
|
||||
if gopts.verbosity >= 2 && !gopts.JSON {
|
||||
msg.P("open repository")
|
||||
printer.P("open repository")
|
||||
}
|
||||
|
||||
ctx, repo, unlock, err := openWithAppendLock(ctx, gopts, opts.DryRun, msg)
|
||||
ctx, repo, unlock, err := openWithAppendLock(ctx, gopts, opts.DryRun, printer)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer unlock()
|
||||
|
||||
var progressPrinter backup.ProgressPrinter
|
||||
if gopts.JSON {
|
||||
progressPrinter = backup.NewJSONProgress(term, gopts.verbosity)
|
||||
} else {
|
||||
progressPrinter = backup.NewTextProgress(term, gopts.verbosity)
|
||||
}
|
||||
progressReporter := backup.NewProgress(progressPrinter,
|
||||
calculateProgressInterval(!gopts.Quiet, gopts.JSON))
|
||||
progressReporter := backup.NewProgress(printer,
|
||||
ui.CalculateProgressInterval(!gopts.Quiet, gopts.JSON, term.CanUpdateStatus()))
|
||||
defer progressReporter.Done()
|
||||
|
||||
// rejectByNameFuncs collect functions that can reject items from the backup based on path only
|
||||
rejectByNameFuncs, err := collectRejectByNameFuncs(opts, repo, msg.E)
|
||||
rejectByNameFuncs, err := collectRejectByNameFuncs(opts, repo, printer.E)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -541,19 +538,18 @@ func runBackup(ctx context.Context, opts BackupOptions, gopts GlobalOptions, ter
|
|||
|
||||
if !gopts.JSON {
|
||||
if parentSnapshot != nil {
|
||||
progressPrinter.P("using parent snapshot %v\n", parentSnapshot.ID().Str())
|
||||
printer.P("using parent snapshot %v\n", parentSnapshot.ID().Str())
|
||||
} else {
|
||||
progressPrinter.P("no parent snapshot found, will read all files\n")
|
||||
printer.P("no parent snapshot found, will read all files\n")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !gopts.JSON {
|
||||
progressPrinter.V("load index files")
|
||||
printer.V("load index files")
|
||||
}
|
||||
|
||||
bar := newIndexTerminalProgress(msg)
|
||||
err = repo.LoadIndex(ctx, bar)
|
||||
err = repo.LoadIndex(ctx, printer)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -570,7 +566,7 @@ func runBackup(ctx context.Context, opts BackupOptions, gopts GlobalOptions, ter
|
|||
|
||||
messageHandler := func(msg string, args ...interface{}) {
|
||||
if !gopts.JSON {
|
||||
progressPrinter.P(msg, args...)
|
||||
printer.P(msg, args...)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -581,12 +577,12 @@ func runBackup(ctx context.Context, opts BackupOptions, gopts GlobalOptions, ter
|
|||
|
||||
if opts.Stdin || opts.StdinCommand {
|
||||
if !gopts.JSON {
|
||||
progressPrinter.V("read data from stdin")
|
||||
printer.V("read data from stdin")
|
||||
}
|
||||
filename := path.Join("/", opts.StdinFilename)
|
||||
var source io.ReadCloser = os.Stdin
|
||||
source := term.InputRaw()
|
||||
if opts.StdinCommand {
|
||||
source, err = fs.NewCommandReader(ctx, args, globalOptions.stderr)
|
||||
source, err = fs.NewCommandReader(ctx, args, printer.E)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -606,7 +602,7 @@ func runBackup(ctx context.Context, opts BackupOptions, gopts GlobalOptions, ter
|
|||
}
|
||||
|
||||
// rejectFuncs collect functions that can reject items from the backup based on path and file info
|
||||
rejectFuncs, err := collectRejectFuncs(opts, targets, targetFS, msg.E)
|
||||
rejectFuncs, err := collectRejectFuncs(opts, targets, targetFS, printer.E)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -622,11 +618,11 @@ func runBackup(ctx context.Context, opts BackupOptions, gopts GlobalOptions, ter
|
|||
sc := archiver.NewScanner(targetFS)
|
||||
sc.SelectByName = selectByNameFilter
|
||||
sc.Select = selectFilter
|
||||
sc.Error = progressPrinter.ScannerError
|
||||
sc.Error = printer.ScannerError
|
||||
sc.Result = progressReporter.ReportTotal
|
||||
|
||||
if !gopts.JSON {
|
||||
progressPrinter.V("start scan on %v", targets)
|
||||
printer.V("start scan on %v", targets)
|
||||
}
|
||||
wg.Go(func() error { return sc.Scan(cancelCtx, targets) })
|
||||
}
|
||||
|
|
@ -671,7 +667,7 @@ func runBackup(ctx context.Context, opts BackupOptions, gopts GlobalOptions, ter
|
|||
}
|
||||
|
||||
if !gopts.JSON {
|
||||
progressPrinter.V("start backup on %v", targets)
|
||||
printer.V("start backup on %v", targets)
|
||||
}
|
||||
_, id, summary, err := arch.Snapshot(ctx, targets, snapshotOpts)
|
||||
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ package main
|
|||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
|
|
@ -13,11 +12,10 @@ import (
|
|||
"github.com/restic/restic/internal/fs"
|
||||
"github.com/restic/restic/internal/restic"
|
||||
rtest "github.com/restic/restic/internal/test"
|
||||
"github.com/restic/restic/internal/ui"
|
||||
)
|
||||
|
||||
func testRunBackupAssumeFailure(t testing.TB, dir string, target []string, opts BackupOptions, gopts GlobalOptions) error {
|
||||
return withTermStatus(gopts, func(ctx context.Context, term ui.Terminal) error {
|
||||
return withTermStatus(t, gopts, func(ctx context.Context, gopts GlobalOptions) error {
|
||||
t.Logf("backing up %v in %v", target, dir)
|
||||
if dir != "" {
|
||||
cleanup := rtest.Chdir(t, dir)
|
||||
|
|
@ -25,7 +23,7 @@ func testRunBackupAssumeFailure(t testing.TB, dir string, target []string, opts
|
|||
}
|
||||
|
||||
opts.GroupBy = restic.SnapshotGroupByOptions{Host: true, Path: true}
|
||||
return runBackup(ctx, opts, gopts, term, target)
|
||||
return runBackup(ctx, opts, gopts, gopts.term, target)
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -56,13 +54,13 @@ func testBackup(t *testing.T, useFsSnapshot bool) {
|
|||
testListSnapshots(t, env.gopts, 1)
|
||||
|
||||
testRunCheck(t, env.gopts)
|
||||
stat1 := dirStats(env.repo)
|
||||
stat1 := dirStats(t, env.repo)
|
||||
|
||||
// second backup, implicit incremental
|
||||
testRunBackup(t, "", []string{env.testdata}, opts, env.gopts)
|
||||
snapshotIDs := testListSnapshots(t, env.gopts, 2)
|
||||
|
||||
stat2 := dirStats(env.repo)
|
||||
stat2 := dirStats(t, env.repo)
|
||||
if stat2.size > stat1.size+stat1.size/10 {
|
||||
t.Error("repository size has grown by more than 10 percent")
|
||||
}
|
||||
|
|
@ -74,7 +72,7 @@ func testBackup(t *testing.T, useFsSnapshot bool) {
|
|||
testRunBackup(t, "", []string{env.testdata}, opts, env.gopts)
|
||||
snapshotIDs = testListSnapshots(t, env.gopts, 3)
|
||||
|
||||
stat3 := dirStats(env.repo)
|
||||
stat3 := dirStats(t, env.repo)
|
||||
if stat3.size > stat1.size+stat1.size/10 {
|
||||
t.Error("repository size has grown by more than 10 percent")
|
||||
}
|
||||
|
|
@ -85,7 +83,7 @@ func testBackup(t *testing.T, useFsSnapshot bool) {
|
|||
restoredir := filepath.Join(env.base, fmt.Sprintf("restore%d", i))
|
||||
t.Logf("restoring snapshot %v to %v", snapshotID.Str(), restoredir)
|
||||
testRunRestore(t, env.gopts, restoredir, snapshotID.String()+":"+toPathInSnapshot(filepath.Dir(env.testdata)))
|
||||
diff := directoriesContentsDiff(env.testdata, filepath.Join(restoredir, "testdata"))
|
||||
diff := directoriesContentsDiff(t, env.testdata, filepath.Join(restoredir, "testdata"))
|
||||
rtest.Assert(t, diff == "", "directories are not equal: %v", diff)
|
||||
}
|
||||
|
||||
|
|
@ -262,22 +260,17 @@ func TestBackupNonExistingFile(t *testing.T) {
|
|||
|
||||
testSetupBackupData(t, env)
|
||||
|
||||
_ = withRestoreGlobalOptions(func() error {
|
||||
globalOptions.stderr = io.Discard
|
||||
p := filepath.Join(env.testdata, "0", "0", "9")
|
||||
dirs := []string{
|
||||
filepath.Join(p, "0"),
|
||||
filepath.Join(p, "1"),
|
||||
filepath.Join(p, "nonexisting"),
|
||||
filepath.Join(p, "5"),
|
||||
}
|
||||
|
||||
p := filepath.Join(env.testdata, "0", "0", "9")
|
||||
dirs := []string{
|
||||
filepath.Join(p, "0"),
|
||||
filepath.Join(p, "1"),
|
||||
filepath.Join(p, "nonexisting"),
|
||||
filepath.Join(p, "5"),
|
||||
}
|
||||
opts := BackupOptions{}
|
||||
|
||||
opts := BackupOptions{}
|
||||
|
||||
testRunBackup(t, "", dirs, opts, env.gopts)
|
||||
return nil
|
||||
})
|
||||
testRunBackup(t, "", dirs, opts, env.gopts)
|
||||
}
|
||||
|
||||
func TestBackupSelfHealing(t *testing.T) {
|
||||
|
|
@ -438,13 +431,13 @@ func TestIncrementalBackup(t *testing.T) {
|
|||
|
||||
testRunBackup(t, "", []string{datadir}, opts, env.gopts)
|
||||
testRunCheck(t, env.gopts)
|
||||
stat1 := dirStats(env.repo)
|
||||
stat1 := dirStats(t, env.repo)
|
||||
|
||||
rtest.OK(t, appendRandomData(testfile, incrementalSecondWrite))
|
||||
|
||||
testRunBackup(t, "", []string{datadir}, opts, env.gopts)
|
||||
testRunCheck(t, env.gopts)
|
||||
stat2 := dirStats(env.repo)
|
||||
stat2 := dirStats(t, env.repo)
|
||||
if stat2.size-stat1.size > incrementalFirstWrite {
|
||||
t.Errorf("repository size has grown by more than %d bytes", incrementalFirstWrite)
|
||||
}
|
||||
|
|
@ -454,7 +447,7 @@ func TestIncrementalBackup(t *testing.T) {
|
|||
|
||||
testRunBackup(t, "", []string{datadir}, opts, env.gopts)
|
||||
testRunCheck(t, env.gopts)
|
||||
stat3 := dirStats(env.repo)
|
||||
stat3 := dirStats(t, env.repo)
|
||||
if stat3.size-stat2.size > incrementalFirstWrite {
|
||||
t.Errorf("repository size has grown by more than %d bytes", incrementalFirstWrite)
|
||||
}
|
||||
|
|
@ -565,7 +558,7 @@ func TestHardLink(t *testing.T) {
|
|||
restoredir := filepath.Join(env.base, fmt.Sprintf("restore%d", i))
|
||||
t.Logf("restoring snapshot %v to %v", snapshotID.Str(), restoredir)
|
||||
testRunRestore(t, env.gopts, restoredir, snapshotID.String())
|
||||
diff := directoriesContentsDiff(env.testdata, filepath.Join(restoredir, "testdata"))
|
||||
diff := directoriesContentsDiff(t, env.testdata, filepath.Join(restoredir, "testdata"))
|
||||
rtest.Assert(t, diff == "", "directories are not equal %v", diff)
|
||||
|
||||
linkResults := createFileSetPerHardlink(filepath.Join(restoredir, "testdata"))
|
||||
|
|
|
|||
|
|
@ -67,7 +67,7 @@ func TestCollectTargets(t *testing.T) {
|
|||
FilesFromRaw: []string{f3.Name()},
|
||||
}
|
||||
|
||||
targets, err := collectTargets(opts, []string{filepath.Join(dir, "cmdline arg")}, t.Logf)
|
||||
targets, err := collectTargets(opts, []string{filepath.Join(dir, "cmdline arg")}, t.Logf, nil)
|
||||
rtest.OK(t, err)
|
||||
sort.Strings(targets)
|
||||
rtest.Equals(t, expect, targets)
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ import (
|
|||
"github.com/spf13/pflag"
|
||||
)
|
||||
|
||||
func newCacheCommand() *cobra.Command {
|
||||
func newCacheCommand(globalOptions *GlobalOptions) *cobra.Command {
|
||||
var opts CacheOptions
|
||||
|
||||
cmd := &cobra.Command{
|
||||
|
|
@ -34,9 +34,7 @@ Exit status is 1 if there was any error.
|
|||
GroupID: cmdGroupDefault,
|
||||
DisableAutoGenTag: true,
|
||||
RunE: func(_ *cobra.Command, args []string) error {
|
||||
term, cancel := setupTermstatus()
|
||||
defer cancel()
|
||||
return runCache(opts, globalOptions, args, term)
|
||||
return runCache(opts, *globalOptions, args, globalOptions.term)
|
||||
},
|
||||
}
|
||||
|
||||
|
|
@ -58,7 +56,7 @@ func (opts *CacheOptions) AddFlags(f *pflag.FlagSet) {
|
|||
}
|
||||
|
||||
func runCache(opts CacheOptions, gopts GlobalOptions, args []string, term ui.Terminal) error {
|
||||
printer := newTerminalProgressPrinter(false, gopts.verbosity, term)
|
||||
printer := ui.NewProgressPrinter(false, gopts.verbosity, term)
|
||||
|
||||
if len(args) > 0 {
|
||||
return errors.Fatal("the cache command expects no arguments, only options - please see `restic help cache` for usage and flags")
|
||||
|
|
@ -163,7 +161,7 @@ func runCache(opts CacheOptions, gopts GlobalOptions, args []string, term ui.Ter
|
|||
})
|
||||
}
|
||||
|
||||
_ = tab.Write(globalOptions.stdout)
|
||||
_ = tab.Write(gopts.term.OutputWriter())
|
||||
printer.S("%d cache dirs in %s", len(dirs), cachedir)
|
||||
|
||||
return nil
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ import (
|
|||
|
||||
var catAllowedCmds = []string{"config", "index", "snapshot", "key", "masterkey", "lock", "pack", "blob", "tree"}
|
||||
|
||||
func newCatCommand() *cobra.Command {
|
||||
func newCatCommand(globalOptions *GlobalOptions) *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "cat [flags] [masterkey|config|pack ID|blob ID|snapshot ID|index ID|key ID|lock ID|tree snapshot:subfolder]",
|
||||
Short: "Print internal objects to stdout",
|
||||
|
|
@ -34,9 +34,7 @@ Exit status is 12 if the password is incorrect.
|
|||
GroupID: cmdGroupDefault,
|
||||
DisableAutoGenTag: true,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
term, cancel := setupTermstatus()
|
||||
defer cancel()
|
||||
return runCat(cmd.Context(), globalOptions, args, term)
|
||||
return runCat(cmd.Context(), *globalOptions, args, globalOptions.term)
|
||||
},
|
||||
ValidArgs: catAllowedCmds,
|
||||
}
|
||||
|
|
@ -67,7 +65,7 @@ func validateCatArgs(args []string) error {
|
|||
}
|
||||
|
||||
func runCat(ctx context.Context, gopts GlobalOptions, args []string, term ui.Terminal) error {
|
||||
printer := newTerminalProgressPrinter(gopts.JSON, gopts.verbosity, term)
|
||||
printer := ui.NewProgressPrinter(gopts.JSON, gopts.verbosity, term)
|
||||
|
||||
if err := validateCatArgs(args); err != nil {
|
||||
return err
|
||||
|
|
@ -170,8 +168,7 @@ func runCat(ctx context.Context, gopts GlobalOptions, args []string, term ui.Ter
|
|||
return err
|
||||
|
||||
case "blob":
|
||||
bar := newIndexTerminalProgress(printer)
|
||||
err = repo.LoadIndex(ctx, bar)
|
||||
err = repo.LoadIndex(ctx, printer)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -198,8 +195,7 @@ func runCat(ctx context.Context, gopts GlobalOptions, args []string, term ui.Ter
|
|||
return errors.Fatalf("could not find snapshot: %v", err)
|
||||
}
|
||||
|
||||
bar := newIndexTerminalProgress(printer)
|
||||
err = repo.LoadIndex(ctx, bar)
|
||||
err = repo.LoadIndex(ctx, printer)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ import (
|
|||
"github.com/restic/restic/internal/ui/progress"
|
||||
)
|
||||
|
||||
func newCheckCommand() *cobra.Command {
|
||||
func newCheckCommand(globalOptions *GlobalOptions) *cobra.Command {
|
||||
var opts CheckOptions
|
||||
cmd := &cobra.Command{
|
||||
Use: "check [flags]",
|
||||
|
|
@ -46,14 +46,12 @@ Exit status is 12 if the password is incorrect.
|
|||
GroupID: cmdGroupDefault,
|
||||
DisableAutoGenTag: true,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
term, cancel := setupTermstatus()
|
||||
defer cancel()
|
||||
summary, err := runCheck(cmd.Context(), opts, globalOptions, args, term)
|
||||
summary, err := runCheck(cmd.Context(), opts, *globalOptions, args, globalOptions.term)
|
||||
if globalOptions.JSON {
|
||||
if err != nil && summary.NumErrors == 0 {
|
||||
summary.NumErrors = 1
|
||||
}
|
||||
term.Print(ui.ToJSONString(summary))
|
||||
globalOptions.term.Print(ui.ToJSONString(summary))
|
||||
}
|
||||
return err
|
||||
},
|
||||
|
|
@ -227,7 +225,7 @@ func runCheck(ctx context.Context, opts CheckOptions, gopts GlobalOptions, args
|
|||
|
||||
var printer progress.Printer
|
||||
if !gopts.JSON {
|
||||
printer = newTerminalProgressPrinter(gopts.JSON, gopts.verbosity, term)
|
||||
printer = ui.NewProgressPrinter(gopts.JSON, gopts.verbosity, term)
|
||||
} else {
|
||||
printer = newJSONErrorPrinter(term)
|
||||
}
|
||||
|
|
@ -251,8 +249,7 @@ func runCheck(ctx context.Context, opts CheckOptions, gopts GlobalOptions, args
|
|||
}
|
||||
|
||||
printer.P("load indexes\n")
|
||||
bar := newIndexTerminalProgress(printer)
|
||||
hints, errs := chkr.LoadIndex(ctx, bar)
|
||||
hints, errs := chkr.LoadIndex(ctx, printer)
|
||||
if ctx.Err() != nil {
|
||||
return summary, ctx.Err()
|
||||
}
|
||||
|
|
@ -540,5 +537,6 @@ func (p *jsonErrorPrinter) E(msg string, args ...interface{}) {
|
|||
}
|
||||
func (*jsonErrorPrinter) S(_ string, _ ...interface{}) {}
|
||||
func (*jsonErrorPrinter) P(_ string, _ ...interface{}) {}
|
||||
func (*jsonErrorPrinter) PT(_ string, _ ...interface{}) {}
|
||||
func (*jsonErrorPrinter) V(_ string, _ ...interface{}) {}
|
||||
func (*jsonErrorPrinter) VV(_ string, _ ...interface{}) {}
|
||||
|
|
|
|||
|
|
@ -1,17 +1,15 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
rtest "github.com/restic/restic/internal/test"
|
||||
"github.com/restic/restic/internal/ui"
|
||||
)
|
||||
|
||||
func testRunCheck(t testing.TB, gopts GlobalOptions) {
|
||||
t.Helper()
|
||||
output, err := testRunCheckOutput(gopts, true)
|
||||
output, err := testRunCheckOutput(t, gopts, true)
|
||||
if err != nil {
|
||||
t.Error(output)
|
||||
t.Fatalf("unexpected error: %+v", err)
|
||||
|
|
@ -20,19 +18,17 @@ func testRunCheck(t testing.TB, gopts GlobalOptions) {
|
|||
|
||||
func testRunCheckMustFail(t testing.TB, gopts GlobalOptions) {
|
||||
t.Helper()
|
||||
_, err := testRunCheckOutput(gopts, false)
|
||||
_, err := testRunCheckOutput(t, gopts, false)
|
||||
rtest.Assert(t, err != nil, "expected non nil error after check of damaged repository")
|
||||
}
|
||||
|
||||
func testRunCheckOutput(gopts GlobalOptions, checkUnused bool) (string, error) {
|
||||
buf := bytes.NewBuffer(nil)
|
||||
gopts.stdout = buf
|
||||
err := withTermStatus(gopts, func(ctx context.Context, term ui.Terminal) error {
|
||||
func testRunCheckOutput(t testing.TB, gopts GlobalOptions, checkUnused bool) (string, error) {
|
||||
buf, err := withCaptureStdout(t, gopts, func(ctx context.Context, gopts GlobalOptions) error {
|
||||
opts := CheckOptions{
|
||||
ReadData: true,
|
||||
CheckUnused: checkUnused,
|
||||
}
|
||||
_, err := runCheck(context.TODO(), opts, gopts, nil, term)
|
||||
_, err := runCheck(context.TODO(), opts, gopts, nil, gopts.term)
|
||||
return err
|
||||
})
|
||||
return buf.String(), err
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ import (
|
|||
"github.com/spf13/pflag"
|
||||
)
|
||||
|
||||
func newCopyCommand() *cobra.Command {
|
||||
func newCopyCommand(globalOptions *GlobalOptions) *cobra.Command {
|
||||
var opts CopyOptions
|
||||
cmd := &cobra.Command{
|
||||
Use: "copy [flags] [snapshotID ...]",
|
||||
|
|
@ -48,9 +48,7 @@ Exit status is 12 if the password is incorrect.
|
|||
GroupID: cmdGroupDefault,
|
||||
DisableAutoGenTag: true,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
term, cancel := setupTermstatus()
|
||||
defer cancel()
|
||||
return runCopy(cmd.Context(), opts, globalOptions, args, term)
|
||||
return runCopy(cmd.Context(), opts, *globalOptions, args, globalOptions.term)
|
||||
},
|
||||
}
|
||||
|
||||
|
|
@ -70,8 +68,8 @@ func (opts *CopyOptions) AddFlags(f *pflag.FlagSet) {
|
|||
}
|
||||
|
||||
func runCopy(ctx context.Context, opts CopyOptions, gopts GlobalOptions, args []string, term ui.Terminal) error {
|
||||
printer := newTerminalProgressPrinter(false, gopts.verbosity, term)
|
||||
secondaryGopts, isFromRepo, err := fillSecondaryGlobalOpts(ctx, opts.secondaryRepoOptions, gopts, "destination", printer)
|
||||
printer := ui.NewProgressPrinter(false, gopts.verbosity, term)
|
||||
secondaryGopts, isFromRepo, err := fillSecondaryGlobalOpts(ctx, opts.secondaryRepoOptions, gopts, "destination")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -103,13 +101,11 @@ func runCopy(ctx context.Context, opts CopyOptions, gopts GlobalOptions, args []
|
|||
}
|
||||
|
||||
debug.Log("Loading source index")
|
||||
bar := newIndexTerminalProgress(printer)
|
||||
if err := srcRepo.LoadIndex(ctx, bar); err != nil {
|
||||
if err := srcRepo.LoadIndex(ctx, printer); err != nil {
|
||||
return err
|
||||
}
|
||||
bar = newIndexTerminalProgress(printer)
|
||||
debug.Log("Loading destination index")
|
||||
if err := dstRepo.LoadIndex(ctx, bar); err != nil {
|
||||
if err := dstRepo.LoadIndex(ctx, printer); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@ import (
|
|||
"testing"
|
||||
|
||||
rtest "github.com/restic/restic/internal/test"
|
||||
"github.com/restic/restic/internal/ui"
|
||||
)
|
||||
|
||||
func testRunCopy(t testing.TB, srcGopts GlobalOptions, dstGopts GlobalOptions) {
|
||||
|
|
@ -23,8 +22,8 @@ func testRunCopy(t testing.TB, srcGopts GlobalOptions, dstGopts GlobalOptions) {
|
|||
},
|
||||
}
|
||||
|
||||
rtest.OK(t, withTermStatus(gopts, func(ctx context.Context, term ui.Terminal) error {
|
||||
return runCopy(context.TODO(), copyOpts, gopts, nil, term)
|
||||
rtest.OK(t, withTermStatus(t, gopts, func(ctx context.Context, gopts GlobalOptions) error {
|
||||
return runCopy(context.TODO(), copyOpts, gopts, nil, gopts.term)
|
||||
}))
|
||||
}
|
||||
|
||||
|
|
@ -48,8 +47,8 @@ func TestCopy(t *testing.T) {
|
|||
copiedSnapshotIDs := testListSnapshots(t, env2.gopts, 3)
|
||||
|
||||
// Check that the copies size seems reasonable
|
||||
stat := dirStats(env.repo)
|
||||
stat2 := dirStats(env2.repo)
|
||||
stat := dirStats(t, env.repo)
|
||||
stat2 := dirStats(t, env2.repo)
|
||||
sizeDiff := int64(stat.size) - int64(stat2.size)
|
||||
if sizeDiff < 0 {
|
||||
sizeDiff = -sizeDiff
|
||||
|
|
@ -72,7 +71,7 @@ func TestCopy(t *testing.T) {
|
|||
testRunRestore(t, env2.gopts, restoredir, snapshotID.String())
|
||||
foundMatch := false
|
||||
for cmpdir := range origRestores {
|
||||
diff := directoriesContentsDiff(restoredir, cmpdir)
|
||||
diff := directoriesContentsDiff(t, restoredir, cmpdir)
|
||||
if diff == "" {
|
||||
delete(origRestores, cmpdir)
|
||||
foundMatch = true
|
||||
|
|
|
|||
|
|
@ -31,25 +31,25 @@ import (
|
|||
"github.com/restic/restic/internal/ui/progress"
|
||||
)
|
||||
|
||||
func registerDebugCommand(cmd *cobra.Command) {
|
||||
func registerDebugCommand(cmd *cobra.Command, globalOptions *GlobalOptions) {
|
||||
cmd.AddCommand(
|
||||
newDebugCommand(),
|
||||
newDebugCommand(globalOptions),
|
||||
)
|
||||
}
|
||||
|
||||
func newDebugCommand() *cobra.Command {
|
||||
func newDebugCommand(globalOptions *GlobalOptions) *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "debug",
|
||||
Short: "Debug commands",
|
||||
GroupID: cmdGroupDefault,
|
||||
DisableAutoGenTag: true,
|
||||
}
|
||||
cmd.AddCommand(newDebugDumpCommand())
|
||||
cmd.AddCommand(newDebugExamineCommand())
|
||||
cmd.AddCommand(newDebugDumpCommand(globalOptions))
|
||||
cmd.AddCommand(newDebugExamineCommand(globalOptions))
|
||||
return cmd
|
||||
}
|
||||
|
||||
func newDebugDumpCommand() *cobra.Command {
|
||||
func newDebugDumpCommand(globalOptions *GlobalOptions) *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "dump [indexes|snapshots|all|packs]",
|
||||
Short: "Dump data structures",
|
||||
|
|
@ -68,15 +68,13 @@ Exit status is 12 if the password is incorrect.
|
|||
`,
|
||||
DisableAutoGenTag: true,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
term, cancel := setupTermstatus()
|
||||
defer cancel()
|
||||
return runDebugDump(cmd.Context(), globalOptions, args, term)
|
||||
return runDebugDump(cmd.Context(), *globalOptions, args, globalOptions.term)
|
||||
},
|
||||
}
|
||||
return cmd
|
||||
}
|
||||
|
||||
func newDebugExamineCommand() *cobra.Command {
|
||||
func newDebugExamineCommand(globalOptions *GlobalOptions) *cobra.Command {
|
||||
var opts DebugExamineOptions
|
||||
|
||||
cmd := &cobra.Command{
|
||||
|
|
@ -84,9 +82,7 @@ func newDebugExamineCommand() *cobra.Command {
|
|||
Short: "Examine a pack file",
|
||||
DisableAutoGenTag: true,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
term, cancel := setupTermstatus()
|
||||
defer cancel()
|
||||
return runDebugExamine(cmd.Context(), globalOptions, opts, args, term)
|
||||
return runDebugExamine(cmd.Context(), *globalOptions, opts, args, globalOptions.term)
|
||||
},
|
||||
}
|
||||
|
||||
|
|
@ -188,7 +184,7 @@ func dumpIndexes(ctx context.Context, repo restic.ListerLoaderUnpacked, wr io.Wr
|
|||
}
|
||||
|
||||
func runDebugDump(ctx context.Context, gopts GlobalOptions, args []string, term ui.Terminal) error {
|
||||
printer := newTerminalProgressPrinter(false, gopts.verbosity, term)
|
||||
printer := ui.NewProgressPrinter(false, gopts.verbosity, term)
|
||||
|
||||
if len(args) != 1 {
|
||||
return errors.Fatal("type not specified")
|
||||
|
|
@ -204,20 +200,20 @@ func runDebugDump(ctx context.Context, gopts GlobalOptions, args []string, term
|
|||
|
||||
switch tpe {
|
||||
case "indexes":
|
||||
return dumpIndexes(ctx, repo, globalOptions.stdout, printer)
|
||||
return dumpIndexes(ctx, repo, gopts.term.OutputWriter(), printer)
|
||||
case "snapshots":
|
||||
return debugPrintSnapshots(ctx, repo, globalOptions.stdout)
|
||||
return debugPrintSnapshots(ctx, repo, gopts.term.OutputWriter())
|
||||
case "packs":
|
||||
return printPacks(ctx, repo, globalOptions.stdout, printer)
|
||||
return printPacks(ctx, repo, gopts.term.OutputWriter(), printer)
|
||||
case "all":
|
||||
printer.S("snapshots:")
|
||||
err := debugPrintSnapshots(ctx, repo, globalOptions.stdout)
|
||||
err := debugPrintSnapshots(ctx, repo, gopts.term.OutputWriter())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
printer.S("indexes:")
|
||||
err = dumpIndexes(ctx, repo, globalOptions.stdout, printer)
|
||||
err = dumpIndexes(ctx, repo, gopts.term.OutputWriter(), printer)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -459,7 +455,7 @@ func storePlainBlob(id restic.ID, prefix string, plain []byte, printer progress.
|
|||
}
|
||||
|
||||
func runDebugExamine(ctx context.Context, gopts GlobalOptions, opts DebugExamineOptions, args []string, term ui.Terminal) error {
|
||||
printer := newTerminalProgressPrinter(false, gopts.verbosity, term)
|
||||
printer := ui.NewProgressPrinter(false, gopts.verbosity, term)
|
||||
|
||||
if opts.ExtractPack && gopts.NoLock {
|
||||
return fmt.Errorf("--extract-pack and --no-lock are mutually exclusive")
|
||||
|
|
@ -488,8 +484,7 @@ func runDebugExamine(ctx context.Context, gopts GlobalOptions, opts DebugExamine
|
|||
return errors.Fatal("no pack files to examine")
|
||||
}
|
||||
|
||||
bar := newIndexTerminalProgress(printer)
|
||||
err = repo.LoadIndex(ctx, bar)
|
||||
err = repo.LoadIndex(ctx, printer)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,6 @@ package main
|
|||
|
||||
import "github.com/spf13/cobra"
|
||||
|
||||
func registerDebugCommand(_ *cobra.Command) {
|
||||
func registerDebugCommand(_ *cobra.Command, _ *GlobalOptions) {
|
||||
// No commands to register in non-debug mode
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ import (
|
|||
"github.com/spf13/pflag"
|
||||
)
|
||||
|
||||
func newDiffCommand() *cobra.Command {
|
||||
func newDiffCommand(globalOptions *GlobalOptions) *cobra.Command {
|
||||
var opts DiffOptions
|
||||
|
||||
cmd := &cobra.Command{
|
||||
|
|
@ -52,9 +52,7 @@ Exit status is 12 if the password is incorrect.
|
|||
GroupID: cmdGroupDefault,
|
||||
DisableAutoGenTag: true,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
term, cancel := setupTermstatus()
|
||||
defer cancel()
|
||||
return runDiff(cmd.Context(), opts, globalOptions, args, term)
|
||||
return runDiff(cmd.Context(), opts, *globalOptions, args, globalOptions.term)
|
||||
},
|
||||
}
|
||||
|
||||
|
|
@ -367,7 +365,7 @@ func runDiff(ctx context.Context, opts DiffOptions, gopts GlobalOptions, args []
|
|||
return errors.Fatalf("specify two snapshot IDs")
|
||||
}
|
||||
|
||||
printer := newTerminalProgressPrinter(gopts.JSON, gopts.verbosity, term)
|
||||
printer := ui.NewProgressPrinter(gopts.JSON, gopts.verbosity, term)
|
||||
|
||||
ctx, repo, unlock, err := openWithReadLock(ctx, gopts, gopts.NoLock, printer)
|
||||
if err != nil {
|
||||
|
|
@ -393,8 +391,7 @@ func runDiff(ctx context.Context, opts DiffOptions, gopts GlobalOptions, args []
|
|||
if !gopts.JSON {
|
||||
printer.P("comparing snapshot %v to %v:\n\n", sn1.ID().Str(), sn2.ID().Str())
|
||||
}
|
||||
bar := newIndexTerminalProgress(printer)
|
||||
if err = repo.LoadIndex(ctx, bar); err != nil {
|
||||
if err = repo.LoadIndex(ctx, printer); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
|
@ -426,7 +423,7 @@ func runDiff(ctx context.Context, opts DiffOptions, gopts GlobalOptions, args []
|
|||
}
|
||||
|
||||
if gopts.JSON {
|
||||
enc := json.NewEncoder(globalOptions.stdout)
|
||||
enc := json.NewEncoder(gopts.term.OutputWriter())
|
||||
c.printChange = func(change *Change) {
|
||||
err := enc.Encode(change)
|
||||
if err != nil {
|
||||
|
|
@ -460,7 +457,7 @@ func runDiff(ctx context.Context, opts DiffOptions, gopts GlobalOptions, args []
|
|||
updateBlobs(repo, stats.BlobsAfter.Sub(both).Sub(stats.BlobsCommon), &stats.Added, printer.E)
|
||||
|
||||
if gopts.JSON {
|
||||
err := json.NewEncoder(globalOptions.stdout).Encode(stats)
|
||||
err := json.NewEncoder(gopts.term.OutputWriter()).Encode(stats)
|
||||
if err != nil {
|
||||
printer.E("JSON encode failed: %v", err)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,17 +12,14 @@ import (
|
|||
"testing"
|
||||
|
||||
rtest "github.com/restic/restic/internal/test"
|
||||
"github.com/restic/restic/internal/ui"
|
||||
)
|
||||
|
||||
func testRunDiffOutput(gopts GlobalOptions, firstSnapshotID string, secondSnapshotID string) (string, error) {
|
||||
buf, err := withCaptureStdout(gopts, func(gopts GlobalOptions) error {
|
||||
func testRunDiffOutput(t testing.TB, gopts GlobalOptions, firstSnapshotID string, secondSnapshotID string) (string, error) {
|
||||
buf, err := withCaptureStdout(t, gopts, func(ctx context.Context, gopts GlobalOptions) error {
|
||||
opts := DiffOptions{
|
||||
ShowMetadata: false,
|
||||
}
|
||||
return withTermStatus(gopts, func(ctx context.Context, term ui.Terminal) error {
|
||||
return runDiff(ctx, opts, gopts, []string{firstSnapshotID, secondSnapshotID}, term)
|
||||
})
|
||||
return runDiff(ctx, opts, gopts, []string{firstSnapshotID, secondSnapshotID}, gopts.term)
|
||||
})
|
||||
return buf.String(), err
|
||||
}
|
||||
|
|
@ -126,10 +123,10 @@ func TestDiff(t *testing.T) {
|
|||
|
||||
// quiet suppresses the diff output except for the summary
|
||||
env.gopts.Quiet = false
|
||||
_, err := testRunDiffOutput(env.gopts, "", secondSnapshotID)
|
||||
_, err := testRunDiffOutput(t, env.gopts, "", secondSnapshotID)
|
||||
rtest.Assert(t, err != nil, "expected error on invalid snapshot id")
|
||||
|
||||
out, err := testRunDiffOutput(env.gopts, firstSnapshotID, secondSnapshotID)
|
||||
out, err := testRunDiffOutput(t, env.gopts, firstSnapshotID, secondSnapshotID)
|
||||
rtest.OK(t, err)
|
||||
|
||||
for _, pattern := range diffOutputRegexPatterns {
|
||||
|
|
@ -140,7 +137,7 @@ func TestDiff(t *testing.T) {
|
|||
|
||||
// check quiet output
|
||||
env.gopts.Quiet = true
|
||||
outQuiet, err := testRunDiffOutput(env.gopts, firstSnapshotID, secondSnapshotID)
|
||||
outQuiet, err := testRunDiffOutput(t, env.gopts, firstSnapshotID, secondSnapshotID)
|
||||
rtest.OK(t, err)
|
||||
|
||||
rtest.Assert(t, len(outQuiet) < len(out), "expected shorter output on quiet mode %v vs. %v", len(outQuiet), len(out))
|
||||
|
|
@ -157,7 +154,7 @@ func TestDiffJSON(t *testing.T) {
|
|||
// quiet suppresses the diff output except for the summary
|
||||
env.gopts.Quiet = false
|
||||
env.gopts.JSON = true
|
||||
out, err := testRunDiffOutput(env.gopts, firstSnapshotID, secondSnapshotID)
|
||||
out, err := testRunDiffOutput(t, env.gopts, firstSnapshotID, secondSnapshotID)
|
||||
rtest.OK(t, err)
|
||||
|
||||
var stat DiffStatsContainer
|
||||
|
|
@ -184,7 +181,7 @@ func TestDiffJSON(t *testing.T) {
|
|||
|
||||
// check quiet output
|
||||
env.gopts.Quiet = true
|
||||
outQuiet, err := testRunDiffOutput(env.gopts, firstSnapshotID, secondSnapshotID)
|
||||
outQuiet, err := testRunDiffOutput(t, env.gopts, firstSnapshotID, secondSnapshotID)
|
||||
rtest.OK(t, err)
|
||||
|
||||
stat = DiffStatsContainer{}
|
||||
|
|
|
|||
|
|
@ -11,14 +11,13 @@ import (
|
|||
"github.com/restic/restic/internal/dump"
|
||||
"github.com/restic/restic/internal/errors"
|
||||
"github.com/restic/restic/internal/restic"
|
||||
"github.com/restic/restic/internal/terminal"
|
||||
"github.com/restic/restic/internal/ui"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
)
|
||||
|
||||
func newDumpCommand() *cobra.Command {
|
||||
func newDumpCommand(globalOptions *GlobalOptions) *cobra.Command {
|
||||
var opts DumpOptions
|
||||
cmd := &cobra.Command{
|
||||
Use: "dump [flags] snapshotID file",
|
||||
|
|
@ -48,9 +47,7 @@ Exit status is 12 if the password is incorrect.
|
|||
GroupID: cmdGroupDefault,
|
||||
DisableAutoGenTag: true,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
term, cancel := setupTermstatus()
|
||||
defer cancel()
|
||||
return runDump(cmd.Context(), opts, globalOptions, args, term)
|
||||
return runDump(cmd.Context(), opts, *globalOptions, args, globalOptions.term)
|
||||
},
|
||||
}
|
||||
|
||||
|
|
@ -133,7 +130,7 @@ func runDump(ctx context.Context, opts DumpOptions, gopts GlobalOptions, args []
|
|||
return errors.Fatal("no file and no snapshot ID specified")
|
||||
}
|
||||
|
||||
printer := newTerminalProgressPrinter(gopts.JSON, gopts.verbosity, term)
|
||||
printer := ui.NewProgressPrinter(gopts.JSON, gopts.verbosity, term)
|
||||
|
||||
switch opts.Archive {
|
||||
case "tar", "zip":
|
||||
|
|
@ -163,8 +160,7 @@ func runDump(ctx context.Context, opts DumpOptions, gopts GlobalOptions, args []
|
|||
return errors.Fatalf("failed to find snapshot: %v", err)
|
||||
}
|
||||
|
||||
bar := newIndexTerminalProgress(printer)
|
||||
err = repo.LoadIndex(ctx, bar)
|
||||
err = repo.LoadIndex(ctx, printer)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -180,7 +176,7 @@ func runDump(ctx context.Context, opts DumpOptions, gopts GlobalOptions, args []
|
|||
}
|
||||
|
||||
outputFileWriter := term.OutputRaw()
|
||||
canWriteArchiveFunc := checkStdoutArchive
|
||||
canWriteArchiveFunc := checkStdoutArchive(term)
|
||||
|
||||
if opts.Target != "" {
|
||||
file, err := os.Create(opts.Target)
|
||||
|
|
@ -204,9 +200,9 @@ func runDump(ctx context.Context, opts DumpOptions, gopts GlobalOptions, args []
|
|||
return nil
|
||||
}
|
||||
|
||||
func checkStdoutArchive() error {
|
||||
if terminal.StdoutIsTerminal() {
|
||||
return fmt.Errorf("stdout is the terminal, please redirect output")
|
||||
func checkStdoutArchive(term ui.Terminal) func() error {
|
||||
if term.OutputIsTerminal() {
|
||||
return func() error { return fmt.Errorf("stdout is the terminal, please redirect output") }
|
||||
}
|
||||
return nil
|
||||
return func() error { return nil }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,6 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/restic/restic/internal/errors"
|
||||
"github.com/restic/restic/internal/feature"
|
||||
"github.com/restic/restic/internal/ui/table"
|
||||
|
|
@ -10,7 +8,7 @@ import (
|
|||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func newFeaturesCommand() *cobra.Command {
|
||||
func newFeaturesCommand(globalOptions *GlobalOptions) *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "features",
|
||||
Short: "Print list of feature flags",
|
||||
|
|
@ -39,7 +37,7 @@ Exit status is 1 if there was any error.
|
|||
return errors.Fatal("the feature command expects no arguments")
|
||||
}
|
||||
|
||||
fmt.Printf("All Feature Flags:\n")
|
||||
globalOptions.term.Print("All Feature Flags:\n")
|
||||
flags := feature.Flag.List()
|
||||
|
||||
tab := table.New()
|
||||
|
|
@ -51,7 +49,7 @@ Exit status is 1 if there was any error.
|
|||
for _, flag := range flags {
|
||||
tab.AddRow(flag)
|
||||
}
|
||||
return tab.Write(globalOptions.stdout)
|
||||
return tab.Write(globalOptions.term.OutputWriter())
|
||||
},
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ import (
|
|||
"github.com/restic/restic/internal/walker"
|
||||
)
|
||||
|
||||
func newFindCommand() *cobra.Command {
|
||||
func newFindCommand(globalOptions *GlobalOptions) *cobra.Command {
|
||||
var opts FindOptions
|
||||
|
||||
cmd := &cobra.Command{
|
||||
|
|
@ -51,9 +51,7 @@ Exit status is 12 if the password is incorrect.
|
|||
GroupID: cmdGroupDefault,
|
||||
DisableAutoGenTag: true,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
term, cancel := setupTermstatus()
|
||||
defer cancel()
|
||||
return runFind(cmd.Context(), opts, globalOptions, args, term)
|
||||
return runFind(cmd.Context(), opts, *globalOptions, args, globalOptions.term)
|
||||
},
|
||||
}
|
||||
|
||||
|
|
@ -584,7 +582,7 @@ func runFind(ctx context.Context, opts FindOptions, gopts GlobalOptions, args []
|
|||
return errors.Fatal("wrong number of arguments")
|
||||
}
|
||||
|
||||
printer := newTerminalProgressPrinter(gopts.JSON, gopts.verbosity, term)
|
||||
printer := ui.NewProgressPrinter(gopts.JSON, gopts.verbosity, term)
|
||||
|
||||
var err error
|
||||
pat := findPattern{pattern: args}
|
||||
|
|
@ -625,8 +623,7 @@ func runFind(ctx context.Context, opts FindOptions, gopts GlobalOptions, args []
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
bar := newIndexTerminalProgress(printer)
|
||||
if err = repo.LoadIndex(ctx, bar); err != nil {
|
||||
if err = repo.LoadIndex(ctx, printer); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -8,16 +8,13 @@ import (
|
|||
"time"
|
||||
|
||||
rtest "github.com/restic/restic/internal/test"
|
||||
"github.com/restic/restic/internal/ui"
|
||||
)
|
||||
|
||||
func testRunFind(t testing.TB, wantJSON bool, opts FindOptions, gopts GlobalOptions, pattern string) []byte {
|
||||
buf, err := withCaptureStdout(gopts, func(gopts GlobalOptions) error {
|
||||
buf, err := withCaptureStdout(t, gopts, func(ctx context.Context, gopts GlobalOptions) error {
|
||||
gopts.JSON = wantJSON
|
||||
|
||||
return withTermStatus(gopts, func(ctx context.Context, term ui.Terminal) error {
|
||||
return runFind(ctx, opts, gopts, []string{pattern}, term)
|
||||
})
|
||||
return runFind(ctx, opts, gopts, []string{pattern}, gopts.term)
|
||||
})
|
||||
rtest.OK(t, err)
|
||||
return buf.Bytes()
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ import (
|
|||
"github.com/spf13/pflag"
|
||||
)
|
||||
|
||||
func newForgetCommand() *cobra.Command {
|
||||
func newForgetCommand(globalOptions *GlobalOptions) *cobra.Command {
|
||||
var opts ForgetOptions
|
||||
var pruneOpts PruneOptions
|
||||
|
||||
|
|
@ -49,9 +49,7 @@ Exit status is 12 if the password is incorrect.
|
|||
GroupID: cmdGroupDefault,
|
||||
DisableAutoGenTag: true,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
term, cancel := setupTermstatus()
|
||||
defer cancel()
|
||||
return runForget(cmd.Context(), opts, pruneOpts, globalOptions, term, args)
|
||||
return runForget(cmd.Context(), opts, pruneOpts, *globalOptions, globalOptions.term, args)
|
||||
},
|
||||
}
|
||||
|
||||
|
|
@ -188,7 +186,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")
|
||||
}
|
||||
|
||||
printer := newTerminalProgressPrinter(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)
|
||||
if err != nil {
|
||||
return err
|
||||
|
|
@ -253,7 +251,7 @@ func runForget(ctx context.Context, opts ForgetOptions, pruneOptions PruneOption
|
|||
}
|
||||
|
||||
if gopts.Verbose >= 1 && !gopts.JSON {
|
||||
err = PrintSnapshotGroupHeader(globalOptions.stdout, k)
|
||||
err = PrintSnapshotGroupHeader(gopts.term.OutputWriter(), k)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -276,7 +274,7 @@ func runForget(ctx context.Context, opts ForgetOptions, pruneOptions PruneOption
|
|||
}
|
||||
if len(keep) != 0 && !gopts.Quiet && !gopts.JSON {
|
||||
printer.P("keep %d snapshots:\n", len(keep))
|
||||
if err := PrintSnapshots(globalOptions.stdout, keep, reasons, opts.Compact); err != nil {
|
||||
if err := PrintSnapshots(gopts.term.OutputWriter(), keep, reasons, opts.Compact); err != nil {
|
||||
return err
|
||||
}
|
||||
printer.P("\n")
|
||||
|
|
@ -285,7 +283,7 @@ func runForget(ctx context.Context, opts ForgetOptions, pruneOptions PruneOption
|
|||
|
||||
if len(remove) != 0 && !gopts.Quiet && !gopts.JSON {
|
||||
printer.P("remove %d snapshots:\n", len(remove))
|
||||
if err := PrintSnapshots(globalOptions.stdout, remove, nil, opts.Compact); err != nil {
|
||||
if err := PrintSnapshots(gopts.term.OutputWriter(), remove, nil, opts.Compact); err != nil {
|
||||
return err
|
||||
}
|
||||
printer.P("\n")
|
||||
|
|
@ -330,7 +328,7 @@ func runForget(ctx context.Context, opts ForgetOptions, pruneOptions PruneOption
|
|||
}
|
||||
|
||||
if gopts.JSON && len(jsonGroups) > 0 {
|
||||
err = printJSONForget(globalOptions.stdout, jsonGroups)
|
||||
err = printJSONForget(gopts.term.OutputWriter(), jsonGroups)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,20 +8,19 @@ import (
|
|||
|
||||
"github.com/restic/restic/internal/restic"
|
||||
rtest "github.com/restic/restic/internal/test"
|
||||
"github.com/restic/restic/internal/ui"
|
||||
)
|
||||
|
||||
func testRunForgetMayFail(gopts GlobalOptions, opts ForgetOptions, args ...string) error {
|
||||
func testRunForgetMayFail(t testing.TB, gopts GlobalOptions, opts ForgetOptions, args ...string) error {
|
||||
pruneOpts := PruneOptions{
|
||||
MaxUnused: "5%",
|
||||
}
|
||||
return withTermStatus(gopts, func(ctx context.Context, term ui.Terminal) error {
|
||||
return runForget(context.TODO(), opts, pruneOpts, gopts, term, args)
|
||||
return withTermStatus(t, gopts, func(ctx context.Context, gopts GlobalOptions) error {
|
||||
return runForget(context.TODO(), opts, pruneOpts, gopts, gopts.term, args)
|
||||
})
|
||||
}
|
||||
|
||||
func testRunForget(t testing.TB, gopts GlobalOptions, opts ForgetOptions, args ...string) {
|
||||
rtest.OK(t, testRunForgetMayFail(gopts, opts, args...))
|
||||
rtest.OK(t, testRunForgetMayFail(t, gopts, opts, args...))
|
||||
}
|
||||
|
||||
func TestRunForgetSafetyNet(t *testing.T) {
|
||||
|
|
@ -38,20 +37,20 @@ func TestRunForgetSafetyNet(t *testing.T) {
|
|||
testListSnapshots(t, env.gopts, 2)
|
||||
|
||||
// --keep-tags invalid
|
||||
err := testRunForgetMayFail(env.gopts, ForgetOptions{
|
||||
err := testRunForgetMayFail(t, env.gopts, ForgetOptions{
|
||||
KeepTags: restic.TagLists{restic.TagList{"invalid"}},
|
||||
GroupBy: restic.SnapshotGroupByOptions{Host: true, Path: true},
|
||||
})
|
||||
rtest.Assert(t, strings.Contains(err.Error(), `refusing to delete last snapshot of snapshot group "host example, path`), "wrong error message got %v", err)
|
||||
|
||||
// disallow `forget --unsafe-allow-remove-all`
|
||||
err = testRunForgetMayFail(env.gopts, ForgetOptions{
|
||||
err = testRunForgetMayFail(t, env.gopts, ForgetOptions{
|
||||
UnsafeAllowRemoveAll: true,
|
||||
})
|
||||
rtest.Assert(t, strings.Contains(err.Error(), `--unsafe-allow-remove-all is not allowed unless a snapshot filter option is specified`), "wrong error message got %v", err)
|
||||
|
||||
// disallow `forget` without options
|
||||
err = testRunForgetMayFail(env.gopts, ForgetOptions{})
|
||||
err = testRunForgetMayFail(t, env.gopts, ForgetOptions{})
|
||||
rtest.Assert(t, strings.Contains(err.Error(), `no policy was specified, no snapshots will be removed`), "wrong error message got %v", err)
|
||||
|
||||
// `forget --host example --unsafe-allow-remove-all` should work
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/restic/restic/internal/errors"
|
||||
"github.com/restic/restic/internal/terminal"
|
||||
"github.com/restic/restic/internal/ui"
|
||||
"github.com/restic/restic/internal/ui/progress"
|
||||
"github.com/spf13/cobra"
|
||||
|
|
@ -14,7 +13,7 @@ import (
|
|||
"github.com/spf13/pflag"
|
||||
)
|
||||
|
||||
func newGenerateCommand() *cobra.Command {
|
||||
func newGenerateCommand(globalOptions *GlobalOptions) *cobra.Command {
|
||||
var opts generateOptions
|
||||
|
||||
cmd := &cobra.Command{
|
||||
|
|
@ -32,9 +31,7 @@ Exit status is 1 if there was any error.
|
|||
`,
|
||||
DisableAutoGenTag: true,
|
||||
RunE: func(_ *cobra.Command, args []string) error {
|
||||
term, cancel := setupTermstatus()
|
||||
defer cancel()
|
||||
return runGenerate(opts, globalOptions, args, term)
|
||||
return runGenerate(opts, *globalOptions, args, globalOptions.term)
|
||||
},
|
||||
}
|
||||
opts.AddFlags(cmd.Flags())
|
||||
|
|
@ -75,10 +72,8 @@ func writeManpages(root *cobra.Command, dir string, printer progress.Printer) er
|
|||
return doc.GenManTree(root, header, dir)
|
||||
}
|
||||
|
||||
func writeCompletion(filename string, shell string, generate func(w io.Writer) error, printer progress.Printer) (err error) {
|
||||
if terminal.StdoutIsTerminal() {
|
||||
printer.P("writing %s completion file to %v", shell, filename)
|
||||
}
|
||||
func writeCompletion(filename string, shell string, generate func(w io.Writer) error, printer progress.Printer, gopts GlobalOptions) (err error) {
|
||||
printer.PT("writing %s completion file to %v", shell, filename)
|
||||
var outWriter io.Writer
|
||||
if filename != "-" {
|
||||
var outFile *os.File
|
||||
|
|
@ -89,7 +84,7 @@ func writeCompletion(filename string, shell string, generate func(w io.Writer) e
|
|||
defer func() { err = outFile.Close() }()
|
||||
outWriter = outFile
|
||||
} else {
|
||||
outWriter = globalOptions.stdout
|
||||
outWriter = gopts.term.OutputWriter()
|
||||
}
|
||||
|
||||
err = generate(outWriter)
|
||||
|
|
@ -120,8 +115,8 @@ func runGenerate(opts generateOptions, gopts GlobalOptions, args []string, term
|
|||
return errors.Fatal("the generate command expects no arguments, only options - please see `restic help generate` for usage and flags")
|
||||
}
|
||||
|
||||
printer := newTerminalProgressPrinter(gopts.JSON, gopts.verbosity, term)
|
||||
cmdRoot := newRootCommand()
|
||||
printer := ui.NewProgressPrinter(gopts.JSON, gopts.verbosity, term)
|
||||
cmdRoot := newRootCommand(&GlobalOptions{})
|
||||
|
||||
if opts.ManDir != "" {
|
||||
err := writeManpages(cmdRoot, opts.ManDir, printer)
|
||||
|
|
@ -136,28 +131,28 @@ func runGenerate(opts generateOptions, gopts GlobalOptions, args []string, term
|
|||
}
|
||||
|
||||
if opts.BashCompletionFile != "" {
|
||||
err := writeCompletion(opts.BashCompletionFile, "bash", cmdRoot.GenBashCompletion, printer)
|
||||
err := writeCompletion(opts.BashCompletionFile, "bash", cmdRoot.GenBashCompletion, printer, gopts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if opts.FishCompletionFile != "" {
|
||||
err := writeCompletion(opts.FishCompletionFile, "fish", func(w io.Writer) error { return cmdRoot.GenFishCompletion(w, true) }, printer)
|
||||
err := writeCompletion(opts.FishCompletionFile, "fish", func(w io.Writer) error { return cmdRoot.GenFishCompletion(w, true) }, printer, gopts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if opts.ZSHCompletionFile != "" {
|
||||
err := writeCompletion(opts.ZSHCompletionFile, "zsh", cmdRoot.GenZshCompletion, printer)
|
||||
err := writeCompletion(opts.ZSHCompletionFile, "zsh", cmdRoot.GenZshCompletion, printer, gopts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if opts.PowerShellCompletionFile != "" {
|
||||
err := writeCompletion(opts.PowerShellCompletionFile, "powershell", cmdRoot.GenPowerShellCompletion, printer)
|
||||
err := writeCompletion(opts.PowerShellCompletionFile, "powershell", cmdRoot.GenPowerShellCompletion, printer, gopts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,14 +6,11 @@ import (
|
|||
"testing"
|
||||
|
||||
rtest "github.com/restic/restic/internal/test"
|
||||
"github.com/restic/restic/internal/ui"
|
||||
)
|
||||
|
||||
func testRunGenerate(gopts GlobalOptions, opts generateOptions) ([]byte, error) {
|
||||
buf, err := withCaptureStdout(gopts, func(gopts GlobalOptions) error {
|
||||
return withTermStatus(gopts, func(ctx context.Context, term ui.Terminal) error {
|
||||
return runGenerate(opts, gopts, []string{}, term)
|
||||
})
|
||||
func testRunGenerate(t testing.TB, gopts GlobalOptions, opts generateOptions) ([]byte, error) {
|
||||
buf, err := withCaptureStdout(t, gopts, func(ctx context.Context, gopts GlobalOptions) error {
|
||||
return runGenerate(opts, gopts, []string{}, gopts.term)
|
||||
})
|
||||
return buf.Bytes(), err
|
||||
}
|
||||
|
|
@ -31,14 +28,14 @@ func TestGenerateStdout(t *testing.T) {
|
|||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
output, err := testRunGenerate(globalOptions, tc.opts)
|
||||
output, err := testRunGenerate(t, GlobalOptions{}, tc.opts)
|
||||
rtest.OK(t, err)
|
||||
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) {
|
||||
_, err := testRunGenerate(globalOptions, generateOptions{BashCompletionFile: "-", FishCompletionFile: "-"})
|
||||
_, err := testRunGenerate(t, GlobalOptions{}, generateOptions{BashCompletionFile: "-", FishCompletionFile: "-"})
|
||||
rtest.Assert(t, err != nil, "generate shell completions to stdout for two shells fails")
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ import (
|
|||
"github.com/spf13/pflag"
|
||||
)
|
||||
|
||||
func newInitCommand() *cobra.Command {
|
||||
func newInitCommand(globalOptions *GlobalOptions) *cobra.Command {
|
||||
var opts InitOptions
|
||||
|
||||
cmd := &cobra.Command{
|
||||
|
|
@ -35,9 +35,7 @@ Exit status is 1 if there was any error.
|
|||
GroupID: cmdGroupDefault,
|
||||
DisableAutoGenTag: true,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
term, cancel := setupTermstatus()
|
||||
defer cancel()
|
||||
return runInit(cmd.Context(), opts, globalOptions, args, term)
|
||||
return runInit(cmd.Context(), opts, *globalOptions, args, globalOptions.term)
|
||||
},
|
||||
}
|
||||
opts.AddFlags(cmd.Flags())
|
||||
|
|
@ -62,7 +60,7 @@ func runInit(ctx context.Context, opts InitOptions, gopts GlobalOptions, args []
|
|||
return errors.Fatal("the init command expects no arguments, only options - please see `restic help init` for usage and flags")
|
||||
}
|
||||
|
||||
printer := newTerminalProgressPrinter(gopts.JSON, gopts.verbosity, term)
|
||||
printer := ui.NewProgressPrinter(gopts.JSON, gopts.verbosity, term)
|
||||
|
||||
var version uint
|
||||
switch opts.RepositoryVersion {
|
||||
|
|
@ -94,8 +92,7 @@ func runInit(ctx context.Context, opts InitOptions, gopts GlobalOptions, args []
|
|||
|
||||
gopts.password, err = ReadPasswordTwice(ctx, gopts,
|
||||
"enter password for new repository: ",
|
||||
"enter password again: ",
|
||||
printer)
|
||||
"enter password again: ")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -134,7 +131,7 @@ func runInit(ctx context.Context, opts InitOptions, gopts GlobalOptions, args []
|
|||
ID: s.Config().ID,
|
||||
Repository: location.StripPassword(gopts.backends, gopts.Repo),
|
||||
}
|
||||
return json.NewEncoder(globalOptions.stdout).Encode(status)
|
||||
return json.NewEncoder(gopts.term.OutputWriter()).Encode(status)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
|
@ -142,7 +139,7 @@ func runInit(ctx context.Context, opts InitOptions, gopts GlobalOptions, args []
|
|||
|
||||
func maybeReadChunkerPolynomial(ctx context.Context, opts InitOptions, gopts GlobalOptions, printer progress.Printer) (*chunker.Pol, error) {
|
||||
if opts.CopyChunkerParameters {
|
||||
otherGopts, _, err := fillSecondaryGlobalOpts(ctx, opts.secondaryRepoOptions, gopts, "secondary", printer)
|
||||
otherGopts, _, err := fillSecondaryGlobalOpts(ctx, opts.secondaryRepoOptions, gopts, "secondary")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,24 +9,23 @@ import (
|
|||
"github.com/restic/restic/internal/repository"
|
||||
"github.com/restic/restic/internal/restic"
|
||||
rtest "github.com/restic/restic/internal/test"
|
||||
"github.com/restic/restic/internal/ui"
|
||||
"github.com/restic/restic/internal/ui/progress"
|
||||
)
|
||||
|
||||
func testRunInit(t testing.TB, opts GlobalOptions) {
|
||||
func testRunInit(t testing.TB, gopts GlobalOptions) {
|
||||
repository.TestUseLowSecurityKDFParameters(t)
|
||||
restic.TestDisableCheckPolynomial(t)
|
||||
restic.TestSetLockTimeout(t, 0)
|
||||
|
||||
err := withTermStatus(opts, func(ctx context.Context, term ui.Terminal) error {
|
||||
return runInit(ctx, InitOptions{}, opts, nil, term)
|
||||
err := withTermStatus(t, gopts, func(ctx context.Context, gopts GlobalOptions) error {
|
||||
return runInit(ctx, InitOptions{}, gopts, nil, gopts.term)
|
||||
})
|
||||
rtest.OK(t, err)
|
||||
t.Logf("repository initialized at %v", opts.Repo)
|
||||
t.Logf("repository initialized at %v", gopts.Repo)
|
||||
|
||||
// create temporary junk files to verify that restic does not trip over them
|
||||
for _, path := range []string{"index", "snapshots", "keys", "locks", filepath.Join("data", "00")} {
|
||||
rtest.OK(t, os.WriteFile(filepath.Join(opts.Repo, path, "tmp12345"), []byte("junk file"), 0o600))
|
||||
rtest.OK(t, os.WriteFile(filepath.Join(gopts.Repo, path, "tmp12345"), []byte("junk file"), 0o600))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -44,21 +43,29 @@ func TestInitCopyChunkerParams(t *testing.T) {
|
|||
password: env2.gopts.password,
|
||||
},
|
||||
}
|
||||
err := withTermStatus(env.gopts, func(ctx context.Context, term ui.Terminal) error {
|
||||
return runInit(ctx, initOpts, env.gopts, nil, term)
|
||||
err := withTermStatus(t, env.gopts, func(ctx context.Context, gopts GlobalOptions) error {
|
||||
return runInit(ctx, initOpts, gopts, nil, gopts.term)
|
||||
})
|
||||
rtest.Assert(t, err != nil, "expected invalid init options to fail")
|
||||
|
||||
initOpts.CopyChunkerParameters = true
|
||||
err = withTermStatus(env.gopts, func(ctx context.Context, term ui.Terminal) error {
|
||||
return runInit(ctx, initOpts, env.gopts, nil, term)
|
||||
err = withTermStatus(t, env.gopts, func(ctx context.Context, gopts GlobalOptions) error {
|
||||
return runInit(ctx, initOpts, gopts, nil, gopts.term)
|
||||
})
|
||||
rtest.OK(t, err)
|
||||
|
||||
repo, err := OpenRepository(context.TODO(), env.gopts, &progress.NoopPrinter{})
|
||||
var repo *repository.Repository
|
||||
err = withTermStatus(t, env.gopts, func(ctx context.Context, gopts GlobalOptions) error {
|
||||
repo, err = OpenRepository(ctx, gopts, &progress.NoopPrinter{})
|
||||
return err
|
||||
})
|
||||
rtest.OK(t, err)
|
||||
|
||||
otherRepo, err := OpenRepository(context.TODO(), env2.gopts, &progress.NoopPrinter{})
|
||||
var otherRepo *repository.Repository
|
||||
err = withTermStatus(t, env2.gopts, func(ctx context.Context, gopts GlobalOptions) error {
|
||||
otherRepo, err = OpenRepository(ctx, gopts, &progress.NoopPrinter{})
|
||||
return err
|
||||
})
|
||||
rtest.OK(t, err)
|
||||
|
||||
rtest.Assert(t, repo.Config().ChunkerPolynomial == otherRepo.Config().ChunkerPolynomial,
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import (
|
|||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func newKeyCommand() *cobra.Command {
|
||||
func newKeyCommand(globalOptions *GlobalOptions) *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "key",
|
||||
Short: "Manage keys (passwords)",
|
||||
|
|
@ -17,10 +17,10 @@ per repository.
|
|||
}
|
||||
|
||||
cmd.AddCommand(
|
||||
newKeyAddCommand(),
|
||||
newKeyListCommand(),
|
||||
newKeyPasswdCommand(),
|
||||
newKeyRemoveCommand(),
|
||||
newKeyAddCommand(globalOptions),
|
||||
newKeyListCommand(globalOptions),
|
||||
newKeyPasswdCommand(globalOptions),
|
||||
newKeyRemoveCommand(globalOptions),
|
||||
)
|
||||
return cmd
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ import (
|
|||
"github.com/spf13/pflag"
|
||||
)
|
||||
|
||||
func newKeyAddCommand() *cobra.Command {
|
||||
func newKeyAddCommand(globalOptions *GlobalOptions) *cobra.Command {
|
||||
var opts KeyAddOptions
|
||||
|
||||
cmd := &cobra.Command{
|
||||
|
|
@ -32,9 +32,7 @@ Exit status is 12 if the password is incorrect.
|
|||
`,
|
||||
DisableAutoGenTag: true,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
term, cancel := setupTermstatus()
|
||||
defer cancel()
|
||||
return runKeyAdd(cmd.Context(), globalOptions, opts, args, term)
|
||||
return runKeyAdd(cmd.Context(), *globalOptions, opts, args, globalOptions.term)
|
||||
},
|
||||
}
|
||||
|
||||
|
|
@ -61,7 +59,7 @@ func runKeyAdd(ctx context.Context, gopts GlobalOptions, opts KeyAddOptions, arg
|
|||
return fmt.Errorf("the key add command expects no arguments, only options - please see `restic help key add` for usage and flags")
|
||||
}
|
||||
|
||||
printer := newTerminalProgressPrinter(false, gopts.verbosity, term)
|
||||
printer := ui.NewProgressPrinter(false, gopts.verbosity, term)
|
||||
ctx, repo, unlock, err := openWithAppendLock(ctx, gopts, false, printer)
|
||||
if err != nil {
|
||||
return err
|
||||
|
|
@ -72,7 +70,7 @@ func runKeyAdd(ctx context.Context, gopts GlobalOptions, opts KeyAddOptions, arg
|
|||
}
|
||||
|
||||
func addKey(ctx context.Context, repo *repository.Repository, gopts GlobalOptions, opts KeyAddOptions, printer progress.Printer) error {
|
||||
pw, err := getNewPassword(ctx, gopts, opts.NewPasswordFile, opts.InsecureNoPassword, printer)
|
||||
pw, err := getNewPassword(ctx, gopts, opts.NewPasswordFile, opts.InsecureNoPassword)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -95,7 +93,7 @@ func addKey(ctx context.Context, repo *repository.Repository, gopts GlobalOption
|
|||
// testKeyNewPassword is used to set a new password during integration testing.
|
||||
var testKeyNewPassword string
|
||||
|
||||
func getNewPassword(ctx context.Context, gopts GlobalOptions, newPasswordFile string, insecureNoPassword bool, printer progress.Printer) (string, error) {
|
||||
func getNewPassword(ctx context.Context, gopts GlobalOptions, newPasswordFile string, insecureNoPassword bool) (string, error) {
|
||||
if testKeyNewPassword != "" {
|
||||
return testKeyNewPassword, nil
|
||||
}
|
||||
|
|
@ -127,8 +125,7 @@ func getNewPassword(ctx context.Context, gopts GlobalOptions, newPasswordFile st
|
|||
|
||||
return ReadPasswordTwice(ctx, newopts,
|
||||
"enter new password: ",
|
||||
"enter password again: ",
|
||||
printer)
|
||||
"enter password again: ")
|
||||
}
|
||||
|
||||
func switchToNewKeyAndRemoveIfBroken(ctx context.Context, repo *repository.Repository, key *repository.Key, pw string) error {
|
||||
|
|
|
|||
|
|
@ -12,15 +12,12 @@ import (
|
|||
"github.com/restic/restic/internal/backend"
|
||||
"github.com/restic/restic/internal/repository"
|
||||
rtest "github.com/restic/restic/internal/test"
|
||||
"github.com/restic/restic/internal/ui"
|
||||
"github.com/restic/restic/internal/ui/progress"
|
||||
)
|
||||
|
||||
func testRunKeyListOtherIDs(t testing.TB, gopts GlobalOptions) []string {
|
||||
buf, err := withCaptureStdout(gopts, func(gopts GlobalOptions) error {
|
||||
return withTermStatus(gopts, func(ctx context.Context, term ui.Terminal) error {
|
||||
return runKeyList(ctx, gopts, []string{}, term)
|
||||
})
|
||||
buf, err := withCaptureStdout(t, gopts, func(ctx context.Context, gopts GlobalOptions) error {
|
||||
return runKeyList(ctx, gopts, []string{}, gopts.term)
|
||||
})
|
||||
rtest.OK(t, err)
|
||||
|
||||
|
|
@ -43,8 +40,8 @@ func testRunKeyAddNewKey(t testing.TB, newPassword string, gopts GlobalOptions)
|
|||
testKeyNewPassword = ""
|
||||
}()
|
||||
|
||||
err := withTermStatus(gopts, func(ctx context.Context, term ui.Terminal) error {
|
||||
return runKeyAdd(ctx, gopts, KeyAddOptions{}, []string{}, term)
|
||||
err := withTermStatus(t, gopts, func(ctx context.Context, gopts GlobalOptions) error {
|
||||
return runKeyAdd(ctx, gopts, KeyAddOptions{}, []string{}, gopts.term)
|
||||
})
|
||||
rtest.OK(t, err)
|
||||
}
|
||||
|
|
@ -56,21 +53,24 @@ func testRunKeyAddNewKeyUserHost(t testing.TB, gopts GlobalOptions) {
|
|||
}()
|
||||
|
||||
t.Log("adding key for john@example.com")
|
||||
err := withTermStatus(gopts, func(ctx context.Context, term ui.Terminal) error {
|
||||
err := withTermStatus(t, gopts, func(ctx context.Context, gopts GlobalOptions) error {
|
||||
return runKeyAdd(ctx, gopts, KeyAddOptions{
|
||||
Username: "john",
|
||||
Hostname: "example.com",
|
||||
}, []string{}, term)
|
||||
}, []string{}, gopts.term)
|
||||
})
|
||||
rtest.OK(t, err)
|
||||
|
||||
repo, err := OpenRepository(context.TODO(), gopts, &progress.NoopPrinter{})
|
||||
rtest.OK(t, err)
|
||||
key, err := repository.SearchKey(context.TODO(), repo, testKeyNewPassword, 2, "")
|
||||
rtest.OK(t, err)
|
||||
_ = withTermStatus(t, gopts, func(ctx context.Context, gopts GlobalOptions) error {
|
||||
repo, err := OpenRepository(ctx, gopts, &progress.NoopPrinter{})
|
||||
rtest.OK(t, err)
|
||||
key, err := repository.SearchKey(ctx, repo, testKeyNewPassword, 2, "")
|
||||
rtest.OK(t, err)
|
||||
|
||||
rtest.Equals(t, "john", key.Username)
|
||||
rtest.Equals(t, "example.com", key.Hostname)
|
||||
rtest.Equals(t, "john", key.Username)
|
||||
rtest.Equals(t, "example.com", key.Hostname)
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func testRunKeyPasswd(t testing.TB, newPassword string, gopts GlobalOptions) {
|
||||
|
|
@ -79,8 +79,8 @@ func testRunKeyPasswd(t testing.TB, newPassword string, gopts GlobalOptions) {
|
|||
testKeyNewPassword = ""
|
||||
}()
|
||||
|
||||
err := withTermStatus(gopts, func(ctx context.Context, term ui.Terminal) error {
|
||||
return runKeyPasswd(ctx, gopts, KeyPasswdOptions{}, []string{}, term)
|
||||
err := withTermStatus(t, gopts, func(ctx context.Context, gopts GlobalOptions) error {
|
||||
return runKeyPasswd(ctx, gopts, KeyPasswdOptions{}, []string{}, gopts.term)
|
||||
})
|
||||
rtest.OK(t, err)
|
||||
}
|
||||
|
|
@ -88,8 +88,8 @@ func testRunKeyPasswd(t testing.TB, newPassword string, gopts GlobalOptions) {
|
|||
func testRunKeyRemove(t testing.TB, gopts GlobalOptions, IDs []string) {
|
||||
t.Logf("remove %d keys: %q\n", len(IDs), IDs)
|
||||
for _, id := range IDs {
|
||||
err := withTermStatus(gopts, func(ctx context.Context, term ui.Terminal) error {
|
||||
return runKeyRemove(ctx, gopts, []string{id}, term)
|
||||
err := withTermStatus(t, gopts, func(ctx context.Context, gopts GlobalOptions) error {
|
||||
return runKeyRemove(ctx, gopts, []string{id}, gopts.term)
|
||||
})
|
||||
rtest.OK(t, err)
|
||||
}
|
||||
|
|
@ -121,8 +121,8 @@ func TestKeyAddRemove(t *testing.T) {
|
|||
|
||||
env.gopts.password = passwordList[len(passwordList)-1]
|
||||
t.Logf("testing access with last password %q\n", env.gopts.password)
|
||||
err := withTermStatus(env.gopts, func(ctx context.Context, term ui.Terminal) error {
|
||||
return runKeyList(ctx, env.gopts, []string{}, term)
|
||||
err := withTermStatus(t, env.gopts, func(ctx context.Context, gopts GlobalOptions) error {
|
||||
return runKeyList(ctx, gopts, []string{}, gopts.term)
|
||||
})
|
||||
rtest.OK(t, err)
|
||||
testRunCheck(t, env.gopts)
|
||||
|
|
@ -135,21 +135,21 @@ func TestKeyAddInvalid(t *testing.T) {
|
|||
defer cleanup()
|
||||
testRunInit(t, env.gopts)
|
||||
|
||||
err := withTermStatus(env.gopts, func(ctx context.Context, term ui.Terminal) error {
|
||||
return runKeyAdd(ctx, env.gopts, KeyAddOptions{
|
||||
err := withTermStatus(t, env.gopts, func(ctx context.Context, gopts GlobalOptions) error {
|
||||
return runKeyAdd(ctx, gopts, KeyAddOptions{
|
||||
NewPasswordFile: "some-file",
|
||||
InsecureNoPassword: true,
|
||||
}, []string{}, term)
|
||||
}, []string{}, gopts.term)
|
||||
})
|
||||
rtest.Assert(t, strings.Contains(err.Error(), "only either"), "unexpected error message, got %q", err)
|
||||
|
||||
pwfile := filepath.Join(t.TempDir(), "pwfile")
|
||||
rtest.OK(t, os.WriteFile(pwfile, []byte{}, 0o666))
|
||||
|
||||
err = withTermStatus(env.gopts, func(ctx context.Context, term ui.Terminal) error {
|
||||
return runKeyAdd(ctx, env.gopts, KeyAddOptions{
|
||||
err = withTermStatus(t, env.gopts, func(ctx context.Context, gopts GlobalOptions) error {
|
||||
return runKeyAdd(ctx, gopts, KeyAddOptions{
|
||||
NewPasswordFile: pwfile,
|
||||
}, []string{}, 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)
|
||||
}
|
||||
|
|
@ -161,10 +161,10 @@ func TestKeyAddEmpty(t *testing.T) {
|
|||
defer cleanup()
|
||||
testRunInit(t, env.gopts)
|
||||
|
||||
err := withTermStatus(env.gopts, func(ctx context.Context, term ui.Terminal) error {
|
||||
return runKeyAdd(ctx, env.gopts, KeyAddOptions{
|
||||
err := withTermStatus(t, env.gopts, func(ctx context.Context, gopts GlobalOptions) error {
|
||||
return runKeyAdd(ctx, gopts, KeyAddOptions{
|
||||
InsecureNoPassword: true,
|
||||
}, []string{}, term)
|
||||
}, []string{}, gopts.term)
|
||||
})
|
||||
rtest.OK(t, err)
|
||||
|
||||
|
|
@ -196,21 +196,21 @@ func TestKeyProblems(t *testing.T) {
|
|||
testKeyNewPassword = ""
|
||||
}()
|
||||
|
||||
err := withTermStatus(env.gopts, func(ctx context.Context, term ui.Terminal) error {
|
||||
return runKeyPasswd(ctx, env.gopts, KeyPasswdOptions{}, []string{}, term)
|
||||
err := withTermStatus(t, env.gopts, func(ctx context.Context, gopts GlobalOptions) error {
|
||||
return runKeyPasswd(ctx, gopts, KeyPasswdOptions{}, []string{}, gopts.term)
|
||||
})
|
||||
t.Log(err)
|
||||
rtest.Assert(t, err != nil, "expected passwd change to fail")
|
||||
|
||||
err = withTermStatus(env.gopts, func(ctx context.Context, term ui.Terminal) error {
|
||||
return runKeyAdd(ctx, env.gopts, KeyAddOptions{}, []string{}, term)
|
||||
err = withTermStatus(t, env.gopts, func(ctx context.Context, gopts GlobalOptions) error {
|
||||
return runKeyAdd(ctx, gopts, KeyAddOptions{}, []string{}, gopts.term)
|
||||
})
|
||||
t.Log(err)
|
||||
rtest.Assert(t, err != nil, "expected key adding to fail")
|
||||
|
||||
t.Logf("testing access with initial password %q\n", env.gopts.password)
|
||||
err = withTermStatus(env.gopts, func(ctx context.Context, term ui.Terminal) error {
|
||||
return runKeyList(ctx, env.gopts, []string{}, term)
|
||||
err = withTermStatus(t, env.gopts, func(ctx context.Context, gopts GlobalOptions) error {
|
||||
return runKeyList(ctx, gopts, []string{}, gopts.term)
|
||||
})
|
||||
rtest.OK(t, err)
|
||||
testRunCheck(t, env.gopts)
|
||||
|
|
@ -225,32 +225,32 @@ func TestKeyCommandInvalidArguments(t *testing.T) {
|
|||
return &emptySaveBackend{r}, nil
|
||||
}
|
||||
|
||||
err := withTermStatus(env.gopts, func(ctx context.Context, term ui.Terminal) error {
|
||||
return runKeyAdd(ctx, env.gopts, KeyAddOptions{}, []string{"johndoe"}, term)
|
||||
err := withTermStatus(t, env.gopts, func(ctx context.Context, gopts GlobalOptions) error {
|
||||
return runKeyAdd(ctx, gopts, KeyAddOptions{}, []string{"johndoe"}, gopts.term)
|
||||
})
|
||||
t.Log(err)
|
||||
rtest.Assert(t, err != nil && strings.Contains(err.Error(), "no arguments"), "unexpected error for key add: %v", err)
|
||||
|
||||
err = withTermStatus(env.gopts, func(ctx context.Context, term ui.Terminal) error {
|
||||
return runKeyPasswd(ctx, env.gopts, KeyPasswdOptions{}, []string{"johndoe"}, term)
|
||||
err = withTermStatus(t, env.gopts, func(ctx context.Context, gopts GlobalOptions) error {
|
||||
return runKeyPasswd(ctx, gopts, KeyPasswdOptions{}, []string{"johndoe"}, gopts.term)
|
||||
})
|
||||
t.Log(err)
|
||||
rtest.Assert(t, err != nil && strings.Contains(err.Error(), "no arguments"), "unexpected error for key passwd: %v", err)
|
||||
|
||||
err = withTermStatus(env.gopts, func(ctx context.Context, term ui.Terminal) error {
|
||||
return runKeyList(ctx, env.gopts, []string{"johndoe"}, term)
|
||||
err = withTermStatus(t, env.gopts, func(ctx context.Context, gopts GlobalOptions) error {
|
||||
return runKeyList(ctx, gopts, []string{"johndoe"}, gopts.term)
|
||||
})
|
||||
t.Log(err)
|
||||
rtest.Assert(t, err != nil && strings.Contains(err.Error(), "no arguments"), "unexpected error for key list: %v", err)
|
||||
|
||||
err = withTermStatus(env.gopts, func(ctx context.Context, term ui.Terminal) error {
|
||||
return runKeyRemove(ctx, env.gopts, []string{}, term)
|
||||
err = withTermStatus(t, env.gopts, func(ctx context.Context, gopts GlobalOptions) error {
|
||||
return runKeyRemove(ctx, gopts, []string{}, gopts.term)
|
||||
})
|
||||
t.Log(err)
|
||||
rtest.Assert(t, err != nil && strings.Contains(err.Error(), "one argument"), "unexpected error for key remove: %v", err)
|
||||
|
||||
err = withTermStatus(env.gopts, func(ctx context.Context, term ui.Terminal) error {
|
||||
return runKeyRemove(ctx, env.gopts, []string{"john", "doe"}, term)
|
||||
err = withTermStatus(t, env.gopts, func(ctx context.Context, gopts GlobalOptions) error {
|
||||
return runKeyRemove(ctx, gopts, []string{"john", "doe"}, gopts.term)
|
||||
})
|
||||
t.Log(err)
|
||||
rtest.Assert(t, err != nil && strings.Contains(err.Error(), "one argument"), "unexpected error for key remove: %v", err)
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ import (
|
|||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func newKeyListCommand() *cobra.Command {
|
||||
func newKeyListCommand(globalOptions *GlobalOptions) *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "list",
|
||||
Short: "List keys (passwords)",
|
||||
|
|
@ -34,9 +34,7 @@ Exit status is 12 if the password is incorrect.
|
|||
`,
|
||||
DisableAutoGenTag: true,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
term, cancel := setupTermstatus()
|
||||
defer cancel()
|
||||
return runKeyList(cmd.Context(), globalOptions, args, term)
|
||||
return runKeyList(cmd.Context(), *globalOptions, args, globalOptions.term)
|
||||
},
|
||||
}
|
||||
return cmd
|
||||
|
|
@ -47,7 +45,7 @@ func runKeyList(ctx context.Context, gopts GlobalOptions, args []string, term ui
|
|||
return fmt.Errorf("the key list command expects no arguments, only options - please see `restic help key list` for usage and flags")
|
||||
}
|
||||
|
||||
printer := newTerminalProgressPrinter(gopts.JSON, gopts.verbosity, term)
|
||||
printer := ui.NewProgressPrinter(gopts.JSON, gopts.verbosity, term)
|
||||
ctx, repo, unlock, err := openWithReadLock(ctx, gopts, gopts.NoLock, printer)
|
||||
if err != nil {
|
||||
return err
|
||||
|
|
@ -97,7 +95,7 @@ func listKeys(ctx context.Context, s *repository.Repository, gopts GlobalOptions
|
|||
}
|
||||
|
||||
if gopts.JSON {
|
||||
return json.NewEncoder(globalOptions.stdout).Encode(keys)
|
||||
return json.NewEncoder(gopts.term.OutputWriter()).Encode(keys)
|
||||
}
|
||||
|
||||
tab := table.New()
|
||||
|
|
@ -110,5 +108,5 @@ func listKeys(ctx context.Context, s *repository.Repository, gopts GlobalOptions
|
|||
tab.AddRow(key)
|
||||
}
|
||||
|
||||
return tab.Write(globalOptions.stdout)
|
||||
return tab.Write(gopts.term.OutputWriter())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ import (
|
|||
"github.com/spf13/pflag"
|
||||
)
|
||||
|
||||
func newKeyPasswdCommand() *cobra.Command {
|
||||
func newKeyPasswdCommand(globalOptions *GlobalOptions) *cobra.Command {
|
||||
var opts KeyPasswdOptions
|
||||
|
||||
cmd := &cobra.Command{
|
||||
|
|
@ -33,9 +33,7 @@ Exit status is 12 if the password is incorrect.
|
|||
`,
|
||||
DisableAutoGenTag: true,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
term, cancel := setupTermstatus()
|
||||
defer cancel()
|
||||
return runKeyPasswd(cmd.Context(), globalOptions, opts, args, term)
|
||||
return runKeyPasswd(cmd.Context(), *globalOptions, opts, args, globalOptions.term)
|
||||
},
|
||||
}
|
||||
|
||||
|
|
@ -56,7 +54,7 @@ func runKeyPasswd(ctx context.Context, gopts GlobalOptions, opts KeyPasswdOption
|
|||
return fmt.Errorf("the key passwd command expects no arguments, only options - please see `restic help key passwd` for usage and flags")
|
||||
}
|
||||
|
||||
printer := newTerminalProgressPrinter(false, gopts.verbosity, term)
|
||||
printer := ui.NewProgressPrinter(false, gopts.verbosity, term)
|
||||
ctx, repo, unlock, err := openWithExclusiveLock(ctx, gopts, false, printer)
|
||||
if err != nil {
|
||||
return err
|
||||
|
|
@ -67,7 +65,7 @@ func runKeyPasswd(ctx context.Context, gopts GlobalOptions, opts KeyPasswdOption
|
|||
}
|
||||
|
||||
func changePassword(ctx context.Context, repo *repository.Repository, gopts GlobalOptions, opts KeyPasswdOptions, printer progress.Printer) error {
|
||||
pw, err := getNewPassword(ctx, gopts, opts.NewPasswordFile, opts.InsecureNoPassword, printer)
|
||||
pw, err := getNewPassword(ctx, gopts, opts.NewPasswordFile, opts.InsecureNoPassword)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ import (
|
|||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func newKeyRemoveCommand() *cobra.Command {
|
||||
func newKeyRemoveCommand(globalOptions *GlobalOptions) *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "remove [ID]",
|
||||
Short: "Remove key ID (password) from the repository.",
|
||||
|
|
@ -31,9 +31,7 @@ Exit status is 12 if the password is incorrect.
|
|||
`,
|
||||
DisableAutoGenTag: true,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
term, cancel := setupTermstatus()
|
||||
defer cancel()
|
||||
return runKeyRemove(cmd.Context(), globalOptions, args, term)
|
||||
return runKeyRemove(cmd.Context(), *globalOptions, args, globalOptions.term)
|
||||
},
|
||||
}
|
||||
return cmd
|
||||
|
|
@ -44,7 +42,7 @@ func runKeyRemove(ctx context.Context, gopts GlobalOptions, args []string, term
|
|||
return fmt.Errorf("key remove expects one argument as the key id")
|
||||
}
|
||||
|
||||
printer := newTerminalProgressPrinter(gopts.JSON, gopts.verbosity, term)
|
||||
printer := ui.NewProgressPrinter(gopts.JSON, gopts.verbosity, term)
|
||||
ctx, repo, unlock, err := openWithExclusiveLock(ctx, gopts, false, printer)
|
||||
if err != nil {
|
||||
return err
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ import (
|
|||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func newListCommand() *cobra.Command {
|
||||
func newListCommand(globalOptions *GlobalOptions) *cobra.Command {
|
||||
var listAllowedArgs = []string{"blobs", "packs", "index", "snapshots", "keys", "locks"}
|
||||
var listAllowedArgsUseString = strings.Join(listAllowedArgs, "|")
|
||||
|
||||
|
|
@ -34,9 +34,7 @@ Exit status is 12 if the password is incorrect.
|
|||
DisableAutoGenTag: true,
|
||||
GroupID: cmdGroupDefault,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
term, cancel := setupTermstatus()
|
||||
defer cancel()
|
||||
return runList(cmd.Context(), globalOptions, args, term)
|
||||
return runList(cmd.Context(), *globalOptions, args, globalOptions.term)
|
||||
},
|
||||
ValidArgs: listAllowedArgs,
|
||||
Args: cobra.MatchAll(cobra.ExactArgs(1), cobra.OnlyValidArgs),
|
||||
|
|
@ -45,7 +43,7 @@ Exit status is 12 if the password is incorrect.
|
|||
}
|
||||
|
||||
func runList(ctx context.Context, gopts GlobalOptions, args []string, term ui.Terminal) error {
|
||||
printer := newTerminalProgressPrinter(false, gopts.verbosity, term)
|
||||
printer := ui.NewProgressPrinter(false, gopts.verbosity, term)
|
||||
|
||||
if len(args) != 1 {
|
||||
return errors.Fatal("type not specified")
|
||||
|
|
|
|||
|
|
@ -8,14 +8,11 @@ import (
|
|||
|
||||
"github.com/restic/restic/internal/restic"
|
||||
rtest "github.com/restic/restic/internal/test"
|
||||
"github.com/restic/restic/internal/ui"
|
||||
)
|
||||
|
||||
func testRunList(t testing.TB, opts GlobalOptions, tpe string) restic.IDs {
|
||||
buf, err := withCaptureStdout(opts, func(opts GlobalOptions) error {
|
||||
return withTermStatus(opts, func(ctx context.Context, term ui.Terminal) error {
|
||||
return runList(ctx, opts, []string{tpe}, term)
|
||||
})
|
||||
func testRunList(t testing.TB, gopts GlobalOptions, tpe string) restic.IDs {
|
||||
buf, err := withCaptureStdout(t, gopts, func(ctx context.Context, gopts GlobalOptions) error {
|
||||
return runList(ctx, gopts, []string{tpe}, gopts.term)
|
||||
})
|
||||
rtest.OK(t, err)
|
||||
return parseIDsFromReader(t, buf)
|
||||
|
|
@ -39,9 +36,9 @@ func parseIDsFromReader(t testing.TB, rd io.Reader) restic.IDs {
|
|||
return IDs
|
||||
}
|
||||
|
||||
func testListSnapshots(t testing.TB, opts GlobalOptions, expected int) restic.IDs {
|
||||
func testListSnapshots(t testing.TB, gopts GlobalOptions, expected int) restic.IDs {
|
||||
t.Helper()
|
||||
snapshotIDs := testRunList(t, opts, "snapshots")
|
||||
snapshotIDs := testRunList(t, gopts, "snapshots")
|
||||
rtest.Assert(t, len(snapshotIDs) == expected, "expected %v snapshot, got %v", expected, snapshotIDs)
|
||||
return snapshotIDs
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ import (
|
|||
"github.com/restic/restic/internal/walker"
|
||||
)
|
||||
|
||||
func newLsCommand() *cobra.Command {
|
||||
func newLsCommand(globalOptions *GlobalOptions) *cobra.Command {
|
||||
var opts LsOptions
|
||||
|
||||
cmd := &cobra.Command{
|
||||
|
|
@ -60,9 +60,7 @@ Exit status is 12 if the password is incorrect.
|
|||
DisableAutoGenTag: true,
|
||||
GroupID: cmdGroupDefault,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
term, cancel := setupTermstatus()
|
||||
defer cancel()
|
||||
return runLs(cmd.Context(), opts, globalOptions, args, term)
|
||||
return runLs(cmd.Context(), opts, *globalOptions, args, globalOptions.term)
|
||||
},
|
||||
}
|
||||
opts.AddFlags(cmd.Flags())
|
||||
|
|
@ -304,7 +302,7 @@ type toSortOutput struct {
|
|||
}
|
||||
|
||||
func runLs(ctx context.Context, opts LsOptions, gopts GlobalOptions, args []string, term ui.Terminal) error {
|
||||
termPrinter := newTerminalProgressPrinter(gopts.JSON, gopts.verbosity, term)
|
||||
termPrinter := ui.NewProgressPrinter(gopts.JSON, gopts.verbosity, term)
|
||||
|
||||
if len(args) == 0 {
|
||||
return errors.Fatal("no snapshot ID specified, specify snapshot ID or use special ID 'latest'")
|
||||
|
|
@ -375,8 +373,7 @@ func runLs(ctx context.Context, opts LsOptions, gopts GlobalOptions, args []stri
|
|||
return err
|
||||
}
|
||||
|
||||
bar := newIndexTerminalProgress(termPrinter)
|
||||
if err = repo.LoadIndex(ctx, bar); err != nil {
|
||||
if err = repo.LoadIndex(ctx, termPrinter); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
|
@ -384,11 +381,11 @@ func runLs(ctx context.Context, opts LsOptions, gopts GlobalOptions, args []stri
|
|||
|
||||
if gopts.JSON {
|
||||
printer = &jsonLsPrinter{
|
||||
enc: json.NewEncoder(globalOptions.stdout),
|
||||
enc: json.NewEncoder(gopts.term.OutputWriter()),
|
||||
}
|
||||
} else if opts.Ncdu {
|
||||
printer = &ncduLsPrinter{
|
||||
out: globalOptions.stdout,
|
||||
out: gopts.term.OutputWriter(),
|
||||
}
|
||||
} else {
|
||||
printer = &textLsPrinter{
|
||||
|
|
|
|||
|
|
@ -10,15 +10,12 @@ import (
|
|||
|
||||
"github.com/restic/restic/internal/restic"
|
||||
rtest "github.com/restic/restic/internal/test"
|
||||
"github.com/restic/restic/internal/ui"
|
||||
)
|
||||
|
||||
func testRunLsWithOpts(t testing.TB, gopts GlobalOptions, opts LsOptions, args []string) []byte {
|
||||
buf, err := withCaptureStdout(gopts, func(gopts GlobalOptions) error {
|
||||
buf, err := withCaptureStdout(t, gopts, func(ctx context.Context, gopts GlobalOptions) error {
|
||||
gopts.Quiet = true
|
||||
return withTermStatus(gopts, func(ctx context.Context, term ui.Terminal) error {
|
||||
return runLs(context.TODO(), opts, gopts, args, term)
|
||||
})
|
||||
return runLs(context.TODO(), opts, gopts, args, gopts.term)
|
||||
})
|
||||
rtest.OK(t, err)
|
||||
return buf.Bytes()
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ import (
|
|||
"github.com/spf13/pflag"
|
||||
)
|
||||
|
||||
func newMigrateCommand() *cobra.Command {
|
||||
func newMigrateCommand(globalOptions *GlobalOptions) *cobra.Command {
|
||||
var opts MigrateOptions
|
||||
|
||||
cmd := &cobra.Command{
|
||||
|
|
@ -35,9 +35,7 @@ Exit status is 12 if the password is incorrect.
|
|||
DisableAutoGenTag: true,
|
||||
GroupID: cmdGroupDefault,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
term, cancel := setupTermstatus()
|
||||
defer cancel()
|
||||
return runMigrate(cmd.Context(), opts, globalOptions, args, term)
|
||||
return runMigrate(cmd.Context(), opts, *globalOptions, args, globalOptions.term)
|
||||
},
|
||||
}
|
||||
|
||||
|
|
@ -136,7 +134,7 @@ func applyMigrations(ctx context.Context, opts MigrateOptions, gopts GlobalOptio
|
|||
}
|
||||
|
||||
func runMigrate(ctx context.Context, opts MigrateOptions, gopts GlobalOptions, args []string, term ui.Terminal) error {
|
||||
printer := newTerminalProgressPrinter(false, gopts.verbosity, term)
|
||||
printer := ui.NewProgressPrinter(false, gopts.verbosity, term)
|
||||
|
||||
ctx, repo, unlock, err := openWithExclusiveLock(ctx, gopts, false, printer)
|
||||
if err != nil {
|
||||
|
|
|
|||
|
|
@ -24,11 +24,11 @@ import (
|
|||
"github.com/anacrolix/fuse/fs"
|
||||
)
|
||||
|
||||
func registerMountCommand(cmdRoot *cobra.Command) {
|
||||
cmdRoot.AddCommand(newMountCommand())
|
||||
func registerMountCommand(cmdRoot *cobra.Command, globalOptions *GlobalOptions) {
|
||||
cmdRoot.AddCommand(newMountCommand(globalOptions))
|
||||
}
|
||||
|
||||
func newMountCommand() *cobra.Command {
|
||||
func newMountCommand(globalOptions *GlobalOptions) *cobra.Command {
|
||||
var opts MountOptions
|
||||
|
||||
cmd := &cobra.Command{
|
||||
|
|
@ -82,9 +82,7 @@ Exit status is 12 if the password is incorrect.
|
|||
DisableAutoGenTag: true,
|
||||
GroupID: cmdGroupDefault,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
term, cancel := setupTermstatus()
|
||||
defer cancel()
|
||||
return runMount(cmd.Context(), opts, globalOptions, args, term)
|
||||
return runMount(cmd.Context(), opts, *globalOptions, args, globalOptions.term)
|
||||
},
|
||||
}
|
||||
|
||||
|
|
@ -116,7 +114,7 @@ func (opts *MountOptions) AddFlags(f *pflag.FlagSet) {
|
|||
}
|
||||
|
||||
func runMount(ctx context.Context, opts MountOptions, gopts GlobalOptions, args []string, term ui.Terminal) error {
|
||||
printer := newTerminalProgressPrinter(false, gopts.verbosity, term)
|
||||
printer := ui.NewProgressPrinter(false, gopts.verbosity, term)
|
||||
|
||||
if opts.TimeTemplate == "" {
|
||||
return errors.Fatal("time template string cannot be empty")
|
||||
|
|
@ -148,8 +146,7 @@ func runMount(ctx context.Context, opts MountOptions, gopts GlobalOptions, args
|
|||
}
|
||||
defer unlock()
|
||||
|
||||
bar := newIndexTerminalProgress(printer)
|
||||
err = repo.LoadIndex(ctx, bar)
|
||||
err = repo.LoadIndex(ctx, printer)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,6 @@ package main
|
|||
|
||||
import "github.com/spf13/cobra"
|
||||
|
||||
func registerMountCommand(_ *cobra.Command) {
|
||||
func registerMountCommand(_ *cobra.Command, _ *GlobalOptions) {
|
||||
// Mount command not supported on these platforms
|
||||
}
|
||||
|
|
|
|||
|
|
@ -62,8 +62,8 @@ func testRunMount(t testing.TB, gopts GlobalOptions, dir string, wg *sync.WaitGr
|
|||
opts := MountOptions{
|
||||
TimeTemplate: time.RFC3339,
|
||||
}
|
||||
rtest.OK(t, withTermStatus(gopts, func(ctx context.Context, term ui.Terminal) error {
|
||||
return runMount(context.TODO(), opts, gopts, []string{dir}, term)
|
||||
rtest.OK(t, withTermStatus(t, gopts, func(ctx context.Context, gopts GlobalOptions) error {
|
||||
return runMount(context.TODO(), opts, gopts, []string{dir}, gopts.term)
|
||||
}))
|
||||
}
|
||||
|
||||
|
|
@ -128,8 +128,8 @@ func checkSnapshots(t testing.TB, gopts GlobalOptions, mountpoint string, snapsh
|
|||
}
|
||||
}
|
||||
|
||||
err := withTermStatus(gopts, func(ctx context.Context, term ui.Terminal) error {
|
||||
printer := newTerminalProgressPrinter(gopts.JSON, gopts.verbosity, term)
|
||||
err := withTermStatus(t, gopts, func(ctx context.Context, gopts GlobalOptions) error {
|
||||
printer := ui.NewProgressPrinter(gopts.JSON, gopts.verbosity, gopts.term)
|
||||
_, repo, unlock, err := openWithReadLock(ctx, gopts, false, printer)
|
||||
if err != nil {
|
||||
return err
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import (
|
|||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func newOptionsCommand() *cobra.Command {
|
||||
func newOptionsCommand(globalOptions *GlobalOptions) *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "options",
|
||||
Short: "Print list of extended options",
|
||||
|
|
@ -24,7 +24,7 @@ Exit status is 1 if there was any error.
|
|||
GroupID: cmdGroupAdvanced,
|
||||
DisableAutoGenTag: true,
|
||||
Run: func(_ *cobra.Command, _ []string) {
|
||||
fmt.Printf("All Extended Options:\n")
|
||||
globalOptions.term.Print("All Extended Options:")
|
||||
var maxLen int
|
||||
for _, opt := range options.List() {
|
||||
if l := len(opt.Namespace + "." + opt.Name); l > maxLen {
|
||||
|
|
@ -32,7 +32,7 @@ Exit status is 1 if there was any error.
|
|||
}
|
||||
}
|
||||
for _, opt := range options.List() {
|
||||
fmt.Printf(" %*s %s\n", -maxLen, opt.Namespace+"."+opt.Name, opt.Text)
|
||||
globalOptions.term.Print(fmt.Sprintf(" %*s %s", -maxLen, opt.Namespace+"."+opt.Name, opt.Text))
|
||||
}
|
||||
},
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ import (
|
|||
"github.com/spf13/pflag"
|
||||
)
|
||||
|
||||
func newPruneCommand() *cobra.Command {
|
||||
func newPruneCommand(globalOptions *GlobalOptions) *cobra.Command {
|
||||
var opts PruneOptions
|
||||
|
||||
cmd := &cobra.Command{
|
||||
|
|
@ -40,9 +40,7 @@ Exit status is 12 if the password is incorrect.
|
|||
GroupID: cmdGroupDefault,
|
||||
DisableAutoGenTag: true,
|
||||
RunE: func(cmd *cobra.Command, _ []string) error {
|
||||
term, cancel := setupTermstatus()
|
||||
defer cancel()
|
||||
return runPrune(cmd.Context(), opts, globalOptions, term)
|
||||
return runPrune(cmd.Context(), opts, *globalOptions, globalOptions.term)
|
||||
},
|
||||
}
|
||||
|
||||
|
|
@ -168,7 +166,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")
|
||||
}
|
||||
|
||||
printer := newTerminalProgressPrinter(false, gopts.verbosity, term)
|
||||
printer := ui.NewProgressPrinter(false, gopts.verbosity, term)
|
||||
ctx, repo, unlock, err := openWithExclusiveLock(ctx, gopts, opts.DryRun && gopts.NoLock, printer)
|
||||
if err != nil {
|
||||
return err
|
||||
|
|
@ -192,8 +190,7 @@ func runPruneWithRepo(ctx context.Context, opts PruneOptions, repo *repository.R
|
|||
}
|
||||
|
||||
// loading the index before the snapshots is ok, as we use an exclusive lock here
|
||||
bar := newIndexTerminalProgress(printer)
|
||||
err := repo.LoadIndex(ctx, bar)
|
||||
err := repo.LoadIndex(ctx, printer)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,28 +9,27 @@ import (
|
|||
"github.com/restic/restic/internal/backend"
|
||||
"github.com/restic/restic/internal/repository"
|
||||
rtest "github.com/restic/restic/internal/test"
|
||||
"github.com/restic/restic/internal/ui"
|
||||
)
|
||||
|
||||
func testRunPrune(t testing.TB, gopts GlobalOptions, opts PruneOptions) {
|
||||
t.Helper()
|
||||
rtest.OK(t, testRunPruneOutput(gopts, opts))
|
||||
rtest.OK(t, testRunPruneOutput(t, gopts, opts))
|
||||
}
|
||||
|
||||
func testRunPruneMustFail(t testing.TB, gopts GlobalOptions, opts PruneOptions) {
|
||||
t.Helper()
|
||||
err := testRunPruneOutput(gopts, opts)
|
||||
err := testRunPruneOutput(t, gopts, opts)
|
||||
rtest.Assert(t, err != nil, "expected non nil error")
|
||||
}
|
||||
|
||||
func testRunPruneOutput(gopts GlobalOptions, opts PruneOptions) error {
|
||||
func testRunPruneOutput(t testing.TB, gopts GlobalOptions, opts PruneOptions) error {
|
||||
oldHook := gopts.backendTestHook
|
||||
gopts.backendTestHook = func(r backend.Backend) (backend.Backend, error) { return newListOnceBackend(r), nil }
|
||||
defer func() {
|
||||
gopts.backendTestHook = oldHook
|
||||
}()
|
||||
return withTermStatus(gopts, func(ctx context.Context, term ui.Terminal) error {
|
||||
return runPrune(context.TODO(), opts, gopts, term)
|
||||
return withTermStatus(t, gopts, func(ctx context.Context, gopts GlobalOptions) error {
|
||||
return runPrune(context.TODO(), opts, gopts, gopts.term)
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -90,7 +89,7 @@ func createPrunableRepo(t *testing.T, env *testEnvironment) {
|
|||
}
|
||||
|
||||
func testRunForgetJSON(t testing.TB, gopts GlobalOptions, args ...string) {
|
||||
buf, err := withCaptureStdout(gopts, func(gopts GlobalOptions) error {
|
||||
buf, err := withCaptureStdout(t, gopts, func(ctx context.Context, gopts GlobalOptions) error {
|
||||
gopts.JSON = true
|
||||
opts := ForgetOptions{
|
||||
DryRun: true,
|
||||
|
|
@ -99,9 +98,7 @@ func testRunForgetJSON(t testing.TB, gopts GlobalOptions, args ...string) {
|
|||
pruneOpts := PruneOptions{
|
||||
MaxUnused: "5%",
|
||||
}
|
||||
return withTermStatus(gopts, func(ctx context.Context, term ui.Terminal) error {
|
||||
return runForget(context.TODO(), opts, pruneOpts, gopts, term, args)
|
||||
})
|
||||
return runForget(context.TODO(), opts, pruneOpts, gopts, gopts.term, args)
|
||||
})
|
||||
rtest.OK(t, err)
|
||||
|
||||
|
|
@ -122,8 +119,8 @@ func testPrune(t *testing.T, pruneOpts PruneOptions, checkOpts CheckOptions) {
|
|||
|
||||
createPrunableRepo(t, env)
|
||||
testRunPrune(t, env.gopts, pruneOpts)
|
||||
rtest.OK(t, withTermStatus(env.gopts, func(ctx context.Context, term ui.Terminal) error {
|
||||
_, err := runCheck(context.TODO(), checkOpts, env.gopts, nil, term)
|
||||
rtest.OK(t, withTermStatus(t, env.gopts, func(ctx context.Context, gopts GlobalOptions) error {
|
||||
_, err := runCheck(context.TODO(), checkOpts, gopts, nil, gopts.term)
|
||||
return err
|
||||
}))
|
||||
}
|
||||
|
|
@ -158,8 +155,8 @@ func TestPruneWithDamagedRepository(t *testing.T) {
|
|||
env.gopts.backendTestHook = oldHook
|
||||
}()
|
||||
// prune should fail
|
||||
rtest.Equals(t, repository.ErrPacksMissing, withTermStatus(env.gopts, func(ctx context.Context, term ui.Terminal) error {
|
||||
return runPrune(context.TODO(), pruneDefaultOptions, env.gopts, term)
|
||||
rtest.Equals(t, repository.ErrPacksMissing, withTermStatus(t, env.gopts, func(ctx context.Context, gopts GlobalOptions) error {
|
||||
return runPrune(context.TODO(), pruneDefaultOptions, gopts, gopts.term)
|
||||
}), "prune should have reported index not complete error")
|
||||
}
|
||||
|
||||
|
|
@ -231,8 +228,8 @@ func testEdgeCaseRepo(t *testing.T, tarfile string, optionsCheck CheckOptions, o
|
|||
if checkOK {
|
||||
testRunCheck(t, env.gopts)
|
||||
} else {
|
||||
rtest.Assert(t, withTermStatus(env.gopts, func(ctx context.Context, term ui.Terminal) error {
|
||||
_, err := runCheck(context.TODO(), optionsCheck, env.gopts, nil, term)
|
||||
rtest.Assert(t, withTermStatus(t, env.gopts, func(ctx context.Context, gopts GlobalOptions) error {
|
||||
_, err := runCheck(context.TODO(), optionsCheck, gopts, nil, gopts.term)
|
||||
return err
|
||||
}) != nil,
|
||||
"check should have reported an error")
|
||||
|
|
@ -242,8 +239,8 @@ func testEdgeCaseRepo(t *testing.T, tarfile string, optionsCheck CheckOptions, o
|
|||
testRunPrune(t, env.gopts, optionsPrune)
|
||||
testRunCheck(t, env.gopts)
|
||||
} else {
|
||||
rtest.Assert(t, withTermStatus(env.gopts, func(ctx context.Context, term ui.Terminal) error {
|
||||
return runPrune(context.TODO(), optionsPrune, env.gopts, term)
|
||||
rtest.Assert(t, withTermStatus(t, env.gopts, func(ctx context.Context, gopts GlobalOptions) error {
|
||||
return runPrune(context.TODO(), optionsPrune, gopts, gopts.term)
|
||||
}) != nil,
|
||||
"prune should have reported an error")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ import (
|
|||
"golang.org/x/sync/errgroup"
|
||||
)
|
||||
|
||||
func newRecoverCommand() *cobra.Command {
|
||||
func newRecoverCommand(globalOptions *GlobalOptions) *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "recover [flags]",
|
||||
Short: "Recover data from the repository not referenced by snapshots",
|
||||
|
|
@ -35,9 +35,7 @@ Exit status is 12 if the password is incorrect.
|
|||
GroupID: cmdGroupDefault,
|
||||
DisableAutoGenTag: true,
|
||||
RunE: func(cmd *cobra.Command, _ []string) error {
|
||||
term, cancel := setupTermstatus()
|
||||
defer cancel()
|
||||
return runRecover(cmd.Context(), globalOptions, term)
|
||||
return runRecover(cmd.Context(), *globalOptions, globalOptions.term)
|
||||
},
|
||||
}
|
||||
return cmd
|
||||
|
|
@ -49,7 +47,7 @@ func runRecover(ctx context.Context, gopts GlobalOptions, term ui.Terminal) erro
|
|||
return err
|
||||
}
|
||||
|
||||
printer := newTerminalProgressPrinter(false, gopts.verbosity, term)
|
||||
printer := ui.NewProgressPrinter(false, gopts.verbosity, term)
|
||||
ctx, repo, unlock, err := openWithExclusiveLock(ctx, gopts, false, printer)
|
||||
if err != nil {
|
||||
return err
|
||||
|
|
@ -68,8 +66,7 @@ func runRecover(ctx context.Context, gopts GlobalOptions, term ui.Terminal) erro
|
|||
}
|
||||
|
||||
printer.P("load index files\n")
|
||||
bar := newIndexTerminalProgress(printer)
|
||||
if err = repo.LoadIndex(ctx, bar); err != nil {
|
||||
if err = repo.LoadIndex(ctx, printer); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
|
@ -87,7 +84,7 @@ func runRecover(ctx context.Context, gopts GlobalOptions, term ui.Terminal) erro
|
|||
}
|
||||
|
||||
printer.P("load %d trees\n", len(trees))
|
||||
bar = printer.NewCounter("trees loaded")
|
||||
bar := printer.NewCounter("trees loaded")
|
||||
bar.SetMax(uint64(len(trees)))
|
||||
for id := range trees {
|
||||
tree, err := restic.LoadTree(ctx, repo, id)
|
||||
|
|
|
|||
|
|
@ -5,12 +5,11 @@ import (
|
|||
"testing"
|
||||
|
||||
rtest "github.com/restic/restic/internal/test"
|
||||
"github.com/restic/restic/internal/ui"
|
||||
)
|
||||
|
||||
func testRunRecover(t testing.TB, gopts GlobalOptions) {
|
||||
rtest.OK(t, withTermStatus(gopts, func(ctx context.Context, term ui.Terminal) error {
|
||||
return runRecover(context.TODO(), gopts, term)
|
||||
rtest.OK(t, withTermStatus(t, gopts, func(ctx context.Context, gopts GlobalOptions) error {
|
||||
return runRecover(context.TODO(), gopts, gopts.term)
|
||||
}))
|
||||
}
|
||||
|
||||
|
|
@ -33,7 +32,7 @@ func TestRecover(t *testing.T) {
|
|||
ids = testListSnapshots(t, env.gopts, 1)
|
||||
testRunCheck(t, env.gopts)
|
||||
// check that the root tree is included in the snapshot
|
||||
rtest.OK(t, withTermStatus(env.gopts, func(ctx context.Context, term ui.Terminal) error {
|
||||
return runCat(context.TODO(), env.gopts, []string{"tree", ids[0].String() + ":" + sn.Tree.Str()}, term)
|
||||
rtest.OK(t, withTermStatus(t, env.gopts, func(ctx context.Context, gopts GlobalOptions) error {
|
||||
return runCat(context.TODO(), gopts, []string{"tree", ids[0].String() + ":" + sn.Tree.Str()}, gopts.term)
|
||||
}))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import (
|
|||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func newRepairCommand() *cobra.Command {
|
||||
func newRepairCommand(globalOptions *GlobalOptions) *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "repair",
|
||||
Short: "Repair the repository",
|
||||
|
|
@ -13,9 +13,9 @@ func newRepairCommand() *cobra.Command {
|
|||
}
|
||||
|
||||
cmd.AddCommand(
|
||||
newRepairIndexCommand(),
|
||||
newRepairPacksCommand(),
|
||||
newRepairSnapshotsCommand(),
|
||||
newRepairIndexCommand(globalOptions),
|
||||
newRepairPacksCommand(globalOptions),
|
||||
newRepairSnapshotsCommand(globalOptions),
|
||||
)
|
||||
return cmd
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ import (
|
|||
"github.com/spf13/pflag"
|
||||
)
|
||||
|
||||
func newRepairIndexCommand() *cobra.Command {
|
||||
func newRepairIndexCommand(globalOptions *GlobalOptions) *cobra.Command {
|
||||
var opts RepairIndexOptions
|
||||
|
||||
cmd := &cobra.Command{
|
||||
|
|
@ -30,9 +30,7 @@ Exit status is 12 if the password is incorrect.
|
|||
`,
|
||||
DisableAutoGenTag: true,
|
||||
RunE: func(cmd *cobra.Command, _ []string) error {
|
||||
term, cancel := setupTermstatus()
|
||||
defer cancel()
|
||||
return runRebuildIndex(cmd.Context(), opts, globalOptions, term)
|
||||
return runRebuildIndex(cmd.Context(), opts, *globalOptions, globalOptions.term)
|
||||
},
|
||||
}
|
||||
|
||||
|
|
@ -49,10 +47,10 @@ func (opts *RepairIndexOptions) AddFlags(f *pflag.FlagSet) {
|
|||
f.BoolVar(&opts.ReadAllPacks, "read-all-packs", false, "read all pack files to generate new index from scratch")
|
||||
}
|
||||
|
||||
func newRebuildIndexCommand() *cobra.Command {
|
||||
func newRebuildIndexCommand(globalOptions *GlobalOptions) *cobra.Command {
|
||||
var opts RepairIndexOptions
|
||||
|
||||
replacement := newRepairIndexCommand()
|
||||
replacement := newRepairIndexCommand(globalOptions)
|
||||
cmd := &cobra.Command{
|
||||
Use: "rebuild-index [flags]",
|
||||
Short: replacement.Short,
|
||||
|
|
@ -62,9 +60,7 @@ func newRebuildIndexCommand() *cobra.Command {
|
|||
// must create a new instance of the run function as it captures opts
|
||||
// by reference
|
||||
RunE: func(cmd *cobra.Command, _ []string) error {
|
||||
term, cancel := setupTermstatus()
|
||||
defer cancel()
|
||||
return runRebuildIndex(cmd.Context(), opts, globalOptions, term)
|
||||
return runRebuildIndex(cmd.Context(), opts, *globalOptions, globalOptions.term)
|
||||
},
|
||||
}
|
||||
|
||||
|
|
@ -73,7 +69,7 @@ func newRebuildIndexCommand() *cobra.Command {
|
|||
}
|
||||
|
||||
func runRebuildIndex(ctx context.Context, opts RepairIndexOptions, gopts GlobalOptions, term ui.Terminal) error {
|
||||
printer := newTerminalProgressPrinter(false, gopts.verbosity, term)
|
||||
printer := ui.NewProgressPrinter(false, gopts.verbosity, term)
|
||||
|
||||
ctx, repo, unlock, err := openWithExclusiveLock(ctx, gopts, false, printer)
|
||||
if err != nil {
|
||||
|
|
|
|||
|
|
@ -13,15 +13,12 @@ import (
|
|||
"github.com/restic/restic/internal/repository/index"
|
||||
"github.com/restic/restic/internal/restic"
|
||||
rtest "github.com/restic/restic/internal/test"
|
||||
"github.com/restic/restic/internal/ui"
|
||||
)
|
||||
|
||||
func testRunRebuildIndex(t testing.TB, gopts GlobalOptions) {
|
||||
rtest.OK(t, withRestoreGlobalOptions(func() error {
|
||||
return withTermStatus(gopts, func(ctx context.Context, term ui.Terminal) error {
|
||||
globalOptions.stdout = io.Discard
|
||||
return runRebuildIndex(context.TODO(), RepairIndexOptions{}, gopts, term)
|
||||
})
|
||||
rtest.OK(t, withTermStatus(t, gopts, func(ctx context.Context, gopts GlobalOptions) error {
|
||||
gopts.Quiet = true
|
||||
return runRebuildIndex(context.TODO(), RepairIndexOptions{}, gopts, gopts.term)
|
||||
}))
|
||||
}
|
||||
|
||||
|
|
@ -32,7 +29,7 @@ func testRebuildIndex(t *testing.T, backendTestHook backendWrapper) {
|
|||
datafile := filepath.Join("..", "..", "internal", "checker", "testdata", "duplicate-packs-in-index-test-repo.tar.gz")
|
||||
rtest.SetupTarTestFixture(t, env.base, datafile)
|
||||
|
||||
out, err := testRunCheckOutput(env.gopts, false)
|
||||
out, err := testRunCheckOutput(t, env.gopts, false)
|
||||
if !strings.Contains(out, "contained in several indexes") {
|
||||
t.Fatalf("did not find checker hint for packs in several indexes")
|
||||
}
|
||||
|
|
@ -49,7 +46,7 @@ func testRebuildIndex(t *testing.T, backendTestHook backendWrapper) {
|
|||
testRunRebuildIndex(t, env.gopts)
|
||||
|
||||
env.gopts.backendTestHook = nil
|
||||
out, err = testRunCheckOutput(env.gopts, false)
|
||||
out, err = testRunCheckOutput(t, env.gopts, false)
|
||||
if len(out) != 0 {
|
||||
t.Fatalf("expected no output from the checker, got: %v", out)
|
||||
}
|
||||
|
|
@ -128,14 +125,12 @@ func TestRebuildIndexFailsOnAppendOnly(t *testing.T) {
|
|||
datafile := filepath.Join("..", "..", "internal", "checker", "testdata", "duplicate-packs-in-index-test-repo.tar.gz")
|
||||
rtest.SetupTarTestFixture(t, env.base, datafile)
|
||||
|
||||
err := withRestoreGlobalOptions(func() error {
|
||||
env.gopts.backendTestHook = func(r backend.Backend) (backend.Backend, error) {
|
||||
return &appendOnlyBackend{r}, nil
|
||||
}
|
||||
return withTermStatus(env.gopts, func(ctx context.Context, term ui.Terminal) error {
|
||||
globalOptions.stdout = io.Discard
|
||||
return runRebuildIndex(context.TODO(), RepairIndexOptions{}, env.gopts, term)
|
||||
})
|
||||
env.gopts.backendTestHook = func(r backend.Backend) (backend.Backend, error) {
|
||||
return &appendOnlyBackend{r}, nil
|
||||
}
|
||||
err := withTermStatus(t, env.gopts, func(ctx context.Context, gopts GlobalOptions) error {
|
||||
gopts.Quiet = true
|
||||
return runRebuildIndex(context.TODO(), RepairIndexOptions{}, gopts, gopts.term)
|
||||
})
|
||||
|
||||
if err == nil {
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ import (
|
|||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func newRepairPacksCommand() *cobra.Command {
|
||||
func newRepairPacksCommand(globalOptions *GlobalOptions) *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "packs [packIDs...]",
|
||||
Short: "Salvage damaged pack files",
|
||||
|
|
@ -32,9 +32,7 @@ Exit status is 12 if the password is incorrect.
|
|||
`,
|
||||
DisableAutoGenTag: true,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
term, cancel := setupTermstatus()
|
||||
defer cancel()
|
||||
return runRepairPacks(cmd.Context(), globalOptions, term, args)
|
||||
return runRepairPacks(cmd.Context(), *globalOptions, globalOptions.term, args)
|
||||
},
|
||||
}
|
||||
return cmd
|
||||
|
|
@ -53,7 +51,7 @@ func runRepairPacks(ctx context.Context, gopts GlobalOptions, term ui.Terminal,
|
|||
return errors.Fatal("no ids specified")
|
||||
}
|
||||
|
||||
printer := newTerminalProgressPrinter(false, gopts.verbosity, term)
|
||||
printer := ui.NewProgressPrinter(false, gopts.verbosity, term)
|
||||
|
||||
ctx, repo, unlock, err := openWithExclusiveLock(ctx, gopts, false, printer)
|
||||
if err != nil {
|
||||
|
|
@ -61,8 +59,7 @@ func runRepairPacks(ctx context.Context, gopts GlobalOptions, term ui.Terminal,
|
|||
}
|
||||
defer unlock()
|
||||
|
||||
bar := newIndexTerminalProgress(printer)
|
||||
err = repo.LoadIndex(ctx, bar)
|
||||
err = repo.LoadIndex(ctx, printer)
|
||||
if err != nil {
|
||||
return errors.Fatalf("%s", err)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ import (
|
|||
"github.com/spf13/pflag"
|
||||
)
|
||||
|
||||
func newRepairSnapshotsCommand() *cobra.Command {
|
||||
func newRepairSnapshotsCommand(globalOptions *GlobalOptions) *cobra.Command {
|
||||
var opts RepairOptions
|
||||
|
||||
cmd := &cobra.Command{
|
||||
|
|
@ -50,9 +50,7 @@ Exit status is 12 if the password is incorrect.
|
|||
`,
|
||||
DisableAutoGenTag: true,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
term, cancel := setupTermstatus()
|
||||
defer cancel()
|
||||
return runRepairSnapshots(cmd.Context(), globalOptions, opts, args, term)
|
||||
return runRepairSnapshots(cmd.Context(), *globalOptions, opts, args, globalOptions.term)
|
||||
},
|
||||
}
|
||||
|
||||
|
|
@ -76,7 +74,7 @@ func (opts *RepairOptions) AddFlags(f *pflag.FlagSet) {
|
|||
}
|
||||
|
||||
func runRepairSnapshots(ctx context.Context, gopts GlobalOptions, opts RepairOptions, args []string, term ui.Terminal) error {
|
||||
printer := newTerminalProgressPrinter(false, gopts.verbosity, term)
|
||||
printer := ui.NewProgressPrinter(false, gopts.verbosity, term)
|
||||
|
||||
ctx, repo, unlock, err := openWithExclusiveLock(ctx, gopts, opts.DryRun, printer)
|
||||
if err != nil {
|
||||
|
|
@ -89,8 +87,7 @@ func runRepairSnapshots(ctx context.Context, gopts GlobalOptions, opts RepairOpt
|
|||
return err
|
||||
}
|
||||
|
||||
bar := newIndexTerminalProgress(printer)
|
||||
if err := repo.LoadIndex(ctx, bar); err != nil {
|
||||
if err := repo.LoadIndex(ctx, printer); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -12,7 +12,6 @@ import (
|
|||
|
||||
"github.com/restic/restic/internal/restic"
|
||||
rtest "github.com/restic/restic/internal/test"
|
||||
"github.com/restic/restic/internal/ui"
|
||||
)
|
||||
|
||||
func testRunRepairSnapshot(t testing.TB, gopts GlobalOptions, forget bool) {
|
||||
|
|
@ -20,8 +19,8 @@ func testRunRepairSnapshot(t testing.TB, gopts GlobalOptions, forget bool) {
|
|||
Forget: forget,
|
||||
}
|
||||
|
||||
rtest.OK(t, withTermStatus(gopts, func(ctx context.Context, term ui.Terminal) error {
|
||||
return runRepairSnapshots(context.TODO(), gopts, opts, nil, term)
|
||||
rtest.OK(t, withTermStatus(t, gopts, func(ctx context.Context, gopts GlobalOptions) error {
|
||||
return runRepairSnapshots(context.TODO(), gopts, opts, nil, gopts.term)
|
||||
}))
|
||||
}
|
||||
|
||||
|
|
@ -67,7 +66,7 @@ func TestRepairSnapshotsWithLostData(t *testing.T) {
|
|||
// repository must be ok after removing the broken snapshots
|
||||
testRunForget(t, env.gopts, ForgetOptions{}, snapshotIDs[0].String(), snapshotIDs[1].String())
|
||||
testListSnapshots(t, env.gopts, 2)
|
||||
_, err := testRunCheckOutput(env.gopts, false)
|
||||
_, err := testRunCheckOutput(t, env.gopts, false)
|
||||
rtest.OK(t, err)
|
||||
}
|
||||
|
||||
|
|
@ -96,7 +95,7 @@ func TestRepairSnapshotsWithLostTree(t *testing.T) {
|
|||
testRunRebuildIndex(t, env.gopts)
|
||||
testRunRepairSnapshot(t, env.gopts, true)
|
||||
testListSnapshots(t, env.gopts, 1)
|
||||
_, err := testRunCheckOutput(env.gopts, false)
|
||||
_, err := testRunCheckOutput(t, env.gopts, false)
|
||||
rtest.OK(t, err)
|
||||
}
|
||||
|
||||
|
|
@ -119,7 +118,7 @@ func TestRepairSnapshotsWithLostRootTree(t *testing.T) {
|
|||
testRunRebuildIndex(t, env.gopts)
|
||||
testRunRepairSnapshot(t, env.gopts, true)
|
||||
testListSnapshots(t, env.gopts, 0)
|
||||
_, err := testRunCheckOutput(env.gopts, false)
|
||||
_, err := testRunCheckOutput(t, env.gopts, false)
|
||||
rtest.OK(t, err)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ import (
|
|||
"github.com/spf13/pflag"
|
||||
)
|
||||
|
||||
func newRestoreCommand() *cobra.Command {
|
||||
func newRestoreCommand(globalOptions *GlobalOptions) *cobra.Command {
|
||||
var opts RestoreOptions
|
||||
|
||||
cmd := &cobra.Command{
|
||||
|
|
@ -46,9 +46,7 @@ Exit status is 12 if the password is incorrect.
|
|||
GroupID: cmdGroupDefault,
|
||||
DisableAutoGenTag: true,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
term, cancel := setupTermstatus()
|
||||
defer cancel()
|
||||
return runRestore(cmd.Context(), opts, globalOptions, term, args)
|
||||
return runRestore(cmd.Context(), opts, *globalOptions, globalOptions.term, args)
|
||||
},
|
||||
}
|
||||
|
||||
|
|
@ -91,13 +89,19 @@ func (opts *RestoreOptions) AddFlags(f *pflag.FlagSet) {
|
|||
func runRestore(ctx context.Context, opts RestoreOptions, gopts GlobalOptions,
|
||||
term ui.Terminal, args []string) error {
|
||||
|
||||
msg := newTerminalProgressPrinter(gopts.JSON, gopts.verbosity, term)
|
||||
excludePatternFns, err := opts.ExcludePatternOptions.CollectPatterns(msg.E)
|
||||
var printer restoreui.ProgressPrinter
|
||||
if gopts.JSON {
|
||||
printer = restoreui.NewJSONProgress(term, gopts.verbosity)
|
||||
} else {
|
||||
printer = restoreui.NewTextProgress(term, gopts.verbosity)
|
||||
}
|
||||
|
||||
excludePatternFns, err := opts.ExcludePatternOptions.CollectPatterns(printer.E)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
includePatternFns, err := opts.IncludePatternOptions.CollectPatterns(msg.E)
|
||||
includePatternFns, err := opts.IncludePatternOptions.CollectPatterns(printer.E)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -132,7 +136,7 @@ func runRestore(ctx context.Context, opts RestoreOptions, gopts GlobalOptions,
|
|||
|
||||
debug.Log("restore %v to %v", snapshotIDString, opts.Target)
|
||||
|
||||
ctx, repo, unlock, err := openWithReadLock(ctx, gopts, gopts.NoLock, msg)
|
||||
ctx, repo, unlock, err := openWithReadLock(ctx, gopts, gopts.NoLock, printer)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -147,8 +151,7 @@ func runRestore(ctx context.Context, opts RestoreOptions, gopts GlobalOptions,
|
|||
return errors.Fatalf("failed to find snapshot: %v", err)
|
||||
}
|
||||
|
||||
bar := newIndexTerminalProgress(msg)
|
||||
err = repo.LoadIndex(ctx, bar)
|
||||
err = repo.LoadIndex(ctx, printer)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -158,14 +161,7 @@ func runRestore(ctx context.Context, opts RestoreOptions, gopts GlobalOptions,
|
|||
return err
|
||||
}
|
||||
|
||||
var printer restoreui.ProgressPrinter
|
||||
if gopts.JSON {
|
||||
printer = restoreui.NewJSONProgress(term, gopts.verbosity)
|
||||
} else {
|
||||
printer = restoreui.NewTextProgress(term, gopts.verbosity)
|
||||
}
|
||||
|
||||
progress := restoreui.NewProgress(printer, calculateProgressInterval(!gopts.Quiet, gopts.JSON))
|
||||
progress := restoreui.NewProgress(printer, ui.CalculateProgressInterval(!gopts.Quiet, gopts.JSON, term.CanUpdateStatus()))
|
||||
res := restorer.NewRestorer(repo, sn, restorer.Options{
|
||||
DryRun: opts.DryRun,
|
||||
Sparse: opts.Sparse,
|
||||
|
|
@ -180,13 +176,13 @@ func runRestore(ctx context.Context, opts RestoreOptions, gopts GlobalOptions,
|
|||
return progress.Error(location, err)
|
||||
}
|
||||
res.Warn = func(message string) {
|
||||
msg.E("Warning: %s\n", message)
|
||||
printer.E("Warning: %s\n", message)
|
||||
}
|
||||
res.Info = func(message string) {
|
||||
if gopts.JSON {
|
||||
return
|
||||
}
|
||||
msg.P("Info: %s\n", message)
|
||||
printer.P("Info: %s\n", message)
|
||||
}
|
||||
|
||||
selectExcludeFilter := func(item string, isDir bool) (selectedForRestore bool, childMayBeSelected bool) {
|
||||
|
|
@ -234,13 +230,13 @@ func runRestore(ctx context.Context, opts RestoreOptions, gopts GlobalOptions,
|
|||
res.SelectFilter = selectIncludeFilter
|
||||
}
|
||||
|
||||
res.XattrSelectFilter, err = getXattrSelectFilter(opts, msg)
|
||||
res.XattrSelectFilter, err = getXattrSelectFilter(opts, printer)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !gopts.JSON {
|
||||
msg.P("restoring %s to %s\n", res.Snapshot(), opts.Target)
|
||||
printer.P("restoring %s to %s\n", res.Snapshot(), opts.Target)
|
||||
}
|
||||
|
||||
countRestoredFiles, err := res.RestoreTo(ctx, opts.Target)
|
||||
|
|
@ -256,11 +252,11 @@ func runRestore(ctx context.Context, opts RestoreOptions, gopts GlobalOptions,
|
|||
|
||||
if opts.Verify {
|
||||
if !gopts.JSON {
|
||||
msg.P("verifying files in %s\n", opts.Target)
|
||||
printer.P("verifying files in %s\n", opts.Target)
|
||||
}
|
||||
var count int
|
||||
t0 := time.Now()
|
||||
bar := msg.NewCounterTerminalOnly("files verified")
|
||||
bar := printer.NewCounterTerminalOnly("files verified")
|
||||
count, err = res.VerifyFiles(ctx, opts.Target, countRestoredFiles, bar)
|
||||
if err != nil {
|
||||
return err
|
||||
|
|
@ -270,7 +266,7 @@ func runRestore(ctx context.Context, opts RestoreOptions, gopts GlobalOptions,
|
|||
}
|
||||
|
||||
if !gopts.JSON {
|
||||
msg.P("finished verifying %d files in %s (took %s)\n", count, opts.Target,
|
||||
printer.P("finished verifying %d files in %s (took %s)\n", count, opts.Target,
|
||||
time.Since(t0).Round(time.Millisecond))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ package main
|
|||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"math/rand"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
|
@ -14,11 +13,10 @@ import (
|
|||
|
||||
"github.com/restic/restic/internal/restic"
|
||||
rtest "github.com/restic/restic/internal/test"
|
||||
"github.com/restic/restic/internal/ui"
|
||||
)
|
||||
|
||||
func testRunRestore(t testing.TB, opts GlobalOptions, dir string, snapshotID string) {
|
||||
testRunRestoreExcludes(t, opts, dir, snapshotID, nil)
|
||||
func testRunRestore(t testing.TB, gopts GlobalOptions, dir string, snapshotID string) {
|
||||
testRunRestoreExcludes(t, gopts, dir, snapshotID, nil)
|
||||
}
|
||||
|
||||
func testRunRestoreExcludes(t testing.TB, gopts GlobalOptions, dir string, snapshotID string, excludes []string) {
|
||||
|
|
@ -27,12 +25,12 @@ func testRunRestoreExcludes(t testing.TB, gopts GlobalOptions, dir string, snaps
|
|||
}
|
||||
opts.Excludes = excludes
|
||||
|
||||
rtest.OK(t, testRunRestoreAssumeFailure(snapshotID, opts, gopts))
|
||||
rtest.OK(t, testRunRestoreAssumeFailure(t, snapshotID, opts, gopts))
|
||||
}
|
||||
|
||||
func testRunRestoreAssumeFailure(snapshotID string, opts RestoreOptions, gopts GlobalOptions) error {
|
||||
return withTermStatus(gopts, func(ctx context.Context, term ui.Terminal) error {
|
||||
return runRestore(ctx, opts, gopts, term, []string{snapshotID})
|
||||
func testRunRestoreAssumeFailure(t testing.TB, snapshotID string, opts RestoreOptions, gopts GlobalOptions) error {
|
||||
return withTermStatus(t, gopts, func(ctx context.Context, gopts GlobalOptions) error {
|
||||
return runRestore(ctx, opts, gopts, gopts.term, []string{snapshotID})
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -45,7 +43,7 @@ func testRunRestoreLatest(t testing.TB, gopts GlobalOptions, dir string, paths [
|
|||
},
|
||||
}
|
||||
|
||||
rtest.OK(t, testRunRestoreAssumeFailure("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) {
|
||||
|
|
@ -54,7 +52,7 @@ func testRunRestoreIncludes(t testing.TB, gopts GlobalOptions, dir string, snaps
|
|||
}
|
||||
opts.Includes = includes
|
||||
|
||||
rtest.OK(t, testRunRestoreAssumeFailure(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) {
|
||||
|
|
@ -63,7 +61,7 @@ func testRunRestoreIncludesFromFile(t testing.TB, gopts GlobalOptions, dir strin
|
|||
}
|
||||
opts.IncludeFiles = []string{includesFile}
|
||||
|
||||
rtest.OK(t, testRunRestoreAssumeFailure(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) {
|
||||
|
|
@ -72,7 +70,7 @@ func testRunRestoreExcludesFromFile(t testing.TB, gopts GlobalOptions, dir strin
|
|||
}
|
||||
opts.ExcludeFiles = []string{excludesFile}
|
||||
|
||||
rtest.OK(t, testRunRestoreAssumeFailure(snapshotID.String(), opts, gopts))
|
||||
rtest.OK(t, testRunRestoreAssumeFailure(t, snapshotID.String(), opts, gopts))
|
||||
}
|
||||
|
||||
func TestRestoreMustFailWhenUsingBothIncludesAndExcludes(t *testing.T) {
|
||||
|
|
@ -93,7 +91,7 @@ func TestRestoreMustFailWhenUsingBothIncludesAndExcludes(t *testing.T) {
|
|||
restoreOpts.Includes = includePatterns
|
||||
restoreOpts.Excludes = excludePatterns
|
||||
|
||||
err := testRunRestoreAssumeFailure("latest", restoreOpts, env.gopts)
|
||||
err := testRunRestoreAssumeFailure(t, "latest", restoreOpts, env.gopts)
|
||||
rtest.Assert(t, err != nil && strings.Contains(err.Error(), "exclude and include patterns are mutually exclusive"),
|
||||
"expected: %s error, got %v", "exclude and include patterns are mutually exclusive", err)
|
||||
}
|
||||
|
|
@ -257,7 +255,7 @@ func TestRestore(t *testing.T) {
|
|||
restoredir := filepath.Join(env.base, "restore")
|
||||
testRunRestoreLatest(t, env.gopts, restoredir, nil, nil)
|
||||
|
||||
diff := directoriesContentsDiff(env.testdata, filepath.Join(restoredir, filepath.Base(env.testdata)))
|
||||
diff := directoriesContentsDiff(t, env.testdata, filepath.Join(restoredir, filepath.Base(env.testdata)))
|
||||
rtest.Assert(t, diff == "", "directories are not equal %v", diff)
|
||||
}
|
||||
|
||||
|
|
@ -337,11 +335,7 @@ func TestRestoreWithPermissionFailure(t *testing.T) {
|
|||
|
||||
snapshots := testListSnapshots(t, env.gopts, 1)
|
||||
|
||||
_ = withRestoreGlobalOptions(func() error {
|
||||
globalOptions.stderr = io.Discard
|
||||
testRunRestore(t, env.gopts, filepath.Join(env.base, "restore"), snapshots[0].String())
|
||||
return nil
|
||||
})
|
||||
testRunRestore(t, env.gopts, filepath.Join(env.base, "restore"), snapshots[0].String())
|
||||
|
||||
// make sure that all files have been restored, regardless of any
|
||||
// permission errors
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ import (
|
|||
"github.com/restic/restic/internal/walker"
|
||||
)
|
||||
|
||||
func newRewriteCommand() *cobra.Command {
|
||||
func newRewriteCommand(globalOptions *GlobalOptions) *cobra.Command {
|
||||
var opts RewriteOptions
|
||||
|
||||
cmd := &cobra.Command{
|
||||
|
|
@ -60,9 +60,7 @@ Exit status is 12 if the password is incorrect.
|
|||
GroupID: cmdGroupDefault,
|
||||
DisableAutoGenTag: true,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
term, cancel := setupTermstatus()
|
||||
defer cancel()
|
||||
return runRewrite(cmd.Context(), opts, globalOptions, args, term)
|
||||
return runRewrite(cmd.Context(), opts, *globalOptions, args, globalOptions.term)
|
||||
},
|
||||
}
|
||||
|
||||
|
|
@ -296,7 +294,7 @@ func runRewrite(ctx context.Context, opts RewriteOptions, gopts GlobalOptions, a
|
|||
return errors.Fatal("Nothing to do: no excludes provided and no new metadata provided")
|
||||
}
|
||||
|
||||
printer := newTerminalProgressPrinter(false, gopts.verbosity, term)
|
||||
printer := ui.NewProgressPrinter(false, gopts.verbosity, term)
|
||||
|
||||
var (
|
||||
repo *repository.Repository
|
||||
|
|
@ -320,8 +318,7 @@ func runRewrite(ctx context.Context, opts RewriteOptions, gopts GlobalOptions, a
|
|||
return err
|
||||
}
|
||||
|
||||
bar := newIndexTerminalProgress(printer)
|
||||
if err = repo.LoadIndex(ctx, bar); err != nil {
|
||||
if err = repo.LoadIndex(ctx, printer); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -20,8 +20,8 @@ func testRunRewriteExclude(t testing.TB, gopts GlobalOptions, excludes []string,
|
|||
Metadata: metadata,
|
||||
}
|
||||
|
||||
rtest.OK(t, withTermStatus(gopts, func(ctx context.Context, term ui.Terminal) error {
|
||||
return runRewrite(context.TODO(), opts, gopts, nil, term)
|
||||
rtest.OK(t, withTermStatus(t, gopts, func(ctx context.Context, gopts GlobalOptions) error {
|
||||
return runRewrite(context.TODO(), opts, gopts, nil, gopts.term)
|
||||
}))
|
||||
}
|
||||
|
||||
|
|
@ -41,9 +41,9 @@ func getSnapshot(t testing.TB, snapshotID restic.ID, env *testEnvironment) *rest
|
|||
t.Helper()
|
||||
|
||||
var snapshots []*restic.Snapshot
|
||||
err := withTermStatus(env.gopts, func(ctx context.Context, term ui.Terminal) error {
|
||||
printer := newTerminalProgressPrinter(env.gopts.JSON, env.gopts.verbosity, term)
|
||||
ctx, repo, unlock, err := openWithReadLock(ctx, env.gopts, false, printer)
|
||||
err := withTermStatus(t, env.gopts, func(ctx context.Context, gopts GlobalOptions) error {
|
||||
printer := ui.NewProgressPrinter(gopts.JSON, gopts.verbosity, gopts.term)
|
||||
ctx, repo, unlock, err := openWithReadLock(ctx, gopts, false, printer)
|
||||
rtest.OK(t, err)
|
||||
defer unlock()
|
||||
|
||||
|
|
@ -117,9 +117,9 @@ func testRewriteMetadata(t *testing.T, metadata snapshotMetadataArgs) {
|
|||
testRunRewriteExclude(t, env.gopts, []string{}, true, metadata)
|
||||
|
||||
var snapshots []*restic.Snapshot
|
||||
err := withTermStatus(env.gopts, func(ctx context.Context, term ui.Terminal) error {
|
||||
printer := newTerminalProgressPrinter(env.gopts.JSON, env.gopts.verbosity, term)
|
||||
ctx, repo, unlock, err := openWithReadLock(ctx, env.gopts, false, printer)
|
||||
err := withTermStatus(t, env.gopts, func(ctx context.Context, gopts GlobalOptions) error {
|
||||
printer := ui.NewProgressPrinter(gopts.JSON, gopts.verbosity, gopts.term)
|
||||
ctx, repo, unlock, err := openWithReadLock(ctx, gopts, false, printer)
|
||||
rtest.OK(t, err)
|
||||
defer unlock()
|
||||
|
||||
|
|
@ -157,17 +157,17 @@ func TestRewriteSnaphotSummary(t *testing.T) {
|
|||
defer cleanup()
|
||||
createBasicRewriteRepo(t, env)
|
||||
|
||||
rtest.OK(t, withTermStatus(env.gopts, func(ctx context.Context, term ui.Terminal) error {
|
||||
return runRewrite(context.TODO(), RewriteOptions{SnapshotSummary: true}, env.gopts, []string{}, term)
|
||||
rtest.OK(t, withTermStatus(t, env.gopts, func(ctx context.Context, gopts GlobalOptions) error {
|
||||
return runRewrite(context.TODO(), RewriteOptions{SnapshotSummary: true}, gopts, []string{}, gopts.term)
|
||||
}))
|
||||
// no new snapshot should be created as the snapshot already has a summary
|
||||
snapshots := testListSnapshots(t, env.gopts, 1)
|
||||
|
||||
// replace snapshot by one without a summary
|
||||
var oldSummary *restic.SnapshotSummary
|
||||
err := withTermStatus(env.gopts, func(ctx context.Context, term ui.Terminal) error {
|
||||
printer := newTerminalProgressPrinter(env.gopts.JSON, env.gopts.verbosity, term)
|
||||
_, repo, unlock, err := openWithExclusiveLock(ctx, env.gopts, false, printer)
|
||||
err := withTermStatus(t, env.gopts, func(ctx context.Context, gopts GlobalOptions) error {
|
||||
printer := ui.NewProgressPrinter(gopts.JSON, gopts.verbosity, gopts.term)
|
||||
_, repo, unlock, err := openWithExclusiveLock(ctx, gopts, false, printer)
|
||||
rtest.OK(t, err)
|
||||
defer unlock()
|
||||
|
||||
|
|
@ -182,8 +182,8 @@ func TestRewriteSnaphotSummary(t *testing.T) {
|
|||
rtest.OK(t, err)
|
||||
|
||||
// rewrite snapshot and lookup ID of new snapshot
|
||||
rtest.OK(t, withTermStatus(env.gopts, func(ctx context.Context, term ui.Terminal) error {
|
||||
return runRewrite(context.TODO(), RewriteOptions{SnapshotSummary: true}, env.gopts, []string{}, term)
|
||||
rtest.OK(t, withTermStatus(t, env.gopts, func(ctx context.Context, gopts GlobalOptions) error {
|
||||
return runRewrite(context.TODO(), RewriteOptions{SnapshotSummary: true}, gopts, []string{}, gopts.term)
|
||||
}))
|
||||
newSnapshots := testListSnapshots(t, env.gopts, 2)
|
||||
newSnapshot := restic.NewIDSet(newSnapshots...).Sub(restic.NewIDSet(snapshots...)).List()[0]
|
||||
|
|
|
|||
|
|
@ -14,13 +14,13 @@ import (
|
|||
"github.com/spf13/pflag"
|
||||
)
|
||||
|
||||
func registerSelfUpdateCommand(cmd *cobra.Command) {
|
||||
func registerSelfUpdateCommand(cmd *cobra.Command, globalOptions *GlobalOptions) {
|
||||
cmd.AddCommand(
|
||||
newSelfUpdateCommand(),
|
||||
newSelfUpdateCommand(globalOptions),
|
||||
)
|
||||
}
|
||||
|
||||
func newSelfUpdateCommand() *cobra.Command {
|
||||
func newSelfUpdateCommand(globalOptions *GlobalOptions) *cobra.Command {
|
||||
var opts SelfUpdateOptions
|
||||
|
||||
cmd := &cobra.Command{
|
||||
|
|
@ -43,9 +43,7 @@ Exit status is 12 if the password is incorrect.
|
|||
`,
|
||||
DisableAutoGenTag: true,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
term, cancel := setupTermstatus()
|
||||
defer cancel()
|
||||
return runSelfUpdate(cmd.Context(), opts, globalOptions, args, term)
|
||||
return runSelfUpdate(cmd.Context(), opts, *globalOptions, args, globalOptions.term)
|
||||
},
|
||||
}
|
||||
|
||||
|
|
@ -88,7 +86,7 @@ func runSelfUpdate(ctx context.Context, opts SelfUpdateOptions, gopts GlobalOpti
|
|||
}
|
||||
}
|
||||
|
||||
printer := newTerminalProgressPrinter(false, gopts.verbosity, term)
|
||||
printer := ui.NewProgressPrinter(false, gopts.verbosity, term)
|
||||
printer.P("writing restic to %v", opts.Output)
|
||||
|
||||
v, err := selfupdate.DownloadLatestStableRelease(ctx, opts.Output, version, printer.P)
|
||||
|
|
|
|||
|
|
@ -4,6 +4,6 @@ package main
|
|||
|
||||
import "github.com/spf13/cobra"
|
||||
|
||||
func registerSelfUpdateCommand(_ *cobra.Command) {
|
||||
func registerSelfUpdateCommand(_ *cobra.Command, _ *GlobalOptions) {
|
||||
// No commands to register in non-selfupdate mode
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ import (
|
|||
"github.com/spf13/pflag"
|
||||
)
|
||||
|
||||
func newSnapshotsCommand() *cobra.Command {
|
||||
func newSnapshotsCommand(globalOptions *GlobalOptions) *cobra.Command {
|
||||
var opts SnapshotOptions
|
||||
|
||||
cmd := &cobra.Command{
|
||||
|
|
@ -36,9 +36,7 @@ Exit status is 12 if the password is incorrect.
|
|||
GroupID: cmdGroupDefault,
|
||||
DisableAutoGenTag: true,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
term, cancel := setupTermstatus()
|
||||
defer cancel()
|
||||
return runSnapshots(cmd.Context(), opts, globalOptions, args, term)
|
||||
return runSnapshots(cmd.Context(), opts, *globalOptions, args, globalOptions.term)
|
||||
},
|
||||
}
|
||||
|
||||
|
|
@ -69,7 +67,7 @@ func (opts *SnapshotOptions) AddFlags(f *pflag.FlagSet) {
|
|||
}
|
||||
|
||||
func runSnapshots(ctx context.Context, opts SnapshotOptions, gopts GlobalOptions, args []string, term ui.Terminal) error {
|
||||
printer := newTerminalProgressPrinter(gopts.JSON, gopts.verbosity, term)
|
||||
printer := ui.NewProgressPrinter(gopts.JSON, gopts.verbosity, term)
|
||||
ctx, repo, unlock, err := openWithReadLock(ctx, gopts, gopts.NoLock, printer)
|
||||
if err != nil {
|
||||
return err
|
||||
|
|
@ -105,7 +103,7 @@ func runSnapshots(ctx context.Context, opts SnapshotOptions, gopts GlobalOptions
|
|||
}
|
||||
|
||||
if gopts.JSON {
|
||||
err := printSnapshotGroupJSON(globalOptions.stdout, snapshotGroups, grouped)
|
||||
err := printSnapshotGroupJSON(gopts.term.OutputWriter(), snapshotGroups, grouped)
|
||||
if err != nil {
|
||||
printer.E("error printing snapshots: %v", err)
|
||||
}
|
||||
|
|
@ -118,12 +116,12 @@ func runSnapshots(ctx context.Context, opts SnapshotOptions, gopts GlobalOptions
|
|||
}
|
||||
|
||||
if grouped {
|
||||
err := PrintSnapshotGroupHeader(globalOptions.stdout, k)
|
||||
err := PrintSnapshotGroupHeader(gopts.term.OutputWriter(), k)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
err := PrintSnapshots(globalOptions.stdout, list, nil, opts.Compact)
|
||||
err := PrintSnapshots(gopts.term.OutputWriter(), list, nil, opts.Compact)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -301,9 +299,7 @@ func PrintSnapshotGroupHeader(stdout io.Writer, groupKeyJSON string) error {
|
|||
}
|
||||
|
||||
// Info
|
||||
if _, err := fmt.Fprintf(stdout, "snapshots"); err != nil {
|
||||
return err
|
||||
}
|
||||
header := "snapshots"
|
||||
var infoStrings []string
|
||||
if key.Hostname != "" {
|
||||
infoStrings = append(infoStrings, "host ["+key.Hostname+"]")
|
||||
|
|
@ -315,12 +311,10 @@ func PrintSnapshotGroupHeader(stdout io.Writer, groupKeyJSON string) error {
|
|||
infoStrings = append(infoStrings, "paths ["+strings.Join(key.Paths, ", ")+"]")
|
||||
}
|
||||
if infoStrings != nil {
|
||||
if _, err := fmt.Fprintf(stdout, " for (%s)", strings.Join(infoStrings, ", ")); err != nil {
|
||||
return err
|
||||
}
|
||||
header += " for (" + strings.Join(infoStrings, ", ") + ")"
|
||||
}
|
||||
_, err = fmt.Fprintf(stdout, ":\n")
|
||||
|
||||
header += ":\n"
|
||||
_, err = stdout.Write([]byte(header))
|
||||
return err
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -7,17 +7,14 @@ import (
|
|||
|
||||
"github.com/restic/restic/internal/restic"
|
||||
rtest "github.com/restic/restic/internal/test"
|
||||
"github.com/restic/restic/internal/ui"
|
||||
)
|
||||
|
||||
func testRunSnapshots(t testing.TB, gopts GlobalOptions) (newest *Snapshot, snapmap map[restic.ID]Snapshot) {
|
||||
buf, err := withCaptureStdout(gopts, func(gopts GlobalOptions) error {
|
||||
buf, err := withCaptureStdout(t, gopts, func(ctx context.Context, gopts GlobalOptions) error {
|
||||
gopts.JSON = true
|
||||
|
||||
opts := SnapshotOptions{}
|
||||
return withTermStatus(gopts, func(ctx context.Context, term ui.Terminal) error {
|
||||
return runSnapshots(ctx, opts, gopts, []string{}, term)
|
||||
})
|
||||
return runSnapshots(ctx, opts, gopts, []string{}, gopts.term)
|
||||
})
|
||||
rtest.OK(t, err)
|
||||
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ import (
|
|||
"github.com/spf13/pflag"
|
||||
)
|
||||
|
||||
func newStatsCommand() *cobra.Command {
|
||||
func newStatsCommand(globalOptions *GlobalOptions) *cobra.Command {
|
||||
var opts StatsOptions
|
||||
|
||||
cmd := &cobra.Command{
|
||||
|
|
@ -63,9 +63,7 @@ Exit status is 12 if the password is incorrect.
|
|||
GroupID: cmdGroupDefault,
|
||||
DisableAutoGenTag: true,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
term, cancel := setupTermstatus()
|
||||
defer cancel()
|
||||
return runStats(cmd.Context(), opts, globalOptions, args, term)
|
||||
return runStats(cmd.Context(), opts, *globalOptions, args, globalOptions.term)
|
||||
},
|
||||
}
|
||||
|
||||
|
|
@ -101,7 +99,7 @@ func runStats(ctx context.Context, opts StatsOptions, gopts GlobalOptions, args
|
|||
return err
|
||||
}
|
||||
|
||||
printer := newTerminalProgressPrinter(gopts.JSON, gopts.verbosity, term)
|
||||
printer := ui.NewProgressPrinter(gopts.JSON, gopts.verbosity, term)
|
||||
|
||||
ctx, repo, unlock, err := openWithReadLock(ctx, gopts, gopts.NoLock, printer)
|
||||
if err != nil {
|
||||
|
|
@ -113,8 +111,7 @@ func runStats(ctx context.Context, opts StatsOptions, gopts GlobalOptions, args
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
bar := newIndexTerminalProgress(printer)
|
||||
if err = repo.LoadIndex(ctx, bar); err != nil {
|
||||
if err = repo.LoadIndex(ctx, printer); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
|
@ -171,7 +168,7 @@ func runStats(ctx context.Context, opts StatsOptions, gopts GlobalOptions, args
|
|||
}
|
||||
|
||||
if gopts.JSON {
|
||||
err = json.NewEncoder(globalOptions.stdout).Encode(stats)
|
||||
err = json.NewEncoder(gopts.term.OutputWriter()).Encode(stats)
|
||||
if err != nil {
|
||||
return fmt.Errorf("encoding output: %v", err)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ import (
|
|||
"github.com/restic/restic/internal/ui"
|
||||
)
|
||||
|
||||
func newTagCommand() *cobra.Command {
|
||||
func newTagCommand(globalOptions *GlobalOptions) *cobra.Command {
|
||||
var opts TagOptions
|
||||
|
||||
cmd := &cobra.Command{
|
||||
|
|
@ -39,9 +39,7 @@ Exit status is 12 if the password is incorrect.
|
|||
GroupID: cmdGroupDefault,
|
||||
DisableAutoGenTag: true,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
term, cancel := setupTermstatus()
|
||||
defer cancel()
|
||||
return runTag(cmd.Context(), opts, globalOptions, term, args)
|
||||
return runTag(cmd.Context(), opts, *globalOptions, globalOptions.term, args)
|
||||
},
|
||||
}
|
||||
|
||||
|
|
@ -119,7 +117,7 @@ func changeTags(ctx context.Context, repo *repository.Repository, sn *restic.Sna
|
|||
}
|
||||
|
||||
func runTag(ctx context.Context, opts TagOptions, gopts GlobalOptions, term ui.Terminal, args []string) error {
|
||||
printer := newTerminalProgressPrinter(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 {
|
||||
return errors.Fatal("nothing to do!")
|
||||
|
|
|
|||
|
|
@ -9,7 +9,9 @@ import (
|
|||
)
|
||||
|
||||
func testRunTag(t testing.TB, opts TagOptions, gopts GlobalOptions) {
|
||||
rtest.OK(t, runTag(context.TODO(), opts, gopts, nil, []string{}))
|
||||
rtest.OK(t, withTermStatus(t, gopts, func(ctx context.Context, gopts GlobalOptions) error {
|
||||
return runTag(context.TODO(), opts, gopts, gopts.term, []string{})
|
||||
}))
|
||||
}
|
||||
|
||||
func TestTag(t *testing.T) {
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ import (
|
|||
"github.com/spf13/pflag"
|
||||
)
|
||||
|
||||
func newUnlockCommand() *cobra.Command {
|
||||
func newUnlockCommand(globalOptions *GlobalOptions) *cobra.Command {
|
||||
var opts UnlockOptions
|
||||
|
||||
cmd := &cobra.Command{
|
||||
|
|
@ -27,9 +27,7 @@ Exit status is 1 if there was any error.
|
|||
GroupID: cmdGroupDefault,
|
||||
DisableAutoGenTag: true,
|
||||
RunE: func(cmd *cobra.Command, _ []string) error {
|
||||
term, cancel := setupTermstatus()
|
||||
defer cancel()
|
||||
return runUnlock(cmd.Context(), opts, globalOptions, term)
|
||||
return runUnlock(cmd.Context(), opts, *globalOptions, globalOptions.term)
|
||||
},
|
||||
}
|
||||
opts.AddFlags(cmd.Flags())
|
||||
|
|
@ -46,7 +44,7 @@ func (opts *UnlockOptions) AddFlags(f *pflag.FlagSet) {
|
|||
}
|
||||
|
||||
func runUnlock(ctx context.Context, opts UnlockOptions, gopts GlobalOptions, term ui.Terminal) error {
|
||||
printer := newTerminalProgressPrinter(gopts.JSON, gopts.verbosity, term)
|
||||
printer := ui.NewProgressPrinter(gopts.JSON, gopts.verbosity, term)
|
||||
repo, err := OpenRepository(ctx, gopts, printer)
|
||||
if err != nil {
|
||||
return err
|
||||
|
|
|
|||
|
|
@ -4,10 +4,11 @@ import (
|
|||
"encoding/json"
|
||||
"runtime"
|
||||
|
||||
"github.com/restic/restic/internal/ui"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func newVersionCommand() *cobra.Command {
|
||||
func newVersionCommand(globalOptions *GlobalOptions) *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "version",
|
||||
Short: "Print version information",
|
||||
|
|
@ -23,9 +24,7 @@ Exit status is 1 if there was any error.
|
|||
`,
|
||||
DisableAutoGenTag: true,
|
||||
Run: func(_ *cobra.Command, _ []string) {
|
||||
term, cancel := setupTermstatus()
|
||||
defer cancel()
|
||||
printer := newTerminalProgressPrinter(globalOptions.JSON, globalOptions.verbosity, term)
|
||||
printer := ui.NewProgressPrinter(globalOptions.JSON, globalOptions.verbosity, globalOptions.term)
|
||||
|
||||
if globalOptions.JSON {
|
||||
type jsonVersion struct {
|
||||
|
|
@ -44,7 +43,7 @@ Exit status is 1 if there was any error.
|
|||
GoArch: runtime.GOARCH,
|
||||
}
|
||||
|
||||
err := json.NewEncoder(globalOptions.stdout).Encode(jsonS)
|
||||
err := json.NewEncoder(globalOptions.term.OutputWriter()).Encode(jsonS)
|
||||
if err != nil {
|
||||
printer.E("JSON encode failed: %v\n", err)
|
||||
return
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import (
|
|||
// TestFlags checks for double defined flags, the commands will panic on
|
||||
// ParseFlags() when a shorthand flag is defined twice.
|
||||
func TestFlags(t *testing.T) {
|
||||
for _, cmd := range newRootCommand().Commands() {
|
||||
for _, cmd := range newRootCommand(&GlobalOptions{}).Commands() {
|
||||
t.Run(cmd.Name(), func(t *testing.T) {
|
||||
cmd.Flags().SetOutput(io.Discard)
|
||||
err := cmd.ParseFlags([]string{"--help"})
|
||||
|
|
|
|||
|
|
@ -1,10 +1,8 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
|
|
@ -32,8 +30,8 @@ import (
|
|||
"github.com/restic/restic/internal/options"
|
||||
"github.com/restic/restic/internal/repository"
|
||||
"github.com/restic/restic/internal/restic"
|
||||
"github.com/restic/restic/internal/terminal"
|
||||
"github.com/restic/restic/internal/textfile"
|
||||
"github.com/restic/restic/internal/ui"
|
||||
"github.com/restic/restic/internal/ui/progress"
|
||||
"github.com/spf13/pflag"
|
||||
|
||||
|
|
@ -75,8 +73,7 @@ type GlobalOptions struct {
|
|||
limiter.Limits
|
||||
|
||||
password string
|
||||
stdout io.Writer
|
||||
stderr io.Writer
|
||||
term ui.Terminal
|
||||
|
||||
backends *location.Registry
|
||||
backendTestHook, backendInnerTestHook backendWrapper
|
||||
|
|
@ -177,12 +174,6 @@ func (opts *GlobalOptions) PreRun(needsPassword bool) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
var globalOptions = GlobalOptions{
|
||||
stdout: os.Stdout,
|
||||
stderr: os.Stderr,
|
||||
backends: collectBackends(),
|
||||
}
|
||||
|
||||
func collectBackends() *location.Registry {
|
||||
backends := location.NewRegistry()
|
||||
backends.Register(azure.NewFactory())
|
||||
|
|
@ -236,45 +227,24 @@ func loadPasswordFromFile(pwdFile string) (string, error) {
|
|||
return strings.TrimSpace(string(s)), errors.Wrap(err, "Readfile")
|
||||
}
|
||||
|
||||
// readPassword reads the password from the given reader directly.
|
||||
func readPassword(in io.Reader) (password string, err error) {
|
||||
sc := bufio.NewScanner(in)
|
||||
sc.Scan()
|
||||
|
||||
return sc.Text(), errors.WithStack(sc.Err())
|
||||
}
|
||||
|
||||
// ReadPassword reads the password from a password file, the environment
|
||||
// variable RESTIC_PASSWORD or prompts the user. If the context is canceled,
|
||||
// the function leaks the password reading goroutine.
|
||||
func ReadPassword(ctx context.Context, opts GlobalOptions, prompt string, printer progress.Printer) (string, error) {
|
||||
if opts.InsecureNoPassword {
|
||||
if opts.password != "" {
|
||||
func ReadPassword(ctx context.Context, gopts GlobalOptions, prompt string) (string, error) {
|
||||
if gopts.InsecureNoPassword {
|
||||
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 "", nil
|
||||
}
|
||||
|
||||
if opts.password != "" {
|
||||
return opts.password, nil
|
||||
}
|
||||
|
||||
var (
|
||||
password string
|
||||
err error
|
||||
)
|
||||
|
||||
if terminal.StdinIsTerminal() {
|
||||
password, err = terminal.ReadPassword(ctx, os.Stdin, os.Stderr, prompt)
|
||||
} else {
|
||||
if terminal.StdoutIsTerminal() {
|
||||
printer.P("reading repository password from stdin")
|
||||
}
|
||||
password, err = readPassword(os.Stdin)
|
||||
if gopts.password != "" {
|
||||
return gopts.password, nil
|
||||
}
|
||||
|
||||
password, err := gopts.term.ReadPassword(ctx, prompt)
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "unable to read password")
|
||||
return "", fmt.Errorf("unable to read password: %w", err)
|
||||
}
|
||||
|
||||
if len(password) == 0 {
|
||||
|
|
@ -287,13 +257,13 @@ func ReadPassword(ctx context.Context, opts GlobalOptions, prompt string, printe
|
|||
// ReadPasswordTwice calls ReadPassword two times and returns an error when the
|
||||
// passwords don't match. If the context is canceled, the function leaks the
|
||||
// password reading goroutine.
|
||||
func ReadPasswordTwice(ctx context.Context, gopts GlobalOptions, prompt1, prompt2 string, printer progress.Printer) (string, error) {
|
||||
pw1, err := ReadPassword(ctx, gopts, prompt1, printer)
|
||||
func ReadPasswordTwice(ctx context.Context, gopts GlobalOptions, prompt1, prompt2 string) (string, error) {
|
||||
pw1, err := ReadPassword(ctx, gopts, prompt1)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if terminal.StdinIsTerminal() {
|
||||
pw2, err := ReadPassword(ctx, gopts, prompt2, printer)
|
||||
if gopts.term.InputIsTerminal() {
|
||||
pw2, err := ReadPassword(ctx, gopts, prompt2)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
|
@ -306,20 +276,20 @@ func ReadPasswordTwice(ctx context.Context, gopts GlobalOptions, prompt1, prompt
|
|||
return pw1, nil
|
||||
}
|
||||
|
||||
func ReadRepo(opts GlobalOptions) (string, error) {
|
||||
if opts.Repo == "" && opts.RepositoryFile == "" {
|
||||
func ReadRepo(gopts GlobalOptions) (string, error) {
|
||||
if gopts.Repo == "" && gopts.RepositoryFile == "" {
|
||||
return "", errors.Fatal("Please specify repository location (-r or --repository-file)")
|
||||
}
|
||||
|
||||
repo := opts.Repo
|
||||
if opts.RepositoryFile != "" {
|
||||
repo := gopts.Repo
|
||||
if gopts.RepositoryFile != "" {
|
||||
if repo != "" {
|
||||
return "", errors.Fatal("Options -r and --repository-file are mutually exclusive, please specify only one")
|
||||
}
|
||||
|
||||
s, err := textfile.Read(opts.RepositoryFile)
|
||||
s, err := textfile.Read(gopts.RepositoryFile)
|
||||
if errors.Is(err, os.ErrNotExist) {
|
||||
return "", errors.Fatalf("%s does not exist", opts.RepositoryFile)
|
||||
return "", errors.Fatalf("%s does not exist", gopts.RepositoryFile)
|
||||
}
|
||||
if err != nil {
|
||||
return "", err
|
||||
|
|
@ -334,47 +304,47 @@ func ReadRepo(opts GlobalOptions) (string, error) {
|
|||
const maxKeys = 20
|
||||
|
||||
// OpenRepository reads the password and opens the repository.
|
||||
func OpenRepository(ctx context.Context, opts GlobalOptions, printer progress.Printer) (*repository.Repository, error) {
|
||||
repo, err := ReadRepo(opts)
|
||||
func OpenRepository(ctx context.Context, gopts GlobalOptions, printer progress.Printer) (*repository.Repository, error) {
|
||||
repo, err := ReadRepo(gopts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
be, err := open(ctx, repo, opts, opts.extended, printer)
|
||||
be, err := open(ctx, repo, gopts, gopts.extended, printer)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
s, err := repository.New(be, repository.Options{
|
||||
Compression: opts.Compression,
|
||||
PackSize: opts.PackSize * 1024 * 1024,
|
||||
NoExtraVerify: opts.NoExtraVerify,
|
||||
Compression: gopts.Compression,
|
||||
PackSize: gopts.PackSize * 1024 * 1024,
|
||||
NoExtraVerify: gopts.NoExtraVerify,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, errors.Fatalf("%s", err)
|
||||
}
|
||||
|
||||
passwordTriesLeft := 1
|
||||
if terminal.StdinIsTerminal() && opts.password == "" && !opts.InsecureNoPassword {
|
||||
if gopts.term.InputIsTerminal() && gopts.password == "" && !gopts.InsecureNoPassword {
|
||||
passwordTriesLeft = 3
|
||||
}
|
||||
|
||||
for ; passwordTriesLeft > 0; passwordTriesLeft-- {
|
||||
opts.password, err = ReadPassword(ctx, opts, "enter password for repository: ", printer)
|
||||
gopts.password, err = ReadPassword(ctx, gopts, "enter password for repository: ")
|
||||
if ctx.Err() != nil {
|
||||
return nil, ctx.Err()
|
||||
}
|
||||
if err != nil && passwordTriesLeft > 1 {
|
||||
opts.password = ""
|
||||
gopts.password = ""
|
||||
printer.E("%s. Try again", err)
|
||||
}
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
err = s.SearchKey(ctx, opts.password, maxKeys, opts.KeyHint)
|
||||
err = s.SearchKey(ctx, gopts.password, maxKeys, gopts.KeyHint)
|
||||
if err != nil && passwordTriesLeft > 1 {
|
||||
opts.password = ""
|
||||
gopts.password = ""
|
||||
printer.E("%s. Try again", err)
|
||||
}
|
||||
}
|
||||
|
|
@ -385,36 +355,32 @@ func OpenRepository(ctx context.Context, opts GlobalOptions, printer progress.Pr
|
|||
return nil, errors.Fatalf("%s", err)
|
||||
}
|
||||
|
||||
if terminal.StdoutIsTerminal() && !opts.JSON {
|
||||
id := s.Config().ID
|
||||
if len(id) > 8 {
|
||||
id = id[:8]
|
||||
}
|
||||
if !opts.JSON {
|
||||
extra := ""
|
||||
if s.Config().Version >= 2 {
|
||||
extra = ", compression level " + opts.Compression.String()
|
||||
}
|
||||
printer.P("repository %v opened (version %v%s)", id, s.Config().Version, extra)
|
||||
}
|
||||
id := s.Config().ID
|
||||
if len(id) > 8 {
|
||||
id = id[:8]
|
||||
}
|
||||
extra := ""
|
||||
if s.Config().Version >= 2 {
|
||||
extra = ", compression level " + gopts.Compression.String()
|
||||
}
|
||||
printer.PT("repository %v opened (version %v%s)", id, s.Config().Version, extra)
|
||||
|
||||
if opts.NoCache {
|
||||
if gopts.NoCache {
|
||||
return s, nil
|
||||
}
|
||||
|
||||
c, err := cache.New(s.Config().ID, opts.CacheDir)
|
||||
c, err := cache.New(s.Config().ID, gopts.CacheDir)
|
||||
if err != nil {
|
||||
printer.E("unable to open cache: %v", err)
|
||||
return s, nil
|
||||
}
|
||||
|
||||
if c.Created && !opts.JSON && terminal.StdoutIsTerminal() {
|
||||
printer.P("created new cache in %v", c.Base)
|
||||
if c.Created {
|
||||
printer.PT("created new cache in %v", c.Base)
|
||||
}
|
||||
|
||||
// start using the cache
|
||||
s.UseCache(c)
|
||||
s.UseCache(c, printer.E)
|
||||
|
||||
oldCacheDirs, err := cache.Old(c.Base)
|
||||
if err != nil {
|
||||
|
|
@ -427,10 +393,8 @@ func OpenRepository(ctx context.Context, opts GlobalOptions, printer progress.Pr
|
|||
}
|
||||
|
||||
// cleanup old cache dirs if instructed to do so
|
||||
if opts.CleanupCache {
|
||||
if terminal.StdoutIsTerminal() && !opts.JSON {
|
||||
printer.P("removing %d old cache dirs from %v", len(oldCacheDirs), c.Base)
|
||||
}
|
||||
if gopts.CleanupCache {
|
||||
printer.PT("removing %d old cache dirs from %v", len(oldCacheDirs), c.Base)
|
||||
for _, item := range oldCacheDirs {
|
||||
dir := filepath.Join(c.Base, item.Name())
|
||||
err = os.RemoveAll(dir)
|
||||
|
|
@ -439,10 +403,8 @@ func OpenRepository(ctx context.Context, opts GlobalOptions, printer progress.Pr
|
|||
}
|
||||
}
|
||||
} else {
|
||||
if terminal.StdoutIsTerminal() {
|
||||
printer.P("found %d old cache directories in %v, run `restic cache --cleanup` to remove them",
|
||||
len(oldCacheDirs), c.Base)
|
||||
}
|
||||
printer.PT("found %d old cache directories in %v, run `restic cache --cleanup` to remove them",
|
||||
len(oldCacheDirs), c.Base)
|
||||
}
|
||||
|
||||
return s, nil
|
||||
|
|
@ -476,7 +438,7 @@ func innerOpen(ctx context.Context, s string, gopts GlobalOptions, opts options.
|
|||
return nil, err
|
||||
}
|
||||
|
||||
rt, err := backend.Transport(globalOptions.TransportOptions)
|
||||
rt, err := backend.Transport(gopts.TransportOptions)
|
||||
if err != nil {
|
||||
return nil, errors.Fatalf("%s", err)
|
||||
}
|
||||
|
|
@ -492,9 +454,9 @@ func innerOpen(ctx context.Context, s string, gopts GlobalOptions, opts options.
|
|||
|
||||
var be backend.Backend
|
||||
if create {
|
||||
be, err = factory.Create(ctx, cfg, rt, lim)
|
||||
be, err = factory.Create(ctx, cfg, rt, lim, printer.E)
|
||||
} else {
|
||||
be, err = factory.Open(ctx, cfg, rt, lim)
|
||||
be, err = factory.Open(ctx, cfg, rt, lim, printer.E)
|
||||
}
|
||||
|
||||
if errors.Is(err, backend.ErrNoRepository) {
|
||||
|
|
|
|||
|
|
@ -5,9 +5,9 @@ package main
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
_ "net/http/pprof"
|
||||
"os"
|
||||
|
||||
"github.com/restic/restic/internal/errors"
|
||||
"github.com/restic/restic/internal/repository"
|
||||
|
|
@ -17,7 +17,7 @@ import (
|
|||
"github.com/pkg/profile"
|
||||
)
|
||||
|
||||
func registerProfiling(cmd *cobra.Command) {
|
||||
func registerProfiling(cmd *cobra.Command, stderr io.Writer) {
|
||||
var profiler profiler
|
||||
|
||||
origPreRun := cmd.PersistentPreRunE
|
||||
|
|
@ -27,7 +27,7 @@ func registerProfiling(cmd *cobra.Command) {
|
|||
return err
|
||||
}
|
||||
}
|
||||
return profiler.Start(profiler.opts)
|
||||
return profiler.Start(profiler.opts, stderr)
|
||||
}
|
||||
|
||||
// Once https://github.com/spf13/cobra/issues/1893 is fixed,
|
||||
|
|
@ -65,19 +65,21 @@ func (opts *ProfileOptions) AddFlags(f *pflag.FlagSet) {
|
|||
f.BoolVar(&opts.insecure, "insecure-kdf", false, "use insecure KDF settings")
|
||||
}
|
||||
|
||||
type fakeTestingTB struct{}
|
||||
|
||||
func (fakeTestingTB) Logf(msg string, args ...interface{}) {
|
||||
fmt.Fprintf(os.Stderr, msg, args...)
|
||||
type fakeTestingTB struct {
|
||||
stderr io.Writer
|
||||
}
|
||||
|
||||
func (p *profiler) Start(profileOpts ProfileOptions) error {
|
||||
func (t fakeTestingTB) Logf(msg string, args ...interface{}) {
|
||||
fmt.Fprintf(t.stderr, msg, args...)
|
||||
}
|
||||
|
||||
func (p *profiler) Start(profileOpts ProfileOptions, stderr io.Writer) error {
|
||||
if profileOpts.listen != "" {
|
||||
fmt.Fprintf(os.Stderr, "running profile HTTP server on %v\n", profileOpts.listen)
|
||||
fmt.Fprintf(stderr, "running profile HTTP server on %v\n", profileOpts.listen)
|
||||
go func() {
|
||||
err := http.ListenAndServe(profileOpts.listen, nil)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "profile HTTP server listen failed: %v\n", err)
|
||||
fmt.Fprintf(stderr, "profile HTTP server listen failed: %v\n", err)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
|
@ -111,7 +113,7 @@ func (p *profiler) Start(profileOpts ProfileOptions) error {
|
|||
}
|
||||
|
||||
if profileOpts.insecure {
|
||||
repository.TestUseLowSecurityKDFParameters(fakeTestingTB{})
|
||||
repository.TestUseLowSecurityKDFParameters(fakeTestingTB{stderr})
|
||||
}
|
||||
|
||||
return nil
|
||||
|
|
|
|||
|
|
@ -3,8 +3,12 @@
|
|||
|
||||
package main
|
||||
|
||||
import "github.com/spf13/cobra"
|
||||
import (
|
||||
"io"
|
||||
|
||||
func registerProfiling(_ *cobra.Command) {
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func registerProfiling(_ *cobra.Command, _ io.Writer) {
|
||||
// No profiling in release mode
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,28 +7,16 @@ import (
|
|||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/restic/restic/internal/errors"
|
||||
rtest "github.com/restic/restic/internal/test"
|
||||
"github.com/restic/restic/internal/ui/progress"
|
||||
)
|
||||
|
||||
type errorReader struct{ err error }
|
||||
|
||||
func (r *errorReader) Read([]byte) (int, error) { return 0, r.err }
|
||||
|
||||
func TestReadPassword(t *testing.T) {
|
||||
want := errors.New("foo")
|
||||
_, err := readPassword(&errorReader{want})
|
||||
rtest.Assert(t, errors.Is(err, want), "wrong error %v", err)
|
||||
}
|
||||
|
||||
func TestReadRepo(t *testing.T) {
|
||||
tempDir := rtest.TempDir(t)
|
||||
|
||||
// test --repo option
|
||||
var opts GlobalOptions
|
||||
opts.Repo = tempDir
|
||||
repo, err := ReadRepo(opts)
|
||||
var gopts GlobalOptions
|
||||
gopts.Repo = tempDir
|
||||
repo, err := ReadRepo(gopts)
|
||||
rtest.OK(t, err)
|
||||
rtest.Equals(t, tempDir, repo)
|
||||
|
||||
|
|
@ -37,15 +25,15 @@ func TestReadRepo(t *testing.T) {
|
|||
err = os.WriteFile(foo, []byte(tempDir+"\n"), 0666)
|
||||
rtest.OK(t, err)
|
||||
|
||||
var opts2 GlobalOptions
|
||||
opts2.RepositoryFile = foo
|
||||
repo, err = ReadRepo(opts2)
|
||||
var gopts2 GlobalOptions
|
||||
gopts2.RepositoryFile = foo
|
||||
repo, err = ReadRepo(gopts2)
|
||||
rtest.OK(t, err)
|
||||
rtest.Equals(t, tempDir, repo)
|
||||
|
||||
var opts3 GlobalOptions
|
||||
opts3.RepositoryFile = foo + "-invalid"
|
||||
_, err = ReadRepo(opts3)
|
||||
var gopts3 GlobalOptions
|
||||
gopts3.RepositoryFile = foo + "-invalid"
|
||||
_, err = ReadRepo(gopts3)
|
||||
if err == nil {
|
||||
t.Fatal("must not read repository path from invalid file path")
|
||||
}
|
||||
|
|
@ -53,11 +41,11 @@ func TestReadRepo(t *testing.T) {
|
|||
|
||||
func TestReadEmptyPassword(t *testing.T) {
|
||||
opts := GlobalOptions{InsecureNoPassword: true}
|
||||
password, err := ReadPassword(context.TODO(), opts, "test", &progress.NoopPrinter{})
|
||||
password, err := ReadPassword(context.TODO(), opts, "test")
|
||||
rtest.OK(t, err)
|
||||
rtest.Equals(t, "", password, "got unexpected password")
|
||||
|
||||
opts.password = "invalid"
|
||||
_, err = ReadPassword(context.TODO(), opts, "test", &progress.NoopPrinter{})
|
||||
_, 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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -71,28 +71,28 @@ func TestRestoreFailsWhenUsingInvalidPatterns(t *testing.T) {
|
|||
var err error
|
||||
|
||||
// Test --exclude
|
||||
err = testRunRestoreAssumeFailure("latest", RestoreOptions{ExcludePatternOptions: filter.ExcludePatternOptions{Excludes: []string{"*[._]log[.-][0-9]", "!*[._]log[.-][0-9]"}}}, env.gopts)
|
||||
err = testRunRestoreAssumeFailure(t, "latest", RestoreOptions{ExcludePatternOptions: filter.ExcludePatternOptions{Excludes: []string{"*[._]log[.-][0-9]", "!*[._]log[.-][0-9]"}}}, env.gopts)
|
||||
|
||||
rtest.Equals(t, `Fatal: --exclude: invalid pattern(s) provided:
|
||||
*[._]log[.-][0-9]
|
||||
!*[._]log[.-][0-9]`, err.Error())
|
||||
|
||||
// Test --iexclude
|
||||
err = testRunRestoreAssumeFailure("latest", RestoreOptions{ExcludePatternOptions: filter.ExcludePatternOptions{InsensitiveExcludes: []string{"*[._]log[.-][0-9]", "!*[._]log[.-][0-9]"}}}, env.gopts)
|
||||
err = testRunRestoreAssumeFailure(t, "latest", RestoreOptions{ExcludePatternOptions: filter.ExcludePatternOptions{InsensitiveExcludes: []string{"*[._]log[.-][0-9]", "!*[._]log[.-][0-9]"}}}, env.gopts)
|
||||
|
||||
rtest.Equals(t, `Fatal: --iexclude: invalid pattern(s) provided:
|
||||
*[._]log[.-][0-9]
|
||||
!*[._]log[.-][0-9]`, err.Error())
|
||||
|
||||
// Test --include
|
||||
err = testRunRestoreAssumeFailure("latest", RestoreOptions{IncludePatternOptions: filter.IncludePatternOptions{Includes: []string{"*[._]log[.-][0-9]", "!*[._]log[.-][0-9]"}}}, env.gopts)
|
||||
err = testRunRestoreAssumeFailure(t, "latest", RestoreOptions{IncludePatternOptions: filter.IncludePatternOptions{Includes: []string{"*[._]log[.-][0-9]", "!*[._]log[.-][0-9]"}}}, env.gopts)
|
||||
|
||||
rtest.Equals(t, `Fatal: --include: invalid pattern(s) provided:
|
||||
*[._]log[.-][0-9]
|
||||
!*[._]log[.-][0-9]`, err.Error())
|
||||
|
||||
// Test --iinclude
|
||||
err = testRunRestoreAssumeFailure("latest", RestoreOptions{IncludePatternOptions: filter.IncludePatternOptions{InsensitiveIncludes: []string{"*[._]log[.-][0-9]", "!*[._]log[.-][0-9]"}}}, env.gopts)
|
||||
err = testRunRestoreAssumeFailure(t, "latest", RestoreOptions{IncludePatternOptions: filter.IncludePatternOptions{InsensitiveIncludes: []string{"*[._]log[.-][0-9]", "!*[._]log[.-][0-9]"}}}, env.gopts)
|
||||
|
||||
rtest.Equals(t, `Fatal: --iinclude: invalid pattern(s) provided:
|
||||
*[._]log[.-][0-9]
|
||||
|
|
@ -112,22 +112,22 @@ func TestRestoreFailsWhenUsingInvalidPatternsFromFile(t *testing.T) {
|
|||
t.Fatalf("Could not write include file: %v", fileErr)
|
||||
}
|
||||
|
||||
err := testRunRestoreAssumeFailure("latest", RestoreOptions{IncludePatternOptions: filter.IncludePatternOptions{IncludeFiles: []string{patternsFile}}}, env.gopts)
|
||||
err := testRunRestoreAssumeFailure(t, "latest", RestoreOptions{IncludePatternOptions: filter.IncludePatternOptions{IncludeFiles: []string{patternsFile}}}, env.gopts)
|
||||
rtest.Equals(t, `Fatal: --include-file: invalid pattern(s) provided:
|
||||
*[._]log[.-][0-9]
|
||||
!*[._]log[.-][0-9]`, err.Error())
|
||||
|
||||
err = testRunRestoreAssumeFailure("latest", RestoreOptions{ExcludePatternOptions: filter.ExcludePatternOptions{ExcludeFiles: []string{patternsFile}}}, env.gopts)
|
||||
err = testRunRestoreAssumeFailure(t, "latest", RestoreOptions{ExcludePatternOptions: filter.ExcludePatternOptions{ExcludeFiles: []string{patternsFile}}}, env.gopts)
|
||||
rtest.Equals(t, `Fatal: --exclude-file: invalid pattern(s) provided:
|
||||
*[._]log[.-][0-9]
|
||||
!*[._]log[.-][0-9]`, err.Error())
|
||||
|
||||
err = testRunRestoreAssumeFailure("latest", RestoreOptions{IncludePatternOptions: filter.IncludePatternOptions{InsensitiveIncludeFiles: []string{patternsFile}}}, env.gopts)
|
||||
err = testRunRestoreAssumeFailure(t, "latest", RestoreOptions{IncludePatternOptions: filter.IncludePatternOptions{InsensitiveIncludeFiles: []string{patternsFile}}}, env.gopts)
|
||||
rtest.Equals(t, `Fatal: --iinclude-file: invalid pattern(s) provided:
|
||||
*[._]log[.-][0-9]
|
||||
!*[._]log[.-][0-9]`, err.Error())
|
||||
|
||||
err = testRunRestoreAssumeFailure("latest", RestoreOptions{ExcludePatternOptions: filter.ExcludePatternOptions{InsensitiveExcludeFiles: []string{patternsFile}}}, env.gopts)
|
||||
err = testRunRestoreAssumeFailure(t, "latest", RestoreOptions{ExcludePatternOptions: filter.ExcludePatternOptions{InsensitiveExcludeFiles: []string{patternsFile}}}, env.gopts)
|
||||
rtest.Equals(t, `Fatal: --iexclude-file: invalid pattern(s) provided:
|
||||
*[._]log[.-][0-9]
|
||||
!*[._]log[.-][0-9]`, err.Error())
|
||||
|
|
|
|||
|
|
@ -30,19 +30,19 @@ type dirEntry struct {
|
|||
link uint64
|
||||
}
|
||||
|
||||
func walkDir(dir string) <-chan *dirEntry {
|
||||
func walkDir(t testing.TB, dir string) <-chan *dirEntry {
|
||||
ch := make(chan *dirEntry, 100)
|
||||
|
||||
go func() {
|
||||
err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "error: %v\n", err)
|
||||
t.Logf("error: %v\n", err)
|
||||
return nil
|
||||
}
|
||||
|
||||
name, err := filepath.Rel(dir, path)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "error: %v\n", err)
|
||||
t.Logf("error: %v\n", err)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
@ -56,7 +56,7 @@ func walkDir(dir string) <-chan *dirEntry {
|
|||
})
|
||||
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Walk() error: %v\n", err)
|
||||
t.Logf("Walk() error: %v\n", err)
|
||||
}
|
||||
|
||||
close(ch)
|
||||
|
|
@ -86,10 +86,10 @@ func sameModTime(fi1, fi2 os.FileInfo) bool {
|
|||
|
||||
// directoriesContentsDiff returns a diff between both directories. If these
|
||||
// contain exactly the same contents, then the diff is an empty string.
|
||||
func directoriesContentsDiff(dir1, dir2 string) string {
|
||||
func directoriesContentsDiff(t testing.TB, dir1, dir2 string) string {
|
||||
var out bytes.Buffer
|
||||
ch1 := walkDir(dir1)
|
||||
ch2 := walkDir(dir2)
|
||||
ch1 := walkDir(t, dir1)
|
||||
ch2 := walkDir(t, dir2)
|
||||
|
||||
var a, b *dirEntry
|
||||
for {
|
||||
|
|
@ -146,8 +146,8 @@ func isFile(fi os.FileInfo) bool {
|
|||
}
|
||||
|
||||
// dirStats walks dir and collects stats.
|
||||
func dirStats(dir string) (stat dirStat) {
|
||||
for entry := range walkDir(dir) {
|
||||
func dirStats(t testing.TB, dir string) (stat dirStat) {
|
||||
for entry := range walkDir(t, dir) {
|
||||
if isFile(entry.fi) {
|
||||
stat.files++
|
||||
stat.size += uint64(entry.fi.Size())
|
||||
|
|
@ -212,22 +212,14 @@ func withTestEnvironment(t testing.TB) (env *testEnvironment, cleanup func()) {
|
|||
Quiet: true,
|
||||
CacheDir: env.cache,
|
||||
password: rtest.TestPassword,
|
||||
// stdout and stderr are written to by Warnf etc. That is the written data
|
||||
// usually consists of one or multiple lines and therefore can be handled well
|
||||
// by t.Log.
|
||||
stdout: &logOutputter{t},
|
||||
stderr: &logOutputter{t},
|
||||
extended: make(options.Options),
|
||||
|
||||
// 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 },
|
||||
// start with default set of backends
|
||||
backends: globalOptions.backends,
|
||||
backends: collectBackends(),
|
||||
}
|
||||
|
||||
// always overwrite global options
|
||||
globalOptions = env.gopts
|
||||
|
||||
cleanup = func() {
|
||||
if !rtest.TestCleanupTempDirs {
|
||||
t.Logf("leaving temporary directory %v used for test", tempdir)
|
||||
|
|
@ -248,8 +240,8 @@ func testSetupBackupData(t testing.TB, env *testEnvironment) string {
|
|||
|
||||
func listPacks(gopts GlobalOptions, t *testing.T) restic.IDSet {
|
||||
var packs restic.IDSet
|
||||
err := withTermStatus(gopts, func(ctx context.Context, term ui.Terminal) error {
|
||||
printer := newTerminalProgressPrinter(gopts.JSON, gopts.verbosity, term)
|
||||
err := withTermStatus(t, gopts, func(ctx context.Context, gopts GlobalOptions) error {
|
||||
printer := ui.NewProgressPrinter(gopts.JSON, gopts.verbosity, gopts.term)
|
||||
ctx, r, unlock, err := openWithReadLock(ctx, gopts, false, printer)
|
||||
rtest.OK(t, err)
|
||||
defer unlock()
|
||||
|
|
@ -267,8 +259,8 @@ func listPacks(gopts GlobalOptions, t *testing.T) restic.IDSet {
|
|||
|
||||
func listTreePacks(gopts GlobalOptions, t *testing.T) restic.IDSet {
|
||||
var treePacks restic.IDSet
|
||||
err := withTermStatus(gopts, func(ctx context.Context, term ui.Terminal) error {
|
||||
printer := newTerminalProgressPrinter(gopts.JSON, gopts.verbosity, term)
|
||||
err := withTermStatus(t, gopts, func(ctx context.Context, gopts GlobalOptions) error {
|
||||
printer := ui.NewProgressPrinter(gopts.JSON, gopts.verbosity, gopts.term)
|
||||
ctx, r, unlock, err := openWithReadLock(ctx, gopts, false, printer)
|
||||
rtest.OK(t, err)
|
||||
defer unlock()
|
||||
|
|
@ -298,8 +290,8 @@ func captureBackend(gopts *GlobalOptions) func() backend.Backend {
|
|||
|
||||
func removePacks(gopts GlobalOptions, t testing.TB, remove restic.IDSet) {
|
||||
be := captureBackend(&gopts)
|
||||
err := withTermStatus(gopts, func(ctx context.Context, term ui.Terminal) error {
|
||||
printer := newTerminalProgressPrinter(gopts.JSON, gopts.verbosity, term)
|
||||
err := withTermStatus(t, gopts, func(ctx context.Context, gopts GlobalOptions) error {
|
||||
printer := ui.NewProgressPrinter(gopts.JSON, gopts.verbosity, gopts.term)
|
||||
ctx, _, unlock, err := openWithExclusiveLock(ctx, gopts, false, printer)
|
||||
rtest.OK(t, err)
|
||||
defer unlock()
|
||||
|
|
@ -314,8 +306,8 @@ func removePacks(gopts GlobalOptions, t testing.TB, remove restic.IDSet) {
|
|||
|
||||
func removePacksExcept(gopts GlobalOptions, t testing.TB, keep restic.IDSet, removeTreePacks bool) {
|
||||
be := captureBackend(&gopts)
|
||||
err := withTermStatus(gopts, func(ctx context.Context, term ui.Terminal) error {
|
||||
printer := newTerminalProgressPrinter(gopts.JSON, gopts.verbosity, term)
|
||||
err := withTermStatus(t, gopts, func(ctx context.Context, gopts GlobalOptions) error {
|
||||
printer := ui.NewProgressPrinter(gopts.JSON, gopts.verbosity, gopts.term)
|
||||
ctx, r, unlock, err := openWithExclusiveLock(ctx, gopts, false, printer)
|
||||
rtest.OK(t, err)
|
||||
defer unlock()
|
||||
|
|
@ -375,8 +367,8 @@ func lastSnapshot(old, new map[string]struct{}) (map[string]struct{}, string) {
|
|||
|
||||
func testLoadSnapshot(t testing.TB, gopts GlobalOptions, id restic.ID) *restic.Snapshot {
|
||||
var snapshot *restic.Snapshot
|
||||
err := withTermStatus(gopts, func(ctx context.Context, term ui.Terminal) error {
|
||||
printer := newTerminalProgressPrinter(gopts.JSON, gopts.verbosity, term)
|
||||
err := withTermStatus(t, gopts, func(ctx context.Context, gopts GlobalOptions) error {
|
||||
printer := ui.NewProgressPrinter(gopts.JSON, gopts.verbosity, gopts.term)
|
||||
_, repo, unlock, err := openWithReadLock(ctx, gopts, false, printer)
|
||||
rtest.OK(t, err)
|
||||
defer unlock()
|
||||
|
|
@ -391,19 +383,16 @@ func testLoadSnapshot(t testing.TB, gopts GlobalOptions, id restic.ID) *restic.S
|
|||
func appendRandomData(filename string, bytes uint) error {
|
||||
f, err := os.OpenFile(filename, os.O_WRONLY|os.O_CREATE, 0666)
|
||||
if err != nil {
|
||||
fmt.Fprint(os.Stderr, err)
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = f.Seek(0, 2)
|
||||
if err != nil {
|
||||
fmt.Fprint(os.Stderr, err)
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = io.Copy(f, io.LimitReader(rand.Reader, int64(bytes)))
|
||||
if err != nil {
|
||||
fmt.Fprint(os.Stderr, err)
|
||||
return err
|
||||
}
|
||||
|
||||
|
|
@ -423,30 +412,25 @@ func testFileSize(filename string, size int64) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func withRestoreGlobalOptions(inner func() error) error {
|
||||
gopts := globalOptions
|
||||
defer func() {
|
||||
globalOptions = gopts
|
||||
}()
|
||||
return inner()
|
||||
}
|
||||
|
||||
func withCaptureStdout(gopts GlobalOptions, inner func(gopts GlobalOptions) error) (*bytes.Buffer, error) {
|
||||
func withCaptureStdout(t testing.TB, gopts GlobalOptions, callback func(ctx context.Context, gopts GlobalOptions) error) (*bytes.Buffer, error) {
|
||||
buf := bytes.NewBuffer(nil)
|
||||
err := withRestoreGlobalOptions(func() error {
|
||||
globalOptions.stdout = buf
|
||||
gopts.stdout = buf
|
||||
return inner(gopts)
|
||||
})
|
||||
|
||||
err := withTermStatusRaw(os.Stdin, buf, &logOutputter{t: t}, gopts, callback)
|
||||
return buf, err
|
||||
}
|
||||
|
||||
func withTermStatus(gopts GlobalOptions, callback func(ctx context.Context, term ui.Terminal) error) error {
|
||||
func withTermStatus(t testing.TB, gopts GlobalOptions, callback func(ctx context.Context, gopts GlobalOptions) error) error {
|
||||
// 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
|
||||
// by t.Log.
|
||||
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 {
|
||||
ctx, cancel := context.WithCancel(context.TODO())
|
||||
var wg sync.WaitGroup
|
||||
|
||||
term := termstatus.New(gopts.stdout, gopts.stderr, gopts.Quiet)
|
||||
term := termstatus.New(stdin, stdout, stderr, gopts.Quiet)
|
||||
gopts.term = term
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
|
|
@ -456,5 +440,5 @@ func withTermStatus(gopts GlobalOptions, callback func(ctx context.Context, term
|
|||
defer wg.Wait()
|
||||
defer cancel()
|
||||
|
||||
return callback(ctx, term)
|
||||
return callback(ctx, gopts)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -87,15 +87,15 @@ func TestListOnce(t *testing.T) {
|
|||
|
||||
createPrunableRepo(t, env)
|
||||
testRunPrune(t, env.gopts, pruneOpts)
|
||||
rtest.OK(t, withTermStatus(env.gopts, func(ctx context.Context, term ui.Terminal) error {
|
||||
_, err := runCheck(context.TODO(), checkOpts, env.gopts, nil, term)
|
||||
rtest.OK(t, withTermStatus(t, env.gopts, func(ctx context.Context, gopts GlobalOptions) error {
|
||||
_, err := runCheck(context.TODO(), checkOpts, gopts, nil, gopts.term)
|
||||
return err
|
||||
}))
|
||||
rtest.OK(t, withTermStatus(env.gopts, func(ctx context.Context, term ui.Terminal) error {
|
||||
return runRebuildIndex(context.TODO(), RepairIndexOptions{}, env.gopts, term)
|
||||
rtest.OK(t, withTermStatus(t, env.gopts, func(ctx context.Context, gopts GlobalOptions) error {
|
||||
return runRebuildIndex(context.TODO(), RepairIndexOptions{}, gopts, gopts.term)
|
||||
}))
|
||||
rtest.OK(t, withTermStatus(env.gopts, func(ctx context.Context, term ui.Terminal) error {
|
||||
return runRebuildIndex(context.TODO(), RepairIndexOptions{ReadAllPacks: true}, env.gopts, term)
|
||||
rtest.OK(t, withTermStatus(t, env.gopts, func(ctx context.Context, gopts GlobalOptions) error {
|
||||
return runRebuildIndex(context.TODO(), RepairIndexOptions{ReadAllPacks: true}, gopts, gopts.term)
|
||||
}))
|
||||
}
|
||||
|
||||
|
|
@ -162,9 +162,9 @@ func TestFindListOnce(t *testing.T) {
|
|||
thirdSnapshot := restic.NewIDSet(testListSnapshots(t, env.gopts, 3)...)
|
||||
|
||||
var snapshotIDs restic.IDSet
|
||||
rtest.OK(t, withTermStatus(env.gopts, func(ctx context.Context, term ui.Terminal) error {
|
||||
printer := newTerminalProgressPrinter(env.gopts.JSON, env.gopts.verbosity, term)
|
||||
ctx, repo, unlock, err := openWithReadLock(ctx, env.gopts, false, printer)
|
||||
rtest.OK(t, withTermStatus(t, env.gopts, func(ctx context.Context, gopts GlobalOptions) error {
|
||||
printer := ui.NewProgressPrinter(gopts.JSON, gopts.verbosity, gopts.term)
|
||||
ctx, repo, unlock, err := openWithReadLock(ctx, gopts, false, printer)
|
||||
rtest.OK(t, err)
|
||||
defer unlock()
|
||||
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ import (
|
|||
"github.com/restic/restic/internal/feature"
|
||||
"github.com/restic/restic/internal/repository"
|
||||
"github.com/restic/restic/internal/restic"
|
||||
"github.com/restic/restic/internal/ui/termstatus"
|
||||
)
|
||||
|
||||
func init() {
|
||||
|
|
@ -31,7 +32,7 @@ var ErrOK = errors.New("ok")
|
|||
var cmdGroupDefault = "default"
|
||||
var cmdGroupAdvanced = "advanced"
|
||||
|
||||
func newRootCommand() *cobra.Command {
|
||||
func newRootCommand(globalOptions *GlobalOptions) *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "restic",
|
||||
Short: "Backup and restore files",
|
||||
|
|
@ -66,41 +67,42 @@ The full documentation can be found at https://restic.readthedocs.io/ .
|
|||
// Use our "generate" command instead of the cobra provided "completion" command
|
||||
cmd.CompletionOptions.DisableDefaultCmd = true
|
||||
|
||||
// globalOptions is passed to commands by reference to allow PersistentPreRunE to modify it
|
||||
cmd.AddCommand(
|
||||
newBackupCommand(),
|
||||
newCacheCommand(),
|
||||
newCatCommand(),
|
||||
newCheckCommand(),
|
||||
newCopyCommand(),
|
||||
newDiffCommand(),
|
||||
newDumpCommand(),
|
||||
newFeaturesCommand(),
|
||||
newFindCommand(),
|
||||
newForgetCommand(),
|
||||
newGenerateCommand(),
|
||||
newInitCommand(),
|
||||
newKeyCommand(),
|
||||
newListCommand(),
|
||||
newLsCommand(),
|
||||
newMigrateCommand(),
|
||||
newOptionsCommand(),
|
||||
newPruneCommand(),
|
||||
newRebuildIndexCommand(),
|
||||
newRecoverCommand(),
|
||||
newRepairCommand(),
|
||||
newRestoreCommand(),
|
||||
newRewriteCommand(),
|
||||
newSnapshotsCommand(),
|
||||
newStatsCommand(),
|
||||
newTagCommand(),
|
||||
newUnlockCommand(),
|
||||
newVersionCommand(),
|
||||
newBackupCommand(globalOptions),
|
||||
newCacheCommand(globalOptions),
|
||||
newCatCommand(globalOptions),
|
||||
newCheckCommand(globalOptions),
|
||||
newCopyCommand(globalOptions),
|
||||
newDiffCommand(globalOptions),
|
||||
newDumpCommand(globalOptions),
|
||||
newFeaturesCommand(globalOptions),
|
||||
newFindCommand(globalOptions),
|
||||
newForgetCommand(globalOptions),
|
||||
newGenerateCommand(globalOptions),
|
||||
newInitCommand(globalOptions),
|
||||
newKeyCommand(globalOptions),
|
||||
newListCommand(globalOptions),
|
||||
newLsCommand(globalOptions),
|
||||
newMigrateCommand(globalOptions),
|
||||
newOptionsCommand(globalOptions),
|
||||
newPruneCommand(globalOptions),
|
||||
newRebuildIndexCommand(globalOptions),
|
||||
newRecoverCommand(globalOptions),
|
||||
newRepairCommand(globalOptions),
|
||||
newRestoreCommand(globalOptions),
|
||||
newRewriteCommand(globalOptions),
|
||||
newSnapshotsCommand(globalOptions),
|
||||
newStatsCommand(globalOptions),
|
||||
newTagCommand(globalOptions),
|
||||
newUnlockCommand(globalOptions),
|
||||
newVersionCommand(globalOptions),
|
||||
)
|
||||
|
||||
registerDebugCommand(cmd)
|
||||
registerMountCommand(cmd)
|
||||
registerSelfUpdateCommand(cmd)
|
||||
registerProfiling(cmd)
|
||||
registerDebugCommand(cmd, globalOptions)
|
||||
registerMountCommand(cmd, globalOptions)
|
||||
registerSelfUpdateCommand(cmd, globalOptions)
|
||||
registerProfiling(cmd, os.Stderr)
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
|
@ -125,7 +127,7 @@ func tweakGoGC() {
|
|||
}
|
||||
}
|
||||
|
||||
func printExitError(code int, message string) {
|
||||
func printExitError(globalOptions GlobalOptions, code int, message string) {
|
||||
if globalOptions.JSON {
|
||||
type jsonExitError struct {
|
||||
MessageType string `json:"message_type"` // exit_error
|
||||
|
|
@ -139,7 +141,7 @@ func printExitError(code int, message string) {
|
|||
Message: message,
|
||||
}
|
||||
|
||||
err := json.NewEncoder(globalOptions.stderr).Encode(jsonS)
|
||||
err := json.NewEncoder(os.Stderr).Encode(jsonS)
|
||||
if err != nil {
|
||||
// ignore error as there's no good way to handle it
|
||||
_, _ = fmt.Fprintf(os.Stderr, "JSON encode failed: %v\n", err)
|
||||
|
|
@ -147,7 +149,7 @@ func printExitError(code int, message string) {
|
|||
return
|
||||
}
|
||||
} else {
|
||||
_, _ = fmt.Fprintf(globalOptions.stderr, "%v\n", message)
|
||||
_, _ = fmt.Fprintf(os.Stderr, "%v\n", message)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -170,16 +172,23 @@ func main() {
|
|||
debug.Log("restic %s compiled with %v on %v/%v",
|
||||
version, runtime.Version(), runtime.GOOS, runtime.GOARCH)
|
||||
|
||||
ctx := createGlobalContext()
|
||||
err = newRootCommand().ExecuteContext(ctx)
|
||||
|
||||
switch err {
|
||||
case nil:
|
||||
err = ctx.Err()
|
||||
case ErrOK:
|
||||
// ErrOK overwrites context cancellation errors
|
||||
err = nil
|
||||
globalOptions := GlobalOptions{
|
||||
backends: collectBackends(),
|
||||
}
|
||||
func() {
|
||||
term, cancel := termstatus.Setup(os.Stdin, os.Stdout, os.Stderr, globalOptions.Quiet)
|
||||
defer cancel()
|
||||
globalOptions.term = term
|
||||
ctx := createGlobalContext(os.Stderr)
|
||||
err = newRootCommand(&globalOptions).ExecuteContext(ctx)
|
||||
switch err {
|
||||
case nil:
|
||||
err = ctx.Err()
|
||||
case ErrOK:
|
||||
// ErrOK overwrites context cancellation errors
|
||||
err = nil
|
||||
}
|
||||
}()
|
||||
|
||||
var exitMessage string
|
||||
switch {
|
||||
|
|
@ -224,7 +233,7 @@ func main() {
|
|||
}
|
||||
|
||||
if exitCode != 0 {
|
||||
printExitError(exitCode, exitMessage)
|
||||
printExitError(globalOptions, exitCode, exitMessage)
|
||||
}
|
||||
Exit(exitCode)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,84 +0,0 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/restic/restic/internal/terminal"
|
||||
"github.com/restic/restic/internal/ui"
|
||||
"github.com/restic/restic/internal/ui/progress"
|
||||
)
|
||||
|
||||
// calculateProgressInterval returns the interval configured via RESTIC_PROGRESS_FPS
|
||||
// or if unset returns an interval for 60fps on interactive terminals and 0 (=disabled)
|
||||
// for non-interactive terminals or when run using the --quiet flag
|
||||
func calculateProgressInterval(show bool, json bool) time.Duration {
|
||||
interval := time.Second / 60
|
||||
fps, err := strconv.ParseFloat(os.Getenv("RESTIC_PROGRESS_FPS"), 64)
|
||||
if err == nil && fps > 0 {
|
||||
if fps > 60 {
|
||||
fps = 60
|
||||
}
|
||||
interval = time.Duration(float64(time.Second) / fps)
|
||||
} else if !json && !terminal.StdoutCanUpdateStatus() || !show {
|
||||
interval = 0
|
||||
}
|
||||
return interval
|
||||
}
|
||||
|
||||
// newTerminalProgressMax returns a progress.Counter that prints to terminal if provided.
|
||||
func newTerminalProgressMax(show bool, max uint64, description string, term ui.Terminal) *progress.Counter {
|
||||
if !show {
|
||||
return nil
|
||||
}
|
||||
interval := calculateProgressInterval(show, false)
|
||||
|
||||
return progress.NewCounter(interval, max, func(v uint64, max uint64, d time.Duration, final bool) {
|
||||
var status string
|
||||
if max == 0 {
|
||||
status = fmt.Sprintf("[%s] %d %s",
|
||||
ui.FormatDuration(d), v, description)
|
||||
} else {
|
||||
status = fmt.Sprintf("[%s] %s %d / %d %s",
|
||||
ui.FormatDuration(d), ui.FormatPercent(v, max), v, max, description)
|
||||
}
|
||||
|
||||
if final {
|
||||
term.SetStatus(nil)
|
||||
term.Print(status)
|
||||
} else {
|
||||
term.SetStatus([]string{status})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
type terminalProgressPrinter struct {
|
||||
term ui.Terminal
|
||||
ui.Message
|
||||
show bool
|
||||
}
|
||||
|
||||
func (t *terminalProgressPrinter) NewCounter(description string) *progress.Counter {
|
||||
return newTerminalProgressMax(t.show, 0, description, t.term)
|
||||
}
|
||||
|
||||
func (t *terminalProgressPrinter) NewCounterTerminalOnly(description string) *progress.Counter {
|
||||
return newTerminalProgressMax(t.show && terminal.StdoutIsTerminal(), 0, description, t.term)
|
||||
}
|
||||
|
||||
func newTerminalProgressPrinter(json bool, verbosity uint, term ui.Terminal) progress.Printer {
|
||||
if json {
|
||||
verbosity = 0
|
||||
}
|
||||
return &terminalProgressPrinter{
|
||||
term: term,
|
||||
Message: *ui.NewMessage(term, verbosity),
|
||||
show: verbosity > 0,
|
||||
}
|
||||
}
|
||||
|
||||
func newIndexTerminalProgress(printer progress.Printer) *progress.Counter {
|
||||
return printer.NewCounterTerminalOnly("index files loaded")
|
||||
}
|
||||
|
|
@ -5,7 +5,6 @@ import (
|
|||
"os"
|
||||
|
||||
"github.com/restic/restic/internal/errors"
|
||||
"github.com/restic/restic/internal/ui/progress"
|
||||
"github.com/spf13/pflag"
|
||||
)
|
||||
|
||||
|
|
@ -60,7 +59,7 @@ func (opts *secondaryRepoOptions) AddFlags(f *pflag.FlagSet, repoPrefix string,
|
|||
opts.PasswordCommand = os.Getenv("RESTIC_FROM_PASSWORD_COMMAND")
|
||||
}
|
||||
|
||||
func fillSecondaryGlobalOpts(ctx context.Context, opts secondaryRepoOptions, gopts GlobalOptions, repoPrefix string, printer progress.Printer) (GlobalOptions, bool, error) {
|
||||
func fillSecondaryGlobalOpts(ctx context.Context, opts secondaryRepoOptions, gopts GlobalOptions, repoPrefix string) (GlobalOptions, bool, error) {
|
||||
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)")
|
||||
}
|
||||
|
|
@ -116,7 +115,7 @@ func fillSecondaryGlobalOpts(ctx context.Context, opts secondaryRepoOptions, gop
|
|||
return GlobalOptions{}, false, err
|
||||
}
|
||||
}
|
||||
dstGopts.password, err = ReadPassword(ctx, dstGopts, "enter password for "+repoPrefix+" repository: ", printer)
|
||||
dstGopts.password, err = ReadPassword(ctx, dstGopts, "enter password for "+repoPrefix+" repository: ")
|
||||
if err != nil {
|
||||
return GlobalOptions{}, false, err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@ import (
|
|||
"testing"
|
||||
|
||||
rtest "github.com/restic/restic/internal/test"
|
||||
"github.com/restic/restic/internal/ui/progress"
|
||||
)
|
||||
|
||||
// TestFillSecondaryGlobalOpts tests valid and invalid data on fillSecondaryGlobalOpts-function
|
||||
|
|
@ -131,12 +130,6 @@ func TestFillSecondaryGlobalOpts(t *testing.T) {
|
|||
PasswordCommand: "notEmpty",
|
||||
},
|
||||
},
|
||||
{
|
||||
// Test must fail as no password is given.
|
||||
Opts: secondaryRepoOptions{
|
||||
Repo: "backupDst",
|
||||
},
|
||||
},
|
||||
{
|
||||
// Test must fail as current and legacy options are mixed
|
||||
Opts: secondaryRepoOptions{
|
||||
|
|
@ -172,7 +165,7 @@ func TestFillSecondaryGlobalOpts(t *testing.T) {
|
|||
|
||||
// Test all valid cases
|
||||
for _, testCase := range validSecondaryRepoTestCases {
|
||||
DstGOpts, isFromRepo, err := fillSecondaryGlobalOpts(context.TODO(), testCase.Opts, gOpts, "destination", &progress.NoopPrinter{})
|
||||
DstGOpts, isFromRepo, err := fillSecondaryGlobalOpts(context.TODO(), testCase.Opts, gOpts, "destination")
|
||||
rtest.OK(t, err)
|
||||
rtest.Equals(t, DstGOpts, testCase.DstGOpts)
|
||||
rtest.Equals(t, isFromRepo, testCase.FromRepo)
|
||||
|
|
@ -180,7 +173,7 @@ func TestFillSecondaryGlobalOpts(t *testing.T) {
|
|||
|
||||
// Test all invalid cases
|
||||
for _, testCase := range invalidSecondaryRepoTestCases {
|
||||
_, _, err := fillSecondaryGlobalOpts(context.TODO(), testCase.Opts, gOpts, "destination", &progress.NoopPrinter{})
|
||||
_, _, err := fillSecondaryGlobalOpts(context.TODO(), testCase.Opts, gOpts, "destination")
|
||||
rtest.Assert(t, err != nil, "Expected error, but function did not return an error")
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,41 +0,0 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
|
||||
"github.com/restic/restic/internal/ui/termstatus"
|
||||
)
|
||||
|
||||
// setupTermstatus creates a new termstatus and reroutes globalOptions.{stdout,stderr} to it
|
||||
// The returned function must be called to shut down the termstatus,
|
||||
//
|
||||
// Expected usage:
|
||||
// ```
|
||||
// term, cancel := setupTermstatus()
|
||||
// defer cancel()
|
||||
// // do stuff
|
||||
// ```
|
||||
func setupTermstatus() (*termstatus.Terminal, func()) {
|
||||
var wg sync.WaitGroup
|
||||
// only shutdown once cancel is called to ensure that no output is lost
|
||||
cancelCtx, cancel := context.WithCancel(context.Background())
|
||||
|
||||
term := termstatus.New(globalOptions.stdout, globalOptions.stderr, globalOptions.Quiet)
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
term.Run(cancelCtx)
|
||||
}()
|
||||
|
||||
// use the termstatus for stdout/stderr
|
||||
prevStdout, prevStderr := globalOptions.stdout, globalOptions.stderr
|
||||
globalOptions.stdout, globalOptions.stderr = termstatus.WrapStdio(term)
|
||||
|
||||
return term, func() {
|
||||
// shutdown termstatus
|
||||
globalOptions.stdout, globalOptions.stderr = prevStdout, prevStderr
|
||||
cancel()
|
||||
wg.Wait()
|
||||
}
|
||||
}
|
||||
|
|
@ -68,7 +68,7 @@ func (s *ItemStats) Add(other ItemStats) {
|
|||
|
||||
// ToNoder returns a restic.Node for a File.
|
||||
type ToNoder interface {
|
||||
ToNode(ignoreXattrListError bool) (*restic.Node, error)
|
||||
ToNode(ignoreXattrListError bool, warnf func(format string, args ...any)) (*restic.Node, error)
|
||||
}
|
||||
|
||||
type archiverRepo interface {
|
||||
|
|
@ -263,7 +263,9 @@ func (arch *Archiver) trackItem(item string, previous, current *restic.Node, s I
|
|||
|
||||
// nodeFromFileInfo returns the restic node from an os.FileInfo.
|
||||
func (arch *Archiver) nodeFromFileInfo(snPath, filename string, meta ToNoder, ignoreXattrListError bool) (*restic.Node, error) {
|
||||
node, err := meta.ToNode(ignoreXattrListError)
|
||||
node, err := meta.ToNode(ignoreXattrListError, func(format string, args ...any) {
|
||||
_ = arch.error(filename, fmt.Errorf(format, args...))
|
||||
})
|
||||
// node does not exist. This prevents all further processing for this file.
|
||||
// If an error and a node are returned, then preserve as much data as possible (see below).
|
||||
if err != nil && node == nil {
|
||||
|
|
|
|||
|
|
@ -551,7 +551,7 @@ func rename(t testing.TB, oldname, newname string) {
|
|||
func nodeFromFile(t testing.TB, localFs fs.FS, filename string) *restic.Node {
|
||||
meta, err := localFs.OpenFile(filename, fs.O_NOFOLLOW, true)
|
||||
rtest.OK(t, err)
|
||||
node, err := meta.ToNode(false)
|
||||
node, err := meta.ToNode(false, t.Logf)
|
||||
rtest.OK(t, err)
|
||||
rtest.OK(t, meta.Close())
|
||||
|
||||
|
|
@ -2287,9 +2287,9 @@ func (f overrideFile) MakeReadable() error {
|
|||
return f.File.MakeReadable()
|
||||
}
|
||||
|
||||
func (f overrideFile) ToNode(ignoreXattrListError bool) (*restic.Node, error) {
|
||||
func (f overrideFile) ToNode(ignoreXattrListError bool, warnf func(format string, args ...any)) (*restic.Node, error) {
|
||||
if f.ofs.overrideNode == nil {
|
||||
return f.File.ToNode(ignoreXattrListError)
|
||||
return f.File.ToNode(ignoreXattrListError, warnf)
|
||||
}
|
||||
return f.ofs.overrideNode, f.ofs.overrideErr
|
||||
}
|
||||
|
|
@ -2321,7 +2321,7 @@ func TestMetadataChanged(t *testing.T) {
|
|||
localFS := &fs.Local{}
|
||||
meta, err := localFS.OpenFile("testfile", fs.O_NOFOLLOW, true)
|
||||
rtest.OK(t, err)
|
||||
want, err := meta.ToNode(false)
|
||||
want, err := meta.ToNode(false, t.Logf)
|
||||
rtest.OK(t, err)
|
||||
rtest.OK(t, meta.Close())
|
||||
|
||||
|
|
@ -2455,7 +2455,7 @@ type mockToNoder struct {
|
|||
err error
|
||||
}
|
||||
|
||||
func (m *mockToNoder) ToNode(_ bool) (*restic.Node, error) {
|
||||
func (m *mockToNoder) ToNode(_ bool, _ func(format string, args ...any)) (*restic.Node, error) {
|
||||
return m.node, m.err
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -50,7 +50,7 @@ func startFileSaver(ctx context.Context, t testing.TB, _ fs.FS) (*fileSaver, con
|
|||
|
||||
s := newFileSaver(ctx, wg, saveBlob, pol, workers, workers)
|
||||
s.NodeFromFileInfo = func(snPath, filename string, meta ToNoder, ignoreXattrListError bool) (*restic.Node, error) {
|
||||
return meta.ToNode(ignoreXattrListError)
|
||||
return meta.ToNode(ignoreXattrListError, t.Logf)
|
||||
}
|
||||
|
||||
return s, ctx, wg
|
||||
|
|
|
|||
|
|
@ -159,13 +159,13 @@ func supportedAccessTiers() []blob.AccessTier {
|
|||
}
|
||||
|
||||
// Open opens the Azure backend at specified container.
|
||||
func Open(_ context.Context, cfg Config, rt http.RoundTripper) (*Backend, error) {
|
||||
func Open(_ context.Context, cfg Config, rt http.RoundTripper, _ func(string, ...interface{})) (*Backend, error) {
|
||||
return open(cfg, rt)
|
||||
}
|
||||
|
||||
// Create opens the Azure backend at specified container and creates the container if
|
||||
// it does not exist yet.
|
||||
func Create(ctx context.Context, cfg Config, rt http.RoundTripper) (*Backend, error) {
|
||||
func Create(ctx context.Context, cfg Config, rt http.RoundTripper, _ func(string, ...interface{})) (*Backend, error) {
|
||||
be, err := open(cfg, rt)
|
||||
|
||||
if err != nil {
|
||||
|
|
|
|||
|
|
@ -116,7 +116,7 @@ func TestBackendAzureAccountToken(t *testing.T) {
|
|||
t.Fatal(err)
|
||||
}
|
||||
|
||||
_, err = azure.Create(ctx, *cfg, tr)
|
||||
_, err = azure.Create(ctx, *cfg, tr, t.Logf)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
|
@ -159,7 +159,7 @@ func TestBackendAzureContainerToken(t *testing.T) {
|
|||
t.Fatal(err)
|
||||
}
|
||||
|
||||
_, err = azure.Create(ctx, *cfg, tr)
|
||||
_, err = azure.Create(ctx, *cfg, tr, t.Logf)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
|
@ -193,7 +193,7 @@ func TestUploadLargeFile(t *testing.T) {
|
|||
t.Fatal(err)
|
||||
}
|
||||
|
||||
be, err := azure.Create(ctx, *cfg, tr)
|
||||
be, err := azure.Create(ctx, *cfg, tr, t.Logf)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -88,7 +88,7 @@ func newClient(ctx context.Context, cfg Config, rt http.RoundTripper) (*b2.Clien
|
|||
}
|
||||
|
||||
// Open opens a connection to the B2 service.
|
||||
func Open(ctx context.Context, cfg Config, rt http.RoundTripper) (backend.Backend, error) {
|
||||
func Open(ctx context.Context, cfg Config, rt http.RoundTripper, _ func(string, ...interface{})) (backend.Backend, error) {
|
||||
debug.Log("cfg %#v", cfg)
|
||||
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
|
|
@ -120,7 +120,7 @@ func Open(ctx context.Context, cfg Config, rt http.RoundTripper) (backend.Backen
|
|||
|
||||
// Create opens a connection to the B2 service. If the bucket does not exist yet,
|
||||
// it is created.
|
||||
func Create(ctx context.Context, cfg Config, rt http.RoundTripper) (backend.Backend, error) {
|
||||
func Create(ctx context.Context, cfg Config, rt http.RoundTripper, _ func(string, ...interface{})) (backend.Backend, error) {
|
||||
debug.Log("cfg %#v", cfg)
|
||||
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
|
|
|
|||
8
internal/backend/cache/backend.go
vendored
8
internal/backend/cache/backend.go
vendored
|
|
@ -2,9 +2,7 @@ package cache
|
|||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"sync"
|
||||
|
||||
"github.com/restic/restic/internal/backend"
|
||||
|
|
@ -22,16 +20,18 @@ type Backend struct {
|
|||
// is finished.
|
||||
inProgressMutex sync.Mutex
|
||||
inProgress map[backend.Handle]chan struct{}
|
||||
errorLog func(string, ...interface{})
|
||||
}
|
||||
|
||||
// ensure Backend implements backend.Backend
|
||||
var _ backend.Backend = &Backend{}
|
||||
|
||||
func newBackend(be backend.Backend, c *Cache) *Backend {
|
||||
func newBackend(be backend.Backend, c *Cache, errorLog func(string, ...interface{})) *Backend {
|
||||
return &Backend{
|
||||
Backend: be,
|
||||
Cache: c,
|
||||
inProgress: make(map[backend.Handle]chan struct{}),
|
||||
errorLog: errorLog,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -253,7 +253,7 @@ func (b *Backend) List(ctx context.Context, t backend.FileType, fn func(f backen
|
|||
// clear the cache for files that are not in the repo anymore, ignore errors
|
||||
err = b.Cache.Clear(t, ids)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "error clearing %s files in cache: %v\n", t.String(), err)
|
||||
b.errorLog("error clearing %s files in cache: %v\n", t.String(), err)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
|
|
|||
12
internal/backend/cache/backend_test.go
vendored
12
internal/backend/cache/backend_test.go
vendored
|
|
@ -67,7 +67,7 @@ func list(t testing.TB, be backend.Backend, fn func(backend.FileInfo) error) {
|
|||
func TestBackend(t *testing.T) {
|
||||
be := mem.New()
|
||||
c := TestNewCache(t)
|
||||
wbe := c.Wrap(be)
|
||||
wbe := c.Wrap(be, t.Logf)
|
||||
|
||||
h, data := randomData(5234142)
|
||||
|
||||
|
|
@ -135,7 +135,7 @@ func (l *loadCountingBackend) Load(ctx context.Context, h backend.Handle, length
|
|||
func TestOutOfBoundsAccess(t *testing.T) {
|
||||
be := &loadCountingBackend{Backend: mem.New()}
|
||||
c := TestNewCache(t)
|
||||
wbe := c.Wrap(be)
|
||||
wbe := c.Wrap(be, t.Logf)
|
||||
|
||||
h, data := randomData(50)
|
||||
save(t, be, h, data)
|
||||
|
|
@ -164,7 +164,7 @@ func TestOutOfBoundsAccess(t *testing.T) {
|
|||
func TestForget(t *testing.T) {
|
||||
be := &loadCountingBackend{Backend: mem.New()}
|
||||
c := TestNewCache(t)
|
||||
wbe := c.Wrap(be)
|
||||
wbe := c.Wrap(be, t.Logf)
|
||||
|
||||
h, data := randomData(50)
|
||||
save(t, be, h, data)
|
||||
|
|
@ -236,7 +236,7 @@ func TestErrorBackend(t *testing.T) {
|
|||
time.Sleep(time.Millisecond)
|
||||
}
|
||||
|
||||
wrappedBE := c.Wrap(errBackend)
|
||||
wrappedBE := c.Wrap(errBackend, t.Logf)
|
||||
var wg sync.WaitGroup
|
||||
for i := 0; i < 5; i++ {
|
||||
wg.Add(1)
|
||||
|
|
@ -249,7 +249,7 @@ func TestErrorBackend(t *testing.T) {
|
|||
func TestAutomaticCacheClear(t *testing.T) {
|
||||
be := mem.New()
|
||||
c := TestNewCache(t)
|
||||
wbe := c.Wrap(be)
|
||||
wbe := c.Wrap(be, t.Logf)
|
||||
|
||||
// add two handles h1 and h2
|
||||
h1, data := randomData(2000)
|
||||
|
|
@ -308,7 +308,7 @@ func TestAutomaticCacheClearInvalidFilename(t *testing.T) {
|
|||
}
|
||||
save(t, be, h, data)
|
||||
|
||||
wbe := c.Wrap(be)
|
||||
wbe := c.Wrap(be, t.Logf)
|
||||
|
||||
// list all files in the backend
|
||||
list(t, wbe, func(_ backend.FileInfo) error { return nil })
|
||||
|
|
|
|||
4
internal/backend/cache/cache.go
vendored
4
internal/backend/cache/cache.go
vendored
|
|
@ -237,8 +237,8 @@ func IsOld(t time.Time, maxAge time.Duration) bool {
|
|||
}
|
||||
|
||||
// Wrap returns a backend with a cache.
|
||||
func (c *Cache) Wrap(be backend.Backend) backend.Backend {
|
||||
return newBackend(be, c)
|
||||
func (c *Cache) Wrap(be backend.Backend, errorLog func(string, ...interface{})) backend.Backend {
|
||||
return newBackend(be, c, errorLog)
|
||||
}
|
||||
|
||||
// BaseDir returns the base directory.
|
||||
|
|
|
|||
|
|
@ -120,7 +120,7 @@ func open(cfg Config, rt http.RoundTripper) (*Backend, error) {
|
|||
}
|
||||
|
||||
// Open opens the gs backend at the specified bucket.
|
||||
func Open(_ context.Context, cfg Config, rt http.RoundTripper) (backend.Backend, error) {
|
||||
func Open(_ context.Context, cfg Config, rt http.RoundTripper, _ func(string, ...interface{})) (backend.Backend, error) {
|
||||
return open(cfg, rt)
|
||||
}
|
||||
|
||||
|
|
@ -129,7 +129,7 @@ func Open(_ context.Context, cfg Config, rt http.RoundTripper) (backend.Backend,
|
|||
//
|
||||
// The service account must have the "storage.buckets.create" permission to
|
||||
// create a bucket the does not yet exist.
|
||||
func Create(ctx context.Context, cfg Config, rt http.RoundTripper) (backend.Backend, error) {
|
||||
func Create(ctx context.Context, cfg Config, rt http.RoundTripper, _ func(string, ...interface{})) (backend.Backend, error) {
|
||||
be, err := open(cfg, rt)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
|
|
|||
|
|
@ -7,10 +7,10 @@ import (
|
|||
"github.com/restic/restic/internal/backend"
|
||||
)
|
||||
|
||||
func WrapBackendConstructor[B backend.Backend, C any](constructor func(ctx context.Context, cfg C) (B, error)) func(ctx context.Context, cfg C, lim Limiter) (backend.Backend, error) {
|
||||
return func(ctx context.Context, cfg C, lim Limiter) (backend.Backend, error) {
|
||||
func WrapBackendConstructor[B backend.Backend, C any](constructor func(ctx context.Context, cfg C, errorLog func(string, ...interface{})) (B, error)) func(ctx context.Context, cfg C, lim Limiter, errorLog func(string, ...interface{})) (backend.Backend, error) {
|
||||
return func(ctx context.Context, cfg C, lim Limiter, errorLog func(string, ...interface{})) (backend.Backend, error) {
|
||||
var be backend.Backend
|
||||
be, err := constructor(ctx, cfg)
|
||||
be, err := constructor(ctx, cfg, errorLog)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ func TestLayout(t *testing.T) {
|
|||
be, err := Open(context.TODO(), Config{
|
||||
Path: repo,
|
||||
Connections: 2,
|
||||
})
|
||||
}, t.Logf)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -52,14 +52,14 @@ func open(cfg Config) (*Local, error) {
|
|||
}
|
||||
|
||||
// Open opens the local backend as specified by config.
|
||||
func Open(_ context.Context, cfg Config) (*Local, error) {
|
||||
func Open(_ context.Context, cfg Config, _ func(string, ...interface{})) (*Local, error) {
|
||||
debug.Log("open local backend at %v", cfg.Path)
|
||||
return open(cfg)
|
||||
}
|
||||
|
||||
// Create creates all the necessary files and directories for a new local
|
||||
// backend at dir. Afterwards a new config blob should be created.
|
||||
func Create(_ context.Context, cfg Config) (*Local, error) {
|
||||
func Create(_ context.Context, cfg Config, _ func(string, ...interface{})) (*Local, error) {
|
||||
debug.Log("create local backend at %v", cfg.Path)
|
||||
|
||||
be, err := open(cfg)
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ func TestNoSpacePermanent(t *testing.T) {
|
|||
|
||||
dir := rtest.TempDir(t)
|
||||
|
||||
be, err := Open(context.Background(), Config{Path: dir, Connections: 2})
|
||||
be, err := Open(context.Background(), Config{Path: dir, Connections: 2}, t.Logf)
|
||||
rtest.OK(t, err)
|
||||
defer func() {
|
||||
rtest.OK(t, be.Close())
|
||||
|
|
|
|||
|
|
@ -66,7 +66,7 @@ func empty(t testing.TB, dir string) {
|
|||
func openclose(t testing.TB, dir string) {
|
||||
cfg := local.Config{Path: dir}
|
||||
|
||||
be, err := local.Open(context.TODO(), cfg)
|
||||
be, err := local.Open(context.TODO(), cfg, t.Logf)
|
||||
if err != nil {
|
||||
t.Logf("Open returned error %v", err)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -33,16 +33,16 @@ type Factory interface {
|
|||
Scheme() string
|
||||
ParseConfig(s string) (interface{}, error)
|
||||
StripPassword(s string) string
|
||||
Create(ctx context.Context, cfg interface{}, rt http.RoundTripper, lim limiter.Limiter) (backend.Backend, error)
|
||||
Open(ctx context.Context, cfg interface{}, rt http.RoundTripper, lim limiter.Limiter) (backend.Backend, error)
|
||||
Create(ctx context.Context, cfg interface{}, rt http.RoundTripper, lim limiter.Limiter, errorLog func(string, ...interface{})) (backend.Backend, error)
|
||||
Open(ctx context.Context, cfg interface{}, rt http.RoundTripper, lim limiter.Limiter, errorLog func(string, ...interface{})) (backend.Backend, error)
|
||||
}
|
||||
|
||||
type genericBackendFactory[C any, T backend.Backend] struct {
|
||||
scheme string
|
||||
parseConfigFn func(s string) (*C, error)
|
||||
stripPasswordFn func(s string) string
|
||||
createFn func(ctx context.Context, cfg C, rt http.RoundTripper, lim limiter.Limiter) (T, error)
|
||||
openFn func(ctx context.Context, cfg C, rt http.RoundTripper, lim limiter.Limiter) (T, error)
|
||||
createFn func(ctx context.Context, cfg C, rt http.RoundTripper, lim limiter.Limiter, errorLog func(string, ...interface{})) (T, error)
|
||||
openFn func(ctx context.Context, cfg C, rt http.RoundTripper, lim limiter.Limiter, errorLog func(string, ...interface{})) (T, error)
|
||||
}
|
||||
|
||||
func (f *genericBackendFactory[C, T]) Scheme() string {
|
||||
|
|
@ -58,29 +58,29 @@ func (f *genericBackendFactory[C, T]) StripPassword(s string) string {
|
|||
}
|
||||
return s
|
||||
}
|
||||
func (f *genericBackendFactory[C, T]) Create(ctx context.Context, cfg interface{}, rt http.RoundTripper, lim limiter.Limiter) (backend.Backend, error) {
|
||||
return f.createFn(ctx, *cfg.(*C), rt, lim)
|
||||
func (f *genericBackendFactory[C, T]) Create(ctx context.Context, cfg interface{}, rt http.RoundTripper, lim limiter.Limiter, errorLog func(string, ...interface{})) (backend.Backend, error) {
|
||||
return f.createFn(ctx, *cfg.(*C), rt, lim, errorLog)
|
||||
}
|
||||
func (f *genericBackendFactory[C, T]) Open(ctx context.Context, cfg interface{}, rt http.RoundTripper, lim limiter.Limiter) (backend.Backend, error) {
|
||||
return f.openFn(ctx, *cfg.(*C), rt, lim)
|
||||
func (f *genericBackendFactory[C, T]) Open(ctx context.Context, cfg interface{}, rt http.RoundTripper, lim limiter.Limiter, errorLog func(string, ...interface{})) (backend.Backend, error) {
|
||||
return f.openFn(ctx, *cfg.(*C), rt, lim, errorLog)
|
||||
}
|
||||
|
||||
func NewHTTPBackendFactory[C any, T backend.Backend](
|
||||
scheme string,
|
||||
parseConfigFn func(s string) (*C, error),
|
||||
stripPasswordFn func(s string) string,
|
||||
createFn func(ctx context.Context, cfg C, rt http.RoundTripper) (T, error),
|
||||
openFn func(ctx context.Context, cfg C, rt http.RoundTripper) (T, error)) Factory {
|
||||
createFn func(ctx context.Context, cfg C, rt http.RoundTripper, errorLog func(string, ...interface{})) (T, error),
|
||||
openFn func(ctx context.Context, cfg C, rt http.RoundTripper, errorLog func(string, ...interface{})) (T, error)) Factory {
|
||||
|
||||
return &genericBackendFactory[C, T]{
|
||||
scheme: scheme,
|
||||
parseConfigFn: parseConfigFn,
|
||||
stripPasswordFn: stripPasswordFn,
|
||||
createFn: func(ctx context.Context, cfg C, rt http.RoundTripper, _ limiter.Limiter) (T, error) {
|
||||
return createFn(ctx, cfg, rt)
|
||||
createFn: func(ctx context.Context, cfg C, rt http.RoundTripper, _ limiter.Limiter, errorLog func(string, ...interface{})) (T, error) {
|
||||
return createFn(ctx, cfg, rt, errorLog)
|
||||
},
|
||||
openFn: func(ctx context.Context, cfg C, rt http.RoundTripper, _ limiter.Limiter) (T, error) {
|
||||
return openFn(ctx, cfg, rt)
|
||||
openFn: func(ctx context.Context, cfg C, rt http.RoundTripper, _ limiter.Limiter, errorLog func(string, ...interface{})) (T, error) {
|
||||
return openFn(ctx, cfg, rt, errorLog)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
@ -89,18 +89,18 @@ func NewLimitedBackendFactory[C any, T backend.Backend](
|
|||
scheme string,
|
||||
parseConfigFn func(s string) (*C, error),
|
||||
stripPasswordFn func(s string) string,
|
||||
createFn func(ctx context.Context, cfg C, lim limiter.Limiter) (T, error),
|
||||
openFn func(ctx context.Context, cfg C, lim limiter.Limiter) (T, error)) Factory {
|
||||
createFn func(ctx context.Context, cfg C, lim limiter.Limiter, errorLog func(string, ...interface{})) (T, error),
|
||||
openFn func(ctx context.Context, cfg C, lim limiter.Limiter, errorLog func(string, ...interface{})) (T, error)) Factory {
|
||||
|
||||
return &genericBackendFactory[C, T]{
|
||||
scheme: scheme,
|
||||
parseConfigFn: parseConfigFn,
|
||||
stripPasswordFn: stripPasswordFn,
|
||||
createFn: func(ctx context.Context, cfg C, _ http.RoundTripper, lim limiter.Limiter) (T, error) {
|
||||
return createFn(ctx, cfg, lim)
|
||||
createFn: func(ctx context.Context, cfg C, _ http.RoundTripper, lim limiter.Limiter, errorLog func(string, ...interface{})) (T, error) {
|
||||
return createFn(ctx, cfg, lim, errorLog)
|
||||
},
|
||||
openFn: func(ctx context.Context, cfg C, _ http.RoundTripper, lim limiter.Limiter) (T, error) {
|
||||
return openFn(ctx, cfg, lim)
|
||||
openFn: func(ctx context.Context, cfg C, _ http.RoundTripper, lim limiter.Limiter, errorLog func(string, ...interface{})) (T, error) {
|
||||
return openFn(ctx, cfg, lim, errorLog)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -33,10 +33,10 @@ func NewFactory() location.Factory {
|
|||
return &struct{}{}, nil
|
||||
},
|
||||
location.NoPassword,
|
||||
func(_ context.Context, _ struct{}, _ http.RoundTripper) (*MemoryBackend, error) {
|
||||
func(_ context.Context, _ struct{}, _ http.RoundTripper, _ func(string, ...interface{})) (*MemoryBackend, error) {
|
||||
return be, nil
|
||||
},
|
||||
func(_ context.Context, _ struct{}, _ http.RoundTripper) (*MemoryBackend, error) {
|
||||
func(_ context.Context, _ struct{}, _ http.RoundTripper, _ func(string, ...interface{})) (*MemoryBackend, error) {
|
||||
return be, nil
|
||||
},
|
||||
)
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ func NewFactory() location.Factory {
|
|||
}
|
||||
|
||||
// run starts command with args and initializes the StdioConn.
|
||||
func run(command string, args ...string) (*StdioConn, *sync.WaitGroup, chan struct{}, func() error, error) {
|
||||
func run(errorLog func(string, ...interface{}), command string, args ...string) (*StdioConn, *sync.WaitGroup, chan struct{}, func() error, error) {
|
||||
cmd := exec.Command(command, args...)
|
||||
|
||||
p, err := cmd.StderrPipe()
|
||||
|
|
@ -61,7 +61,7 @@ func run(command string, args ...string) (*StdioConn, *sync.WaitGroup, chan stru
|
|||
defer close(waitCh)
|
||||
sc := bufio.NewScanner(p)
|
||||
for sc.Scan() {
|
||||
fmt.Fprintf(os.Stderr, "rclone: %v\n", sc.Text())
|
||||
errorLog("rclone: %v\n", sc.Text())
|
||||
}
|
||||
debug.Log("command has exited, closing waitCh")
|
||||
}()
|
||||
|
|
@ -140,7 +140,7 @@ func wrapConn(c *StdioConn, lim limiter.Limiter) *wrappedConn {
|
|||
}
|
||||
|
||||
// New initializes a Backend and starts the process.
|
||||
func newBackend(ctx context.Context, cfg Config, lim limiter.Limiter) (*Backend, error) {
|
||||
func newBackend(ctx context.Context, cfg Config, lim limiter.Limiter, errorLog func(string, ...interface{})) (*Backend, error) {
|
||||
var (
|
||||
args []string
|
||||
err error
|
||||
|
|
@ -170,7 +170,7 @@ func newBackend(ctx context.Context, cfg Config, lim limiter.Limiter) (*Backend,
|
|||
arg0, args := args[0], args[1:]
|
||||
|
||||
debug.Log("running command: %v %v", arg0, args)
|
||||
stdioConn, wg, waitCh, bg, err := run(arg0, args...)
|
||||
stdioConn, wg, waitCh, bg, err := run(errorLog, arg0, args...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -263,8 +263,8 @@ func newBackend(ctx context.Context, cfg Config, lim limiter.Limiter) (*Backend,
|
|||
}
|
||||
|
||||
// Open starts an rclone process with the given config.
|
||||
func Open(ctx context.Context, cfg Config, lim limiter.Limiter) (*Backend, error) {
|
||||
be, err := newBackend(ctx, cfg, lim)
|
||||
func Open(ctx context.Context, cfg Config, lim limiter.Limiter, errorLog func(string, ...interface{})) (*Backend, error) {
|
||||
be, err := newBackend(ctx, cfg, lim, errorLog)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -279,7 +279,7 @@ func Open(ctx context.Context, cfg Config, lim limiter.Limiter) (*Backend, error
|
|||
URL: url,
|
||||
}
|
||||
|
||||
restBackend, err := rest.Open(ctx, restConfig, debug.RoundTripper(be.tr))
|
||||
restBackend, err := rest.Open(ctx, restConfig, debug.RoundTripper(be.tr), errorLog)
|
||||
if err != nil {
|
||||
_ = be.Close()
|
||||
return nil, err
|
||||
|
|
@ -290,8 +290,8 @@ func Open(ctx context.Context, cfg Config, lim limiter.Limiter) (*Backend, error
|
|||
}
|
||||
|
||||
// Create initializes a new restic repo with rclone.
|
||||
func Create(ctx context.Context, cfg Config, lim limiter.Limiter) (*Backend, error) {
|
||||
be, err := newBackend(ctx, cfg, lim)
|
||||
func Create(ctx context.Context, cfg Config, lim limiter.Limiter, errorLog func(string, ...interface{})) (*Backend, error) {
|
||||
be, err := newBackend(ctx, cfg, lim, errorLog)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -308,7 +308,7 @@ func Create(ctx context.Context, cfg Config, lim limiter.Limiter) (*Backend, err
|
|||
URL: url,
|
||||
}
|
||||
|
||||
restBackend, err := rest.Create(ctx, restConfig, debug.RoundTripper(be.tr))
|
||||
restBackend, err := rest.Create(ctx, restConfig, debug.RoundTripper(be.tr), errorLog)
|
||||
if err != nil {
|
||||
_ = be.Close()
|
||||
return nil, err
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ func TestRcloneExit(t *testing.T) {
|
|||
dir := rtest.TempDir(t)
|
||||
cfg := NewConfig()
|
||||
cfg.Remote = dir
|
||||
be, err := Open(context.TODO(), cfg, nil)
|
||||
be, err := Open(context.TODO(), cfg, nil, t.Logf)
|
||||
var e *exec.Error
|
||||
if errors.As(err, &e) && e.Err == exec.ErrNotFound {
|
||||
t.Skipf("program %q not found", e.Name)
|
||||
|
|
@ -45,7 +45,7 @@ func TestRcloneFailedStart(t *testing.T) {
|
|||
cfg := NewConfig()
|
||||
// exits with exit code 1
|
||||
cfg.Program = "false"
|
||||
_, err := Open(context.TODO(), cfg, nil)
|
||||
_, err := Open(context.TODO(), cfg, nil, t.Logf)
|
||||
var e *exec.ExitError
|
||||
if !errors.As(err, &e) {
|
||||
// unexpected error
|
||||
|
|
|
|||
|
|
@ -55,7 +55,7 @@ const (
|
|||
)
|
||||
|
||||
// Open opens the REST backend with the given config.
|
||||
func Open(_ context.Context, cfg Config, rt http.RoundTripper) (*Backend, error) {
|
||||
func Open(_ context.Context, cfg Config, rt http.RoundTripper, _ func(string, ...interface{})) (*Backend, error) {
|
||||
// use url without trailing slash for layout
|
||||
url := cfg.URL.String()
|
||||
if url[len(url)-1] == '/' {
|
||||
|
|
@ -84,8 +84,8 @@ func drainAndClose(resp *http.Response) error {
|
|||
}
|
||||
|
||||
// Create creates a new REST on server configured in config.
|
||||
func Create(ctx context.Context, cfg Config, rt http.RoundTripper) (*Backend, error) {
|
||||
be, err := Open(ctx, cfg, rt)
|
||||
func Create(ctx context.Context, cfg Config, rt http.RoundTripper, errorLog func(string, ...interface{})) (*Backend, error) {
|
||||
be, err := Open(ctx, cfg, rt, errorLog)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -117,7 +117,7 @@ func TestListAPI(t *testing.T) {
|
|||
URL: srvURL,
|
||||
}
|
||||
|
||||
be, err := rest.Open(context.TODO(), cfg, http.DefaultTransport)
|
||||
be, err := rest.Open(context.TODO(), cfg, http.DefaultTransport, t.Logf)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -106,7 +106,7 @@ func runRESTServer(ctx context.Context, t testing.TB, dir, reqListenAddr string)
|
|||
matched = true
|
||||
}
|
||||
}
|
||||
_, _ = fmt.Fprintln(os.Stdout, line) // print all output to console
|
||||
t.Log(line)
|
||||
}
|
||||
}()
|
||||
|
||||
|
|
|
|||
|
|
@ -195,13 +195,13 @@ func getCredentials(cfg Config, tr http.RoundTripper) (*credentials.Credentials,
|
|||
|
||||
// Open opens the S3 backend at bucket and region. The bucket is created if it
|
||||
// does not exist yet.
|
||||
func Open(_ context.Context, cfg Config, rt http.RoundTripper) (backend.Backend, error) {
|
||||
func Open(_ context.Context, cfg Config, rt http.RoundTripper, _ func(string, ...interface{})) (backend.Backend, error) {
|
||||
return open(cfg, rt)
|
||||
}
|
||||
|
||||
// Create opens the S3 backend at bucket and region and creates the bucket if
|
||||
// it does not exist yet.
|
||||
func Create(ctx context.Context, cfg Config, rt http.RoundTripper) (backend.Backend, error) {
|
||||
func Create(ctx context.Context, cfg Config, rt http.RoundTripper, _ func(string, ...interface{})) (backend.Backend, error) {
|
||||
be, err := open(cfg, rt)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "open")
|
||||
|
|
|
|||
|
|
@ -117,9 +117,9 @@ func newMinioTestSuite(t testing.TB) (*test.Suite[s3.Config], func()) {
|
|||
return &cfg, nil
|
||||
},
|
||||
|
||||
Factory: location.NewHTTPBackendFactory("s3", s3.ParseConfig, location.NoPassword, func(ctx context.Context, cfg s3.Config, rt http.RoundTripper) (be backend.Backend, err error) {
|
||||
Factory: location.NewHTTPBackendFactory("s3", s3.ParseConfig, location.NoPassword, func(ctx context.Context, cfg s3.Config, rt http.RoundTripper, errorLog func(string, ...interface{})) (be backend.Backend, err error) {
|
||||
for i := 0; i < 50; i++ {
|
||||
be, err = s3.Create(ctx, cfg, rt)
|
||||
be, err = s3.Create(ctx, cfg, rt, errorLog)
|
||||
if err != nil {
|
||||
t.Logf("s3 open: try %d: error %v", i, err)
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ func TestLayout(t *testing.T) {
|
|||
Command: fmt.Sprintf("%q -e", sftpServer),
|
||||
Path: repo,
|
||||
Connections: 5,
|
||||
})
|
||||
}, t.Logf)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue