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 (
"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()

View File

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

View File

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

View File

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

View File

@@ -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,
}

View File

@@ -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
},

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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

View File

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

View File

@@ -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",

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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

View File

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

View File

@@ -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

View File

@@ -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),

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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

View File

@@ -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",

View File

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

View File

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

View File

@@ -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

View File

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

View File

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

View File

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

View File

@@ -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 {

View File

@@ -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

View File

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

View File

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

View File

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

View File

@@ -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

View File

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

View File

@@ -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]

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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 {

View File

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

View File

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

View File

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

View File

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

View File

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

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"
"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