deduplicate termstatus setup

This commit is contained in:
Michael Eischer
2025-09-14 17:21:30 +02:00
parent 1ae2d08d1b
commit 3410808dcf
68 changed files with 335 additions and 450 deletions

View File

@@ -3,6 +3,7 @@ package main
import ( import (
"context" "context"
"fmt" "fmt"
"io"
"os" "os"
"os/signal" "os/signal"
"syscall" "syscall"
@@ -10,27 +11,27 @@ import (
"github.com/restic/restic/internal/debug" "github.com/restic/restic/internal/debug"
) )
func createGlobalContext() context.Context { func createGlobalContext(stderr io.Writer) context.Context {
ctx, cancel := context.WithCancel(context.Background()) ctx, cancel := context.WithCancel(context.Background())
ch := make(chan os.Signal, 1) ch := make(chan os.Signal, 1)
go cleanupHandler(ch, cancel) go cleanupHandler(ch, cancel, stderr)
signal.Notify(ch, syscall.SIGINT, syscall.SIGTERM) signal.Notify(ch, syscall.SIGINT, syscall.SIGTERM)
return ctx return ctx
} }
// cleanupHandler handles the SIGINT and SIGTERM signals. // 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 s := <-c
debug.Log("signal %v received, cleaning up", s) debug.Log("signal %v received, cleaning up", s)
// ignore error as there's no good way to handle it // 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 != "" { if val, _ := os.LookupEnv("RESTIC_DEBUG_STACKTRACE_SIGINT"); val != "" {
_, _ = os.Stderr.WriteString("\n--- STACKTRACE START ---\n\n") _, _ = stderr.Write([]byte("\n--- STACKTRACE START ---\n\n"))
_, _ = os.Stderr.WriteString(debug.DumpStacktrace()) _, _ = stderr.Write([]byte(debug.DumpStacktrace()))
_, _ = os.Stderr.WriteString("\n--- STACKTRACE END ---\n") _, _ = stderr.Write([]byte("\n--- STACKTRACE END ---\n"))
} }
cancel() cancel()

View File

@@ -30,7 +30,7 @@ import (
"github.com/restic/restic/internal/ui/backup" "github.com/restic/restic/internal/ui/backup"
) )
func newBackupCommand() *cobra.Command { func newBackupCommand(globalOptions *GlobalOptions) *cobra.Command {
var opts BackupOptions var opts BackupOptions
cmd := &cobra.Command{ cmd := &cobra.Command{
@@ -63,9 +63,7 @@ Exit status is 12 if the password is incorrect.
GroupID: cmdGroupDefault, GroupID: cmdGroupDefault,
DisableAutoGenTag: true, DisableAutoGenTag: true,
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
term, cancel := setupTermstatus() return runBackup(cmd.Context(), opts, *globalOptions, globalOptions.term, args)
defer cancel()
return runBackup(cmd.Context(), opts, globalOptions, term, args)
}, },
} }

View File

@@ -13,11 +13,10 @@ import (
"github.com/restic/restic/internal/fs" "github.com/restic/restic/internal/fs"
"github.com/restic/restic/internal/restic" "github.com/restic/restic/internal/restic"
rtest "github.com/restic/restic/internal/test" rtest "github.com/restic/restic/internal/test"
"github.com/restic/restic/internal/ui"
) )
func testRunBackupAssumeFailure(t testing.TB, dir string, target []string, opts BackupOptions, gopts GlobalOptions) error { func testRunBackupAssumeFailure(t testing.TB, dir string, target []string, opts BackupOptions, gopts GlobalOptions) error {
return withTermStatus(gopts, func(ctx context.Context, term ui.Terminal) error { return withTermStatus(gopts, func(ctx context.Context, gopts GlobalOptions) error {
t.Logf("backing up %v in %v", target, dir) t.Logf("backing up %v in %v", target, dir)
if dir != "" { if dir != "" {
cleanup := rtest.Chdir(t, dir) cleanup := rtest.Chdir(t, dir)
@@ -25,7 +24,7 @@ func testRunBackupAssumeFailure(t testing.TB, dir string, target []string, opts
} }
opts.GroupBy = restic.SnapshotGroupByOptions{Host: true, Path: true} opts.GroupBy = restic.SnapshotGroupByOptions{Host: true, Path: true}
return runBackup(ctx, opts, gopts, term, target) return runBackup(ctx, opts, gopts, gopts.term, target)
}) })
} }

View File

@@ -16,7 +16,7 @@ import (
"github.com/spf13/pflag" "github.com/spf13/pflag"
) )
func newCacheCommand() *cobra.Command { func newCacheCommand(globalOptions *GlobalOptions) *cobra.Command {
var opts CacheOptions var opts CacheOptions
cmd := &cobra.Command{ cmd := &cobra.Command{
@@ -34,9 +34,7 @@ Exit status is 1 if there was any error.
GroupID: cmdGroupDefault, GroupID: cmdGroupDefault,
DisableAutoGenTag: true, DisableAutoGenTag: true,
RunE: func(_ *cobra.Command, args []string) error { RunE: func(_ *cobra.Command, args []string) error {
term, cancel := setupTermstatus() return runCache(opts, *globalOptions, args, globalOptions.term)
defer cancel()
return runCache(opts, globalOptions, args, term)
}, },
} }

View File

@@ -15,7 +15,7 @@ import (
var catAllowedCmds = []string{"config", "index", "snapshot", "key", "masterkey", "lock", "pack", "blob", "tree"} var catAllowedCmds = []string{"config", "index", "snapshot", "key", "masterkey", "lock", "pack", "blob", "tree"}
func newCatCommand() *cobra.Command { func newCatCommand(globalOptions *GlobalOptions) *cobra.Command {
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "cat [flags] [masterkey|config|pack ID|blob ID|snapshot ID|index ID|key ID|lock ID|tree snapshot:subfolder]", Use: "cat [flags] [masterkey|config|pack ID|blob ID|snapshot ID|index ID|key ID|lock ID|tree snapshot:subfolder]",
Short: "Print internal objects to stdout", Short: "Print internal objects to stdout",
@@ -34,9 +34,7 @@ Exit status is 12 if the password is incorrect.
GroupID: cmdGroupDefault, GroupID: cmdGroupDefault,
DisableAutoGenTag: true, DisableAutoGenTag: true,
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
term, cancel := setupTermstatus() return runCat(cmd.Context(), *globalOptions, args, globalOptions.term)
defer cancel()
return runCat(cmd.Context(), globalOptions, args, term)
}, },
ValidArgs: catAllowedCmds, ValidArgs: catAllowedCmds,
} }

View File

@@ -22,7 +22,7 @@ import (
"github.com/restic/restic/internal/ui/progress" "github.com/restic/restic/internal/ui/progress"
) )
func newCheckCommand() *cobra.Command { func newCheckCommand(globalOptions *GlobalOptions) *cobra.Command {
var opts CheckOptions var opts CheckOptions
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "check [flags]", Use: "check [flags]",
@@ -46,14 +46,12 @@ Exit status is 12 if the password is incorrect.
GroupID: cmdGroupDefault, GroupID: cmdGroupDefault,
DisableAutoGenTag: true, DisableAutoGenTag: true,
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
term, cancel := setupTermstatus() summary, err := runCheck(cmd.Context(), opts, *globalOptions, args, globalOptions.term)
defer cancel()
summary, err := runCheck(cmd.Context(), opts, globalOptions, args, term)
if globalOptions.JSON { if globalOptions.JSON {
if err != nil && summary.NumErrors == 0 { if err != nil && summary.NumErrors == 0 {
summary.NumErrors = 1 summary.NumErrors = 1
} }
term.Print(ui.ToJSONString(summary)) globalOptions.term.Print(ui.ToJSONString(summary))
} }
return err return err
}, },

View File

@@ -5,7 +5,6 @@ import (
"testing" "testing"
rtest "github.com/restic/restic/internal/test" rtest "github.com/restic/restic/internal/test"
"github.com/restic/restic/internal/ui"
) )
func testRunCheck(t testing.TB, gopts GlobalOptions) { func testRunCheck(t testing.TB, gopts GlobalOptions) {
@@ -25,12 +24,12 @@ func testRunCheckMustFail(t testing.TB, gopts GlobalOptions) {
func testRunCheckOutput(gopts GlobalOptions, checkUnused bool) (string, error) { func testRunCheckOutput(gopts GlobalOptions, checkUnused bool) (string, error) {
buf, err := withCaptureStdout(gopts, func(gopts GlobalOptions) error { buf, err := withCaptureStdout(gopts, func(gopts GlobalOptions) error {
return withTermStatus(gopts, func(ctx context.Context, term ui.Terminal) error { return withTermStatus(gopts, func(ctx context.Context, gopts GlobalOptions) error {
opts := CheckOptions{ opts := CheckOptions{
ReadData: true, ReadData: true,
CheckUnused: checkUnused, CheckUnused: checkUnused,
} }
_, err := runCheck(context.TODO(), opts, gopts, nil, term) _, err := runCheck(context.TODO(), opts, gopts, nil, gopts.term)
return err return err
}) })
}) })

View File

@@ -16,7 +16,7 @@ import (
"github.com/spf13/pflag" "github.com/spf13/pflag"
) )
func newCopyCommand() *cobra.Command { func newCopyCommand(globalOptions *GlobalOptions) *cobra.Command {
var opts CopyOptions var opts CopyOptions
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "copy [flags] [snapshotID ...]", Use: "copy [flags] [snapshotID ...]",
@@ -48,9 +48,7 @@ Exit status is 12 if the password is incorrect.
GroupID: cmdGroupDefault, GroupID: cmdGroupDefault,
DisableAutoGenTag: true, DisableAutoGenTag: true,
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
term, cancel := setupTermstatus() return runCopy(cmd.Context(), opts, *globalOptions, args, globalOptions.term)
defer cancel()
return runCopy(cmd.Context(), opts, globalOptions, args, term)
}, },
} }

View File

@@ -7,7 +7,6 @@ import (
"testing" "testing"
rtest "github.com/restic/restic/internal/test" rtest "github.com/restic/restic/internal/test"
"github.com/restic/restic/internal/ui"
) )
func testRunCopy(t testing.TB, srcGopts GlobalOptions, dstGopts GlobalOptions) { 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 { rtest.OK(t, withTermStatus(gopts, func(ctx context.Context, gopts GlobalOptions) error {
return runCopy(context.TODO(), copyOpts, gopts, nil, term) return runCopy(context.TODO(), copyOpts, gopts, nil, gopts.term)
})) }))
} }

View File

@@ -31,25 +31,25 @@ import (
"github.com/restic/restic/internal/ui/progress" "github.com/restic/restic/internal/ui/progress"
) )
func registerDebugCommand(cmd *cobra.Command) { func registerDebugCommand(cmd *cobra.Command, globalOptions *GlobalOptions) {
cmd.AddCommand( cmd.AddCommand(
newDebugCommand(), newDebugCommand(globalOptions),
) )
} }
func newDebugCommand() *cobra.Command { func newDebugCommand(globalOptions *GlobalOptions) *cobra.Command {
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "debug", Use: "debug",
Short: "Debug commands", Short: "Debug commands",
GroupID: cmdGroupDefault, GroupID: cmdGroupDefault,
DisableAutoGenTag: true, DisableAutoGenTag: true,
} }
cmd.AddCommand(newDebugDumpCommand()) cmd.AddCommand(newDebugDumpCommand(globalOptions))
cmd.AddCommand(newDebugExamineCommand()) cmd.AddCommand(newDebugExamineCommand(globalOptions))
return cmd return cmd
} }
func newDebugDumpCommand() *cobra.Command { func newDebugDumpCommand(globalOptions *GlobalOptions) *cobra.Command {
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "dump [indexes|snapshots|all|packs]", Use: "dump [indexes|snapshots|all|packs]",
Short: "Dump data structures", Short: "Dump data structures",
@@ -68,15 +68,13 @@ Exit status is 12 if the password is incorrect.
`, `,
DisableAutoGenTag: true, DisableAutoGenTag: true,
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
term, cancel := setupTermstatus() return runDebugDump(cmd.Context(), *globalOptions, args, globalOptions.term)
defer cancel()
return runDebugDump(cmd.Context(), globalOptions, args, term)
}, },
} }
return cmd return cmd
} }
func newDebugExamineCommand() *cobra.Command { func newDebugExamineCommand(globalOptions *GlobalOptions) *cobra.Command {
var opts DebugExamineOptions var opts DebugExamineOptions
cmd := &cobra.Command{ cmd := &cobra.Command{
@@ -84,9 +82,7 @@ func newDebugExamineCommand() *cobra.Command {
Short: "Examine a pack file", Short: "Examine a pack file",
DisableAutoGenTag: true, DisableAutoGenTag: true,
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
term, cancel := setupTermstatus() return runDebugExamine(cmd.Context(), *globalOptions, opts, args, globalOptions.term)
defer cancel()
return runDebugExamine(cmd.Context(), globalOptions, opts, args, term)
}, },
} }

View File

@@ -4,6 +4,6 @@ package main
import "github.com/spf13/cobra" import "github.com/spf13/cobra"
func registerDebugCommand(_ *cobra.Command) { func registerDebugCommand(_ *cobra.Command, _ *GlobalOptions) {
// No commands to register in non-debug mode // No commands to register in non-debug mode
} }

View File

@@ -15,7 +15,7 @@ import (
"github.com/spf13/pflag" "github.com/spf13/pflag"
) )
func newDiffCommand() *cobra.Command { func newDiffCommand(globalOptions *GlobalOptions) *cobra.Command {
var opts DiffOptions var opts DiffOptions
cmd := &cobra.Command{ cmd := &cobra.Command{
@@ -52,9 +52,7 @@ Exit status is 12 if the password is incorrect.
GroupID: cmdGroupDefault, GroupID: cmdGroupDefault,
DisableAutoGenTag: true, DisableAutoGenTag: true,
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
term, cancel := setupTermstatus() return runDiff(cmd.Context(), opts, *globalOptions, args, globalOptions.term)
defer cancel()
return runDiff(cmd.Context(), opts, globalOptions, args, term)
}, },
} }

View File

@@ -12,7 +12,6 @@ import (
"testing" "testing"
rtest "github.com/restic/restic/internal/test" rtest "github.com/restic/restic/internal/test"
"github.com/restic/restic/internal/ui"
) )
func testRunDiffOutput(gopts GlobalOptions, firstSnapshotID string, secondSnapshotID string) (string, error) { func testRunDiffOutput(gopts GlobalOptions, firstSnapshotID string, secondSnapshotID string) (string, error) {
@@ -20,8 +19,8 @@ func testRunDiffOutput(gopts GlobalOptions, firstSnapshotID string, secondSnapsh
opts := DiffOptions{ opts := DiffOptions{
ShowMetadata: false, ShowMetadata: false,
} }
return withTermStatus(gopts, func(ctx context.Context, term ui.Terminal) error { return withTermStatus(gopts, func(ctx context.Context, gopts GlobalOptions) error {
return runDiff(ctx, opts, gopts, []string{firstSnapshotID, secondSnapshotID}, term) return runDiff(ctx, opts, gopts, []string{firstSnapshotID, secondSnapshotID}, gopts.term)
}) })
}) })
return buf.String(), err return buf.String(), err

View File

@@ -17,7 +17,7 @@ import (
"github.com/spf13/pflag" "github.com/spf13/pflag"
) )
func newDumpCommand() *cobra.Command { func newDumpCommand(globalOptions *GlobalOptions) *cobra.Command {
var opts DumpOptions var opts DumpOptions
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "dump [flags] snapshotID file", Use: "dump [flags] snapshotID file",
@@ -47,9 +47,7 @@ Exit status is 12 if the password is incorrect.
GroupID: cmdGroupDefault, GroupID: cmdGroupDefault,
DisableAutoGenTag: true, DisableAutoGenTag: true,
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
term, cancel := setupTermstatus() return runDump(cmd.Context(), opts, *globalOptions, args, globalOptions.term)
defer cancel()
return runDump(cmd.Context(), opts, globalOptions, args, term)
}, },
} }

View File

@@ -10,7 +10,7 @@ import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
func newFeaturesCommand() *cobra.Command { func newFeaturesCommand(globalOptions *GlobalOptions) *cobra.Command {
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "features", Use: "features",
Short: "Print list of feature flags", Short: "Print list of feature flags",

View File

@@ -20,7 +20,7 @@ import (
"github.com/restic/restic/internal/walker" "github.com/restic/restic/internal/walker"
) )
func newFindCommand() *cobra.Command { func newFindCommand(globalOptions *GlobalOptions) *cobra.Command {
var opts FindOptions var opts FindOptions
cmd := &cobra.Command{ cmd := &cobra.Command{
@@ -51,9 +51,7 @@ Exit status is 12 if the password is incorrect.
GroupID: cmdGroupDefault, GroupID: cmdGroupDefault,
DisableAutoGenTag: true, DisableAutoGenTag: true,
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
term, cancel := setupTermstatus() return runFind(cmd.Context(), opts, *globalOptions, args, globalOptions.term)
defer cancel()
return runFind(cmd.Context(), opts, globalOptions, args, term)
}, },
} }

View File

@@ -8,15 +8,14 @@ import (
"time" "time"
rtest "github.com/restic/restic/internal/test" 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 { 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(gopts, func(gopts GlobalOptions) error {
gopts.JSON = wantJSON gopts.JSON = wantJSON
return withTermStatus(gopts, func(ctx context.Context, term ui.Terminal) error { return withTermStatus(gopts, func(ctx context.Context, gopts GlobalOptions) error {
return runFind(ctx, opts, gopts, []string{pattern}, term) return runFind(ctx, opts, gopts, []string{pattern}, gopts.term)
}) })
}) })
rtest.OK(t, err) rtest.OK(t, err)

View File

@@ -14,7 +14,7 @@ import (
"github.com/spf13/pflag" "github.com/spf13/pflag"
) )
func newForgetCommand() *cobra.Command { func newForgetCommand(globalOptions *GlobalOptions) *cobra.Command {
var opts ForgetOptions var opts ForgetOptions
var pruneOpts PruneOptions var pruneOpts PruneOptions
@@ -49,9 +49,7 @@ Exit status is 12 if the password is incorrect.
GroupID: cmdGroupDefault, GroupID: cmdGroupDefault,
DisableAutoGenTag: true, DisableAutoGenTag: true,
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
term, cancel := setupTermstatus() return runForget(cmd.Context(), opts, pruneOpts, *globalOptions, globalOptions.term, args)
defer cancel()
return runForget(cmd.Context(), opts, pruneOpts, globalOptions, term, args)
}, },
} }

View File

@@ -8,15 +8,14 @@ import (
"github.com/restic/restic/internal/restic" "github.com/restic/restic/internal/restic"
rtest "github.com/restic/restic/internal/test" rtest "github.com/restic/restic/internal/test"
"github.com/restic/restic/internal/ui"
) )
func testRunForgetMayFail(gopts GlobalOptions, opts ForgetOptions, args ...string) error { func testRunForgetMayFail(gopts GlobalOptions, opts ForgetOptions, args ...string) error {
pruneOpts := PruneOptions{ pruneOpts := PruneOptions{
MaxUnused: "5%", MaxUnused: "5%",
} }
return withTermStatus(gopts, func(ctx context.Context, term ui.Terminal) error { return withTermStatus(gopts, func(ctx context.Context, gopts GlobalOptions) error {
return runForget(context.TODO(), opts, pruneOpts, gopts, term, args) return runForget(context.TODO(), opts, pruneOpts, gopts, gopts.term, args)
}) })
} }

View File

@@ -13,7 +13,7 @@ import (
"github.com/spf13/pflag" "github.com/spf13/pflag"
) )
func newGenerateCommand() *cobra.Command { func newGenerateCommand(globalOptions *GlobalOptions) *cobra.Command {
var opts generateOptions var opts generateOptions
cmd := &cobra.Command{ cmd := &cobra.Command{
@@ -31,9 +31,7 @@ Exit status is 1 if there was any error.
`, `,
DisableAutoGenTag: true, DisableAutoGenTag: true,
RunE: func(_ *cobra.Command, args []string) error { RunE: func(_ *cobra.Command, args []string) error {
term, cancel := setupTermstatus() return runGenerate(opts, *globalOptions, args, globalOptions.term)
defer cancel()
return runGenerate(opts, globalOptions, args, term)
}, },
} }
opts.AddFlags(cmd.Flags()) opts.AddFlags(cmd.Flags())
@@ -118,7 +116,7 @@ func runGenerate(opts generateOptions, gopts GlobalOptions, args []string, term
} }
printer := newTerminalProgressPrinter(gopts.JSON, gopts.verbosity, term) printer := newTerminalProgressPrinter(gopts.JSON, gopts.verbosity, term)
cmdRoot := newRootCommand() cmdRoot := newRootCommand(&GlobalOptions{})
if opts.ManDir != "" { if opts.ManDir != "" {
err := writeManpages(cmdRoot, opts.ManDir, printer) err := writeManpages(cmdRoot, opts.ManDir, printer)

View File

@@ -6,13 +6,12 @@ import (
"testing" "testing"
rtest "github.com/restic/restic/internal/test" rtest "github.com/restic/restic/internal/test"
"github.com/restic/restic/internal/ui"
) )
func testRunGenerate(gopts GlobalOptions, opts generateOptions) ([]byte, error) { func testRunGenerate(gopts GlobalOptions, opts generateOptions) ([]byte, error) {
buf, err := withCaptureStdout(gopts, func(gopts GlobalOptions) error { buf, err := withCaptureStdout(gopts, func(gopts GlobalOptions) error {
return withTermStatus(gopts, func(ctx context.Context, term ui.Terminal) error { return withTermStatus(gopts, func(ctx context.Context, gopts GlobalOptions) error {
return runGenerate(opts, gopts, []string{}, term) return runGenerate(opts, gopts, []string{}, gopts.term)
}) })
}) })
return buf.Bytes(), err return buf.Bytes(), err
@@ -31,14 +30,14 @@ func TestGenerateStdout(t *testing.T) {
for _, tc := range testCases { for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) { t.Run(tc.name, func(t *testing.T) {
output, err := testRunGenerate(globalOptions, tc.opts) output, err := testRunGenerate(GlobalOptions{}, tc.opts)
rtest.OK(t, err) rtest.OK(t, err)
rtest.Assert(t, strings.Contains(string(output), "# "+tc.name+" completion for restic"), "has no expected completion header") rtest.Assert(t, strings.Contains(string(output), "# "+tc.name+" completion for restic"), "has no expected completion header")
}) })
} }
t.Run("Generate shell completions to stdout for two shells", func(t *testing.T) { t.Run("Generate shell completions to stdout for two shells", func(t *testing.T) {
_, err := testRunGenerate(globalOptions, generateOptions{BashCompletionFile: "-", FishCompletionFile: "-"}) _, err := testRunGenerate(GlobalOptions{}, generateOptions{BashCompletionFile: "-", FishCompletionFile: "-"})
rtest.Assert(t, err != nil, "generate shell completions to stdout for two shells fails") rtest.Assert(t, err != nil, "generate shell completions to stdout for two shells fails")
}) })
} }

View File

@@ -17,7 +17,7 @@ import (
"github.com/spf13/pflag" "github.com/spf13/pflag"
) )
func newInitCommand() *cobra.Command { func newInitCommand(globalOptions *GlobalOptions) *cobra.Command {
var opts InitOptions var opts InitOptions
cmd := &cobra.Command{ cmd := &cobra.Command{
@@ -35,9 +35,7 @@ Exit status is 1 if there was any error.
GroupID: cmdGroupDefault, GroupID: cmdGroupDefault,
DisableAutoGenTag: true, DisableAutoGenTag: true,
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
term, cancel := setupTermstatus() return runInit(cmd.Context(), opts, *globalOptions, args, globalOptions.term)
defer cancel()
return runInit(cmd.Context(), opts, globalOptions, args, term)
}, },
} }
opts.AddFlags(cmd.Flags()) opts.AddFlags(cmd.Flags())

View File

@@ -9,7 +9,6 @@ import (
"github.com/restic/restic/internal/repository" "github.com/restic/restic/internal/repository"
"github.com/restic/restic/internal/restic" "github.com/restic/restic/internal/restic"
rtest "github.com/restic/restic/internal/test" rtest "github.com/restic/restic/internal/test"
"github.com/restic/restic/internal/ui"
"github.com/restic/restic/internal/ui/progress" "github.com/restic/restic/internal/ui/progress"
) )
@@ -18,8 +17,8 @@ func testRunInit(t testing.TB, opts GlobalOptions) {
restic.TestDisableCheckPolynomial(t) restic.TestDisableCheckPolynomial(t)
restic.TestSetLockTimeout(t, 0) restic.TestSetLockTimeout(t, 0)
err := withTermStatus(opts, func(ctx context.Context, term ui.Terminal) error { err := withTermStatus(opts, func(ctx context.Context, gopts GlobalOptions) error {
return runInit(ctx, InitOptions{}, opts, nil, term) return runInit(ctx, InitOptions{}, opts, nil, gopts.term)
}) })
rtest.OK(t, err) rtest.OK(t, err)
t.Logf("repository initialized at %v", opts.Repo) t.Logf("repository initialized at %v", opts.Repo)
@@ -44,14 +43,14 @@ func TestInitCopyChunkerParams(t *testing.T) {
password: env2.gopts.password, password: env2.gopts.password,
}, },
} }
err := withTermStatus(env.gopts, func(ctx context.Context, term ui.Terminal) error { err := withTermStatus(env.gopts, func(ctx context.Context, gopts GlobalOptions) error {
return runInit(ctx, initOpts, env.gopts, nil, term) return runInit(ctx, initOpts, gopts, nil, gopts.term)
}) })
rtest.Assert(t, err != nil, "expected invalid init options to fail") rtest.Assert(t, err != nil, "expected invalid init options to fail")
initOpts.CopyChunkerParameters = true initOpts.CopyChunkerParameters = true
err = withTermStatus(env.gopts, func(ctx context.Context, term ui.Terminal) error { err = withTermStatus(env.gopts, func(ctx context.Context, gopts GlobalOptions) error {
return runInit(ctx, initOpts, env.gopts, nil, term) return runInit(ctx, initOpts, gopts, nil, gopts.term)
}) })
rtest.OK(t, err) rtest.OK(t, err)

View File

@@ -4,7 +4,7 @@ import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
func newKeyCommand() *cobra.Command { func newKeyCommand(globalOptions *GlobalOptions) *cobra.Command {
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "key", Use: "key",
Short: "Manage keys (passwords)", Short: "Manage keys (passwords)",
@@ -17,10 +17,10 @@ per repository.
} }
cmd.AddCommand( cmd.AddCommand(
newKeyAddCommand(), newKeyAddCommand(globalOptions),
newKeyListCommand(), newKeyListCommand(globalOptions),
newKeyPasswdCommand(), newKeyPasswdCommand(globalOptions),
newKeyRemoveCommand(), newKeyRemoveCommand(globalOptions),
) )
return cmd return cmd
} }

View File

@@ -12,7 +12,7 @@ import (
"github.com/spf13/pflag" "github.com/spf13/pflag"
) )
func newKeyAddCommand() *cobra.Command { func newKeyAddCommand(globalOptions *GlobalOptions) *cobra.Command {
var opts KeyAddOptions var opts KeyAddOptions
cmd := &cobra.Command{ cmd := &cobra.Command{
@@ -32,9 +32,7 @@ Exit status is 12 if the password is incorrect.
`, `,
DisableAutoGenTag: true, DisableAutoGenTag: true,
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
term, cancel := setupTermstatus() return runKeyAdd(cmd.Context(), *globalOptions, opts, args, globalOptions.term)
defer cancel()
return runKeyAdd(cmd.Context(), globalOptions, opts, args, term)
}, },
} }

View File

@@ -12,14 +12,13 @@ import (
"github.com/restic/restic/internal/backend" "github.com/restic/restic/internal/backend"
"github.com/restic/restic/internal/repository" "github.com/restic/restic/internal/repository"
rtest "github.com/restic/restic/internal/test" rtest "github.com/restic/restic/internal/test"
"github.com/restic/restic/internal/ui"
"github.com/restic/restic/internal/ui/progress" "github.com/restic/restic/internal/ui/progress"
) )
func testRunKeyListOtherIDs(t testing.TB, gopts GlobalOptions) []string { func testRunKeyListOtherIDs(t testing.TB, gopts GlobalOptions) []string {
buf, err := withCaptureStdout(gopts, func(gopts GlobalOptions) error { buf, err := withCaptureStdout(gopts, func(gopts GlobalOptions) error {
return withTermStatus(gopts, func(ctx context.Context, term ui.Terminal) error { return withTermStatus(gopts, func(ctx context.Context, gopts GlobalOptions) error {
return runKeyList(ctx, gopts, []string{}, term) return runKeyList(ctx, gopts, []string{}, gopts.term)
}) })
}) })
rtest.OK(t, err) rtest.OK(t, err)
@@ -43,8 +42,8 @@ func testRunKeyAddNewKey(t testing.TB, newPassword string, gopts GlobalOptions)
testKeyNewPassword = "" testKeyNewPassword = ""
}() }()
err := withTermStatus(gopts, func(ctx context.Context, term ui.Terminal) error { err := withTermStatus(gopts, func(ctx context.Context, gopts GlobalOptions) error {
return runKeyAdd(ctx, gopts, KeyAddOptions{}, []string{}, term) return runKeyAdd(ctx, gopts, KeyAddOptions{}, []string{}, gopts.term)
}) })
rtest.OK(t, err) rtest.OK(t, err)
} }
@@ -56,11 +55,11 @@ func testRunKeyAddNewKeyUserHost(t testing.TB, gopts GlobalOptions) {
}() }()
t.Log("adding key for john@example.com") t.Log("adding key for john@example.com")
err := withTermStatus(gopts, func(ctx context.Context, term ui.Terminal) error { err := withTermStatus(gopts, func(ctx context.Context, gopts GlobalOptions) error {
return runKeyAdd(ctx, gopts, KeyAddOptions{ return runKeyAdd(ctx, gopts, KeyAddOptions{
Username: "john", Username: "john",
Hostname: "example.com", Hostname: "example.com",
}, []string{}, term) }, []string{}, gopts.term)
}) })
rtest.OK(t, err) rtest.OK(t, err)
@@ -79,8 +78,8 @@ func testRunKeyPasswd(t testing.TB, newPassword string, gopts GlobalOptions) {
testKeyNewPassword = "" testKeyNewPassword = ""
}() }()
err := withTermStatus(gopts, func(ctx context.Context, term ui.Terminal) error { err := withTermStatus(gopts, func(ctx context.Context, gopts GlobalOptions) error {
return runKeyPasswd(ctx, gopts, KeyPasswdOptions{}, []string{}, term) return runKeyPasswd(ctx, gopts, KeyPasswdOptions{}, []string{}, gopts.term)
}) })
rtest.OK(t, err) rtest.OK(t, err)
} }
@@ -88,8 +87,8 @@ func testRunKeyPasswd(t testing.TB, newPassword string, gopts GlobalOptions) {
func testRunKeyRemove(t testing.TB, gopts GlobalOptions, IDs []string) { func testRunKeyRemove(t testing.TB, gopts GlobalOptions, IDs []string) {
t.Logf("remove %d keys: %q\n", len(IDs), IDs) t.Logf("remove %d keys: %q\n", len(IDs), IDs)
for _, id := range IDs { for _, id := range IDs {
err := withTermStatus(gopts, func(ctx context.Context, term ui.Terminal) error { err := withTermStatus(gopts, func(ctx context.Context, gopts GlobalOptions) error {
return runKeyRemove(ctx, gopts, []string{id}, term) return runKeyRemove(ctx, gopts, []string{id}, gopts.term)
}) })
rtest.OK(t, err) rtest.OK(t, err)
} }
@@ -121,8 +120,8 @@ func TestKeyAddRemove(t *testing.T) {
env.gopts.password = passwordList[len(passwordList)-1] env.gopts.password = passwordList[len(passwordList)-1]
t.Logf("testing access with last password %q\n", env.gopts.password) t.Logf("testing access with last password %q\n", env.gopts.password)
err := withTermStatus(env.gopts, func(ctx context.Context, term ui.Terminal) error { err := withTermStatus(env.gopts, func(ctx context.Context, gopts GlobalOptions) error {
return runKeyList(ctx, env.gopts, []string{}, term) return runKeyList(ctx, gopts, []string{}, gopts.term)
}) })
rtest.OK(t, err) rtest.OK(t, err)
testRunCheck(t, env.gopts) testRunCheck(t, env.gopts)
@@ -135,21 +134,21 @@ func TestKeyAddInvalid(t *testing.T) {
defer cleanup() defer cleanup()
testRunInit(t, env.gopts) testRunInit(t, env.gopts)
err := withTermStatus(env.gopts, func(ctx context.Context, term ui.Terminal) error { err := withTermStatus(env.gopts, func(ctx context.Context, gopts GlobalOptions) error {
return runKeyAdd(ctx, env.gopts, KeyAddOptions{ return runKeyAdd(ctx, gopts, KeyAddOptions{
NewPasswordFile: "some-file", NewPasswordFile: "some-file",
InsecureNoPassword: true, InsecureNoPassword: true,
}, []string{}, term) }, []string{}, gopts.term)
}) })
rtest.Assert(t, strings.Contains(err.Error(), "only either"), "unexpected error message, got %q", err) rtest.Assert(t, strings.Contains(err.Error(), "only either"), "unexpected error message, got %q", err)
pwfile := filepath.Join(t.TempDir(), "pwfile") pwfile := filepath.Join(t.TempDir(), "pwfile")
rtest.OK(t, os.WriteFile(pwfile, []byte{}, 0o666)) rtest.OK(t, os.WriteFile(pwfile, []byte{}, 0o666))
err = withTermStatus(env.gopts, func(ctx context.Context, term ui.Terminal) error { err = withTermStatus(env.gopts, func(ctx context.Context, gopts GlobalOptions) error {
return runKeyAdd(ctx, env.gopts, KeyAddOptions{ return runKeyAdd(ctx, gopts, KeyAddOptions{
NewPasswordFile: pwfile, 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) rtest.Assert(t, strings.Contains(err.Error(), "an empty password is not allowed by default"), "unexpected error message, got %q", err)
} }
@@ -161,10 +160,10 @@ func TestKeyAddEmpty(t *testing.T) {
defer cleanup() defer cleanup()
testRunInit(t, env.gopts) testRunInit(t, env.gopts)
err := withTermStatus(env.gopts, func(ctx context.Context, term ui.Terminal) error { err := withTermStatus(env.gopts, func(ctx context.Context, gopts GlobalOptions) error {
return runKeyAdd(ctx, env.gopts, KeyAddOptions{ return runKeyAdd(ctx, gopts, KeyAddOptions{
InsecureNoPassword: true, InsecureNoPassword: true,
}, []string{}, term) }, []string{}, gopts.term)
}) })
rtest.OK(t, err) rtest.OK(t, err)
@@ -196,21 +195,21 @@ func TestKeyProblems(t *testing.T) {
testKeyNewPassword = "" testKeyNewPassword = ""
}() }()
err := withTermStatus(env.gopts, func(ctx context.Context, term ui.Terminal) error { err := withTermStatus(env.gopts, func(ctx context.Context, gopts GlobalOptions) error {
return runKeyPasswd(ctx, env.gopts, KeyPasswdOptions{}, []string{}, term) return runKeyPasswd(ctx, gopts, KeyPasswdOptions{}, []string{}, gopts.term)
}) })
t.Log(err) t.Log(err)
rtest.Assert(t, err != nil, "expected passwd change to fail") rtest.Assert(t, err != nil, "expected passwd change to fail")
err = withTermStatus(env.gopts, func(ctx context.Context, term ui.Terminal) error { err = withTermStatus(env.gopts, func(ctx context.Context, gopts GlobalOptions) error {
return runKeyAdd(ctx, env.gopts, KeyAddOptions{}, []string{}, term) return runKeyAdd(ctx, gopts, KeyAddOptions{}, []string{}, gopts.term)
}) })
t.Log(err) t.Log(err)
rtest.Assert(t, err != nil, "expected key adding to fail") rtest.Assert(t, err != nil, "expected key adding to fail")
t.Logf("testing access with initial password %q\n", env.gopts.password) t.Logf("testing access with initial password %q\n", env.gopts.password)
err = withTermStatus(env.gopts, func(ctx context.Context, term ui.Terminal) error { err = withTermStatus(env.gopts, func(ctx context.Context, gopts GlobalOptions) error {
return runKeyList(ctx, env.gopts, []string{}, term) return runKeyList(ctx, gopts, []string{}, gopts.term)
}) })
rtest.OK(t, err) rtest.OK(t, err)
testRunCheck(t, env.gopts) testRunCheck(t, env.gopts)
@@ -225,32 +224,32 @@ func TestKeyCommandInvalidArguments(t *testing.T) {
return &emptySaveBackend{r}, nil return &emptySaveBackend{r}, nil
} }
err := withTermStatus(env.gopts, func(ctx context.Context, term ui.Terminal) error { err := withTermStatus(env.gopts, func(ctx context.Context, gopts GlobalOptions) error {
return runKeyAdd(ctx, env.gopts, KeyAddOptions{}, []string{"johndoe"}, term) return runKeyAdd(ctx, gopts, KeyAddOptions{}, []string{"johndoe"}, gopts.term)
}) })
t.Log(err) t.Log(err)
rtest.Assert(t, err != nil && strings.Contains(err.Error(), "no arguments"), "unexpected error for key add: %v", err) rtest.Assert(t, err != nil && strings.Contains(err.Error(), "no arguments"), "unexpected error for key add: %v", err)
err = withTermStatus(env.gopts, func(ctx context.Context, term ui.Terminal) error { err = withTermStatus(env.gopts, func(ctx context.Context, gopts GlobalOptions) error {
return runKeyPasswd(ctx, env.gopts, KeyPasswdOptions{}, []string{"johndoe"}, term) return runKeyPasswd(ctx, gopts, KeyPasswdOptions{}, []string{"johndoe"}, gopts.term)
}) })
t.Log(err) t.Log(err)
rtest.Assert(t, err != nil && strings.Contains(err.Error(), "no arguments"), "unexpected error for key passwd: %v", err) rtest.Assert(t, err != nil && strings.Contains(err.Error(), "no arguments"), "unexpected error for key passwd: %v", err)
err = withTermStatus(env.gopts, func(ctx context.Context, term ui.Terminal) error { err = withTermStatus(env.gopts, func(ctx context.Context, gopts GlobalOptions) error {
return runKeyList(ctx, env.gopts, []string{"johndoe"}, term) return runKeyList(ctx, gopts, []string{"johndoe"}, gopts.term)
}) })
t.Log(err) t.Log(err)
rtest.Assert(t, err != nil && strings.Contains(err.Error(), "no arguments"), "unexpected error for key list: %v", err) rtest.Assert(t, err != nil && strings.Contains(err.Error(), "no arguments"), "unexpected error for key list: %v", err)
err = withTermStatus(env.gopts, func(ctx context.Context, term ui.Terminal) error { err = withTermStatus(env.gopts, func(ctx context.Context, gopts GlobalOptions) error {
return runKeyRemove(ctx, env.gopts, []string{}, term) return runKeyRemove(ctx, gopts, []string{}, gopts.term)
}) })
t.Log(err) t.Log(err)
rtest.Assert(t, err != nil && strings.Contains(err.Error(), "one argument"), "unexpected error for key remove: %v", err) rtest.Assert(t, err != nil && strings.Contains(err.Error(), "one argument"), "unexpected error for key remove: %v", err)
err = withTermStatus(env.gopts, func(ctx context.Context, term ui.Terminal) error { err = withTermStatus(env.gopts, func(ctx context.Context, gopts GlobalOptions) error {
return runKeyRemove(ctx, env.gopts, []string{"john", "doe"}, term) return runKeyRemove(ctx, gopts, []string{"john", "doe"}, gopts.term)
}) })
t.Log(err) t.Log(err)
rtest.Assert(t, err != nil && strings.Contains(err.Error(), "one argument"), "unexpected error for key remove: %v", err) rtest.Assert(t, err != nil && strings.Contains(err.Error(), "one argument"), "unexpected error for key remove: %v", err)

View File

@@ -14,7 +14,7 @@ import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
func newKeyListCommand() *cobra.Command { func newKeyListCommand(globalOptions *GlobalOptions) *cobra.Command {
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "list", Use: "list",
Short: "List keys (passwords)", Short: "List keys (passwords)",
@@ -34,9 +34,7 @@ Exit status is 12 if the password is incorrect.
`, `,
DisableAutoGenTag: true, DisableAutoGenTag: true,
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
term, cancel := setupTermstatus() return runKeyList(cmd.Context(), *globalOptions, args, globalOptions.term)
defer cancel()
return runKeyList(cmd.Context(), globalOptions, args, term)
}, },
} }
return cmd return cmd

View File

@@ -12,7 +12,7 @@ import (
"github.com/spf13/pflag" "github.com/spf13/pflag"
) )
func newKeyPasswdCommand() *cobra.Command { func newKeyPasswdCommand(globalOptions *GlobalOptions) *cobra.Command {
var opts KeyPasswdOptions var opts KeyPasswdOptions
cmd := &cobra.Command{ cmd := &cobra.Command{
@@ -33,9 +33,7 @@ Exit status is 12 if the password is incorrect.
`, `,
DisableAutoGenTag: true, DisableAutoGenTag: true,
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
term, cancel := setupTermstatus() return runKeyPasswd(cmd.Context(), *globalOptions, opts, args, globalOptions.term)
defer cancel()
return runKeyPasswd(cmd.Context(), globalOptions, opts, args, term)
}, },
} }

View File

@@ -12,7 +12,7 @@ import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
func newKeyRemoveCommand() *cobra.Command { func newKeyRemoveCommand(globalOptions *GlobalOptions) *cobra.Command {
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "remove [ID]", Use: "remove [ID]",
Short: "Remove key ID (password) from the repository.", Short: "Remove key ID (password) from the repository.",
@@ -31,9 +31,7 @@ Exit status is 12 if the password is incorrect.
`, `,
DisableAutoGenTag: true, DisableAutoGenTag: true,
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
term, cancel := setupTermstatus() return runKeyRemove(cmd.Context(), *globalOptions, args, globalOptions.term)
defer cancel()
return runKeyRemove(cmd.Context(), globalOptions, args, term)
}, },
} }
return cmd return cmd

View File

@@ -12,7 +12,7 @@ import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
func newListCommand() *cobra.Command { func newListCommand(globalOptions *GlobalOptions) *cobra.Command {
var listAllowedArgs = []string{"blobs", "packs", "index", "snapshots", "keys", "locks"} var listAllowedArgs = []string{"blobs", "packs", "index", "snapshots", "keys", "locks"}
var listAllowedArgsUseString = strings.Join(listAllowedArgs, "|") var listAllowedArgsUseString = strings.Join(listAllowedArgs, "|")
@@ -34,9 +34,7 @@ Exit status is 12 if the password is incorrect.
DisableAutoGenTag: true, DisableAutoGenTag: true,
GroupID: cmdGroupDefault, GroupID: cmdGroupDefault,
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
term, cancel := setupTermstatus() return runList(cmd.Context(), *globalOptions, args, globalOptions.term)
defer cancel()
return runList(cmd.Context(), globalOptions, args, term)
}, },
ValidArgs: listAllowedArgs, ValidArgs: listAllowedArgs,
Args: cobra.MatchAll(cobra.ExactArgs(1), cobra.OnlyValidArgs), Args: cobra.MatchAll(cobra.ExactArgs(1), cobra.OnlyValidArgs),

View File

@@ -8,13 +8,12 @@ import (
"github.com/restic/restic/internal/restic" "github.com/restic/restic/internal/restic"
rtest "github.com/restic/restic/internal/test" rtest "github.com/restic/restic/internal/test"
"github.com/restic/restic/internal/ui"
) )
func testRunList(t testing.TB, opts GlobalOptions, tpe string) restic.IDs { func testRunList(t testing.TB, opts GlobalOptions, tpe string) restic.IDs {
buf, err := withCaptureStdout(opts, func(opts GlobalOptions) error { buf, err := withCaptureStdout(opts, func(opts GlobalOptions) error {
return withTermStatus(opts, func(ctx context.Context, term ui.Terminal) error { return withTermStatus(opts, func(ctx context.Context, gopts GlobalOptions) error {
return runList(ctx, opts, []string{tpe}, term) return runList(ctx, opts, []string{tpe}, gopts.term)
}) })
}) })
rtest.OK(t, err) rtest.OK(t, err)

View File

@@ -22,7 +22,7 @@ import (
"github.com/restic/restic/internal/walker" "github.com/restic/restic/internal/walker"
) )
func newLsCommand() *cobra.Command { func newLsCommand(globalOptions *GlobalOptions) *cobra.Command {
var opts LsOptions var opts LsOptions
cmd := &cobra.Command{ cmd := &cobra.Command{
@@ -60,9 +60,7 @@ Exit status is 12 if the password is incorrect.
DisableAutoGenTag: true, DisableAutoGenTag: true,
GroupID: cmdGroupDefault, GroupID: cmdGroupDefault,
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
term, cancel := setupTermstatus() return runLs(cmd.Context(), opts, *globalOptions, args, globalOptions.term)
defer cancel()
return runLs(cmd.Context(), opts, globalOptions, args, term)
}, },
} }
opts.AddFlags(cmd.Flags()) opts.AddFlags(cmd.Flags())

View File

@@ -10,14 +10,13 @@ import (
"github.com/restic/restic/internal/restic" "github.com/restic/restic/internal/restic"
rtest "github.com/restic/restic/internal/test" rtest "github.com/restic/restic/internal/test"
"github.com/restic/restic/internal/ui"
) )
func testRunLsWithOpts(t testing.TB, gopts GlobalOptions, opts LsOptions, args []string) []byte { func testRunLsWithOpts(t testing.TB, gopts GlobalOptions, opts LsOptions, args []string) []byte {
buf, err := withCaptureStdout(gopts, func(gopts GlobalOptions) error { buf, err := withCaptureStdout(gopts, func(gopts GlobalOptions) error {
gopts.Quiet = true gopts.Quiet = true
return withTermStatus(gopts, func(ctx context.Context, term ui.Terminal) error { return withTermStatus(gopts, func(ctx context.Context, gopts GlobalOptions) error {
return runLs(context.TODO(), opts, gopts, args, term) return runLs(context.TODO(), opts, gopts, args, gopts.term)
}) })
}) })
rtest.OK(t, err) rtest.OK(t, err)

View File

@@ -12,7 +12,7 @@ import (
"github.com/spf13/pflag" "github.com/spf13/pflag"
) )
func newMigrateCommand() *cobra.Command { func newMigrateCommand(globalOptions *GlobalOptions) *cobra.Command {
var opts MigrateOptions var opts MigrateOptions
cmd := &cobra.Command{ cmd := &cobra.Command{
@@ -35,9 +35,7 @@ Exit status is 12 if the password is incorrect.
DisableAutoGenTag: true, DisableAutoGenTag: true,
GroupID: cmdGroupDefault, GroupID: cmdGroupDefault,
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
term, cancel := setupTermstatus() return runMigrate(cmd.Context(), opts, *globalOptions, args, globalOptions.term)
defer cancel()
return runMigrate(cmd.Context(), opts, globalOptions, args, term)
}, },
} }

View File

@@ -24,11 +24,11 @@ import (
"github.com/anacrolix/fuse/fs" "github.com/anacrolix/fuse/fs"
) )
func registerMountCommand(cmdRoot *cobra.Command) { func registerMountCommand(cmdRoot *cobra.Command, globalOptions *GlobalOptions) {
cmdRoot.AddCommand(newMountCommand()) cmdRoot.AddCommand(newMountCommand(globalOptions))
} }
func newMountCommand() *cobra.Command { func newMountCommand(globalOptions *GlobalOptions) *cobra.Command {
var opts MountOptions var opts MountOptions
cmd := &cobra.Command{ cmd := &cobra.Command{
@@ -82,9 +82,7 @@ Exit status is 12 if the password is incorrect.
DisableAutoGenTag: true, DisableAutoGenTag: true,
GroupID: cmdGroupDefault, GroupID: cmdGroupDefault,
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
term, cancel := setupTermstatus() return runMount(cmd.Context(), opts, *globalOptions, args, globalOptions.term)
defer cancel()
return runMount(cmd.Context(), opts, globalOptions, args, term)
}, },
} }

View File

@@ -5,6 +5,6 @@ package main
import "github.com/spf13/cobra" import "github.com/spf13/cobra"
func registerMountCommand(_ *cobra.Command) { func registerMountCommand(_ *cobra.Command, _ *GlobalOptions) {
// Mount command not supported on these platforms // Mount command not supported on these platforms
} }

View File

@@ -16,7 +16,6 @@ import (
"github.com/restic/restic/internal/debug" "github.com/restic/restic/internal/debug"
"github.com/restic/restic/internal/restic" "github.com/restic/restic/internal/restic"
rtest "github.com/restic/restic/internal/test" rtest "github.com/restic/restic/internal/test"
"github.com/restic/restic/internal/ui"
) )
const ( const (
@@ -62,8 +61,8 @@ func testRunMount(t testing.TB, gopts GlobalOptions, dir string, wg *sync.WaitGr
opts := MountOptions{ opts := MountOptions{
TimeTemplate: time.RFC3339, TimeTemplate: time.RFC3339,
} }
rtest.OK(t, withTermStatus(gopts, func(ctx context.Context, term ui.Terminal) error { rtest.OK(t, withTermStatus(gopts, func(ctx context.Context, gopts GlobalOptions) error {
return runMount(context.TODO(), opts, gopts, []string{dir}, term) return runMount(context.TODO(), opts, gopts, []string{dir}, gopts.term)
})) }))
} }
@@ -128,8 +127,8 @@ func checkSnapshots(t testing.TB, gopts GlobalOptions, mountpoint string, snapsh
} }
} }
err := withTermStatus(gopts, func(ctx context.Context, term ui.Terminal) error { err := withTermStatus(gopts, func(ctx context.Context, gopts GlobalOptions) error {
printer := newTerminalProgressPrinter(gopts.JSON, gopts.verbosity, term) printer := newTerminalProgressPrinter(gopts.JSON, gopts.verbosity, gopts.term)
_, repo, unlock, err := openWithReadLock(ctx, gopts, false, printer) _, repo, unlock, err := openWithReadLock(ctx, gopts, false, printer)
if err != nil { if err != nil {
return err return err

View File

@@ -8,7 +8,7 @@ import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
func newOptionsCommand() *cobra.Command { func newOptionsCommand(globalOptions *GlobalOptions) *cobra.Command {
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "options", Use: "options",
Short: "Print list of extended options", Short: "Print list of extended options",

View File

@@ -18,7 +18,7 @@ import (
"github.com/spf13/pflag" "github.com/spf13/pflag"
) )
func newPruneCommand() *cobra.Command { func newPruneCommand(globalOptions *GlobalOptions) *cobra.Command {
var opts PruneOptions var opts PruneOptions
cmd := &cobra.Command{ cmd := &cobra.Command{
@@ -40,9 +40,7 @@ Exit status is 12 if the password is incorrect.
GroupID: cmdGroupDefault, GroupID: cmdGroupDefault,
DisableAutoGenTag: true, DisableAutoGenTag: true,
RunE: func(cmd *cobra.Command, _ []string) error { RunE: func(cmd *cobra.Command, _ []string) error {
term, cancel := setupTermstatus() return runPrune(cmd.Context(), opts, *globalOptions, globalOptions.term)
defer cancel()
return runPrune(cmd.Context(), opts, globalOptions, term)
}, },
} }

View File

@@ -9,7 +9,6 @@ import (
"github.com/restic/restic/internal/backend" "github.com/restic/restic/internal/backend"
"github.com/restic/restic/internal/repository" "github.com/restic/restic/internal/repository"
rtest "github.com/restic/restic/internal/test" rtest "github.com/restic/restic/internal/test"
"github.com/restic/restic/internal/ui"
) )
func testRunPrune(t testing.TB, gopts GlobalOptions, opts PruneOptions) { func testRunPrune(t testing.TB, gopts GlobalOptions, opts PruneOptions) {
@@ -29,8 +28,8 @@ func testRunPruneOutput(gopts GlobalOptions, opts PruneOptions) error {
defer func() { defer func() {
gopts.backendTestHook = oldHook gopts.backendTestHook = oldHook
}() }()
return withTermStatus(gopts, func(ctx context.Context, term ui.Terminal) error { return withTermStatus(gopts, func(ctx context.Context, gopts GlobalOptions) error {
return runPrune(context.TODO(), opts, gopts, term) return runPrune(context.TODO(), opts, gopts, gopts.term)
}) })
} }
@@ -99,8 +98,8 @@ func testRunForgetJSON(t testing.TB, gopts GlobalOptions, args ...string) {
pruneOpts := PruneOptions{ pruneOpts := PruneOptions{
MaxUnused: "5%", MaxUnused: "5%",
} }
return withTermStatus(gopts, func(ctx context.Context, term ui.Terminal) error { return withTermStatus(gopts, func(ctx context.Context, gopts GlobalOptions) error {
return runForget(context.TODO(), opts, pruneOpts, gopts, term, args) return runForget(context.TODO(), opts, pruneOpts, gopts, gopts.term, args)
}) })
}) })
rtest.OK(t, err) rtest.OK(t, err)
@@ -122,8 +121,8 @@ func testPrune(t *testing.T, pruneOpts PruneOptions, checkOpts CheckOptions) {
createPrunableRepo(t, env) createPrunableRepo(t, env)
testRunPrune(t, env.gopts, pruneOpts) testRunPrune(t, env.gopts, pruneOpts)
rtest.OK(t, withTermStatus(env.gopts, func(ctx context.Context, term ui.Terminal) error { rtest.OK(t, withTermStatus(env.gopts, func(ctx context.Context, gopts GlobalOptions) error {
_, err := runCheck(context.TODO(), checkOpts, env.gopts, nil, term) _, err := runCheck(context.TODO(), checkOpts, gopts, nil, gopts.term)
return err return err
})) }))
} }
@@ -158,8 +157,8 @@ func TestPruneWithDamagedRepository(t *testing.T) {
env.gopts.backendTestHook = oldHook env.gopts.backendTestHook = oldHook
}() }()
// prune should fail // prune should fail
rtest.Equals(t, repository.ErrPacksMissing, withTermStatus(env.gopts, func(ctx context.Context, term ui.Terminal) error { rtest.Equals(t, repository.ErrPacksMissing, withTermStatus(env.gopts, func(ctx context.Context, gopts GlobalOptions) error {
return runPrune(context.TODO(), pruneDefaultOptions, env.gopts, term) return runPrune(context.TODO(), pruneDefaultOptions, gopts, gopts.term)
}), "prune should have reported index not complete error") }), "prune should have reported index not complete error")
} }
@@ -231,8 +230,8 @@ func testEdgeCaseRepo(t *testing.T, tarfile string, optionsCheck CheckOptions, o
if checkOK { if checkOK {
testRunCheck(t, env.gopts) testRunCheck(t, env.gopts)
} else { } else {
rtest.Assert(t, withTermStatus(env.gopts, func(ctx context.Context, term ui.Terminal) error { rtest.Assert(t, withTermStatus(env.gopts, func(ctx context.Context, gopts GlobalOptions) error {
_, err := runCheck(context.TODO(), optionsCheck, env.gopts, nil, term) _, err := runCheck(context.TODO(), optionsCheck, gopts, nil, gopts.term)
return err return err
}) != nil, }) != nil,
"check should have reported an error") "check should have reported an error")
@@ -242,8 +241,8 @@ func testEdgeCaseRepo(t *testing.T, tarfile string, optionsCheck CheckOptions, o
testRunPrune(t, env.gopts, optionsPrune) testRunPrune(t, env.gopts, optionsPrune)
testRunCheck(t, env.gopts) testRunCheck(t, env.gopts)
} else { } else {
rtest.Assert(t, withTermStatus(env.gopts, func(ctx context.Context, term ui.Terminal) error { rtest.Assert(t, withTermStatus(env.gopts, func(ctx context.Context, gopts GlobalOptions) error {
return runPrune(context.TODO(), optionsPrune, env.gopts, term) return runPrune(context.TODO(), optionsPrune, gopts, gopts.term)
}) != nil, }) != nil,
"prune should have reported an error") "prune should have reported an error")
} }

View File

@@ -14,7 +14,7 @@ import (
"golang.org/x/sync/errgroup" "golang.org/x/sync/errgroup"
) )
func newRecoverCommand() *cobra.Command { func newRecoverCommand(globalOptions *GlobalOptions) *cobra.Command {
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "recover [flags]", Use: "recover [flags]",
Short: "Recover data from the repository not referenced by snapshots", Short: "Recover data from the repository not referenced by snapshots",
@@ -35,9 +35,7 @@ Exit status is 12 if the password is incorrect.
GroupID: cmdGroupDefault, GroupID: cmdGroupDefault,
DisableAutoGenTag: true, DisableAutoGenTag: true,
RunE: func(cmd *cobra.Command, _ []string) error { RunE: func(cmd *cobra.Command, _ []string) error {
term, cancel := setupTermstatus() return runRecover(cmd.Context(), *globalOptions, globalOptions.term)
defer cancel()
return runRecover(cmd.Context(), globalOptions, term)
}, },
} }
return cmd return cmd

View File

@@ -5,12 +5,11 @@ import (
"testing" "testing"
rtest "github.com/restic/restic/internal/test" rtest "github.com/restic/restic/internal/test"
"github.com/restic/restic/internal/ui"
) )
func testRunRecover(t testing.TB, gopts GlobalOptions) { func testRunRecover(t testing.TB, gopts GlobalOptions) {
rtest.OK(t, withTermStatus(gopts, func(ctx context.Context, term ui.Terminal) error { rtest.OK(t, withTermStatus(gopts, func(ctx context.Context, gopts GlobalOptions) error {
return runRecover(context.TODO(), gopts, term) return runRecover(context.TODO(), gopts, gopts.term)
})) }))
} }
@@ -33,7 +32,7 @@ func TestRecover(t *testing.T) {
ids = testListSnapshots(t, env.gopts, 1) ids = testListSnapshots(t, env.gopts, 1)
testRunCheck(t, env.gopts) testRunCheck(t, env.gopts)
// check that the root tree is included in the snapshot // check that the root tree is included in the snapshot
rtest.OK(t, withTermStatus(env.gopts, func(ctx context.Context, term ui.Terminal) error { rtest.OK(t, withTermStatus(env.gopts, func(ctx context.Context, gopts GlobalOptions) error {
return runCat(context.TODO(), env.gopts, []string{"tree", ids[0].String() + ":" + sn.Tree.Str()}, term) return runCat(context.TODO(), gopts, []string{"tree", ids[0].String() + ":" + sn.Tree.Str()}, gopts.term)
})) }))
} }

View File

@@ -4,7 +4,7 @@ import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
func newRepairCommand() *cobra.Command { func newRepairCommand(globalOptions *GlobalOptions) *cobra.Command {
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "repair", Use: "repair",
Short: "Repair the repository", Short: "Repair the repository",
@@ -13,9 +13,9 @@ func newRepairCommand() *cobra.Command {
} }
cmd.AddCommand( cmd.AddCommand(
newRepairIndexCommand(), newRepairIndexCommand(globalOptions),
newRepairPacksCommand(), newRepairPacksCommand(globalOptions),
newRepairSnapshotsCommand(), newRepairSnapshotsCommand(globalOptions),
) )
return cmd return cmd
} }

View File

@@ -9,7 +9,7 @@ import (
"github.com/spf13/pflag" "github.com/spf13/pflag"
) )
func newRepairIndexCommand() *cobra.Command { func newRepairIndexCommand(globalOptions *GlobalOptions) *cobra.Command {
var opts RepairIndexOptions var opts RepairIndexOptions
cmd := &cobra.Command{ cmd := &cobra.Command{
@@ -30,9 +30,7 @@ Exit status is 12 if the password is incorrect.
`, `,
DisableAutoGenTag: true, DisableAutoGenTag: true,
RunE: func(cmd *cobra.Command, _ []string) error { RunE: func(cmd *cobra.Command, _ []string) error {
term, cancel := setupTermstatus() return runRebuildIndex(cmd.Context(), opts, *globalOptions, globalOptions.term)
defer cancel()
return runRebuildIndex(cmd.Context(), opts, 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") 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 var opts RepairIndexOptions
replacement := newRepairIndexCommand() replacement := newRepairIndexCommand(globalOptions)
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "rebuild-index [flags]", Use: "rebuild-index [flags]",
Short: replacement.Short, Short: replacement.Short,
@@ -62,9 +60,7 @@ func newRebuildIndexCommand() *cobra.Command {
// must create a new instance of the run function as it captures opts // must create a new instance of the run function as it captures opts
// by reference // by reference
RunE: func(cmd *cobra.Command, _ []string) error { RunE: func(cmd *cobra.Command, _ []string) error {
term, cancel := setupTermstatus() return runRebuildIndex(cmd.Context(), opts, *globalOptions, globalOptions.term)
defer cancel()
return runRebuildIndex(cmd.Context(), opts, globalOptions, term)
}, },
} }

View File

@@ -13,15 +13,12 @@ import (
"github.com/restic/restic/internal/repository/index" "github.com/restic/restic/internal/repository/index"
"github.com/restic/restic/internal/restic" "github.com/restic/restic/internal/restic"
rtest "github.com/restic/restic/internal/test" rtest "github.com/restic/restic/internal/test"
"github.com/restic/restic/internal/ui"
) )
func testRunRebuildIndex(t testing.TB, gopts GlobalOptions) { func testRunRebuildIndex(t testing.TB, gopts GlobalOptions) {
rtest.OK(t, withRestoreGlobalOptions(func() error { rtest.OK(t, withTermStatus(gopts, func(ctx context.Context, gopts GlobalOptions) error {
return withTermStatus(gopts, func(ctx context.Context, term ui.Terminal) error { gopts.stdout = io.Discard
gopts.stdout = io.Discard return runRebuildIndex(context.TODO(), RepairIndexOptions{}, gopts, gopts.term)
return runRebuildIndex(context.TODO(), RepairIndexOptions{}, gopts, term)
})
})) }))
} }
@@ -128,14 +125,12 @@ func TestRebuildIndexFailsOnAppendOnly(t *testing.T) {
datafile := filepath.Join("..", "..", "internal", "checker", "testdata", "duplicate-packs-in-index-test-repo.tar.gz") datafile := filepath.Join("..", "..", "internal", "checker", "testdata", "duplicate-packs-in-index-test-repo.tar.gz")
rtest.SetupTarTestFixture(t, env.base, datafile) rtest.SetupTarTestFixture(t, env.base, datafile)
err := withRestoreGlobalOptions(func() error { env.gopts.backendTestHook = func(r backend.Backend) (backend.Backend, error) {
env.gopts.backendTestHook = func(r backend.Backend) (backend.Backend, error) { return &appendOnlyBackend{r}, nil
return &appendOnlyBackend{r}, nil }
} err := withTermStatus(env.gopts, func(ctx context.Context, gopts GlobalOptions) error {
return withTermStatus(env.gopts, func(ctx context.Context, term ui.Terminal) error { gopts.stdout = io.Discard
env.gopts.stdout = io.Discard return runRebuildIndex(context.TODO(), RepairIndexOptions{}, gopts, gopts.term)
return runRebuildIndex(context.TODO(), RepairIndexOptions{}, env.gopts, term)
})
}) })
if err == nil { if err == nil {

View File

@@ -13,7 +13,7 @@ import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
func newRepairPacksCommand() *cobra.Command { func newRepairPacksCommand(globalOptions *GlobalOptions) *cobra.Command {
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "packs [packIDs...]", Use: "packs [packIDs...]",
Short: "Salvage damaged pack files", Short: "Salvage damaged pack files",
@@ -32,9 +32,7 @@ Exit status is 12 if the password is incorrect.
`, `,
DisableAutoGenTag: true, DisableAutoGenTag: true,
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
term, cancel := setupTermstatus() return runRepairPacks(cmd.Context(), *globalOptions, globalOptions.term, args)
defer cancel()
return runRepairPacks(cmd.Context(), globalOptions, term, args)
}, },
} }
return cmd return cmd

View File

@@ -12,7 +12,7 @@ import (
"github.com/spf13/pflag" "github.com/spf13/pflag"
) )
func newRepairSnapshotsCommand() *cobra.Command { func newRepairSnapshotsCommand(globalOptions *GlobalOptions) *cobra.Command {
var opts RepairOptions var opts RepairOptions
cmd := &cobra.Command{ cmd := &cobra.Command{
@@ -50,9 +50,7 @@ Exit status is 12 if the password is incorrect.
`, `,
DisableAutoGenTag: true, DisableAutoGenTag: true,
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
term, cancel := setupTermstatus() return runRepairSnapshots(cmd.Context(), *globalOptions, opts, args, globalOptions.term)
defer cancel()
return runRepairSnapshots(cmd.Context(), globalOptions, opts, args, term)
}, },
} }

View File

@@ -12,7 +12,6 @@ import (
"github.com/restic/restic/internal/restic" "github.com/restic/restic/internal/restic"
rtest "github.com/restic/restic/internal/test" rtest "github.com/restic/restic/internal/test"
"github.com/restic/restic/internal/ui"
) )
func testRunRepairSnapshot(t testing.TB, gopts GlobalOptions, forget bool) { func testRunRepairSnapshot(t testing.TB, gopts GlobalOptions, forget bool) {
@@ -20,8 +19,8 @@ func testRunRepairSnapshot(t testing.TB, gopts GlobalOptions, forget bool) {
Forget: forget, Forget: forget,
} }
rtest.OK(t, withTermStatus(gopts, func(ctx context.Context, term ui.Terminal) error { rtest.OK(t, withTermStatus(gopts, func(ctx context.Context, gopts GlobalOptions) error {
return runRepairSnapshots(context.TODO(), gopts, opts, nil, term) return runRepairSnapshots(context.TODO(), gopts, opts, nil, gopts.term)
})) }))
} }

View File

@@ -18,7 +18,7 @@ import (
"github.com/spf13/pflag" "github.com/spf13/pflag"
) )
func newRestoreCommand() *cobra.Command { func newRestoreCommand(globalOptions *GlobalOptions) *cobra.Command {
var opts RestoreOptions var opts RestoreOptions
cmd := &cobra.Command{ cmd := &cobra.Command{
@@ -46,9 +46,7 @@ Exit status is 12 if the password is incorrect.
GroupID: cmdGroupDefault, GroupID: cmdGroupDefault,
DisableAutoGenTag: true, DisableAutoGenTag: true,
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
term, cancel := setupTermstatus() return runRestore(cmd.Context(), opts, *globalOptions, globalOptions.term, args)
defer cancel()
return runRestore(cmd.Context(), opts, globalOptions, term, args)
}, },
} }

View File

@@ -14,7 +14,6 @@ import (
"github.com/restic/restic/internal/restic" "github.com/restic/restic/internal/restic"
rtest "github.com/restic/restic/internal/test" rtest "github.com/restic/restic/internal/test"
"github.com/restic/restic/internal/ui"
) )
func testRunRestore(t testing.TB, opts GlobalOptions, dir string, snapshotID string) { func testRunRestore(t testing.TB, opts GlobalOptions, dir string, snapshotID string) {
@@ -31,8 +30,8 @@ func testRunRestoreExcludes(t testing.TB, gopts GlobalOptions, dir string, snaps
} }
func testRunRestoreAssumeFailure(snapshotID string, opts RestoreOptions, gopts GlobalOptions) error { func testRunRestoreAssumeFailure(snapshotID string, opts RestoreOptions, gopts GlobalOptions) error {
return withTermStatus(gopts, func(ctx context.Context, term ui.Terminal) error { return withTermStatus(gopts, func(ctx context.Context, gopts GlobalOptions) error {
return runRestore(ctx, opts, gopts, term, []string{snapshotID}) return runRestore(ctx, opts, gopts, gopts.term, []string{snapshotID})
}) })
} }
@@ -337,11 +336,8 @@ func TestRestoreWithPermissionFailure(t *testing.T) {
snapshots := testListSnapshots(t, env.gopts, 1) snapshots := testListSnapshots(t, env.gopts, 1)
_ = withRestoreGlobalOptions(func() error { env.gopts.stderr = io.Discard
env.gopts.stderr = io.Discard testRunRestore(t, env.gopts, filepath.Join(env.base, "restore"), snapshots[0].String())
testRunRestore(t, env.gopts, filepath.Join(env.base, "restore"), snapshots[0].String())
return nil
})
// make sure that all files have been restored, regardless of any // make sure that all files have been restored, regardless of any
// permission errors // permission errors

View File

@@ -18,7 +18,7 @@ import (
"github.com/restic/restic/internal/walker" "github.com/restic/restic/internal/walker"
) )
func newRewriteCommand() *cobra.Command { func newRewriteCommand(globalOptions *GlobalOptions) *cobra.Command {
var opts RewriteOptions var opts RewriteOptions
cmd := &cobra.Command{ cmd := &cobra.Command{
@@ -60,9 +60,7 @@ Exit status is 12 if the password is incorrect.
GroupID: cmdGroupDefault, GroupID: cmdGroupDefault,
DisableAutoGenTag: true, DisableAutoGenTag: true,
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
term, cancel := setupTermstatus() return runRewrite(cmd.Context(), opts, *globalOptions, args, globalOptions.term)
defer cancel()
return runRewrite(cmd.Context(), opts, globalOptions, args, term)
}, },
} }

View File

@@ -20,8 +20,8 @@ func testRunRewriteExclude(t testing.TB, gopts GlobalOptions, excludes []string,
Metadata: metadata, Metadata: metadata,
} }
rtest.OK(t, withTermStatus(gopts, func(ctx context.Context, term ui.Terminal) error { rtest.OK(t, withTermStatus(gopts, func(ctx context.Context, gopts GlobalOptions) error {
return runRewrite(context.TODO(), opts, gopts, nil, term) 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() t.Helper()
var snapshots []*restic.Snapshot var snapshots []*restic.Snapshot
err := withTermStatus(env.gopts, func(ctx context.Context, term ui.Terminal) error { err := withTermStatus(env.gopts, func(ctx context.Context, gopts GlobalOptions) error {
printer := newTerminalProgressPrinter(env.gopts.JSON, env.gopts.verbosity, term) printer := newTerminalProgressPrinter(gopts.JSON, gopts.verbosity, gopts.term)
ctx, repo, unlock, err := openWithReadLock(ctx, env.gopts, false, printer) ctx, repo, unlock, err := openWithReadLock(ctx, gopts, false, printer)
rtest.OK(t, err) rtest.OK(t, err)
defer unlock() defer unlock()
@@ -117,9 +117,9 @@ func testRewriteMetadata(t *testing.T, metadata snapshotMetadataArgs) {
testRunRewriteExclude(t, env.gopts, []string{}, true, metadata) testRunRewriteExclude(t, env.gopts, []string{}, true, metadata)
var snapshots []*restic.Snapshot var snapshots []*restic.Snapshot
err := withTermStatus(env.gopts, func(ctx context.Context, term ui.Terminal) error { err := withTermStatus(env.gopts, func(ctx context.Context, gopts GlobalOptions) error {
printer := newTerminalProgressPrinter(env.gopts.JSON, env.gopts.verbosity, term) printer := newTerminalProgressPrinter(gopts.JSON, gopts.verbosity, gopts.term)
ctx, repo, unlock, err := openWithReadLock(ctx, env.gopts, false, printer) ctx, repo, unlock, err := openWithReadLock(ctx, gopts, false, printer)
rtest.OK(t, err) rtest.OK(t, err)
defer unlock() defer unlock()
@@ -157,17 +157,17 @@ func TestRewriteSnaphotSummary(t *testing.T) {
defer cleanup() defer cleanup()
createBasicRewriteRepo(t, env) createBasicRewriteRepo(t, env)
rtest.OK(t, withTermStatus(env.gopts, func(ctx context.Context, term ui.Terminal) error { rtest.OK(t, withTermStatus(env.gopts, func(ctx context.Context, gopts GlobalOptions) error {
return runRewrite(context.TODO(), RewriteOptions{SnapshotSummary: true}, env.gopts, []string{}, term) return runRewrite(context.TODO(), RewriteOptions{SnapshotSummary: true}, gopts, []string{}, gopts.term)
})) }))
// no new snapshot should be created as the snapshot already has a summary // no new snapshot should be created as the snapshot already has a summary
snapshots := testListSnapshots(t, env.gopts, 1) snapshots := testListSnapshots(t, env.gopts, 1)
// replace snapshot by one without a summary // replace snapshot by one without a summary
var oldSummary *restic.SnapshotSummary var oldSummary *restic.SnapshotSummary
err := withTermStatus(env.gopts, func(ctx context.Context, term ui.Terminal) error { err := withTermStatus(env.gopts, func(ctx context.Context, gopts GlobalOptions) error {
printer := newTerminalProgressPrinter(env.gopts.JSON, env.gopts.verbosity, term) printer := newTerminalProgressPrinter(gopts.JSON, gopts.verbosity, gopts.term)
_, repo, unlock, err := openWithExclusiveLock(ctx, env.gopts, false, printer) _, repo, unlock, err := openWithExclusiveLock(ctx, gopts, false, printer)
rtest.OK(t, err) rtest.OK(t, err)
defer unlock() defer unlock()
@@ -182,8 +182,8 @@ func TestRewriteSnaphotSummary(t *testing.T) {
rtest.OK(t, err) rtest.OK(t, err)
// rewrite snapshot and lookup ID of new snapshot // rewrite snapshot and lookup ID of new snapshot
rtest.OK(t, withTermStatus(env.gopts, func(ctx context.Context, term ui.Terminal) error { rtest.OK(t, withTermStatus(env.gopts, func(ctx context.Context, gopts GlobalOptions) error {
return runRewrite(context.TODO(), RewriteOptions{SnapshotSummary: true}, env.gopts, []string{}, term) return runRewrite(context.TODO(), RewriteOptions{SnapshotSummary: true}, gopts, []string{}, gopts.term)
})) }))
newSnapshots := testListSnapshots(t, env.gopts, 2) newSnapshots := testListSnapshots(t, env.gopts, 2)
newSnapshot := restic.NewIDSet(newSnapshots...).Sub(restic.NewIDSet(snapshots...)).List()[0] newSnapshot := restic.NewIDSet(newSnapshots...).Sub(restic.NewIDSet(snapshots...)).List()[0]

View File

@@ -14,13 +14,13 @@ import (
"github.com/spf13/pflag" "github.com/spf13/pflag"
) )
func registerSelfUpdateCommand(cmd *cobra.Command) { func registerSelfUpdateCommand(cmd *cobra.Command, globalOptions *GlobalOptions) {
cmd.AddCommand( cmd.AddCommand(
newSelfUpdateCommand(), newSelfUpdateCommand(globalOptions),
) )
} }
func newSelfUpdateCommand() *cobra.Command { func newSelfUpdateCommand(globalOptions *GlobalOptions) *cobra.Command {
var opts SelfUpdateOptions var opts SelfUpdateOptions
cmd := &cobra.Command{ cmd := &cobra.Command{
@@ -43,9 +43,7 @@ Exit status is 12 if the password is incorrect.
`, `,
DisableAutoGenTag: true, DisableAutoGenTag: true,
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
term, cancel := setupTermstatus() return runSelfUpdate(cmd.Context(), opts, *globalOptions, args, globalOptions.term)
defer cancel()
return runSelfUpdate(cmd.Context(), opts, globalOptions, args, term)
}, },
} }

View File

@@ -4,6 +4,6 @@ package main
import "github.com/spf13/cobra" import "github.com/spf13/cobra"
func registerSelfUpdateCommand(_ *cobra.Command) { func registerSelfUpdateCommand(_ *cobra.Command, _ *GlobalOptions) {
// No commands to register in non-selfupdate mode // No commands to register in non-selfupdate mode
} }

View File

@@ -15,7 +15,7 @@ import (
"github.com/spf13/pflag" "github.com/spf13/pflag"
) )
func newSnapshotsCommand() *cobra.Command { func newSnapshotsCommand(globalOptions *GlobalOptions) *cobra.Command {
var opts SnapshotOptions var opts SnapshotOptions
cmd := &cobra.Command{ cmd := &cobra.Command{
@@ -36,9 +36,7 @@ Exit status is 12 if the password is incorrect.
GroupID: cmdGroupDefault, GroupID: cmdGroupDefault,
DisableAutoGenTag: true, DisableAutoGenTag: true,
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
term, cancel := setupTermstatus() return runSnapshots(cmd.Context(), opts, *globalOptions, args, globalOptions.term)
defer cancel()
return runSnapshots(cmd.Context(), opts, globalOptions, args, term)
}, },
} }

View File

@@ -7,7 +7,6 @@ import (
"github.com/restic/restic/internal/restic" "github.com/restic/restic/internal/restic"
rtest "github.com/restic/restic/internal/test" rtest "github.com/restic/restic/internal/test"
"github.com/restic/restic/internal/ui"
) )
func testRunSnapshots(t testing.TB, gopts GlobalOptions) (newest *Snapshot, snapmap map[restic.ID]Snapshot) { func testRunSnapshots(t testing.TB, gopts GlobalOptions) (newest *Snapshot, snapmap map[restic.ID]Snapshot) {
@@ -15,8 +14,8 @@ func testRunSnapshots(t testing.TB, gopts GlobalOptions) (newest *Snapshot, snap
gopts.JSON = true gopts.JSON = true
opts := SnapshotOptions{} opts := SnapshotOptions{}
return withTermStatus(gopts, func(ctx context.Context, term ui.Terminal) error { return withTermStatus(gopts, func(ctx context.Context, gopts GlobalOptions) error {
return runSnapshots(ctx, opts, gopts, []string{}, term) return runSnapshots(ctx, opts, gopts, []string{}, gopts.term)
}) })
}) })
rtest.OK(t, err) rtest.OK(t, err)

View File

@@ -22,7 +22,7 @@ import (
"github.com/spf13/pflag" "github.com/spf13/pflag"
) )
func newStatsCommand() *cobra.Command { func newStatsCommand(globalOptions *GlobalOptions) *cobra.Command {
var opts StatsOptions var opts StatsOptions
cmd := &cobra.Command{ cmd := &cobra.Command{
@@ -63,9 +63,7 @@ Exit status is 12 if the password is incorrect.
GroupID: cmdGroupDefault, GroupID: cmdGroupDefault,
DisableAutoGenTag: true, DisableAutoGenTag: true,
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
term, cancel := setupTermstatus() return runStats(cmd.Context(), opts, *globalOptions, args, globalOptions.term)
defer cancel()
return runStats(cmd.Context(), opts, globalOptions, args, term)
}, },
} }

View File

@@ -13,7 +13,7 @@ import (
"github.com/restic/restic/internal/ui" "github.com/restic/restic/internal/ui"
) )
func newTagCommand() *cobra.Command { func newTagCommand(globalOptions *GlobalOptions) *cobra.Command {
var opts TagOptions var opts TagOptions
cmd := &cobra.Command{ cmd := &cobra.Command{
@@ -39,9 +39,7 @@ Exit status is 12 if the password is incorrect.
GroupID: cmdGroupDefault, GroupID: cmdGroupDefault,
DisableAutoGenTag: true, DisableAutoGenTag: true,
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
term, cancel := setupTermstatus() return runTag(cmd.Context(), opts, *globalOptions, globalOptions.term, args)
defer cancel()
return runTag(cmd.Context(), opts, globalOptions, term, args)
}, },
} }

View File

@@ -6,12 +6,11 @@ import (
"github.com/restic/restic/internal/restic" "github.com/restic/restic/internal/restic"
rtest "github.com/restic/restic/internal/test" rtest "github.com/restic/restic/internal/test"
"github.com/restic/restic/internal/ui"
) )
func testRunTag(t testing.TB, opts TagOptions, gopts GlobalOptions) { func testRunTag(t testing.TB, opts TagOptions, gopts GlobalOptions) {
rtest.OK(t, withTermStatus(gopts, func(ctx context.Context, term ui.Terminal) error { rtest.OK(t, withTermStatus(gopts, func(ctx context.Context, gopts GlobalOptions) error {
return runTag(context.TODO(), opts, gopts, term, []string{}) return runTag(context.TODO(), opts, gopts, gopts.term, []string{})
})) }))
} }

View File

@@ -9,7 +9,7 @@ import (
"github.com/spf13/pflag" "github.com/spf13/pflag"
) )
func newUnlockCommand() *cobra.Command { func newUnlockCommand(globalOptions *GlobalOptions) *cobra.Command {
var opts UnlockOptions var opts UnlockOptions
cmd := &cobra.Command{ cmd := &cobra.Command{
@@ -27,9 +27,7 @@ Exit status is 1 if there was any error.
GroupID: cmdGroupDefault, GroupID: cmdGroupDefault,
DisableAutoGenTag: true, DisableAutoGenTag: true,
RunE: func(cmd *cobra.Command, _ []string) error { RunE: func(cmd *cobra.Command, _ []string) error {
term, cancel := setupTermstatus() return runUnlock(cmd.Context(), opts, *globalOptions, globalOptions.term)
defer cancel()
return runUnlock(cmd.Context(), opts, globalOptions, term)
}, },
} }
opts.AddFlags(cmd.Flags()) opts.AddFlags(cmd.Flags())

View File

@@ -7,7 +7,7 @@ import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
func newVersionCommand() *cobra.Command { func newVersionCommand(globalOptions *GlobalOptions) *cobra.Command {
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "version", Use: "version",
Short: "Print version information", Short: "Print version information",
@@ -23,9 +23,7 @@ Exit status is 1 if there was any error.
`, `,
DisableAutoGenTag: true, DisableAutoGenTag: true,
Run: func(_ *cobra.Command, _ []string) { Run: func(_ *cobra.Command, _ []string) {
term, cancel := setupTermstatus() printer := newTerminalProgressPrinter(globalOptions.JSON, globalOptions.verbosity, globalOptions.term)
defer cancel()
printer := newTerminalProgressPrinter(globalOptions.JSON, globalOptions.verbosity, term)
if globalOptions.JSON { if globalOptions.JSON {
type jsonVersion struct { type jsonVersion struct {

View File

@@ -8,7 +8,7 @@ import (
// TestFlags checks for double defined flags, the commands will panic on // TestFlags checks for double defined flags, the commands will panic on
// ParseFlags() when a shorthand flag is defined twice. // ParseFlags() when a shorthand flag is defined twice.
func TestFlags(t *testing.T) { func TestFlags(t *testing.T) {
for _, cmd := range newRootCommand().Commands() { for _, cmd := range newRootCommand(&GlobalOptions{}).Commands() {
t.Run(cmd.Name(), func(t *testing.T) { t.Run(cmd.Name(), func(t *testing.T) {
cmd.Flags().SetOutput(io.Discard) cmd.Flags().SetOutput(io.Discard)
err := cmd.ParseFlags([]string{"--help"}) err := cmd.ParseFlags([]string{"--help"})

View File

@@ -34,6 +34,7 @@ import (
"github.com/restic/restic/internal/restic" "github.com/restic/restic/internal/restic"
"github.com/restic/restic/internal/terminal" "github.com/restic/restic/internal/terminal"
"github.com/restic/restic/internal/textfile" "github.com/restic/restic/internal/textfile"
"github.com/restic/restic/internal/ui"
"github.com/restic/restic/internal/ui/progress" "github.com/restic/restic/internal/ui/progress"
"github.com/spf13/pflag" "github.com/spf13/pflag"
@@ -77,6 +78,7 @@ type GlobalOptions struct {
password string password string
stdout io.Writer stdout io.Writer
stderr io.Writer stderr io.Writer
term ui.Terminal
backends *location.Registry backends *location.Registry
backendTestHook, backendInnerTestHook backendWrapper backendTestHook, backendInnerTestHook backendWrapper
@@ -177,12 +179,6 @@ func (opts *GlobalOptions) PreRun(needsPassword bool) error {
return nil return nil
} }
var globalOptions = GlobalOptions{
stdout: os.Stdout,
stderr: os.Stderr,
backends: collectBackends(),
}
func collectBackends() *location.Registry { func collectBackends() *location.Registry {
backends := location.NewRegistry() backends := location.NewRegistry()
backends.Register(azure.NewFactory()) backends.Register(azure.NewFactory())

View File

@@ -20,7 +20,6 @@ import (
"github.com/restic/restic/internal/repository" "github.com/restic/restic/internal/repository"
"github.com/restic/restic/internal/restic" "github.com/restic/restic/internal/restic"
rtest "github.com/restic/restic/internal/test" rtest "github.com/restic/restic/internal/test"
"github.com/restic/restic/internal/ui"
"github.com/restic/restic/internal/ui/termstatus" "github.com/restic/restic/internal/ui/termstatus"
) )
@@ -222,12 +221,9 @@ func withTestEnvironment(t testing.TB) (env *testEnvironment, cleanup func()) {
// replace this hook with "nil" if listing a filetype more than once is necessary // replace this hook with "nil" if listing a filetype more than once is necessary
backendTestHook: func(r backend.Backend) (backend.Backend, error) { return newOrderedListOnceBackend(r), nil }, backendTestHook: func(r backend.Backend) (backend.Backend, error) { return newOrderedListOnceBackend(r), nil },
// start with default set of backends // start with default set of backends
backends: globalOptions.backends, backends: collectBackends(),
} }
// always overwrite global options
globalOptions = env.gopts
cleanup = func() { cleanup = func() {
if !rtest.TestCleanupTempDirs { if !rtest.TestCleanupTempDirs {
t.Logf("leaving temporary directory %v used for test", tempdir) t.Logf("leaving temporary directory %v used for test", tempdir)
@@ -248,8 +244,8 @@ func testSetupBackupData(t testing.TB, env *testEnvironment) string {
func listPacks(gopts GlobalOptions, t *testing.T) restic.IDSet { func listPacks(gopts GlobalOptions, t *testing.T) restic.IDSet {
var packs restic.IDSet var packs restic.IDSet
err := withTermStatus(gopts, func(ctx context.Context, term ui.Terminal) error { err := withTermStatus(gopts, func(ctx context.Context, gopts GlobalOptions) error {
printer := newTerminalProgressPrinter(gopts.JSON, gopts.verbosity, term) printer := newTerminalProgressPrinter(gopts.JSON, gopts.verbosity, gopts.term)
ctx, r, unlock, err := openWithReadLock(ctx, gopts, false, printer) ctx, r, unlock, err := openWithReadLock(ctx, gopts, false, printer)
rtest.OK(t, err) rtest.OK(t, err)
defer unlock() defer unlock()
@@ -267,8 +263,8 @@ func listPacks(gopts GlobalOptions, t *testing.T) restic.IDSet {
func listTreePacks(gopts GlobalOptions, t *testing.T) restic.IDSet { func listTreePacks(gopts GlobalOptions, t *testing.T) restic.IDSet {
var treePacks restic.IDSet var treePacks restic.IDSet
err := withTermStatus(gopts, func(ctx context.Context, term ui.Terminal) error { err := withTermStatus(gopts, func(ctx context.Context, gopts GlobalOptions) error {
printer := newTerminalProgressPrinter(gopts.JSON, gopts.verbosity, term) printer := newTerminalProgressPrinter(gopts.JSON, gopts.verbosity, gopts.term)
ctx, r, unlock, err := openWithReadLock(ctx, gopts, false, printer) ctx, r, unlock, err := openWithReadLock(ctx, gopts, false, printer)
rtest.OK(t, err) rtest.OK(t, err)
defer unlock() defer unlock()
@@ -298,8 +294,8 @@ func captureBackend(gopts *GlobalOptions) func() backend.Backend {
func removePacks(gopts GlobalOptions, t testing.TB, remove restic.IDSet) { func removePacks(gopts GlobalOptions, t testing.TB, remove restic.IDSet) {
be := captureBackend(&gopts) be := captureBackend(&gopts)
err := withTermStatus(gopts, func(ctx context.Context, term ui.Terminal) error { err := withTermStatus(gopts, func(ctx context.Context, gopts GlobalOptions) error {
printer := newTerminalProgressPrinter(gopts.JSON, gopts.verbosity, term) printer := newTerminalProgressPrinter(gopts.JSON, gopts.verbosity, gopts.term)
ctx, _, unlock, err := openWithExclusiveLock(ctx, gopts, false, printer) ctx, _, unlock, err := openWithExclusiveLock(ctx, gopts, false, printer)
rtest.OK(t, err) rtest.OK(t, err)
defer unlock() defer unlock()
@@ -314,8 +310,8 @@ func removePacks(gopts GlobalOptions, t testing.TB, remove restic.IDSet) {
func removePacksExcept(gopts GlobalOptions, t testing.TB, keep restic.IDSet, removeTreePacks bool) { func removePacksExcept(gopts GlobalOptions, t testing.TB, keep restic.IDSet, removeTreePacks bool) {
be := captureBackend(&gopts) be := captureBackend(&gopts)
err := withTermStatus(gopts, func(ctx context.Context, term ui.Terminal) error { err := withTermStatus(gopts, func(ctx context.Context, gopts GlobalOptions) error {
printer := newTerminalProgressPrinter(gopts.JSON, gopts.verbosity, term) printer := newTerminalProgressPrinter(gopts.JSON, gopts.verbosity, gopts.term)
ctx, r, unlock, err := openWithExclusiveLock(ctx, gopts, false, printer) ctx, r, unlock, err := openWithExclusiveLock(ctx, gopts, false, printer)
rtest.OK(t, err) rtest.OK(t, err)
defer unlock() defer unlock()
@@ -375,8 +371,8 @@ func lastSnapshot(old, new map[string]struct{}) (map[string]struct{}, string) {
func testLoadSnapshot(t testing.TB, gopts GlobalOptions, id restic.ID) *restic.Snapshot { func testLoadSnapshot(t testing.TB, gopts GlobalOptions, id restic.ID) *restic.Snapshot {
var snapshot *restic.Snapshot var snapshot *restic.Snapshot
err := withTermStatus(gopts, func(ctx context.Context, term ui.Terminal) error { err := withTermStatus(gopts, func(ctx context.Context, gopts GlobalOptions) error {
printer := newTerminalProgressPrinter(gopts.JSON, gopts.verbosity, term) printer := newTerminalProgressPrinter(gopts.JSON, gopts.verbosity, gopts.term)
_, repo, unlock, err := openWithReadLock(ctx, gopts, false, printer) _, repo, unlock, err := openWithReadLock(ctx, gopts, false, printer)
rtest.OK(t, err) rtest.OK(t, err)
defer unlock() defer unlock()
@@ -420,30 +416,19 @@ func testFileSize(filename string, size int64) error {
return nil 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(gopts GlobalOptions, inner func(gopts GlobalOptions) error) (*bytes.Buffer, error) {
buf := bytes.NewBuffer(nil) buf := bytes.NewBuffer(nil)
err := withRestoreGlobalOptions(func() error { gopts.stdout = buf
globalOptions.stdout = buf err := inner(gopts)
gopts.stdout = buf
return inner(gopts)
})
return buf, err return buf, err
} }
func withTermStatus(gopts GlobalOptions, callback func(ctx context.Context, term ui.Terminal) error) error { func withTermStatus(gopts GlobalOptions, callback func(ctx context.Context, gopts GlobalOptions) error) error {
ctx, cancel := context.WithCancel(context.TODO()) ctx, cancel := context.WithCancel(context.TODO())
var wg sync.WaitGroup var wg sync.WaitGroup
term := termstatus.New(gopts.stdout, gopts.stderr, gopts.Quiet) term := termstatus.New(gopts.stdout, gopts.stderr, gopts.Quiet)
gopts.term = term
wg.Add(1) wg.Add(1)
go func() { go func() {
defer wg.Done() defer wg.Done()
@@ -453,5 +438,5 @@ func withTermStatus(gopts GlobalOptions, callback func(ctx context.Context, term
defer wg.Wait() defer wg.Wait()
defer cancel() defer cancel()
return callback(ctx, term) return callback(ctx, gopts)
} }

View File

@@ -12,7 +12,6 @@ import (
"github.com/restic/restic/internal/errors" "github.com/restic/restic/internal/errors"
"github.com/restic/restic/internal/restic" "github.com/restic/restic/internal/restic"
rtest "github.com/restic/restic/internal/test" rtest "github.com/restic/restic/internal/test"
"github.com/restic/restic/internal/ui"
) )
func TestCheckRestoreNoLock(t *testing.T) { func TestCheckRestoreNoLock(t *testing.T) {
@@ -87,15 +86,15 @@ func TestListOnce(t *testing.T) {
createPrunableRepo(t, env) createPrunableRepo(t, env)
testRunPrune(t, env.gopts, pruneOpts) testRunPrune(t, env.gopts, pruneOpts)
rtest.OK(t, withTermStatus(env.gopts, func(ctx context.Context, term ui.Terminal) error { rtest.OK(t, withTermStatus(env.gopts, func(ctx context.Context, gopts GlobalOptions) error {
_, err := runCheck(context.TODO(), checkOpts, env.gopts, nil, term) _, err := runCheck(context.TODO(), checkOpts, gopts, nil, gopts.term)
return err return err
})) }))
rtest.OK(t, withTermStatus(env.gopts, func(ctx context.Context, term ui.Terminal) error { rtest.OK(t, withTermStatus(env.gopts, func(ctx context.Context, gopts GlobalOptions) error {
return runRebuildIndex(context.TODO(), RepairIndexOptions{}, env.gopts, term) return runRebuildIndex(context.TODO(), RepairIndexOptions{}, gopts, gopts.term)
})) }))
rtest.OK(t, withTermStatus(env.gopts, func(ctx context.Context, term ui.Terminal) error { rtest.OK(t, withTermStatus(env.gopts, func(ctx context.Context, gopts GlobalOptions) error {
return runRebuildIndex(context.TODO(), RepairIndexOptions{ReadAllPacks: true}, env.gopts, term) return runRebuildIndex(context.TODO(), RepairIndexOptions{ReadAllPacks: true}, gopts, gopts.term)
})) }))
} }
@@ -162,9 +161,9 @@ func TestFindListOnce(t *testing.T) {
thirdSnapshot := restic.NewIDSet(testListSnapshots(t, env.gopts, 3)...) thirdSnapshot := restic.NewIDSet(testListSnapshots(t, env.gopts, 3)...)
var snapshotIDs restic.IDSet var snapshotIDs restic.IDSet
rtest.OK(t, withTermStatus(env.gopts, func(ctx context.Context, term ui.Terminal) error { rtest.OK(t, withTermStatus(env.gopts, func(ctx context.Context, gopts GlobalOptions) error {
printer := newTerminalProgressPrinter(env.gopts.JSON, env.gopts.verbosity, term) printer := newTerminalProgressPrinter(gopts.JSON, gopts.verbosity, gopts.term)
ctx, repo, unlock, err := openWithReadLock(ctx, env.gopts, false, printer) ctx, repo, unlock, err := openWithReadLock(ctx, gopts, false, printer)
rtest.OK(t, err) rtest.OK(t, err)
defer unlock() defer unlock()

View File

@@ -19,6 +19,7 @@ import (
"github.com/restic/restic/internal/feature" "github.com/restic/restic/internal/feature"
"github.com/restic/restic/internal/repository" "github.com/restic/restic/internal/repository"
"github.com/restic/restic/internal/restic" "github.com/restic/restic/internal/restic"
"github.com/restic/restic/internal/ui/termstatus"
) )
func init() { func init() {
@@ -31,7 +32,7 @@ var ErrOK = errors.New("ok")
var cmdGroupDefault = "default" var cmdGroupDefault = "default"
var cmdGroupAdvanced = "advanced" var cmdGroupAdvanced = "advanced"
func newRootCommand() *cobra.Command { func newRootCommand(globalOptions *GlobalOptions) *cobra.Command {
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "restic", Use: "restic",
Short: "Backup and restore files", Short: "Backup and restore files",
@@ -66,40 +67,41 @@ The full documentation can be found at https://restic.readthedocs.io/ .
// Use our "generate" command instead of the cobra provided "completion" command // Use our "generate" command instead of the cobra provided "completion" command
cmd.CompletionOptions.DisableDefaultCmd = true cmd.CompletionOptions.DisableDefaultCmd = true
// globalOptions is passed to commands by reference to allow PersistentPreRunE to modify it
cmd.AddCommand( cmd.AddCommand(
newBackupCommand(), newBackupCommand(globalOptions),
newCacheCommand(), newCacheCommand(globalOptions),
newCatCommand(), newCatCommand(globalOptions),
newCheckCommand(), newCheckCommand(globalOptions),
newCopyCommand(), newCopyCommand(globalOptions),
newDiffCommand(), newDiffCommand(globalOptions),
newDumpCommand(), newDumpCommand(globalOptions),
newFeaturesCommand(), newFeaturesCommand(globalOptions),
newFindCommand(), newFindCommand(globalOptions),
newForgetCommand(), newForgetCommand(globalOptions),
newGenerateCommand(), newGenerateCommand(globalOptions),
newInitCommand(), newInitCommand(globalOptions),
newKeyCommand(), newKeyCommand(globalOptions),
newListCommand(), newListCommand(globalOptions),
newLsCommand(), newLsCommand(globalOptions),
newMigrateCommand(), newMigrateCommand(globalOptions),
newOptionsCommand(), newOptionsCommand(globalOptions),
newPruneCommand(), newPruneCommand(globalOptions),
newRebuildIndexCommand(), newRebuildIndexCommand(globalOptions),
newRecoverCommand(), newRecoverCommand(globalOptions),
newRepairCommand(), newRepairCommand(globalOptions),
newRestoreCommand(), newRestoreCommand(globalOptions),
newRewriteCommand(), newRewriteCommand(globalOptions),
newSnapshotsCommand(), newSnapshotsCommand(globalOptions),
newStatsCommand(), newStatsCommand(globalOptions),
newTagCommand(), newTagCommand(globalOptions),
newUnlockCommand(), newUnlockCommand(globalOptions),
newVersionCommand(), newVersionCommand(globalOptions),
) )
registerDebugCommand(cmd) registerDebugCommand(cmd, globalOptions)
registerMountCommand(cmd) registerMountCommand(cmd, globalOptions)
registerSelfUpdateCommand(cmd) registerSelfUpdateCommand(cmd, globalOptions)
registerProfiling(cmd, os.Stderr) registerProfiling(cmd, os.Stderr)
return cmd 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 { if globalOptions.JSON {
type jsonExitError struct { type jsonExitError struct {
MessageType string `json:"message_type"` // exit_error MessageType string `json:"message_type"` // exit_error
@@ -139,7 +141,7 @@ func printExitError(code int, message string) {
Message: message, Message: message,
} }
err := json.NewEncoder(globalOptions.stderr).Encode(jsonS) err := json.NewEncoder(os.Stderr).Encode(jsonS)
if err != nil { if err != nil {
// ignore error as there's no good way to handle it // ignore error as there's no good way to handle it
_, _ = fmt.Fprintf(os.Stderr, "JSON encode failed: %v\n", err) _, _ = fmt.Fprintf(os.Stderr, "JSON encode failed: %v\n", err)
@@ -147,7 +149,7 @@ func printExitError(code int, message string) {
return return
} }
} else { } else {
_, _ = fmt.Fprintf(globalOptions.stderr, "%v\n", message) _, _ = fmt.Fprintf(os.Stderr, "%v\n", message)
} }
} }
@@ -170,16 +172,26 @@ func main() {
debug.Log("restic %s compiled with %v on %v/%v", debug.Log("restic %s compiled with %v on %v/%v",
version, runtime.Version(), runtime.GOOS, runtime.GOARCH) version, runtime.Version(), runtime.GOOS, runtime.GOARCH)
ctx := createGlobalContext() globalOptions := GlobalOptions{
err = newRootCommand().ExecuteContext(ctx) stdout: os.Stdout,
stderr: os.Stderr,
switch err { backends: collectBackends(),
case nil:
err = ctx.Err()
case ErrOK:
// ErrOK overwrites context cancellation errors
err = nil
} }
func() {
term, cancel := termstatus.Setup(os.Stdout, os.Stderr, globalOptions.Quiet)
defer cancel()
globalOptions.stdout, globalOptions.stderr = termstatus.WrapStdio(term)
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 var exitMessage string
switch { switch {
@@ -224,7 +236,7 @@ func main() {
} }
if exitCode != 0 { if exitCode != 0 {
printExitError(exitCode, exitMessage) printExitError(globalOptions, exitCode, exitMessage)
} }
Exit(exitCode) Exit(exitCode)
} }

View File

@@ -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()
}
}

View File

@@ -5,6 +5,7 @@ import (
"fmt" "fmt"
"io" "io"
"strings" "strings"
"sync"
"github.com/restic/restic/internal/terminal" "github.com/restic/restic/internal/terminal"
"github.com/restic/restic/internal/ui" "github.com/restic/restic/internal/ui"
@@ -46,6 +47,34 @@ type fder interface {
Fd() uintptr Fd() uintptr
} }
// Setup creates a new termstatus.
// The returned function must be called to shut down the termstatus,
//
// Expected usage:
// ```
// term, cancel := termstatus.Setup(os.stdout, os.stderr, false)
// defer cancel()
// // do stuff
// ```
func Setup(stdout, stderr io.Writer, quiet bool) (*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 := New(stdout, stderr, quiet)
wg.Add(1)
go func() {
defer wg.Done()
term.Run(cancelCtx)
}()
return term, func() {
// shutdown termstatus
cancel()
wg.Wait()
}
}
// New returns a new Terminal for wr. A goroutine is started to update the // New returns a new Terminal for wr. A goroutine is started to update the
// terminal. It is terminated when ctx is cancelled. When wr is redirected to // terminal. It is terminated when ctx is cancelled. When wr is redirected to
// a file (e.g. via shell output redirection) or is just an io.Writer (not the // a file (e.g. via shell output redirection) or is just an io.Writer (not the