diff --git a/cmd/restic/cleanup.go b/cmd/restic/cleanup.go index b01029c0c..54a384f37 100644 --- a/cmd/restic/cleanup.go +++ b/cmd/restic/cleanup.go @@ -3,6 +3,7 @@ package main import ( "context" "fmt" + "io" "os" "os/signal" "syscall" @@ -10,27 +11,27 @@ import ( "github.com/restic/restic/internal/debug" ) -func createGlobalContext() context.Context { +func createGlobalContext(stderr io.Writer) context.Context { ctx, cancel := context.WithCancel(context.Background()) ch := make(chan os.Signal, 1) - go cleanupHandler(ch, cancel) + go cleanupHandler(ch, cancel, stderr) signal.Notify(ch, syscall.SIGINT, syscall.SIGTERM) return ctx } // cleanupHandler handles the SIGINT and SIGTERM signals. -func cleanupHandler(c <-chan os.Signal, cancel context.CancelFunc) { +func cleanupHandler(c <-chan os.Signal, cancel context.CancelFunc, stderr io.Writer) { s := <-c debug.Log("signal %v received, cleaning up", s) // ignore error as there's no good way to handle it - _, _ = fmt.Fprintf(os.Stderr, "\rsignal %v received, cleaning up \n", s) + _, _ = fmt.Fprintf(stderr, "\rsignal %v received, cleaning up \n", s) if val, _ := os.LookupEnv("RESTIC_DEBUG_STACKTRACE_SIGINT"); val != "" { - _, _ = os.Stderr.WriteString("\n--- STACKTRACE START ---\n\n") - _, _ = os.Stderr.WriteString(debug.DumpStacktrace()) - _, _ = os.Stderr.WriteString("\n--- STACKTRACE END ---\n") + _, _ = stderr.Write([]byte("\n--- STACKTRACE START ---\n\n")) + _, _ = stderr.Write([]byte(debug.DumpStacktrace())) + _, _ = stderr.Write([]byte("\n--- STACKTRACE END ---\n")) } cancel() diff --git a/cmd/restic/cmd_backup.go b/cmd/restic/cmd_backup.go index 09dbab183..e62e0d1c2 100644 --- a/cmd/restic/cmd_backup.go +++ b/cmd/restic/cmd_backup.go @@ -30,7 +30,7 @@ import ( "github.com/restic/restic/internal/ui/backup" ) -func newBackupCommand() *cobra.Command { +func newBackupCommand(globalOptions *GlobalOptions) *cobra.Command { var opts BackupOptions cmd := &cobra.Command{ @@ -63,9 +63,7 @@ Exit status is 12 if the password is incorrect. GroupID: cmdGroupDefault, DisableAutoGenTag: true, RunE: func(cmd *cobra.Command, args []string) error { - term, cancel := setupTermstatus() - defer cancel() - return runBackup(cmd.Context(), opts, globalOptions, term, args) + return runBackup(cmd.Context(), opts, *globalOptions, globalOptions.term, args) }, } diff --git a/cmd/restic/cmd_backup_integration_test.go b/cmd/restic/cmd_backup_integration_test.go index ff4998991..1c59db852 100644 --- a/cmd/restic/cmd_backup_integration_test.go +++ b/cmd/restic/cmd_backup_integration_test.go @@ -13,11 +13,10 @@ import ( "github.com/restic/restic/internal/fs" "github.com/restic/restic/internal/restic" rtest "github.com/restic/restic/internal/test" - "github.com/restic/restic/internal/ui" ) func testRunBackupAssumeFailure(t testing.TB, dir string, target []string, opts BackupOptions, gopts GlobalOptions) error { - return withTermStatus(gopts, func(ctx context.Context, term ui.Terminal) error { + return withTermStatus(gopts, func(ctx context.Context, gopts GlobalOptions) error { t.Logf("backing up %v in %v", target, dir) if dir != "" { cleanup := rtest.Chdir(t, dir) @@ -25,7 +24,7 @@ func testRunBackupAssumeFailure(t testing.TB, dir string, target []string, opts } opts.GroupBy = restic.SnapshotGroupByOptions{Host: true, Path: true} - return runBackup(ctx, opts, gopts, term, target) + return runBackup(ctx, opts, gopts, gopts.term, target) }) } diff --git a/cmd/restic/cmd_cache.go b/cmd/restic/cmd_cache.go index f3f82954d..d61992f43 100644 --- a/cmd/restic/cmd_cache.go +++ b/cmd/restic/cmd_cache.go @@ -16,7 +16,7 @@ import ( "github.com/spf13/pflag" ) -func newCacheCommand() *cobra.Command { +func newCacheCommand(globalOptions *GlobalOptions) *cobra.Command { var opts CacheOptions cmd := &cobra.Command{ @@ -34,9 +34,7 @@ Exit status is 1 if there was any error. GroupID: cmdGroupDefault, DisableAutoGenTag: true, RunE: func(_ *cobra.Command, args []string) error { - term, cancel := setupTermstatus() - defer cancel() - return runCache(opts, globalOptions, args, term) + return runCache(opts, *globalOptions, args, globalOptions.term) }, } diff --git a/cmd/restic/cmd_cat.go b/cmd/restic/cmd_cat.go index 1cbb54ef4..cca356740 100644 --- a/cmd/restic/cmd_cat.go +++ b/cmd/restic/cmd_cat.go @@ -15,7 +15,7 @@ import ( var catAllowedCmds = []string{"config", "index", "snapshot", "key", "masterkey", "lock", "pack", "blob", "tree"} -func newCatCommand() *cobra.Command { +func newCatCommand(globalOptions *GlobalOptions) *cobra.Command { cmd := &cobra.Command{ Use: "cat [flags] [masterkey|config|pack ID|blob ID|snapshot ID|index ID|key ID|lock ID|tree snapshot:subfolder]", Short: "Print internal objects to stdout", @@ -34,9 +34,7 @@ Exit status is 12 if the password is incorrect. GroupID: cmdGroupDefault, DisableAutoGenTag: true, RunE: func(cmd *cobra.Command, args []string) error { - term, cancel := setupTermstatus() - defer cancel() - return runCat(cmd.Context(), globalOptions, args, term) + return runCat(cmd.Context(), *globalOptions, args, globalOptions.term) }, ValidArgs: catAllowedCmds, } diff --git a/cmd/restic/cmd_check.go b/cmd/restic/cmd_check.go index 5fddcfc0c..04789dd4e 100644 --- a/cmd/restic/cmd_check.go +++ b/cmd/restic/cmd_check.go @@ -22,7 +22,7 @@ import ( "github.com/restic/restic/internal/ui/progress" ) -func newCheckCommand() *cobra.Command { +func newCheckCommand(globalOptions *GlobalOptions) *cobra.Command { var opts CheckOptions cmd := &cobra.Command{ Use: "check [flags]", @@ -46,14 +46,12 @@ Exit status is 12 if the password is incorrect. GroupID: cmdGroupDefault, DisableAutoGenTag: true, RunE: func(cmd *cobra.Command, args []string) error { - term, cancel := setupTermstatus() - defer cancel() - summary, err := runCheck(cmd.Context(), opts, globalOptions, args, term) + summary, err := runCheck(cmd.Context(), opts, *globalOptions, args, globalOptions.term) if globalOptions.JSON { if err != nil && summary.NumErrors == 0 { summary.NumErrors = 1 } - term.Print(ui.ToJSONString(summary)) + globalOptions.term.Print(ui.ToJSONString(summary)) } return err }, diff --git a/cmd/restic/cmd_check_integration_test.go b/cmd/restic/cmd_check_integration_test.go index 004f54b6b..b87bd1149 100644 --- a/cmd/restic/cmd_check_integration_test.go +++ b/cmd/restic/cmd_check_integration_test.go @@ -5,7 +5,6 @@ import ( "testing" rtest "github.com/restic/restic/internal/test" - "github.com/restic/restic/internal/ui" ) 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) { 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{ ReadData: true, CheckUnused: checkUnused, } - _, err := runCheck(context.TODO(), opts, gopts, nil, term) + _, err := runCheck(context.TODO(), opts, gopts, nil, gopts.term) return err }) }) diff --git a/cmd/restic/cmd_copy.go b/cmd/restic/cmd_copy.go index eaa3ce846..ade86668c 100644 --- a/cmd/restic/cmd_copy.go +++ b/cmd/restic/cmd_copy.go @@ -16,7 +16,7 @@ import ( "github.com/spf13/pflag" ) -func newCopyCommand() *cobra.Command { +func newCopyCommand(globalOptions *GlobalOptions) *cobra.Command { var opts CopyOptions cmd := &cobra.Command{ Use: "copy [flags] [snapshotID ...]", @@ -48,9 +48,7 @@ Exit status is 12 if the password is incorrect. GroupID: cmdGroupDefault, DisableAutoGenTag: true, RunE: func(cmd *cobra.Command, args []string) error { - term, cancel := setupTermstatus() - defer cancel() - return runCopy(cmd.Context(), opts, globalOptions, args, term) + return runCopy(cmd.Context(), opts, *globalOptions, args, globalOptions.term) }, } diff --git a/cmd/restic/cmd_copy_integration_test.go b/cmd/restic/cmd_copy_integration_test.go index 27f67bc73..c3b529a6c 100644 --- a/cmd/restic/cmd_copy_integration_test.go +++ b/cmd/restic/cmd_copy_integration_test.go @@ -7,7 +7,6 @@ import ( "testing" rtest "github.com/restic/restic/internal/test" - "github.com/restic/restic/internal/ui" ) func testRunCopy(t testing.TB, srcGopts GlobalOptions, dstGopts GlobalOptions) { @@ -23,8 +22,8 @@ func testRunCopy(t testing.TB, srcGopts GlobalOptions, dstGopts GlobalOptions) { }, } - rtest.OK(t, withTermStatus(gopts, func(ctx context.Context, term ui.Terminal) error { - return runCopy(context.TODO(), copyOpts, gopts, nil, term) + rtest.OK(t, withTermStatus(gopts, func(ctx context.Context, gopts GlobalOptions) error { + return runCopy(context.TODO(), copyOpts, gopts, nil, gopts.term) })) } diff --git a/cmd/restic/cmd_debug.go b/cmd/restic/cmd_debug.go index a41a0d087..27041cf57 100644 --- a/cmd/restic/cmd_debug.go +++ b/cmd/restic/cmd_debug.go @@ -31,25 +31,25 @@ import ( "github.com/restic/restic/internal/ui/progress" ) -func registerDebugCommand(cmd *cobra.Command) { +func registerDebugCommand(cmd *cobra.Command, globalOptions *GlobalOptions) { cmd.AddCommand( - newDebugCommand(), + newDebugCommand(globalOptions), ) } -func newDebugCommand() *cobra.Command { +func newDebugCommand(globalOptions *GlobalOptions) *cobra.Command { cmd := &cobra.Command{ Use: "debug", Short: "Debug commands", GroupID: cmdGroupDefault, DisableAutoGenTag: true, } - cmd.AddCommand(newDebugDumpCommand()) - cmd.AddCommand(newDebugExamineCommand()) + cmd.AddCommand(newDebugDumpCommand(globalOptions)) + cmd.AddCommand(newDebugExamineCommand(globalOptions)) return cmd } -func newDebugDumpCommand() *cobra.Command { +func newDebugDumpCommand(globalOptions *GlobalOptions) *cobra.Command { cmd := &cobra.Command{ Use: "dump [indexes|snapshots|all|packs]", Short: "Dump data structures", @@ -68,15 +68,13 @@ Exit status is 12 if the password is incorrect. `, DisableAutoGenTag: true, RunE: func(cmd *cobra.Command, args []string) error { - term, cancel := setupTermstatus() - defer cancel() - return runDebugDump(cmd.Context(), globalOptions, args, term) + return runDebugDump(cmd.Context(), *globalOptions, args, globalOptions.term) }, } return cmd } -func newDebugExamineCommand() *cobra.Command { +func newDebugExamineCommand(globalOptions *GlobalOptions) *cobra.Command { var opts DebugExamineOptions cmd := &cobra.Command{ @@ -84,9 +82,7 @@ func newDebugExamineCommand() *cobra.Command { Short: "Examine a pack file", DisableAutoGenTag: true, RunE: func(cmd *cobra.Command, args []string) error { - term, cancel := setupTermstatus() - defer cancel() - return runDebugExamine(cmd.Context(), globalOptions, opts, args, term) + return runDebugExamine(cmd.Context(), *globalOptions, opts, args, globalOptions.term) }, } diff --git a/cmd/restic/cmd_debug_disabled.go b/cmd/restic/cmd_debug_disabled.go index 34d06a467..ba794925c 100644 --- a/cmd/restic/cmd_debug_disabled.go +++ b/cmd/restic/cmd_debug_disabled.go @@ -4,6 +4,6 @@ package main import "github.com/spf13/cobra" -func registerDebugCommand(_ *cobra.Command) { +func registerDebugCommand(_ *cobra.Command, _ *GlobalOptions) { // No commands to register in non-debug mode } diff --git a/cmd/restic/cmd_diff.go b/cmd/restic/cmd_diff.go index 7650a225f..0b9a4ad2a 100644 --- a/cmd/restic/cmd_diff.go +++ b/cmd/restic/cmd_diff.go @@ -15,7 +15,7 @@ import ( "github.com/spf13/pflag" ) -func newDiffCommand() *cobra.Command { +func newDiffCommand(globalOptions *GlobalOptions) *cobra.Command { var opts DiffOptions cmd := &cobra.Command{ @@ -52,9 +52,7 @@ Exit status is 12 if the password is incorrect. GroupID: cmdGroupDefault, DisableAutoGenTag: true, RunE: func(cmd *cobra.Command, args []string) error { - term, cancel := setupTermstatus() - defer cancel() - return runDiff(cmd.Context(), opts, globalOptions, args, term) + return runDiff(cmd.Context(), opts, *globalOptions, args, globalOptions.term) }, } diff --git a/cmd/restic/cmd_diff_integration_test.go b/cmd/restic/cmd_diff_integration_test.go index ee20671c4..14cd33d6d 100644 --- a/cmd/restic/cmd_diff_integration_test.go +++ b/cmd/restic/cmd_diff_integration_test.go @@ -12,7 +12,6 @@ import ( "testing" rtest "github.com/restic/restic/internal/test" - "github.com/restic/restic/internal/ui" ) func testRunDiffOutput(gopts GlobalOptions, firstSnapshotID string, secondSnapshotID string) (string, error) { @@ -20,8 +19,8 @@ func testRunDiffOutput(gopts GlobalOptions, firstSnapshotID string, secondSnapsh opts := DiffOptions{ ShowMetadata: false, } - return withTermStatus(gopts, func(ctx context.Context, term ui.Terminal) error { - return runDiff(ctx, opts, gopts, []string{firstSnapshotID, secondSnapshotID}, term) + return withTermStatus(gopts, func(ctx context.Context, gopts GlobalOptions) error { + return runDiff(ctx, opts, gopts, []string{firstSnapshotID, secondSnapshotID}, gopts.term) }) }) return buf.String(), err diff --git a/cmd/restic/cmd_dump.go b/cmd/restic/cmd_dump.go index 2cd15c06f..522e4a65d 100644 --- a/cmd/restic/cmd_dump.go +++ b/cmd/restic/cmd_dump.go @@ -17,7 +17,7 @@ import ( "github.com/spf13/pflag" ) -func newDumpCommand() *cobra.Command { +func newDumpCommand(globalOptions *GlobalOptions) *cobra.Command { var opts DumpOptions cmd := &cobra.Command{ Use: "dump [flags] snapshotID file", @@ -47,9 +47,7 @@ Exit status is 12 if the password is incorrect. GroupID: cmdGroupDefault, DisableAutoGenTag: true, RunE: func(cmd *cobra.Command, args []string) error { - term, cancel := setupTermstatus() - defer cancel() - return runDump(cmd.Context(), opts, globalOptions, args, term) + return runDump(cmd.Context(), opts, *globalOptions, args, globalOptions.term) }, } diff --git a/cmd/restic/cmd_features.go b/cmd/restic/cmd_features.go index 7770e6a9f..8541b1c34 100644 --- a/cmd/restic/cmd_features.go +++ b/cmd/restic/cmd_features.go @@ -10,7 +10,7 @@ import ( "github.com/spf13/cobra" ) -func newFeaturesCommand() *cobra.Command { +func newFeaturesCommand(globalOptions *GlobalOptions) *cobra.Command { cmd := &cobra.Command{ Use: "features", Short: "Print list of feature flags", diff --git a/cmd/restic/cmd_find.go b/cmd/restic/cmd_find.go index d7aa3665d..a1ad9668f 100644 --- a/cmd/restic/cmd_find.go +++ b/cmd/restic/cmd_find.go @@ -20,7 +20,7 @@ import ( "github.com/restic/restic/internal/walker" ) -func newFindCommand() *cobra.Command { +func newFindCommand(globalOptions *GlobalOptions) *cobra.Command { var opts FindOptions cmd := &cobra.Command{ @@ -51,9 +51,7 @@ Exit status is 12 if the password is incorrect. GroupID: cmdGroupDefault, DisableAutoGenTag: true, RunE: func(cmd *cobra.Command, args []string) error { - term, cancel := setupTermstatus() - defer cancel() - return runFind(cmd.Context(), opts, globalOptions, args, term) + return runFind(cmd.Context(), opts, *globalOptions, args, globalOptions.term) }, } diff --git a/cmd/restic/cmd_find_integration_test.go b/cmd/restic/cmd_find_integration_test.go index ad34923ed..834228664 100644 --- a/cmd/restic/cmd_find_integration_test.go +++ b/cmd/restic/cmd_find_integration_test.go @@ -8,15 +8,14 @@ import ( "time" rtest "github.com/restic/restic/internal/test" - "github.com/restic/restic/internal/ui" ) func testRunFind(t testing.TB, wantJSON bool, opts FindOptions, gopts GlobalOptions, pattern string) []byte { buf, err := withCaptureStdout(gopts, func(gopts GlobalOptions) error { gopts.JSON = wantJSON - return withTermStatus(gopts, func(ctx context.Context, term ui.Terminal) error { - return runFind(ctx, opts, gopts, []string{pattern}, term) + return withTermStatus(gopts, func(ctx context.Context, gopts GlobalOptions) error { + return runFind(ctx, opts, gopts, []string{pattern}, gopts.term) }) }) rtest.OK(t, err) diff --git a/cmd/restic/cmd_forget.go b/cmd/restic/cmd_forget.go index 42739cdf0..7a9a8105a 100644 --- a/cmd/restic/cmd_forget.go +++ b/cmd/restic/cmd_forget.go @@ -14,7 +14,7 @@ import ( "github.com/spf13/pflag" ) -func newForgetCommand() *cobra.Command { +func newForgetCommand(globalOptions *GlobalOptions) *cobra.Command { var opts ForgetOptions var pruneOpts PruneOptions @@ -49,9 +49,7 @@ Exit status is 12 if the password is incorrect. GroupID: cmdGroupDefault, DisableAutoGenTag: true, RunE: func(cmd *cobra.Command, args []string) error { - term, cancel := setupTermstatus() - defer cancel() - return runForget(cmd.Context(), opts, pruneOpts, globalOptions, term, args) + return runForget(cmd.Context(), opts, pruneOpts, *globalOptions, globalOptions.term, args) }, } diff --git a/cmd/restic/cmd_forget_integration_test.go b/cmd/restic/cmd_forget_integration_test.go index d3be8a60d..0a110cc70 100644 --- a/cmd/restic/cmd_forget_integration_test.go +++ b/cmd/restic/cmd_forget_integration_test.go @@ -8,15 +8,14 @@ import ( "github.com/restic/restic/internal/restic" rtest "github.com/restic/restic/internal/test" - "github.com/restic/restic/internal/ui" ) func testRunForgetMayFail(gopts GlobalOptions, opts ForgetOptions, args ...string) error { pruneOpts := PruneOptions{ MaxUnused: "5%", } - return withTermStatus(gopts, func(ctx context.Context, term ui.Terminal) error { - return runForget(context.TODO(), opts, pruneOpts, gopts, term, args) + return withTermStatus(gopts, func(ctx context.Context, gopts GlobalOptions) error { + return runForget(context.TODO(), opts, pruneOpts, gopts, gopts.term, args) }) } diff --git a/cmd/restic/cmd_generate.go b/cmd/restic/cmd_generate.go index 477910507..e2fdf7fc3 100644 --- a/cmd/restic/cmd_generate.go +++ b/cmd/restic/cmd_generate.go @@ -13,7 +13,7 @@ import ( "github.com/spf13/pflag" ) -func newGenerateCommand() *cobra.Command { +func newGenerateCommand(globalOptions *GlobalOptions) *cobra.Command { var opts generateOptions cmd := &cobra.Command{ @@ -31,9 +31,7 @@ Exit status is 1 if there was any error. `, DisableAutoGenTag: true, RunE: func(_ *cobra.Command, args []string) error { - term, cancel := setupTermstatus() - defer cancel() - return runGenerate(opts, globalOptions, args, term) + return runGenerate(opts, *globalOptions, args, globalOptions.term) }, } opts.AddFlags(cmd.Flags()) @@ -118,7 +116,7 @@ func runGenerate(opts generateOptions, gopts GlobalOptions, args []string, term } printer := newTerminalProgressPrinter(gopts.JSON, gopts.verbosity, term) - cmdRoot := newRootCommand() + cmdRoot := newRootCommand(&GlobalOptions{}) if opts.ManDir != "" { err := writeManpages(cmdRoot, opts.ManDir, printer) diff --git a/cmd/restic/cmd_generate_integration_test.go b/cmd/restic/cmd_generate_integration_test.go index 858e72453..c1354a5cb 100644 --- a/cmd/restic/cmd_generate_integration_test.go +++ b/cmd/restic/cmd_generate_integration_test.go @@ -6,13 +6,12 @@ import ( "testing" rtest "github.com/restic/restic/internal/test" - "github.com/restic/restic/internal/ui" ) func testRunGenerate(gopts GlobalOptions, opts generateOptions) ([]byte, error) { buf, err := withCaptureStdout(gopts, func(gopts GlobalOptions) error { - return withTermStatus(gopts, func(ctx context.Context, term ui.Terminal) error { - return runGenerate(opts, gopts, []string{}, term) + return withTermStatus(gopts, func(ctx context.Context, gopts GlobalOptions) error { + return runGenerate(opts, gopts, []string{}, gopts.term) }) }) return buf.Bytes(), err @@ -31,14 +30,14 @@ func TestGenerateStdout(t *testing.T) { for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { - output, err := testRunGenerate(globalOptions, tc.opts) + output, err := testRunGenerate(GlobalOptions{}, tc.opts) rtest.OK(t, err) rtest.Assert(t, strings.Contains(string(output), "# "+tc.name+" completion for restic"), "has no expected completion header") }) } t.Run("Generate shell completions to stdout for two shells", func(t *testing.T) { - _, err := testRunGenerate(globalOptions, generateOptions{BashCompletionFile: "-", FishCompletionFile: "-"}) + _, err := testRunGenerate(GlobalOptions{}, generateOptions{BashCompletionFile: "-", FishCompletionFile: "-"}) rtest.Assert(t, err != nil, "generate shell completions to stdout for two shells fails") }) } diff --git a/cmd/restic/cmd_init.go b/cmd/restic/cmd_init.go index 8e8488355..e358ffd8a 100644 --- a/cmd/restic/cmd_init.go +++ b/cmd/restic/cmd_init.go @@ -17,7 +17,7 @@ import ( "github.com/spf13/pflag" ) -func newInitCommand() *cobra.Command { +func newInitCommand(globalOptions *GlobalOptions) *cobra.Command { var opts InitOptions cmd := &cobra.Command{ @@ -35,9 +35,7 @@ Exit status is 1 if there was any error. GroupID: cmdGroupDefault, DisableAutoGenTag: true, RunE: func(cmd *cobra.Command, args []string) error { - term, cancel := setupTermstatus() - defer cancel() - return runInit(cmd.Context(), opts, globalOptions, args, term) + return runInit(cmd.Context(), opts, *globalOptions, args, globalOptions.term) }, } opts.AddFlags(cmd.Flags()) diff --git a/cmd/restic/cmd_init_integration_test.go b/cmd/restic/cmd_init_integration_test.go index 8ce14a23a..e5fba798a 100644 --- a/cmd/restic/cmd_init_integration_test.go +++ b/cmd/restic/cmd_init_integration_test.go @@ -9,7 +9,6 @@ import ( "github.com/restic/restic/internal/repository" "github.com/restic/restic/internal/restic" rtest "github.com/restic/restic/internal/test" - "github.com/restic/restic/internal/ui" "github.com/restic/restic/internal/ui/progress" ) @@ -18,8 +17,8 @@ func testRunInit(t testing.TB, opts GlobalOptions) { restic.TestDisableCheckPolynomial(t) restic.TestSetLockTimeout(t, 0) - err := withTermStatus(opts, func(ctx context.Context, term ui.Terminal) error { - return runInit(ctx, InitOptions{}, opts, nil, term) + err := withTermStatus(opts, func(ctx context.Context, gopts GlobalOptions) error { + return runInit(ctx, InitOptions{}, opts, nil, gopts.term) }) rtest.OK(t, err) t.Logf("repository initialized at %v", opts.Repo) @@ -44,14 +43,14 @@ func TestInitCopyChunkerParams(t *testing.T) { password: env2.gopts.password, }, } - err := withTermStatus(env.gopts, func(ctx context.Context, term ui.Terminal) error { - return runInit(ctx, initOpts, env.gopts, nil, term) + err := withTermStatus(env.gopts, func(ctx context.Context, gopts GlobalOptions) error { + return runInit(ctx, initOpts, gopts, nil, gopts.term) }) rtest.Assert(t, err != nil, "expected invalid init options to fail") initOpts.CopyChunkerParameters = true - err = withTermStatus(env.gopts, func(ctx context.Context, term ui.Terminal) error { - return runInit(ctx, initOpts, env.gopts, nil, term) + err = withTermStatus(env.gopts, func(ctx context.Context, gopts GlobalOptions) error { + return runInit(ctx, initOpts, gopts, nil, gopts.term) }) rtest.OK(t, err) diff --git a/cmd/restic/cmd_key.go b/cmd/restic/cmd_key.go index 29d38bdce..508b428e0 100644 --- a/cmd/restic/cmd_key.go +++ b/cmd/restic/cmd_key.go @@ -4,7 +4,7 @@ import ( "github.com/spf13/cobra" ) -func newKeyCommand() *cobra.Command { +func newKeyCommand(globalOptions *GlobalOptions) *cobra.Command { cmd := &cobra.Command{ Use: "key", Short: "Manage keys (passwords)", @@ -17,10 +17,10 @@ per repository. } cmd.AddCommand( - newKeyAddCommand(), - newKeyListCommand(), - newKeyPasswdCommand(), - newKeyRemoveCommand(), + newKeyAddCommand(globalOptions), + newKeyListCommand(globalOptions), + newKeyPasswdCommand(globalOptions), + newKeyRemoveCommand(globalOptions), ) return cmd } diff --git a/cmd/restic/cmd_key_add.go b/cmd/restic/cmd_key_add.go index 1c3fc04f6..28b91dfe3 100644 --- a/cmd/restic/cmd_key_add.go +++ b/cmd/restic/cmd_key_add.go @@ -12,7 +12,7 @@ import ( "github.com/spf13/pflag" ) -func newKeyAddCommand() *cobra.Command { +func newKeyAddCommand(globalOptions *GlobalOptions) *cobra.Command { var opts KeyAddOptions cmd := &cobra.Command{ @@ -32,9 +32,7 @@ Exit status is 12 if the password is incorrect. `, DisableAutoGenTag: true, RunE: func(cmd *cobra.Command, args []string) error { - term, cancel := setupTermstatus() - defer cancel() - return runKeyAdd(cmd.Context(), globalOptions, opts, args, term) + return runKeyAdd(cmd.Context(), *globalOptions, opts, args, globalOptions.term) }, } diff --git a/cmd/restic/cmd_key_integration_test.go b/cmd/restic/cmd_key_integration_test.go index d24e71b09..903fab07e 100644 --- a/cmd/restic/cmd_key_integration_test.go +++ b/cmd/restic/cmd_key_integration_test.go @@ -12,14 +12,13 @@ import ( "github.com/restic/restic/internal/backend" "github.com/restic/restic/internal/repository" rtest "github.com/restic/restic/internal/test" - "github.com/restic/restic/internal/ui" "github.com/restic/restic/internal/ui/progress" ) func testRunKeyListOtherIDs(t testing.TB, gopts GlobalOptions) []string { buf, err := withCaptureStdout(gopts, func(gopts GlobalOptions) error { - return withTermStatus(gopts, func(ctx context.Context, term ui.Terminal) error { - return runKeyList(ctx, gopts, []string{}, term) + return withTermStatus(gopts, func(ctx context.Context, gopts GlobalOptions) error { + return runKeyList(ctx, gopts, []string{}, gopts.term) }) }) rtest.OK(t, err) @@ -43,8 +42,8 @@ func testRunKeyAddNewKey(t testing.TB, newPassword string, gopts GlobalOptions) testKeyNewPassword = "" }() - err := withTermStatus(gopts, func(ctx context.Context, term ui.Terminal) error { - return runKeyAdd(ctx, gopts, KeyAddOptions{}, []string{}, term) + err := withTermStatus(gopts, func(ctx context.Context, gopts GlobalOptions) error { + return runKeyAdd(ctx, gopts, KeyAddOptions{}, []string{}, gopts.term) }) rtest.OK(t, err) } @@ -56,11 +55,11 @@ func testRunKeyAddNewKeyUserHost(t testing.TB, gopts GlobalOptions) { }() t.Log("adding key for john@example.com") - err := withTermStatus(gopts, func(ctx context.Context, term ui.Terminal) error { + err := withTermStatus(gopts, func(ctx context.Context, gopts GlobalOptions) error { return runKeyAdd(ctx, gopts, KeyAddOptions{ Username: "john", Hostname: "example.com", - }, []string{}, term) + }, []string{}, gopts.term) }) rtest.OK(t, err) @@ -79,8 +78,8 @@ func testRunKeyPasswd(t testing.TB, newPassword string, gopts GlobalOptions) { testKeyNewPassword = "" }() - err := withTermStatus(gopts, func(ctx context.Context, term ui.Terminal) error { - return runKeyPasswd(ctx, gopts, KeyPasswdOptions{}, []string{}, term) + err := withTermStatus(gopts, func(ctx context.Context, gopts GlobalOptions) error { + return runKeyPasswd(ctx, gopts, KeyPasswdOptions{}, []string{}, gopts.term) }) 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) { t.Logf("remove %d keys: %q\n", len(IDs), IDs) for _, id := range IDs { - err := withTermStatus(gopts, func(ctx context.Context, term ui.Terminal) error { - return runKeyRemove(ctx, gopts, []string{id}, term) + err := withTermStatus(gopts, func(ctx context.Context, gopts GlobalOptions) error { + return runKeyRemove(ctx, gopts, []string{id}, gopts.term) }) rtest.OK(t, err) } @@ -121,8 +120,8 @@ func TestKeyAddRemove(t *testing.T) { env.gopts.password = passwordList[len(passwordList)-1] t.Logf("testing access with last password %q\n", env.gopts.password) - err := withTermStatus(env.gopts, func(ctx context.Context, term ui.Terminal) error { - return runKeyList(ctx, env.gopts, []string{}, term) + err := withTermStatus(env.gopts, func(ctx context.Context, gopts GlobalOptions) error { + return runKeyList(ctx, gopts, []string{}, gopts.term) }) rtest.OK(t, err) testRunCheck(t, env.gopts) @@ -135,21 +134,21 @@ func TestKeyAddInvalid(t *testing.T) { defer cleanup() testRunInit(t, env.gopts) - err := withTermStatus(env.gopts, func(ctx context.Context, term ui.Terminal) error { - return runKeyAdd(ctx, env.gopts, KeyAddOptions{ + err := withTermStatus(env.gopts, func(ctx context.Context, gopts GlobalOptions) error { + return runKeyAdd(ctx, gopts, KeyAddOptions{ NewPasswordFile: "some-file", InsecureNoPassword: true, - }, []string{}, term) + }, []string{}, gopts.term) }) rtest.Assert(t, strings.Contains(err.Error(), "only either"), "unexpected error message, got %q", err) pwfile := filepath.Join(t.TempDir(), "pwfile") rtest.OK(t, os.WriteFile(pwfile, []byte{}, 0o666)) - err = withTermStatus(env.gopts, func(ctx context.Context, term ui.Terminal) error { - return runKeyAdd(ctx, env.gopts, KeyAddOptions{ + err = withTermStatus(env.gopts, func(ctx context.Context, gopts GlobalOptions) error { + return runKeyAdd(ctx, gopts, KeyAddOptions{ NewPasswordFile: pwfile, - }, []string{}, term) + }, []string{}, gopts.term) }) rtest.Assert(t, strings.Contains(err.Error(), "an empty password is not allowed by default"), "unexpected error message, got %q", err) } @@ -161,10 +160,10 @@ func TestKeyAddEmpty(t *testing.T) { defer cleanup() testRunInit(t, env.gopts) - err := withTermStatus(env.gopts, func(ctx context.Context, term ui.Terminal) error { - return runKeyAdd(ctx, env.gopts, KeyAddOptions{ + err := withTermStatus(env.gopts, func(ctx context.Context, gopts GlobalOptions) error { + return runKeyAdd(ctx, gopts, KeyAddOptions{ InsecureNoPassword: true, - }, []string{}, term) + }, []string{}, gopts.term) }) rtest.OK(t, err) @@ -196,21 +195,21 @@ func TestKeyProblems(t *testing.T) { testKeyNewPassword = "" }() - err := withTermStatus(env.gopts, func(ctx context.Context, term ui.Terminal) error { - return runKeyPasswd(ctx, env.gopts, KeyPasswdOptions{}, []string{}, term) + err := withTermStatus(env.gopts, func(ctx context.Context, gopts GlobalOptions) error { + return runKeyPasswd(ctx, gopts, KeyPasswdOptions{}, []string{}, gopts.term) }) t.Log(err) rtest.Assert(t, err != nil, "expected passwd change to fail") - err = withTermStatus(env.gopts, func(ctx context.Context, term ui.Terminal) error { - return runKeyAdd(ctx, env.gopts, KeyAddOptions{}, []string{}, term) + err = withTermStatus(env.gopts, func(ctx context.Context, gopts GlobalOptions) error { + return runKeyAdd(ctx, gopts, KeyAddOptions{}, []string{}, gopts.term) }) t.Log(err) rtest.Assert(t, err != nil, "expected key adding to fail") t.Logf("testing access with initial password %q\n", env.gopts.password) - err = withTermStatus(env.gopts, func(ctx context.Context, term ui.Terminal) error { - return runKeyList(ctx, env.gopts, []string{}, term) + err = withTermStatus(env.gopts, func(ctx context.Context, gopts GlobalOptions) error { + return runKeyList(ctx, gopts, []string{}, gopts.term) }) rtest.OK(t, err) testRunCheck(t, env.gopts) @@ -225,32 +224,32 @@ func TestKeyCommandInvalidArguments(t *testing.T) { return &emptySaveBackend{r}, nil } - err := withTermStatus(env.gopts, func(ctx context.Context, term ui.Terminal) error { - return runKeyAdd(ctx, env.gopts, KeyAddOptions{}, []string{"johndoe"}, term) + err := withTermStatus(env.gopts, func(ctx context.Context, gopts GlobalOptions) error { + return runKeyAdd(ctx, gopts, KeyAddOptions{}, []string{"johndoe"}, gopts.term) }) t.Log(err) rtest.Assert(t, err != nil && strings.Contains(err.Error(), "no arguments"), "unexpected error for key add: %v", err) - err = withTermStatus(env.gopts, func(ctx context.Context, term ui.Terminal) error { - return runKeyPasswd(ctx, env.gopts, KeyPasswdOptions{}, []string{"johndoe"}, term) + err = withTermStatus(env.gopts, func(ctx context.Context, gopts GlobalOptions) error { + return runKeyPasswd(ctx, gopts, KeyPasswdOptions{}, []string{"johndoe"}, gopts.term) }) t.Log(err) rtest.Assert(t, err != nil && strings.Contains(err.Error(), "no arguments"), "unexpected error for key passwd: %v", err) - err = withTermStatus(env.gopts, func(ctx context.Context, term ui.Terminal) error { - return runKeyList(ctx, env.gopts, []string{"johndoe"}, term) + err = withTermStatus(env.gopts, func(ctx context.Context, gopts GlobalOptions) error { + return runKeyList(ctx, gopts, []string{"johndoe"}, gopts.term) }) t.Log(err) rtest.Assert(t, err != nil && strings.Contains(err.Error(), "no arguments"), "unexpected error for key list: %v", err) - err = withTermStatus(env.gopts, func(ctx context.Context, term ui.Terminal) error { - return runKeyRemove(ctx, env.gopts, []string{}, term) + err = withTermStatus(env.gopts, func(ctx context.Context, gopts GlobalOptions) error { + return runKeyRemove(ctx, gopts, []string{}, gopts.term) }) t.Log(err) rtest.Assert(t, err != nil && strings.Contains(err.Error(), "one argument"), "unexpected error for key remove: %v", err) - err = withTermStatus(env.gopts, func(ctx context.Context, term ui.Terminal) error { - return runKeyRemove(ctx, env.gopts, []string{"john", "doe"}, term) + err = withTermStatus(env.gopts, func(ctx context.Context, gopts GlobalOptions) error { + return runKeyRemove(ctx, gopts, []string{"john", "doe"}, gopts.term) }) t.Log(err) rtest.Assert(t, err != nil && strings.Contains(err.Error(), "one argument"), "unexpected error for key remove: %v", err) diff --git a/cmd/restic/cmd_key_list.go b/cmd/restic/cmd_key_list.go index bdc0a7f82..21eee0c79 100644 --- a/cmd/restic/cmd_key_list.go +++ b/cmd/restic/cmd_key_list.go @@ -14,7 +14,7 @@ import ( "github.com/spf13/cobra" ) -func newKeyListCommand() *cobra.Command { +func newKeyListCommand(globalOptions *GlobalOptions) *cobra.Command { cmd := &cobra.Command{ Use: "list", Short: "List keys (passwords)", @@ -34,9 +34,7 @@ Exit status is 12 if the password is incorrect. `, DisableAutoGenTag: true, RunE: func(cmd *cobra.Command, args []string) error { - term, cancel := setupTermstatus() - defer cancel() - return runKeyList(cmd.Context(), globalOptions, args, term) + return runKeyList(cmd.Context(), *globalOptions, args, globalOptions.term) }, } return cmd diff --git a/cmd/restic/cmd_key_passwd.go b/cmd/restic/cmd_key_passwd.go index 798a9fbaa..97c782989 100644 --- a/cmd/restic/cmd_key_passwd.go +++ b/cmd/restic/cmd_key_passwd.go @@ -12,7 +12,7 @@ import ( "github.com/spf13/pflag" ) -func newKeyPasswdCommand() *cobra.Command { +func newKeyPasswdCommand(globalOptions *GlobalOptions) *cobra.Command { var opts KeyPasswdOptions cmd := &cobra.Command{ @@ -33,9 +33,7 @@ Exit status is 12 if the password is incorrect. `, DisableAutoGenTag: true, RunE: func(cmd *cobra.Command, args []string) error { - term, cancel := setupTermstatus() - defer cancel() - return runKeyPasswd(cmd.Context(), globalOptions, opts, args, term) + return runKeyPasswd(cmd.Context(), *globalOptions, opts, args, globalOptions.term) }, } diff --git a/cmd/restic/cmd_key_remove.go b/cmd/restic/cmd_key_remove.go index 5adb97d80..0e0c9704a 100644 --- a/cmd/restic/cmd_key_remove.go +++ b/cmd/restic/cmd_key_remove.go @@ -12,7 +12,7 @@ import ( "github.com/spf13/cobra" ) -func newKeyRemoveCommand() *cobra.Command { +func newKeyRemoveCommand(globalOptions *GlobalOptions) *cobra.Command { cmd := &cobra.Command{ Use: "remove [ID]", Short: "Remove key ID (password) from the repository.", @@ -31,9 +31,7 @@ Exit status is 12 if the password is incorrect. `, DisableAutoGenTag: true, RunE: func(cmd *cobra.Command, args []string) error { - term, cancel := setupTermstatus() - defer cancel() - return runKeyRemove(cmd.Context(), globalOptions, args, term) + return runKeyRemove(cmd.Context(), *globalOptions, args, globalOptions.term) }, } return cmd diff --git a/cmd/restic/cmd_list.go b/cmd/restic/cmd_list.go index fc425f07d..2cbfa5e72 100644 --- a/cmd/restic/cmd_list.go +++ b/cmd/restic/cmd_list.go @@ -12,7 +12,7 @@ import ( "github.com/spf13/cobra" ) -func newListCommand() *cobra.Command { +func newListCommand(globalOptions *GlobalOptions) *cobra.Command { var listAllowedArgs = []string{"blobs", "packs", "index", "snapshots", "keys", "locks"} var listAllowedArgsUseString = strings.Join(listAllowedArgs, "|") @@ -34,9 +34,7 @@ Exit status is 12 if the password is incorrect. DisableAutoGenTag: true, GroupID: cmdGroupDefault, RunE: func(cmd *cobra.Command, args []string) error { - term, cancel := setupTermstatus() - defer cancel() - return runList(cmd.Context(), globalOptions, args, term) + return runList(cmd.Context(), *globalOptions, args, globalOptions.term) }, ValidArgs: listAllowedArgs, Args: cobra.MatchAll(cobra.ExactArgs(1), cobra.OnlyValidArgs), diff --git a/cmd/restic/cmd_list_integration_test.go b/cmd/restic/cmd_list_integration_test.go index 69fef1d6b..412bd3a2a 100644 --- a/cmd/restic/cmd_list_integration_test.go +++ b/cmd/restic/cmd_list_integration_test.go @@ -8,13 +8,12 @@ import ( "github.com/restic/restic/internal/restic" rtest "github.com/restic/restic/internal/test" - "github.com/restic/restic/internal/ui" ) func testRunList(t testing.TB, opts GlobalOptions, tpe string) restic.IDs { buf, err := withCaptureStdout(opts, func(opts GlobalOptions) error { - return withTermStatus(opts, func(ctx context.Context, term ui.Terminal) error { - return runList(ctx, opts, []string{tpe}, term) + return withTermStatus(opts, func(ctx context.Context, gopts GlobalOptions) error { + return runList(ctx, opts, []string{tpe}, gopts.term) }) }) rtest.OK(t, err) diff --git a/cmd/restic/cmd_ls.go b/cmd/restic/cmd_ls.go index b56f0b6e3..56bb0f9b6 100644 --- a/cmd/restic/cmd_ls.go +++ b/cmd/restic/cmd_ls.go @@ -22,7 +22,7 @@ import ( "github.com/restic/restic/internal/walker" ) -func newLsCommand() *cobra.Command { +func newLsCommand(globalOptions *GlobalOptions) *cobra.Command { var opts LsOptions cmd := &cobra.Command{ @@ -60,9 +60,7 @@ Exit status is 12 if the password is incorrect. DisableAutoGenTag: true, GroupID: cmdGroupDefault, RunE: func(cmd *cobra.Command, args []string) error { - term, cancel := setupTermstatus() - defer cancel() - return runLs(cmd.Context(), opts, globalOptions, args, term) + return runLs(cmd.Context(), opts, *globalOptions, args, globalOptions.term) }, } opts.AddFlags(cmd.Flags()) diff --git a/cmd/restic/cmd_ls_integration_test.go b/cmd/restic/cmd_ls_integration_test.go index b39e9e582..a4a54c081 100644 --- a/cmd/restic/cmd_ls_integration_test.go +++ b/cmd/restic/cmd_ls_integration_test.go @@ -10,14 +10,13 @@ import ( "github.com/restic/restic/internal/restic" rtest "github.com/restic/restic/internal/test" - "github.com/restic/restic/internal/ui" ) func testRunLsWithOpts(t testing.TB, gopts GlobalOptions, opts LsOptions, args []string) []byte { buf, err := withCaptureStdout(gopts, func(gopts GlobalOptions) error { gopts.Quiet = true - return withTermStatus(gopts, func(ctx context.Context, term ui.Terminal) error { - return runLs(context.TODO(), opts, gopts, args, term) + return withTermStatus(gopts, func(ctx context.Context, gopts GlobalOptions) error { + return runLs(context.TODO(), opts, gopts, args, gopts.term) }) }) rtest.OK(t, err) diff --git a/cmd/restic/cmd_migrate.go b/cmd/restic/cmd_migrate.go index 8e1d23c04..f7ac20f4f 100644 --- a/cmd/restic/cmd_migrate.go +++ b/cmd/restic/cmd_migrate.go @@ -12,7 +12,7 @@ import ( "github.com/spf13/pflag" ) -func newMigrateCommand() *cobra.Command { +func newMigrateCommand(globalOptions *GlobalOptions) *cobra.Command { var opts MigrateOptions cmd := &cobra.Command{ @@ -35,9 +35,7 @@ Exit status is 12 if the password is incorrect. DisableAutoGenTag: true, GroupID: cmdGroupDefault, RunE: func(cmd *cobra.Command, args []string) error { - term, cancel := setupTermstatus() - defer cancel() - return runMigrate(cmd.Context(), opts, globalOptions, args, term) + return runMigrate(cmd.Context(), opts, *globalOptions, args, globalOptions.term) }, } diff --git a/cmd/restic/cmd_mount.go b/cmd/restic/cmd_mount.go index a476422fd..6eb35f837 100644 --- a/cmd/restic/cmd_mount.go +++ b/cmd/restic/cmd_mount.go @@ -24,11 +24,11 @@ import ( "github.com/anacrolix/fuse/fs" ) -func registerMountCommand(cmdRoot *cobra.Command) { - cmdRoot.AddCommand(newMountCommand()) +func registerMountCommand(cmdRoot *cobra.Command, globalOptions *GlobalOptions) { + cmdRoot.AddCommand(newMountCommand(globalOptions)) } -func newMountCommand() *cobra.Command { +func newMountCommand(globalOptions *GlobalOptions) *cobra.Command { var opts MountOptions cmd := &cobra.Command{ @@ -82,9 +82,7 @@ Exit status is 12 if the password is incorrect. DisableAutoGenTag: true, GroupID: cmdGroupDefault, RunE: func(cmd *cobra.Command, args []string) error { - term, cancel := setupTermstatus() - defer cancel() - return runMount(cmd.Context(), opts, globalOptions, args, term) + return runMount(cmd.Context(), opts, *globalOptions, args, globalOptions.term) }, } diff --git a/cmd/restic/cmd_mount_disabled.go b/cmd/restic/cmd_mount_disabled.go index 8b83905e7..57a44940f 100644 --- a/cmd/restic/cmd_mount_disabled.go +++ b/cmd/restic/cmd_mount_disabled.go @@ -5,6 +5,6 @@ package main import "github.com/spf13/cobra" -func registerMountCommand(_ *cobra.Command) { +func registerMountCommand(_ *cobra.Command, _ *GlobalOptions) { // Mount command not supported on these platforms } diff --git a/cmd/restic/cmd_mount_integration_test.go b/cmd/restic/cmd_mount_integration_test.go index ea1451ba6..91c014234 100644 --- a/cmd/restic/cmd_mount_integration_test.go +++ b/cmd/restic/cmd_mount_integration_test.go @@ -16,7 +16,6 @@ import ( "github.com/restic/restic/internal/debug" "github.com/restic/restic/internal/restic" rtest "github.com/restic/restic/internal/test" - "github.com/restic/restic/internal/ui" ) const ( @@ -62,8 +61,8 @@ func testRunMount(t testing.TB, gopts GlobalOptions, dir string, wg *sync.WaitGr opts := MountOptions{ TimeTemplate: time.RFC3339, } - rtest.OK(t, withTermStatus(gopts, func(ctx context.Context, term ui.Terminal) error { - return runMount(context.TODO(), opts, gopts, []string{dir}, term) + rtest.OK(t, withTermStatus(gopts, func(ctx context.Context, gopts GlobalOptions) error { + 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 { - printer := newTerminalProgressPrinter(gopts.JSON, gopts.verbosity, term) + err := withTermStatus(gopts, func(ctx context.Context, gopts GlobalOptions) error { + printer := newTerminalProgressPrinter(gopts.JSON, gopts.verbosity, gopts.term) _, repo, unlock, err := openWithReadLock(ctx, gopts, false, printer) if err != nil { return err diff --git a/cmd/restic/cmd_options.go b/cmd/restic/cmd_options.go index 34cf8656e..86418c891 100644 --- a/cmd/restic/cmd_options.go +++ b/cmd/restic/cmd_options.go @@ -8,7 +8,7 @@ import ( "github.com/spf13/cobra" ) -func newOptionsCommand() *cobra.Command { +func newOptionsCommand(globalOptions *GlobalOptions) *cobra.Command { cmd := &cobra.Command{ Use: "options", Short: "Print list of extended options", diff --git a/cmd/restic/cmd_prune.go b/cmd/restic/cmd_prune.go index 3adc6a90e..71e96954b 100644 --- a/cmd/restic/cmd_prune.go +++ b/cmd/restic/cmd_prune.go @@ -18,7 +18,7 @@ import ( "github.com/spf13/pflag" ) -func newPruneCommand() *cobra.Command { +func newPruneCommand(globalOptions *GlobalOptions) *cobra.Command { var opts PruneOptions cmd := &cobra.Command{ @@ -40,9 +40,7 @@ Exit status is 12 if the password is incorrect. GroupID: cmdGroupDefault, DisableAutoGenTag: true, RunE: func(cmd *cobra.Command, _ []string) error { - term, cancel := setupTermstatus() - defer cancel() - return runPrune(cmd.Context(), opts, globalOptions, term) + return runPrune(cmd.Context(), opts, *globalOptions, globalOptions.term) }, } diff --git a/cmd/restic/cmd_prune_integration_test.go b/cmd/restic/cmd_prune_integration_test.go index d9103fc8f..df850feff 100644 --- a/cmd/restic/cmd_prune_integration_test.go +++ b/cmd/restic/cmd_prune_integration_test.go @@ -9,7 +9,6 @@ import ( "github.com/restic/restic/internal/backend" "github.com/restic/restic/internal/repository" rtest "github.com/restic/restic/internal/test" - "github.com/restic/restic/internal/ui" ) func testRunPrune(t testing.TB, gopts GlobalOptions, opts PruneOptions) { @@ -29,8 +28,8 @@ func testRunPruneOutput(gopts GlobalOptions, opts PruneOptions) error { defer func() { gopts.backendTestHook = oldHook }() - return withTermStatus(gopts, func(ctx context.Context, term ui.Terminal) error { - return runPrune(context.TODO(), opts, gopts, term) + return withTermStatus(gopts, func(ctx context.Context, gopts GlobalOptions) error { + return runPrune(context.TODO(), opts, gopts, gopts.term) }) } @@ -99,8 +98,8 @@ func testRunForgetJSON(t testing.TB, gopts GlobalOptions, args ...string) { pruneOpts := PruneOptions{ MaxUnused: "5%", } - return withTermStatus(gopts, func(ctx context.Context, term ui.Terminal) error { - return runForget(context.TODO(), opts, pruneOpts, gopts, term, args) + return withTermStatus(gopts, func(ctx context.Context, gopts GlobalOptions) error { + return runForget(context.TODO(), opts, pruneOpts, gopts, gopts.term, args) }) }) rtest.OK(t, err) @@ -122,8 +121,8 @@ func testPrune(t *testing.T, pruneOpts PruneOptions, checkOpts CheckOptions) { createPrunableRepo(t, env) testRunPrune(t, env.gopts, pruneOpts) - rtest.OK(t, withTermStatus(env.gopts, func(ctx context.Context, term ui.Terminal) error { - _, err := runCheck(context.TODO(), checkOpts, env.gopts, nil, term) + rtest.OK(t, withTermStatus(env.gopts, func(ctx context.Context, gopts GlobalOptions) error { + _, err := runCheck(context.TODO(), checkOpts, gopts, nil, gopts.term) return err })) } @@ -158,8 +157,8 @@ func TestPruneWithDamagedRepository(t *testing.T) { env.gopts.backendTestHook = oldHook }() // prune should fail - rtest.Equals(t, repository.ErrPacksMissing, withTermStatus(env.gopts, func(ctx context.Context, term ui.Terminal) error { - return runPrune(context.TODO(), pruneDefaultOptions, env.gopts, term) + rtest.Equals(t, repository.ErrPacksMissing, withTermStatus(env.gopts, func(ctx context.Context, gopts GlobalOptions) error { + return runPrune(context.TODO(), pruneDefaultOptions, gopts, gopts.term) }), "prune should have reported index not complete error") } @@ -231,8 +230,8 @@ func testEdgeCaseRepo(t *testing.T, tarfile string, optionsCheck CheckOptions, o if checkOK { testRunCheck(t, env.gopts) } else { - rtest.Assert(t, withTermStatus(env.gopts, func(ctx context.Context, term ui.Terminal) error { - _, err := runCheck(context.TODO(), optionsCheck, env.gopts, nil, term) + rtest.Assert(t, withTermStatus(env.gopts, func(ctx context.Context, gopts GlobalOptions) error { + _, err := runCheck(context.TODO(), optionsCheck, gopts, nil, gopts.term) return err }) != nil, "check should have reported an error") @@ -242,8 +241,8 @@ func testEdgeCaseRepo(t *testing.T, tarfile string, optionsCheck CheckOptions, o testRunPrune(t, env.gopts, optionsPrune) testRunCheck(t, env.gopts) } else { - rtest.Assert(t, withTermStatus(env.gopts, func(ctx context.Context, term ui.Terminal) error { - return runPrune(context.TODO(), optionsPrune, env.gopts, term) + rtest.Assert(t, withTermStatus(env.gopts, func(ctx context.Context, gopts GlobalOptions) error { + return runPrune(context.TODO(), optionsPrune, gopts, gopts.term) }) != nil, "prune should have reported an error") } diff --git a/cmd/restic/cmd_recover.go b/cmd/restic/cmd_recover.go index 274066eed..2dcf51376 100644 --- a/cmd/restic/cmd_recover.go +++ b/cmd/restic/cmd_recover.go @@ -14,7 +14,7 @@ import ( "golang.org/x/sync/errgroup" ) -func newRecoverCommand() *cobra.Command { +func newRecoverCommand(globalOptions *GlobalOptions) *cobra.Command { cmd := &cobra.Command{ Use: "recover [flags]", Short: "Recover data from the repository not referenced by snapshots", @@ -35,9 +35,7 @@ Exit status is 12 if the password is incorrect. GroupID: cmdGroupDefault, DisableAutoGenTag: true, RunE: func(cmd *cobra.Command, _ []string) error { - term, cancel := setupTermstatus() - defer cancel() - return runRecover(cmd.Context(), globalOptions, term) + return runRecover(cmd.Context(), *globalOptions, globalOptions.term) }, } return cmd diff --git a/cmd/restic/cmd_recover_integration_test.go b/cmd/restic/cmd_recover_integration_test.go index 91dec1505..5d51ee2d9 100644 --- a/cmd/restic/cmd_recover_integration_test.go +++ b/cmd/restic/cmd_recover_integration_test.go @@ -5,12 +5,11 @@ import ( "testing" rtest "github.com/restic/restic/internal/test" - "github.com/restic/restic/internal/ui" ) func testRunRecover(t testing.TB, gopts GlobalOptions) { - rtest.OK(t, withTermStatus(gopts, func(ctx context.Context, term ui.Terminal) error { - return runRecover(context.TODO(), gopts, term) + rtest.OK(t, withTermStatus(gopts, func(ctx context.Context, gopts GlobalOptions) error { + return runRecover(context.TODO(), gopts, gopts.term) })) } @@ -33,7 +32,7 @@ func TestRecover(t *testing.T) { ids = testListSnapshots(t, env.gopts, 1) testRunCheck(t, env.gopts) // check that the root tree is included in the snapshot - rtest.OK(t, withTermStatus(env.gopts, func(ctx context.Context, term ui.Terminal) error { - return runCat(context.TODO(), env.gopts, []string{"tree", ids[0].String() + ":" + sn.Tree.Str()}, term) + rtest.OK(t, withTermStatus(env.gopts, func(ctx context.Context, gopts GlobalOptions) error { + return runCat(context.TODO(), gopts, []string{"tree", ids[0].String() + ":" + sn.Tree.Str()}, gopts.term) })) } diff --git a/cmd/restic/cmd_repair.go b/cmd/restic/cmd_repair.go index bb1c98d27..c6b5a212b 100644 --- a/cmd/restic/cmd_repair.go +++ b/cmd/restic/cmd_repair.go @@ -4,7 +4,7 @@ import ( "github.com/spf13/cobra" ) -func newRepairCommand() *cobra.Command { +func newRepairCommand(globalOptions *GlobalOptions) *cobra.Command { cmd := &cobra.Command{ Use: "repair", Short: "Repair the repository", @@ -13,9 +13,9 @@ func newRepairCommand() *cobra.Command { } cmd.AddCommand( - newRepairIndexCommand(), - newRepairPacksCommand(), - newRepairSnapshotsCommand(), + newRepairIndexCommand(globalOptions), + newRepairPacksCommand(globalOptions), + newRepairSnapshotsCommand(globalOptions), ) return cmd } diff --git a/cmd/restic/cmd_repair_index.go b/cmd/restic/cmd_repair_index.go index 52383f720..163e68a07 100644 --- a/cmd/restic/cmd_repair_index.go +++ b/cmd/restic/cmd_repair_index.go @@ -9,7 +9,7 @@ import ( "github.com/spf13/pflag" ) -func newRepairIndexCommand() *cobra.Command { +func newRepairIndexCommand(globalOptions *GlobalOptions) *cobra.Command { var opts RepairIndexOptions cmd := &cobra.Command{ @@ -30,9 +30,7 @@ Exit status is 12 if the password is incorrect. `, DisableAutoGenTag: true, RunE: func(cmd *cobra.Command, _ []string) error { - term, cancel := setupTermstatus() - defer cancel() - return runRebuildIndex(cmd.Context(), opts, globalOptions, term) + return runRebuildIndex(cmd.Context(), opts, *globalOptions, globalOptions.term) }, } @@ -49,10 +47,10 @@ func (opts *RepairIndexOptions) AddFlags(f *pflag.FlagSet) { f.BoolVar(&opts.ReadAllPacks, "read-all-packs", false, "read all pack files to generate new index from scratch") } -func newRebuildIndexCommand() *cobra.Command { +func newRebuildIndexCommand(globalOptions *GlobalOptions) *cobra.Command { var opts RepairIndexOptions - replacement := newRepairIndexCommand() + replacement := newRepairIndexCommand(globalOptions) cmd := &cobra.Command{ Use: "rebuild-index [flags]", Short: replacement.Short, @@ -62,9 +60,7 @@ func newRebuildIndexCommand() *cobra.Command { // must create a new instance of the run function as it captures opts // by reference RunE: func(cmd *cobra.Command, _ []string) error { - term, cancel := setupTermstatus() - defer cancel() - return runRebuildIndex(cmd.Context(), opts, globalOptions, term) + return runRebuildIndex(cmd.Context(), opts, *globalOptions, globalOptions.term) }, } diff --git a/cmd/restic/cmd_repair_index_integration_test.go b/cmd/restic/cmd_repair_index_integration_test.go index c03c8f3d0..bd9924e63 100644 --- a/cmd/restic/cmd_repair_index_integration_test.go +++ b/cmd/restic/cmd_repair_index_integration_test.go @@ -13,15 +13,12 @@ import ( "github.com/restic/restic/internal/repository/index" "github.com/restic/restic/internal/restic" rtest "github.com/restic/restic/internal/test" - "github.com/restic/restic/internal/ui" ) func testRunRebuildIndex(t testing.TB, gopts GlobalOptions) { - rtest.OK(t, withRestoreGlobalOptions(func() error { - return withTermStatus(gopts, func(ctx context.Context, term ui.Terminal) error { - gopts.stdout = io.Discard - return runRebuildIndex(context.TODO(), RepairIndexOptions{}, gopts, term) - }) + rtest.OK(t, withTermStatus(gopts, func(ctx context.Context, gopts GlobalOptions) error { + gopts.stdout = io.Discard + return runRebuildIndex(context.TODO(), RepairIndexOptions{}, gopts, 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") rtest.SetupTarTestFixture(t, env.base, datafile) - err := withRestoreGlobalOptions(func() error { - env.gopts.backendTestHook = func(r backend.Backend) (backend.Backend, error) { - return &appendOnlyBackend{r}, nil - } - return withTermStatus(env.gopts, func(ctx context.Context, term ui.Terminal) error { - env.gopts.stdout = io.Discard - return runRebuildIndex(context.TODO(), RepairIndexOptions{}, env.gopts, term) - }) + env.gopts.backendTestHook = func(r backend.Backend) (backend.Backend, error) { + return &appendOnlyBackend{r}, nil + } + err := withTermStatus(env.gopts, func(ctx context.Context, gopts GlobalOptions) error { + gopts.stdout = io.Discard + return runRebuildIndex(context.TODO(), RepairIndexOptions{}, gopts, gopts.term) }) if err == nil { diff --git a/cmd/restic/cmd_repair_packs.go b/cmd/restic/cmd_repair_packs.go index e8d6a1196..9161cdb50 100644 --- a/cmd/restic/cmd_repair_packs.go +++ b/cmd/restic/cmd_repair_packs.go @@ -13,7 +13,7 @@ import ( "github.com/spf13/cobra" ) -func newRepairPacksCommand() *cobra.Command { +func newRepairPacksCommand(globalOptions *GlobalOptions) *cobra.Command { cmd := &cobra.Command{ Use: "packs [packIDs...]", Short: "Salvage damaged pack files", @@ -32,9 +32,7 @@ Exit status is 12 if the password is incorrect. `, DisableAutoGenTag: true, RunE: func(cmd *cobra.Command, args []string) error { - term, cancel := setupTermstatus() - defer cancel() - return runRepairPacks(cmd.Context(), globalOptions, term, args) + return runRepairPacks(cmd.Context(), *globalOptions, globalOptions.term, args) }, } return cmd diff --git a/cmd/restic/cmd_repair_snapshots.go b/cmd/restic/cmd_repair_snapshots.go index 49ab7b151..d109e1097 100644 --- a/cmd/restic/cmd_repair_snapshots.go +++ b/cmd/restic/cmd_repair_snapshots.go @@ -12,7 +12,7 @@ import ( "github.com/spf13/pflag" ) -func newRepairSnapshotsCommand() *cobra.Command { +func newRepairSnapshotsCommand(globalOptions *GlobalOptions) *cobra.Command { var opts RepairOptions cmd := &cobra.Command{ @@ -50,9 +50,7 @@ Exit status is 12 if the password is incorrect. `, DisableAutoGenTag: true, RunE: func(cmd *cobra.Command, args []string) error { - term, cancel := setupTermstatus() - defer cancel() - return runRepairSnapshots(cmd.Context(), globalOptions, opts, args, term) + return runRepairSnapshots(cmd.Context(), *globalOptions, opts, args, globalOptions.term) }, } diff --git a/cmd/restic/cmd_repair_snapshots_integration_test.go b/cmd/restic/cmd_repair_snapshots_integration_test.go index 6594d211c..1173461ec 100644 --- a/cmd/restic/cmd_repair_snapshots_integration_test.go +++ b/cmd/restic/cmd_repair_snapshots_integration_test.go @@ -12,7 +12,6 @@ import ( "github.com/restic/restic/internal/restic" rtest "github.com/restic/restic/internal/test" - "github.com/restic/restic/internal/ui" ) func testRunRepairSnapshot(t testing.TB, gopts GlobalOptions, forget bool) { @@ -20,8 +19,8 @@ func testRunRepairSnapshot(t testing.TB, gopts GlobalOptions, forget bool) { Forget: forget, } - rtest.OK(t, withTermStatus(gopts, func(ctx context.Context, term ui.Terminal) error { - return runRepairSnapshots(context.TODO(), gopts, opts, nil, term) + rtest.OK(t, withTermStatus(gopts, func(ctx context.Context, gopts GlobalOptions) error { + return runRepairSnapshots(context.TODO(), gopts, opts, nil, gopts.term) })) } diff --git a/cmd/restic/cmd_restore.go b/cmd/restic/cmd_restore.go index d0bf76d85..5fb7a65ea 100644 --- a/cmd/restic/cmd_restore.go +++ b/cmd/restic/cmd_restore.go @@ -18,7 +18,7 @@ import ( "github.com/spf13/pflag" ) -func newRestoreCommand() *cobra.Command { +func newRestoreCommand(globalOptions *GlobalOptions) *cobra.Command { var opts RestoreOptions cmd := &cobra.Command{ @@ -46,9 +46,7 @@ Exit status is 12 if the password is incorrect. GroupID: cmdGroupDefault, DisableAutoGenTag: true, RunE: func(cmd *cobra.Command, args []string) error { - term, cancel := setupTermstatus() - defer cancel() - return runRestore(cmd.Context(), opts, globalOptions, term, args) + return runRestore(cmd.Context(), opts, *globalOptions, globalOptions.term, args) }, } diff --git a/cmd/restic/cmd_restore_integration_test.go b/cmd/restic/cmd_restore_integration_test.go index 09d5f4d9e..9182f06aa 100644 --- a/cmd/restic/cmd_restore_integration_test.go +++ b/cmd/restic/cmd_restore_integration_test.go @@ -14,7 +14,6 @@ import ( "github.com/restic/restic/internal/restic" rtest "github.com/restic/restic/internal/test" - "github.com/restic/restic/internal/ui" ) func testRunRestore(t testing.TB, opts GlobalOptions, dir string, snapshotID string) { @@ -31,8 +30,8 @@ func testRunRestoreExcludes(t testing.TB, gopts GlobalOptions, dir string, snaps } func testRunRestoreAssumeFailure(snapshotID string, opts RestoreOptions, gopts GlobalOptions) error { - return withTermStatus(gopts, func(ctx context.Context, term ui.Terminal) error { - return runRestore(ctx, opts, gopts, term, []string{snapshotID}) + return withTermStatus(gopts, func(ctx context.Context, gopts GlobalOptions) error { + return runRestore(ctx, opts, gopts, gopts.term, []string{snapshotID}) }) } @@ -337,11 +336,8 @@ func TestRestoreWithPermissionFailure(t *testing.T) { snapshots := testListSnapshots(t, env.gopts, 1) - _ = withRestoreGlobalOptions(func() error { - env.gopts.stderr = io.Discard - testRunRestore(t, env.gopts, filepath.Join(env.base, "restore"), snapshots[0].String()) - return nil - }) + env.gopts.stderr = io.Discard + testRunRestore(t, env.gopts, filepath.Join(env.base, "restore"), snapshots[0].String()) // make sure that all files have been restored, regardless of any // permission errors diff --git a/cmd/restic/cmd_rewrite.go b/cmd/restic/cmd_rewrite.go index 45ec3c13b..26677f7a7 100644 --- a/cmd/restic/cmd_rewrite.go +++ b/cmd/restic/cmd_rewrite.go @@ -18,7 +18,7 @@ import ( "github.com/restic/restic/internal/walker" ) -func newRewriteCommand() *cobra.Command { +func newRewriteCommand(globalOptions *GlobalOptions) *cobra.Command { var opts RewriteOptions cmd := &cobra.Command{ @@ -60,9 +60,7 @@ Exit status is 12 if the password is incorrect. GroupID: cmdGroupDefault, DisableAutoGenTag: true, RunE: func(cmd *cobra.Command, args []string) error { - term, cancel := setupTermstatus() - defer cancel() - return runRewrite(cmd.Context(), opts, globalOptions, args, term) + return runRewrite(cmd.Context(), opts, *globalOptions, args, globalOptions.term) }, } diff --git a/cmd/restic/cmd_rewrite_integration_test.go b/cmd/restic/cmd_rewrite_integration_test.go index f011cdcc1..213ef0319 100644 --- a/cmd/restic/cmd_rewrite_integration_test.go +++ b/cmd/restic/cmd_rewrite_integration_test.go @@ -20,8 +20,8 @@ func testRunRewriteExclude(t testing.TB, gopts GlobalOptions, excludes []string, Metadata: metadata, } - rtest.OK(t, withTermStatus(gopts, func(ctx context.Context, term ui.Terminal) error { - return runRewrite(context.TODO(), opts, gopts, nil, term) + rtest.OK(t, withTermStatus(gopts, func(ctx context.Context, gopts GlobalOptions) error { + return runRewrite(context.TODO(), opts, gopts, nil, gopts.term) })) } @@ -41,9 +41,9 @@ func getSnapshot(t testing.TB, snapshotID restic.ID, env *testEnvironment) *rest t.Helper() var snapshots []*restic.Snapshot - err := withTermStatus(env.gopts, func(ctx context.Context, term ui.Terminal) error { - printer := newTerminalProgressPrinter(env.gopts.JSON, env.gopts.verbosity, term) - ctx, repo, unlock, err := openWithReadLock(ctx, env.gopts, false, printer) + err := withTermStatus(env.gopts, func(ctx context.Context, gopts GlobalOptions) error { + printer := newTerminalProgressPrinter(gopts.JSON, gopts.verbosity, gopts.term) + ctx, repo, unlock, err := openWithReadLock(ctx, gopts, false, printer) rtest.OK(t, err) defer unlock() @@ -117,9 +117,9 @@ func testRewriteMetadata(t *testing.T, metadata snapshotMetadataArgs) { testRunRewriteExclude(t, env.gopts, []string{}, true, metadata) var snapshots []*restic.Snapshot - err := withTermStatus(env.gopts, func(ctx context.Context, term ui.Terminal) error { - printer := newTerminalProgressPrinter(env.gopts.JSON, env.gopts.verbosity, term) - ctx, repo, unlock, err := openWithReadLock(ctx, env.gopts, false, printer) + err := withTermStatus(env.gopts, func(ctx context.Context, gopts GlobalOptions) error { + printer := newTerminalProgressPrinter(gopts.JSON, gopts.verbosity, gopts.term) + ctx, repo, unlock, err := openWithReadLock(ctx, gopts, false, printer) rtest.OK(t, err) defer unlock() @@ -157,17 +157,17 @@ func TestRewriteSnaphotSummary(t *testing.T) { defer cleanup() createBasicRewriteRepo(t, env) - rtest.OK(t, withTermStatus(env.gopts, func(ctx context.Context, term ui.Terminal) error { - return runRewrite(context.TODO(), RewriteOptions{SnapshotSummary: true}, env.gopts, []string{}, term) + rtest.OK(t, withTermStatus(env.gopts, func(ctx context.Context, gopts GlobalOptions) error { + return runRewrite(context.TODO(), RewriteOptions{SnapshotSummary: true}, gopts, []string{}, gopts.term) })) // no new snapshot should be created as the snapshot already has a summary snapshots := testListSnapshots(t, env.gopts, 1) // replace snapshot by one without a summary var oldSummary *restic.SnapshotSummary - err := withTermStatus(env.gopts, func(ctx context.Context, term ui.Terminal) error { - printer := newTerminalProgressPrinter(env.gopts.JSON, env.gopts.verbosity, term) - _, repo, unlock, err := openWithExclusiveLock(ctx, env.gopts, false, printer) + err := withTermStatus(env.gopts, func(ctx context.Context, gopts GlobalOptions) error { + printer := newTerminalProgressPrinter(gopts.JSON, gopts.verbosity, gopts.term) + _, repo, unlock, err := openWithExclusiveLock(ctx, gopts, false, printer) rtest.OK(t, err) defer unlock() @@ -182,8 +182,8 @@ func TestRewriteSnaphotSummary(t *testing.T) { rtest.OK(t, err) // rewrite snapshot and lookup ID of new snapshot - rtest.OK(t, withTermStatus(env.gopts, func(ctx context.Context, term ui.Terminal) error { - return runRewrite(context.TODO(), RewriteOptions{SnapshotSummary: true}, env.gopts, []string{}, term) + rtest.OK(t, withTermStatus(env.gopts, func(ctx context.Context, gopts GlobalOptions) error { + return runRewrite(context.TODO(), RewriteOptions{SnapshotSummary: true}, gopts, []string{}, gopts.term) })) newSnapshots := testListSnapshots(t, env.gopts, 2) newSnapshot := restic.NewIDSet(newSnapshots...).Sub(restic.NewIDSet(snapshots...)).List()[0] diff --git a/cmd/restic/cmd_self_update.go b/cmd/restic/cmd_self_update.go index b4173ab78..5615db808 100644 --- a/cmd/restic/cmd_self_update.go +++ b/cmd/restic/cmd_self_update.go @@ -14,13 +14,13 @@ import ( "github.com/spf13/pflag" ) -func registerSelfUpdateCommand(cmd *cobra.Command) { +func registerSelfUpdateCommand(cmd *cobra.Command, globalOptions *GlobalOptions) { cmd.AddCommand( - newSelfUpdateCommand(), + newSelfUpdateCommand(globalOptions), ) } -func newSelfUpdateCommand() *cobra.Command { +func newSelfUpdateCommand(globalOptions *GlobalOptions) *cobra.Command { var opts SelfUpdateOptions cmd := &cobra.Command{ @@ -43,9 +43,7 @@ Exit status is 12 if the password is incorrect. `, DisableAutoGenTag: true, RunE: func(cmd *cobra.Command, args []string) error { - term, cancel := setupTermstatus() - defer cancel() - return runSelfUpdate(cmd.Context(), opts, globalOptions, args, term) + return runSelfUpdate(cmd.Context(), opts, *globalOptions, args, globalOptions.term) }, } diff --git a/cmd/restic/cmd_self_update_disabled.go b/cmd/restic/cmd_self_update_disabled.go index ce9af10d2..bca03335f 100644 --- a/cmd/restic/cmd_self_update_disabled.go +++ b/cmd/restic/cmd_self_update_disabled.go @@ -4,6 +4,6 @@ package main import "github.com/spf13/cobra" -func registerSelfUpdateCommand(_ *cobra.Command) { +func registerSelfUpdateCommand(_ *cobra.Command, _ *GlobalOptions) { // No commands to register in non-selfupdate mode } diff --git a/cmd/restic/cmd_snapshots.go b/cmd/restic/cmd_snapshots.go index 8d921194e..741da3331 100644 --- a/cmd/restic/cmd_snapshots.go +++ b/cmd/restic/cmd_snapshots.go @@ -15,7 +15,7 @@ import ( "github.com/spf13/pflag" ) -func newSnapshotsCommand() *cobra.Command { +func newSnapshotsCommand(globalOptions *GlobalOptions) *cobra.Command { var opts SnapshotOptions cmd := &cobra.Command{ @@ -36,9 +36,7 @@ Exit status is 12 if the password is incorrect. GroupID: cmdGroupDefault, DisableAutoGenTag: true, RunE: func(cmd *cobra.Command, args []string) error { - term, cancel := setupTermstatus() - defer cancel() - return runSnapshots(cmd.Context(), opts, globalOptions, args, term) + return runSnapshots(cmd.Context(), opts, *globalOptions, args, globalOptions.term) }, } diff --git a/cmd/restic/cmd_snapshots_integration_test.go b/cmd/restic/cmd_snapshots_integration_test.go index a009b2908..af45fde9c 100644 --- a/cmd/restic/cmd_snapshots_integration_test.go +++ b/cmd/restic/cmd_snapshots_integration_test.go @@ -7,7 +7,6 @@ import ( "github.com/restic/restic/internal/restic" rtest "github.com/restic/restic/internal/test" - "github.com/restic/restic/internal/ui" ) func testRunSnapshots(t testing.TB, gopts GlobalOptions) (newest *Snapshot, snapmap map[restic.ID]Snapshot) { @@ -15,8 +14,8 @@ func testRunSnapshots(t testing.TB, gopts GlobalOptions) (newest *Snapshot, snap gopts.JSON = true opts := SnapshotOptions{} - return withTermStatus(gopts, func(ctx context.Context, term ui.Terminal) error { - return runSnapshots(ctx, opts, gopts, []string{}, term) + return withTermStatus(gopts, func(ctx context.Context, gopts GlobalOptions) error { + return runSnapshots(ctx, opts, gopts, []string{}, gopts.term) }) }) rtest.OK(t, err) diff --git a/cmd/restic/cmd_stats.go b/cmd/restic/cmd_stats.go index f25ed39bb..64230be95 100644 --- a/cmd/restic/cmd_stats.go +++ b/cmd/restic/cmd_stats.go @@ -22,7 +22,7 @@ import ( "github.com/spf13/pflag" ) -func newStatsCommand() *cobra.Command { +func newStatsCommand(globalOptions *GlobalOptions) *cobra.Command { var opts StatsOptions cmd := &cobra.Command{ @@ -63,9 +63,7 @@ Exit status is 12 if the password is incorrect. GroupID: cmdGroupDefault, DisableAutoGenTag: true, RunE: func(cmd *cobra.Command, args []string) error { - term, cancel := setupTermstatus() - defer cancel() - return runStats(cmd.Context(), opts, globalOptions, args, term) + return runStats(cmd.Context(), opts, *globalOptions, args, globalOptions.term) }, } diff --git a/cmd/restic/cmd_tag.go b/cmd/restic/cmd_tag.go index fde4209bc..b1f82acfa 100644 --- a/cmd/restic/cmd_tag.go +++ b/cmd/restic/cmd_tag.go @@ -13,7 +13,7 @@ import ( "github.com/restic/restic/internal/ui" ) -func newTagCommand() *cobra.Command { +func newTagCommand(globalOptions *GlobalOptions) *cobra.Command { var opts TagOptions cmd := &cobra.Command{ @@ -39,9 +39,7 @@ Exit status is 12 if the password is incorrect. GroupID: cmdGroupDefault, DisableAutoGenTag: true, RunE: func(cmd *cobra.Command, args []string) error { - term, cancel := setupTermstatus() - defer cancel() - return runTag(cmd.Context(), opts, globalOptions, term, args) + return runTag(cmd.Context(), opts, *globalOptions, globalOptions.term, args) }, } diff --git a/cmd/restic/cmd_tag_integration_test.go b/cmd/restic/cmd_tag_integration_test.go index cbb08c5bf..9958be485 100644 --- a/cmd/restic/cmd_tag_integration_test.go +++ b/cmd/restic/cmd_tag_integration_test.go @@ -6,12 +6,11 @@ import ( "github.com/restic/restic/internal/restic" rtest "github.com/restic/restic/internal/test" - "github.com/restic/restic/internal/ui" ) func testRunTag(t testing.TB, opts TagOptions, gopts GlobalOptions) { - rtest.OK(t, withTermStatus(gopts, func(ctx context.Context, term ui.Terminal) error { - return runTag(context.TODO(), opts, gopts, term, []string{}) + rtest.OK(t, withTermStatus(gopts, func(ctx context.Context, gopts GlobalOptions) error { + return runTag(context.TODO(), opts, gopts, gopts.term, []string{}) })) } diff --git a/cmd/restic/cmd_unlock.go b/cmd/restic/cmd_unlock.go index 8cea239c6..096d21cac 100644 --- a/cmd/restic/cmd_unlock.go +++ b/cmd/restic/cmd_unlock.go @@ -9,7 +9,7 @@ import ( "github.com/spf13/pflag" ) -func newUnlockCommand() *cobra.Command { +func newUnlockCommand(globalOptions *GlobalOptions) *cobra.Command { var opts UnlockOptions cmd := &cobra.Command{ @@ -27,9 +27,7 @@ Exit status is 1 if there was any error. GroupID: cmdGroupDefault, DisableAutoGenTag: true, RunE: func(cmd *cobra.Command, _ []string) error { - term, cancel := setupTermstatus() - defer cancel() - return runUnlock(cmd.Context(), opts, globalOptions, term) + return runUnlock(cmd.Context(), opts, *globalOptions, globalOptions.term) }, } opts.AddFlags(cmd.Flags()) diff --git a/cmd/restic/cmd_version.go b/cmd/restic/cmd_version.go index 1acfba5ab..a32575389 100644 --- a/cmd/restic/cmd_version.go +++ b/cmd/restic/cmd_version.go @@ -7,7 +7,7 @@ import ( "github.com/spf13/cobra" ) -func newVersionCommand() *cobra.Command { +func newVersionCommand(globalOptions *GlobalOptions) *cobra.Command { cmd := &cobra.Command{ Use: "version", Short: "Print version information", @@ -23,9 +23,7 @@ Exit status is 1 if there was any error. `, DisableAutoGenTag: true, Run: func(_ *cobra.Command, _ []string) { - term, cancel := setupTermstatus() - defer cancel() - printer := newTerminalProgressPrinter(globalOptions.JSON, globalOptions.verbosity, term) + printer := newTerminalProgressPrinter(globalOptions.JSON, globalOptions.verbosity, globalOptions.term) if globalOptions.JSON { type jsonVersion struct { diff --git a/cmd/restic/flags_test.go b/cmd/restic/flags_test.go index b1001b7c5..aa177c45c 100644 --- a/cmd/restic/flags_test.go +++ b/cmd/restic/flags_test.go @@ -8,7 +8,7 @@ import ( // TestFlags checks for double defined flags, the commands will panic on // ParseFlags() when a shorthand flag is defined twice. func TestFlags(t *testing.T) { - for _, cmd := range newRootCommand().Commands() { + for _, cmd := range newRootCommand(&GlobalOptions{}).Commands() { t.Run(cmd.Name(), func(t *testing.T) { cmd.Flags().SetOutput(io.Discard) err := cmd.ParseFlags([]string{"--help"}) diff --git a/cmd/restic/global.go b/cmd/restic/global.go index e3419fedc..14dc8b6da 100644 --- a/cmd/restic/global.go +++ b/cmd/restic/global.go @@ -34,6 +34,7 @@ import ( "github.com/restic/restic/internal/restic" "github.com/restic/restic/internal/terminal" "github.com/restic/restic/internal/textfile" + "github.com/restic/restic/internal/ui" "github.com/restic/restic/internal/ui/progress" "github.com/spf13/pflag" @@ -77,6 +78,7 @@ type GlobalOptions struct { password string stdout io.Writer stderr io.Writer + term ui.Terminal backends *location.Registry backendTestHook, backendInnerTestHook backendWrapper @@ -177,12 +179,6 @@ func (opts *GlobalOptions) PreRun(needsPassword bool) error { return nil } -var globalOptions = GlobalOptions{ - stdout: os.Stdout, - stderr: os.Stderr, - backends: collectBackends(), -} - func collectBackends() *location.Registry { backends := location.NewRegistry() backends.Register(azure.NewFactory()) diff --git a/cmd/restic/integration_helpers_test.go b/cmd/restic/integration_helpers_test.go index 7367bbe70..bfefe807f 100644 --- a/cmd/restic/integration_helpers_test.go +++ b/cmd/restic/integration_helpers_test.go @@ -20,7 +20,6 @@ import ( "github.com/restic/restic/internal/repository" "github.com/restic/restic/internal/restic" rtest "github.com/restic/restic/internal/test" - "github.com/restic/restic/internal/ui" "github.com/restic/restic/internal/ui/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 backendTestHook: func(r backend.Backend) (backend.Backend, error) { return newOrderedListOnceBackend(r), nil }, // start with default set of backends - backends: globalOptions.backends, + backends: collectBackends(), } - // always overwrite global options - globalOptions = env.gopts - cleanup = func() { if !rtest.TestCleanupTempDirs { t.Logf("leaving temporary directory %v used for test", tempdir) @@ -248,8 +244,8 @@ func testSetupBackupData(t testing.TB, env *testEnvironment) string { func listPacks(gopts GlobalOptions, t *testing.T) restic.IDSet { var packs restic.IDSet - err := withTermStatus(gopts, func(ctx context.Context, term ui.Terminal) error { - printer := newTerminalProgressPrinter(gopts.JSON, gopts.verbosity, term) + err := withTermStatus(gopts, func(ctx context.Context, gopts GlobalOptions) error { + printer := newTerminalProgressPrinter(gopts.JSON, gopts.verbosity, gopts.term) ctx, r, unlock, err := openWithReadLock(ctx, gopts, false, printer) rtest.OK(t, err) defer unlock() @@ -267,8 +263,8 @@ func listPacks(gopts GlobalOptions, t *testing.T) restic.IDSet { func listTreePacks(gopts GlobalOptions, t *testing.T) restic.IDSet { var treePacks restic.IDSet - err := withTermStatus(gopts, func(ctx context.Context, term ui.Terminal) error { - printer := newTerminalProgressPrinter(gopts.JSON, gopts.verbosity, term) + err := withTermStatus(gopts, func(ctx context.Context, gopts GlobalOptions) error { + printer := newTerminalProgressPrinter(gopts.JSON, gopts.verbosity, gopts.term) ctx, r, unlock, err := openWithReadLock(ctx, gopts, false, printer) rtest.OK(t, err) defer unlock() @@ -298,8 +294,8 @@ func captureBackend(gopts *GlobalOptions) func() backend.Backend { func removePacks(gopts GlobalOptions, t testing.TB, remove restic.IDSet) { be := captureBackend(&gopts) - err := withTermStatus(gopts, func(ctx context.Context, term ui.Terminal) error { - printer := newTerminalProgressPrinter(gopts.JSON, gopts.verbosity, term) + err := withTermStatus(gopts, func(ctx context.Context, gopts GlobalOptions) error { + printer := newTerminalProgressPrinter(gopts.JSON, gopts.verbosity, gopts.term) ctx, _, unlock, err := openWithExclusiveLock(ctx, gopts, false, printer) rtest.OK(t, err) 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) { be := captureBackend(&gopts) - err := withTermStatus(gopts, func(ctx context.Context, term ui.Terminal) error { - printer := newTerminalProgressPrinter(gopts.JSON, gopts.verbosity, term) + err := withTermStatus(gopts, func(ctx context.Context, gopts GlobalOptions) error { + printer := newTerminalProgressPrinter(gopts.JSON, gopts.verbosity, gopts.term) ctx, r, unlock, err := openWithExclusiveLock(ctx, gopts, false, printer) rtest.OK(t, err) 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 { var snapshot *restic.Snapshot - err := withTermStatus(gopts, func(ctx context.Context, term ui.Terminal) error { - printer := newTerminalProgressPrinter(gopts.JSON, gopts.verbosity, term) + err := withTermStatus(gopts, func(ctx context.Context, gopts GlobalOptions) error { + printer := newTerminalProgressPrinter(gopts.JSON, gopts.verbosity, gopts.term) _, repo, unlock, err := openWithReadLock(ctx, gopts, false, printer) rtest.OK(t, err) defer unlock() @@ -420,30 +416,19 @@ func testFileSize(filename string, size int64) error { return nil } -func withRestoreGlobalOptions(inner func() error) error { - gopts := globalOptions - defer func() { - globalOptions = gopts - }() - return inner() -} - func withCaptureStdout(gopts GlobalOptions, inner func(gopts GlobalOptions) error) (*bytes.Buffer, error) { buf := bytes.NewBuffer(nil) - err := withRestoreGlobalOptions(func() error { - globalOptions.stdout = buf - gopts.stdout = buf - return inner(gopts) - }) - + gopts.stdout = buf + err := inner(gopts) 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()) var wg sync.WaitGroup term := termstatus.New(gopts.stdout, gopts.stderr, gopts.Quiet) + gopts.term = term wg.Add(1) go func() { defer wg.Done() @@ -453,5 +438,5 @@ func withTermStatus(gopts GlobalOptions, callback func(ctx context.Context, term defer wg.Wait() defer cancel() - return callback(ctx, term) + return callback(ctx, gopts) } diff --git a/cmd/restic/integration_test.go b/cmd/restic/integration_test.go index c16f09bf1..c0e98e232 100644 --- a/cmd/restic/integration_test.go +++ b/cmd/restic/integration_test.go @@ -12,7 +12,6 @@ import ( "github.com/restic/restic/internal/errors" "github.com/restic/restic/internal/restic" rtest "github.com/restic/restic/internal/test" - "github.com/restic/restic/internal/ui" ) func TestCheckRestoreNoLock(t *testing.T) { @@ -87,15 +86,15 @@ func TestListOnce(t *testing.T) { createPrunableRepo(t, env) testRunPrune(t, env.gopts, pruneOpts) - rtest.OK(t, withTermStatus(env.gopts, func(ctx context.Context, term ui.Terminal) error { - _, err := runCheck(context.TODO(), checkOpts, env.gopts, nil, term) + rtest.OK(t, withTermStatus(env.gopts, func(ctx context.Context, gopts GlobalOptions) error { + _, err := runCheck(context.TODO(), checkOpts, gopts, nil, gopts.term) return err })) - rtest.OK(t, withTermStatus(env.gopts, func(ctx context.Context, term ui.Terminal) error { - return runRebuildIndex(context.TODO(), RepairIndexOptions{}, env.gopts, term) + rtest.OK(t, withTermStatus(env.gopts, func(ctx context.Context, gopts GlobalOptions) error { + return runRebuildIndex(context.TODO(), RepairIndexOptions{}, gopts, gopts.term) })) - rtest.OK(t, withTermStatus(env.gopts, func(ctx context.Context, term ui.Terminal) error { - return runRebuildIndex(context.TODO(), RepairIndexOptions{ReadAllPacks: true}, env.gopts, term) + rtest.OK(t, withTermStatus(env.gopts, func(ctx context.Context, gopts GlobalOptions) error { + 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)...) var snapshotIDs restic.IDSet - rtest.OK(t, withTermStatus(env.gopts, func(ctx context.Context, term ui.Terminal) error { - printer := newTerminalProgressPrinter(env.gopts.JSON, env.gopts.verbosity, term) - ctx, repo, unlock, err := openWithReadLock(ctx, env.gopts, false, printer) + rtest.OK(t, withTermStatus(env.gopts, func(ctx context.Context, gopts GlobalOptions) error { + printer := newTerminalProgressPrinter(gopts.JSON, gopts.verbosity, gopts.term) + ctx, repo, unlock, err := openWithReadLock(ctx, gopts, false, printer) rtest.OK(t, err) defer unlock() diff --git a/cmd/restic/main.go b/cmd/restic/main.go index f373a418d..91fbf638d 100644 --- a/cmd/restic/main.go +++ b/cmd/restic/main.go @@ -19,6 +19,7 @@ import ( "github.com/restic/restic/internal/feature" "github.com/restic/restic/internal/repository" "github.com/restic/restic/internal/restic" + "github.com/restic/restic/internal/ui/termstatus" ) func init() { @@ -31,7 +32,7 @@ var ErrOK = errors.New("ok") var cmdGroupDefault = "default" var cmdGroupAdvanced = "advanced" -func newRootCommand() *cobra.Command { +func newRootCommand(globalOptions *GlobalOptions) *cobra.Command { cmd := &cobra.Command{ Use: "restic", Short: "Backup and restore files", @@ -66,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 cmd.CompletionOptions.DisableDefaultCmd = true + // globalOptions is passed to commands by reference to allow PersistentPreRunE to modify it cmd.AddCommand( - newBackupCommand(), - newCacheCommand(), - newCatCommand(), - newCheckCommand(), - newCopyCommand(), - newDiffCommand(), - newDumpCommand(), - newFeaturesCommand(), - newFindCommand(), - newForgetCommand(), - newGenerateCommand(), - newInitCommand(), - newKeyCommand(), - newListCommand(), - newLsCommand(), - newMigrateCommand(), - newOptionsCommand(), - newPruneCommand(), - newRebuildIndexCommand(), - newRecoverCommand(), - newRepairCommand(), - newRestoreCommand(), - newRewriteCommand(), - newSnapshotsCommand(), - newStatsCommand(), - newTagCommand(), - newUnlockCommand(), - newVersionCommand(), + newBackupCommand(globalOptions), + newCacheCommand(globalOptions), + newCatCommand(globalOptions), + newCheckCommand(globalOptions), + newCopyCommand(globalOptions), + newDiffCommand(globalOptions), + newDumpCommand(globalOptions), + newFeaturesCommand(globalOptions), + newFindCommand(globalOptions), + newForgetCommand(globalOptions), + newGenerateCommand(globalOptions), + newInitCommand(globalOptions), + newKeyCommand(globalOptions), + newListCommand(globalOptions), + newLsCommand(globalOptions), + newMigrateCommand(globalOptions), + newOptionsCommand(globalOptions), + newPruneCommand(globalOptions), + newRebuildIndexCommand(globalOptions), + newRecoverCommand(globalOptions), + newRepairCommand(globalOptions), + newRestoreCommand(globalOptions), + newRewriteCommand(globalOptions), + newSnapshotsCommand(globalOptions), + newStatsCommand(globalOptions), + newTagCommand(globalOptions), + newUnlockCommand(globalOptions), + newVersionCommand(globalOptions), ) - registerDebugCommand(cmd) - registerMountCommand(cmd) - registerSelfUpdateCommand(cmd) + registerDebugCommand(cmd, globalOptions) + registerMountCommand(cmd, globalOptions) + registerSelfUpdateCommand(cmd, globalOptions) registerProfiling(cmd, os.Stderr) return cmd @@ -125,7 +127,7 @@ func tweakGoGC() { } } -func printExitError(code int, message string) { +func printExitError(globalOptions GlobalOptions, code int, message string) { if globalOptions.JSON { type jsonExitError struct { MessageType string `json:"message_type"` // exit_error @@ -139,7 +141,7 @@ func printExitError(code int, message string) { Message: message, } - err := json.NewEncoder(globalOptions.stderr).Encode(jsonS) + err := json.NewEncoder(os.Stderr).Encode(jsonS) if err != nil { // ignore error as there's no good way to handle it _, _ = fmt.Fprintf(os.Stderr, "JSON encode failed: %v\n", err) @@ -147,7 +149,7 @@ func printExitError(code int, message string) { return } } else { - _, _ = fmt.Fprintf(globalOptions.stderr, "%v\n", message) + _, _ = fmt.Fprintf(os.Stderr, "%v\n", message) } } @@ -170,16 +172,26 @@ func main() { debug.Log("restic %s compiled with %v on %v/%v", version, runtime.Version(), runtime.GOOS, runtime.GOARCH) - ctx := createGlobalContext() - err = newRootCommand().ExecuteContext(ctx) - - switch err { - case nil: - err = ctx.Err() - case ErrOK: - // ErrOK overwrites context cancellation errors - err = nil + globalOptions := GlobalOptions{ + stdout: os.Stdout, + stderr: os.Stderr, + backends: collectBackends(), } + 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 switch { @@ -224,7 +236,7 @@ func main() { } if exitCode != 0 { - printExitError(exitCode, exitMessage) + printExitError(globalOptions, exitCode, exitMessage) } Exit(exitCode) } diff --git a/cmd/restic/termstatus.go b/cmd/restic/termstatus.go deleted file mode 100644 index c0e9a045b..000000000 --- a/cmd/restic/termstatus.go +++ /dev/null @@ -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() - } -} diff --git a/internal/ui/termstatus/status.go b/internal/ui/termstatus/status.go index f1cbd7ef4..a7bd60c31 100644 --- a/internal/ui/termstatus/status.go +++ b/internal/ui/termstatus/status.go @@ -5,6 +5,7 @@ import ( "fmt" "io" "strings" + "sync" "github.com/restic/restic/internal/terminal" "github.com/restic/restic/internal/ui" @@ -46,6 +47,34 @@ type fder interface { 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 // 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