From 66103aea3daa8d6455ecee8a2dae173e2f2d7b8c Mon Sep 17 00:00:00 2001 From: Michael Eischer Date: Sun, 21 Jan 2024 17:43:13 +0100 Subject: [PATCH] Remove all usages of the global command-specific options Now, every command uses an options struct, which is passed to the run* function by the command.RunE method. --- cmd/restic/cmd_backup.go | 2 +- cmd/restic/cmd_debug.go | 46 ++++++++++++----------- cmd/restic/cmd_diff.go | 2 +- cmd/restic/cmd_forget.go | 7 ++-- cmd/restic/cmd_forget_integration_test.go | 5 ++- cmd/restic/cmd_generate.go | 28 +++++++------- cmd/restic/cmd_key.go | 38 ++++++++++--------- cmd/restic/cmd_key_integration_test.go | 25 ++++++------ cmd/restic/cmd_list.go | 6 +-- cmd/restic/cmd_list_integration_test.go | 2 +- cmd/restic/cmd_ls.go | 2 +- cmd/restic/cmd_prune.go | 4 +- cmd/restic/cmd_prune_integration_test.go | 5 ++- 13 files changed, 93 insertions(+), 79 deletions(-) diff --git a/cmd/restic/cmd_backup.go b/cmd/restic/cmd_backup.go index a2b81a759..4b27bb83c 100644 --- a/cmd/restic/cmd_backup.go +++ b/cmd/restic/cmd_backup.go @@ -633,7 +633,7 @@ func runBackup(ctx context.Context, opts BackupOptions, gopts GlobalOptions, ter wg.Go(func() error { return sc.Scan(cancelCtx, targets) }) } - arch := archiver.New(repo, targetFS, archiver.Options{ReadConcurrency: backupOptions.ReadConcurrency}) + arch := archiver.New(repo, targetFS, archiver.Options{ReadConcurrency: opts.ReadConcurrency}) arch.SelectByName = selectByNameFilter arch.Select = selectFilter arch.WithAtime = opts.WithAtime diff --git a/cmd/restic/cmd_debug.go b/cmd/restic/cmd_debug.go index 60413de21..49efb904b 100644 --- a/cmd/restic/cmd_debug.go +++ b/cmd/restic/cmd_debug.go @@ -52,19 +52,23 @@ Exit status is 0 if the command was successful, and non-zero if there was any er }, } -var tryRepair bool -var repairByte bool -var extractPack bool -var reuploadBlobs bool +type DebugExamineOptions struct { + TryRepair bool + RepairByte bool + ExtractPack bool + ReuploadBlobs bool +} + +var debugExamineOpts DebugExamineOptions func init() { cmdRoot.AddCommand(cmdDebug) cmdDebug.AddCommand(cmdDebugDump) cmdDebug.AddCommand(cmdDebugExamine) - cmdDebugExamine.Flags().BoolVar(&extractPack, "extract-pack", false, "write blobs to the current directory") - cmdDebugExamine.Flags().BoolVar(&reuploadBlobs, "reupload-blobs", false, "reupload blobs to the repository") - cmdDebugExamine.Flags().BoolVar(&tryRepair, "try-repair", false, "try to repair broken blobs with single bit flips") - cmdDebugExamine.Flags().BoolVar(&repairByte, "repair-byte", false, "try to repair broken blobs by trying bytes") + cmdDebugExamine.Flags().BoolVar(&debugExamineOpts.ExtractPack, "extract-pack", false, "write blobs to the current directory") + cmdDebugExamine.Flags().BoolVar(&debugExamineOpts.ReuploadBlobs, "reupload-blobs", false, "reupload blobs to the repository") + cmdDebugExamine.Flags().BoolVar(&debugExamineOpts.TryRepair, "try-repair", false, "try to repair broken blobs with single bit flips") + cmdDebugExamine.Flags().BoolVar(&debugExamineOpts.RepairByte, "repair-byte", false, "try to repair broken blobs by trying bytes") } func prettyPrintJSON(wr io.Writer, item interface{}) error { @@ -196,7 +200,7 @@ var cmdDebugExamine = &cobra.Command{ Short: "Examine a pack file", DisableAutoGenTag: true, RunE: func(cmd *cobra.Command, args []string) error { - return runDebugExamine(cmd.Context(), globalOptions, args) + return runDebugExamine(cmd.Context(), globalOptions, debugExamineOpts, args) }, } @@ -315,7 +319,7 @@ func decryptUnsigned(ctx context.Context, k *crypto.Key, buf []byte) []byte { return out } -func loadBlobs(ctx context.Context, repo restic.Repository, packID restic.ID, list []restic.Blob) error { +func loadBlobs(ctx context.Context, opts DebugExamineOptions, repo restic.Repository, packID restic.ID, list []restic.Blob) error { dec, err := zstd.NewReader(nil) if err != nil { panic(err) @@ -328,7 +332,7 @@ func loadBlobs(ctx context.Context, repo restic.Repository, packID restic.ID, li wg, ctx := errgroup.WithContext(ctx) - if reuploadBlobs { + if opts.ReuploadBlobs { repo.StartPackUploader(ctx, wg) } @@ -356,8 +360,8 @@ func loadBlobs(ctx context.Context, repo restic.Repository, packID restic.ID, li filePrefix := "" if err != nil { Warnf("error decrypting blob: %v\n", err) - if tryRepair || repairByte { - plaintext = tryRepairWithBitflip(ctx, key, buf, repairByte) + if opts.TryRepair || opts.RepairByte { + plaintext = tryRepairWithBitflip(ctx, key, buf, opts.RepairByte) } if plaintext != nil { outputPrefix = "repaired " @@ -391,13 +395,13 @@ func loadBlobs(ctx context.Context, repo restic.Repository, packID restic.ID, li Printf(" successfully %vdecrypted blob (length %v), hash is %v, ID matches\n", outputPrefix, len(plaintext), id) prefix = "correct-" } - if extractPack { + if opts.ExtractPack { err = storePlainBlob(id, filePrefix+prefix, plaintext) if err != nil { return err } } - if reuploadBlobs { + if opts.ReuploadBlobs { _, _, _, err := repo.SaveBlob(ctx, blob.Type, plaintext, id, true) if err != nil { return err @@ -406,7 +410,7 @@ func loadBlobs(ctx context.Context, repo restic.Repository, packID restic.ID, li } } - if reuploadBlobs { + if opts.ReuploadBlobs { return repo.Flush(ctx) } return nil @@ -437,7 +441,7 @@ func storePlainBlob(id restic.ID, prefix string, plain []byte) error { return nil } -func runDebugExamine(ctx context.Context, gopts GlobalOptions, args []string) error { +func runDebugExamine(ctx context.Context, gopts GlobalOptions, opts DebugExamineOptions, args []string) error { repo, err := OpenRepository(ctx, gopts) if err != nil { return err @@ -476,7 +480,7 @@ func runDebugExamine(ctx context.Context, gopts GlobalOptions, args []string) er } for _, id := range ids { - err := examinePack(ctx, repo, id) + err := examinePack(ctx, opts, repo, id) if err != nil { Warnf("error: %v\n", err) } @@ -487,7 +491,7 @@ func runDebugExamine(ctx context.Context, gopts GlobalOptions, args []string) er return nil } -func examinePack(ctx context.Context, repo restic.Repository, id restic.ID) error { +func examinePack(ctx context.Context, opts DebugExamineOptions, repo restic.Repository, id restic.ID) error { Printf("examine %v\n", id) h := backend.Handle{ @@ -524,7 +528,7 @@ func examinePack(ctx context.Context, repo restic.Repository, id restic.ID) erro checkPackSize(blobs, fi.Size) - err = loadBlobs(ctx, repo, id, blobs) + err = loadBlobs(ctx, opts, repo, id, blobs) if err != nil { Warnf("error: %v\n", err) } else { @@ -542,7 +546,7 @@ func examinePack(ctx context.Context, repo restic.Repository, id restic.ID) erro checkPackSize(blobs, fi.Size) if !blobsLoaded { - return loadBlobs(ctx, repo, id, blobs) + return loadBlobs(ctx, opts, repo, id, blobs) } return nil } diff --git a/cmd/restic/cmd_diff.go b/cmd/restic/cmd_diff.go index ea40d2860..7878613e1 100644 --- a/cmd/restic/cmd_diff.go +++ b/cmd/restic/cmd_diff.go @@ -401,7 +401,7 @@ func runDiff(ctx context.Context, opts DiffOptions, gopts GlobalOptions, args [] c := &Comparer{ repo: repo, - opts: diffOptions, + opts: opts, printChange: func(change *Change) { Printf("%-5s%v\n", change.Modifier, change.Path) }, diff --git a/cmd/restic/cmd_forget.go b/cmd/restic/cmd_forget.go index a7f39dc4e..65ff449a3 100644 --- a/cmd/restic/cmd_forget.go +++ b/cmd/restic/cmd_forget.go @@ -33,7 +33,7 @@ Exit status is 0 if the command was successful, and non-zero if there was any er `, DisableAutoGenTag: true, RunE: func(cmd *cobra.Command, args []string) error { - return runForget(cmd.Context(), forgetOptions, globalOptions, args) + return runForget(cmd.Context(), forgetOptions, forgetPruneOptions, globalOptions, args) }, } @@ -98,6 +98,7 @@ type ForgetOptions struct { } var forgetOptions ForgetOptions +var forgetPruneOptions PruneOptions func init() { cmdRoot.AddCommand(cmdForget) @@ -132,7 +133,7 @@ func init() { f.BoolVar(&forgetOptions.Prune, "prune", false, "automatically run the 'prune' command if snapshots have been removed") f.SortFlags = false - addPruneOptions(cmdForget) + addPruneOptions(cmdForget, &forgetPruneOptions) } func verifyForgetOptions(opts *ForgetOptions) error { @@ -151,7 +152,7 @@ func verifyForgetOptions(opts *ForgetOptions) error { return nil } -func runForget(ctx context.Context, opts ForgetOptions, gopts GlobalOptions, args []string) error { +func runForget(ctx context.Context, opts ForgetOptions, pruneOptions PruneOptions, gopts GlobalOptions, args []string) error { err := verifyForgetOptions(&opts) if err != nil { return err diff --git a/cmd/restic/cmd_forget_integration_test.go b/cmd/restic/cmd_forget_integration_test.go index 8908d5a5f..1c027a240 100644 --- a/cmd/restic/cmd_forget_integration_test.go +++ b/cmd/restic/cmd_forget_integration_test.go @@ -9,5 +9,8 @@ import ( func testRunForget(t testing.TB, gopts GlobalOptions, args ...string) { opts := ForgetOptions{} - rtest.OK(t, runForget(context.TODO(), opts, gopts, args)) + pruneOpts := PruneOptions{ + MaxUnused: "5%", + } + rtest.OK(t, runForget(context.TODO(), opts, pruneOpts, gopts, args)) } diff --git a/cmd/restic/cmd_generate.go b/cmd/restic/cmd_generate.go index b284767ca..9d1652e93 100644 --- a/cmd/restic/cmd_generate.go +++ b/cmd/restic/cmd_generate.go @@ -21,7 +21,9 @@ EXIT STATUS Exit status is 0 if the command was successful, and non-zero if there was any error. `, DisableAutoGenTag: true, - RunE: runGenerate, + RunE: func(cmd *cobra.Command, args []string) error { + return runGenerate(genOpts, args) + }, } type generateOptions struct { @@ -90,48 +92,48 @@ func writePowerShellCompletion(file string) error { return cmdRoot.GenPowerShellCompletionFile(file) } -func runGenerate(_ *cobra.Command, args []string) error { +func runGenerate(opts generateOptions, args []string) error { if len(args) > 0 { return errors.Fatal("the generate command expects no arguments, only options - please see `restic help generate` for usage and flags") } - if genOpts.ManDir != "" { - err := writeManpages(genOpts.ManDir) + if opts.ManDir != "" { + err := writeManpages(opts.ManDir) if err != nil { return err } } - if genOpts.BashCompletionFile != "" { - err := writeBashCompletion(genOpts.BashCompletionFile) + if opts.BashCompletionFile != "" { + err := writeBashCompletion(opts.BashCompletionFile) if err != nil { return err } } - if genOpts.FishCompletionFile != "" { - err := writeFishCompletion(genOpts.FishCompletionFile) + if opts.FishCompletionFile != "" { + err := writeFishCompletion(opts.FishCompletionFile) if err != nil { return err } } - if genOpts.ZSHCompletionFile != "" { - err := writeZSHCompletion(genOpts.ZSHCompletionFile) + if opts.ZSHCompletionFile != "" { + err := writeZSHCompletion(opts.ZSHCompletionFile) if err != nil { return err } } - if genOpts.PowerShellCompletionFile != "" { - err := writePowerShellCompletion(genOpts.PowerShellCompletionFile) + if opts.PowerShellCompletionFile != "" { + err := writePowerShellCompletion(opts.PowerShellCompletionFile) if err != nil { return err } } var empty generateOptions - if genOpts == empty { + if opts == empty { return errors.Fatal("nothing to do, please specify at least one output file/dir") } diff --git a/cmd/restic/cmd_key.go b/cmd/restic/cmd_key.go index e147f537e..8309f8382 100644 --- a/cmd/restic/cmd_key.go +++ b/cmd/restic/cmd_key.go @@ -29,23 +29,25 @@ Exit status is 0 if the command was successful, and non-zero if there was any er `, DisableAutoGenTag: true, RunE: func(cmd *cobra.Command, args []string) error { - return runKey(cmd.Context(), globalOptions, args) + return runKey(cmd.Context(), globalOptions, keyOpts, args) }, } -var ( - newPasswordFile string - keyUsername string - keyHostname string -) +type KeyOptions struct { + NewPasswordFile string + Username string + Hostname string +} + +var keyOpts KeyOptions func init() { cmdRoot.AddCommand(cmdKey) flags := cmdKey.Flags() - flags.StringVarP(&newPasswordFile, "new-password-file", "", "", "`file` from which to read the new password") - flags.StringVarP(&keyUsername, "user", "", "", "the username for new keys") - flags.StringVarP(&keyHostname, "host", "", "", "the hostname for new keys") + flags.StringVarP(&keyOpts.NewPasswordFile, "new-password-file", "", "", "`file` from which to read the new password") + flags.StringVarP(&keyOpts.Username, "user", "", "", "the username for new keys") + flags.StringVarP(&keyOpts.Hostname, "host", "", "", "the hostname for new keys") } func listKeys(ctx context.Context, s *repository.Repository, gopts GlobalOptions) error { @@ -105,7 +107,7 @@ func listKeys(ctx context.Context, s *repository.Repository, gopts GlobalOptions // testKeyNewPassword is used to set a new password during integration testing. var testKeyNewPassword string -func getNewPassword(gopts GlobalOptions) (string, error) { +func getNewPassword(gopts GlobalOptions, newPasswordFile string) (string, error) { if testKeyNewPassword != "" { return testKeyNewPassword, nil } @@ -124,13 +126,13 @@ func getNewPassword(gopts GlobalOptions) (string, error) { "enter password again: ") } -func addKey(ctx context.Context, repo *repository.Repository, gopts GlobalOptions) error { - pw, err := getNewPassword(gopts) +func addKey(ctx context.Context, repo *repository.Repository, gopts GlobalOptions, opts KeyOptions) error { + pw, err := getNewPassword(gopts, opts.NewPasswordFile) if err != nil { return err } - id, err := repository.AddKey(ctx, repo, pw, keyUsername, keyHostname, repo.Key()) + id, err := repository.AddKey(ctx, repo, pw, opts.Username, opts.Hostname, repo.Key()) if err != nil { return errors.Fatalf("creating new key failed: %v\n", err) } @@ -160,8 +162,8 @@ func deleteKey(ctx context.Context, repo *repository.Repository, id restic.ID) e return nil } -func changePassword(ctx context.Context, repo *repository.Repository, gopts GlobalOptions) error { - pw, err := getNewPassword(gopts) +func changePassword(ctx context.Context, repo *repository.Repository, gopts GlobalOptions, newPasswordFile string) error { + pw, err := getNewPassword(gopts, newPasswordFile) if err != nil { return err } @@ -201,7 +203,7 @@ func switchToNewKeyAndRemoveIfBroken(ctx context.Context, repo *repository.Repos return nil } -func runKey(ctx context.Context, gopts GlobalOptions, args []string) error { +func runKey(ctx context.Context, gopts GlobalOptions, opts KeyOptions, args []string) error { if len(args) < 1 || (args[0] == "remove" && len(args) != 2) || (args[0] != "remove" && len(args) != 1) { return errors.Fatal("wrong number of arguments") } @@ -230,7 +232,7 @@ func runKey(ctx context.Context, gopts GlobalOptions, args []string) error { return err } - return addKey(ctx, repo, gopts) + return addKey(ctx, repo, gopts, opts) case "remove": lock, ctx, err := lockRepoExclusive(ctx, repo, gopts.RetryLock, gopts.JSON) defer unlockRepo(lock) @@ -251,7 +253,7 @@ func runKey(ctx context.Context, gopts GlobalOptions, args []string) error { return err } - return changePassword(ctx, repo, gopts) + return changePassword(ctx, repo, gopts, opts.NewPasswordFile) } return nil diff --git a/cmd/restic/cmd_key_integration_test.go b/cmd/restic/cmd_key_integration_test.go index f68799dde..34474c3af 100644 --- a/cmd/restic/cmd_key_integration_test.go +++ b/cmd/restic/cmd_key_integration_test.go @@ -13,7 +13,7 @@ import ( func testRunKeyListOtherIDs(t testing.TB, gopts GlobalOptions) []string { buf, err := withCaptureStdout(func() error { - return runKey(context.TODO(), gopts, []string{"list"}) + return runKey(context.TODO(), gopts, KeyOptions{}, []string{"list"}) }) rtest.OK(t, err) @@ -36,21 +36,20 @@ func testRunKeyAddNewKey(t testing.TB, newPassword string, gopts GlobalOptions) testKeyNewPassword = "" }() - rtest.OK(t, runKey(context.TODO(), gopts, []string{"add"})) + rtest.OK(t, runKey(context.TODO(), gopts, KeyOptions{}, []string{"add"})) } func testRunKeyAddNewKeyUserHost(t testing.TB, gopts GlobalOptions) { testKeyNewPassword = "john's geheimnis" defer func() { testKeyNewPassword = "" - keyUsername = "" - keyHostname = "" }() - rtest.OK(t, cmdKey.Flags().Parse([]string{"--user=john", "--host=example.com"})) - t.Log("adding key for john@example.com") - rtest.OK(t, runKey(context.TODO(), gopts, []string{"add"})) + rtest.OK(t, runKey(context.TODO(), gopts, KeyOptions{ + Username: "john", + Hostname: "example.com", + }, []string{"add"})) repo, err := OpenRepository(context.TODO(), gopts) rtest.OK(t, err) @@ -67,13 +66,13 @@ func testRunKeyPasswd(t testing.TB, newPassword string, gopts GlobalOptions) { testKeyNewPassword = "" }() - rtest.OK(t, runKey(context.TODO(), gopts, []string{"passwd"})) + rtest.OK(t, runKey(context.TODO(), gopts, KeyOptions{}, []string{"passwd"})) } func testRunKeyRemove(t testing.TB, gopts GlobalOptions, IDs []string) { t.Logf("remove %d keys: %q\n", len(IDs), IDs) for _, id := range IDs { - rtest.OK(t, runKey(context.TODO(), gopts, []string{"remove", id})) + rtest.OK(t, runKey(context.TODO(), gopts, KeyOptions{}, []string{"remove", id})) } } @@ -103,7 +102,7 @@ func TestKeyAddRemove(t *testing.T) { env.gopts.password = passwordList[len(passwordList)-1] t.Logf("testing access with last password %q\n", env.gopts.password) - rtest.OK(t, runKey(context.TODO(), env.gopts, []string{"list"})) + rtest.OK(t, runKey(context.TODO(), env.gopts, KeyOptions{}, []string{"list"})) testRunCheck(t, env.gopts) testRunKeyAddNewKeyUserHost(t, env.gopts) @@ -131,15 +130,15 @@ func TestKeyProblems(t *testing.T) { testKeyNewPassword = "" }() - err := runKey(context.TODO(), env.gopts, []string{"passwd"}) + err := runKey(context.TODO(), env.gopts, KeyOptions{}, []string{"passwd"}) t.Log(err) rtest.Assert(t, err != nil, "expected passwd change to fail") - err = runKey(context.TODO(), env.gopts, []string{"add"}) + err = runKey(context.TODO(), env.gopts, KeyOptions{}, []string{"add"}) 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) - rtest.OK(t, runKey(context.TODO(), env.gopts, []string{"list"})) + rtest.OK(t, runKey(context.TODO(), env.gopts, KeyOptions{}, []string{"list"})) testRunCheck(t, env.gopts) } diff --git a/cmd/restic/cmd_list.go b/cmd/restic/cmd_list.go index 38f8b094a..8be99234f 100644 --- a/cmd/restic/cmd_list.go +++ b/cmd/restic/cmd_list.go @@ -23,7 +23,7 @@ Exit status is 0 if the command was successful, and non-zero if there was any er `, DisableAutoGenTag: true, RunE: func(cmd *cobra.Command, args []string) error { - return runList(cmd.Context(), cmd, globalOptions, args) + return runList(cmd.Context(), globalOptions, args) }, } @@ -31,9 +31,9 @@ func init() { cmdRoot.AddCommand(cmdList) } -func runList(ctx context.Context, cmd *cobra.Command, gopts GlobalOptions, args []string) error { +func runList(ctx context.Context, gopts GlobalOptions, args []string) error { if len(args) != 1 { - return errors.Fatal("type not specified, usage: " + cmd.Use) + return errors.Fatal("type not specified") } repo, err := OpenRepository(ctx, gopts) diff --git a/cmd/restic/cmd_list_integration_test.go b/cmd/restic/cmd_list_integration_test.go index 4140a3ea8..ef2b8bf8f 100644 --- a/cmd/restic/cmd_list_integration_test.go +++ b/cmd/restic/cmd_list_integration_test.go @@ -12,7 +12,7 @@ import ( func testRunList(t testing.TB, tpe string, opts GlobalOptions) restic.IDs { buf, err := withCaptureStdout(func() error { - return runList(context.TODO(), cmdList, opts, []string{tpe}) + return runList(context.TODO(), opts, []string{tpe}) }) rtest.OK(t, err) return parseIDsFromReader(t, buf) diff --git a/cmd/restic/cmd_ls.go b/cmd/restic/cmd_ls.go index 07c49d60f..d30e2819c 100644 --- a/cmd/restic/cmd_ls.go +++ b/cmd/restic/cmd_ls.go @@ -210,7 +210,7 @@ func runLs(ctx context.Context, opts LsOptions, gopts GlobalOptions, args []stri Verbosef("%v filtered by %v:\n", sn, dirs) } printNode = func(path string, node *restic.Node) { - Printf("%s\n", formatNode(path, node, lsOptions.ListLong, lsOptions.HumanReadable)) + Printf("%s\n", formatNode(path, node, opts.ListLong, opts.HumanReadable)) } } diff --git a/cmd/restic/cmd_prune.go b/cmd/restic/cmd_prune.go index 739a450df..10abbf9f0 100644 --- a/cmd/restic/cmd_prune.go +++ b/cmd/restic/cmd_prune.go @@ -66,10 +66,10 @@ func init() { f := cmdPrune.Flags() f.BoolVarP(&pruneOptions.DryRun, "dry-run", "n", false, "do not modify the repository, just print what would be done") f.StringVarP(&pruneOptions.UnsafeNoSpaceRecovery, "unsafe-recover-no-free-space", "", "", "UNSAFE, READ THE DOCUMENTATION BEFORE USING! Try to recover a repository stuck with no free space. Do not use without trying out 'prune --max-repack-size 0' first.") - addPruneOptions(cmdPrune) + addPruneOptions(cmdPrune, &pruneOptions) } -func addPruneOptions(c *cobra.Command) { +func addPruneOptions(c *cobra.Command, pruneOptions *PruneOptions) { f := c.Flags() f.StringVar(&pruneOptions.MaxUnused, "max-unused", "5%", "tolerate given `limit` of unused data (absolute value in bytes with suffixes k/K, m/M, g/G, t/T, a value in % or the word 'unlimited')") f.StringVar(&pruneOptions.MaxRepackSize, "max-repack-size", "", "maximum `size` to repack (allowed suffixes: k/K, m/M, g/G, t/T)") diff --git a/cmd/restic/cmd_prune_integration_test.go b/cmd/restic/cmd_prune_integration_test.go index 53e27ee10..ebfa7ae4e 100644 --- a/cmd/restic/cmd_prune_integration_test.go +++ b/cmd/restic/cmd_prune_integration_test.go @@ -81,7 +81,10 @@ func testRunForgetJSON(t testing.TB, gopts GlobalOptions, args ...string) { DryRun: true, Last: 1, } - return runForget(context.TODO(), opts, gopts, args) + pruneOpts := PruneOptions{ + MaxUnused: "5%", + } + return runForget(context.TODO(), opts, pruneOpts, gopts, args) }) rtest.OK(t, err)