Merge pull request #21850 from MichaelEischer/misc-cleanups

Cleanup `ui/*` package and minor cleanups in `test` and `walker`
This commit is contained in:
Michael Eischer
2026-06-13 17:12:14 +02:00
committed by GitHub
71 changed files with 485 additions and 522 deletions
+1 -2
View File
@@ -533,8 +533,7 @@ func runBackup(ctx context.Context, opts BackupOptions, gopts global.Options, te
}
defer unlock()
progressReporter := backup.NewProgress(printer,
ui.CalculateProgressInterval(!gopts.Quiet, gopts.JSON, term.CanUpdateStatus()))
progressReporter := backup.NewProgress(printer, gopts.Quiet, gopts.JSON, term.CanUpdateStatus())
defer progressReporter.Done()
// rejectByNameFuncs collect functions that can reject items from the backup based on path only
+2 -1
View File
@@ -12,6 +12,7 @@ import (
"github.com/restic/restic/internal/errors"
"github.com/restic/restic/internal/global"
"github.com/restic/restic/internal/ui"
"github.com/restic/restic/internal/ui/progress"
"github.com/restic/restic/internal/ui/table"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
@@ -57,7 +58,7 @@ func (opts *CacheOptions) AddFlags(f *pflag.FlagSet) {
}
func runCache(opts CacheOptions, gopts global.Options, args []string, term ui.Terminal) error {
printer := ui.NewProgressPrinter(false, gopts.Verbosity, term)
printer := progress.NewTerminalPrinter(false, gopts.Verbosity, term)
if len(args) > 0 {
return errors.Fatal("the cache command expects no arguments, only options - please see `restic help cache` for usage and flags")
+2 -1
View File
@@ -13,6 +13,7 @@ import (
"github.com/restic/restic/internal/repository"
"github.com/restic/restic/internal/restic"
"github.com/restic/restic/internal/ui"
"github.com/restic/restic/internal/ui/progress"
)
var catAllowedCmds = []string{"config", "index", "snapshot", "key", "masterkey", "lock", "pack", "blob", "tree"}
@@ -67,7 +68,7 @@ func validateCatArgs(args []string) error {
}
func runCat(ctx context.Context, gopts global.Options, args []string, term ui.Terminal) error {
printer := ui.NewProgressPrinter(gopts.JSON, gopts.Verbosity, term)
printer := progress.NewTerminalPrinter(gopts.JSON, gopts.Verbosity, term)
if err := validateCatArgs(args); err != nil {
return err
+1 -1
View File
@@ -231,7 +231,7 @@ func runCheck(ctx context.Context, opts CheckOptions, gopts global.Options, args
var printer progress.Printer
if !gopts.JSON {
printer = ui.NewProgressPrinter(gopts.JSON, gopts.Verbosity, term)
printer = progress.NewTerminalPrinter(gopts.JSON, gopts.Verbosity, term)
} else {
printer = newJSONErrorPrinter(term)
}
+2 -2
View File
@@ -204,7 +204,7 @@ func TestPrepareCheckCache(t *testing.T) {
rtest.OK(t, err)
}
gopts := global.Options{CacheDir: tmpDirBase}
cleanup := prepareCheckCache(testCase.opts, &gopts, &progress.NoopPrinter{})
cleanup := prepareCheckCache(testCase.opts, &gopts, progress.NewNoopPrinter())
files, err := os.ReadDir(tmpDirBase)
rtest.OK(t, err)
@@ -234,7 +234,7 @@ func TestPrepareCheckCache(t *testing.T) {
func TestPrepareDefaultCheckCache(t *testing.T) {
gopts := global.Options{CacheDir: ""}
cleanup := prepareCheckCache(CheckOptions{}, &gopts, &progress.NoopPrinter{})
cleanup := prepareCheckCache(CheckOptions{}, &gopts, progress.NewNoopPrinter())
_, err := os.ReadDir(gopts.CacheDir)
rtest.OK(t, err)
+1 -1
View File
@@ -106,7 +106,7 @@ func collectAllSnapshots(ctx context.Context, opts CopyOptions,
}
func runCopy(ctx context.Context, opts CopyOptions, gopts global.Options, args []string, term ui.Terminal) error {
printer := ui.NewProgressPrinter(false, gopts.Verbosity, term)
printer := progress.NewTerminalPrinter(false, gopts.Verbosity, term)
secondaryGopts, isFromRepo, err := opts.SecondaryRepoOptions.FillGlobalOpts(ctx, gopts, "destination")
if err != nil {
return err
+2 -2
View File
@@ -9,7 +9,7 @@ import (
"github.com/restic/restic/internal/global"
"github.com/restic/restic/internal/restic"
rtest "github.com/restic/restic/internal/test"
"github.com/restic/restic/internal/ui"
"github.com/restic/restic/internal/ui/progress"
)
func testRunCopy(t testing.TB, srcGopts global.Options, dstGopts global.Options) {
@@ -97,7 +97,7 @@ func TestCopy(t *testing.T) {
func testPackAndBlobCounts(t testing.TB, gopts global.Options) (countTreePacks int, countDataPacks int, countBlobs int) {
rtest.OK(t, withTermStatus(t, gopts, func(ctx context.Context, gopts global.Options) error {
printer := ui.NewProgressPrinter(gopts.JSON, gopts.Verbosity, gopts.Term)
printer := progress.NewTerminalPrinter(gopts.JSON, gopts.Verbosity, gopts.Term)
_, repo, unlock, err := openWithReadLock(ctx, gopts, false, printer)
rtest.OK(t, err)
defer unlock()
+3 -2
View File
@@ -17,6 +17,7 @@ import (
"github.com/restic/restic/internal/repository"
"github.com/restic/restic/internal/restic"
"github.com/restic/restic/internal/ui"
"github.com/restic/restic/internal/ui/progress"
)
func registerDebugCommand(cmd *cobra.Command, globalOptions *global.Options) {
@@ -117,7 +118,7 @@ func debugPrintSnapshots(ctx context.Context, repo *repository.Repository, wr io
}
func runDebugDump(ctx context.Context, gopts global.Options, args []string, term ui.Terminal) error {
printer := ui.NewProgressPrinter(false, gopts.Verbosity, term)
printer := progress.NewTerminalPrinter(false, gopts.Verbosity, term)
if len(args) != 1 {
return errors.Fatal("type not specified")
@@ -158,7 +159,7 @@ func runDebugDump(ctx context.Context, gopts global.Options, args []string, term
}
func runDebugExamine(ctx context.Context, gopts global.Options, opts DebugExamineOptions, args []string, term ui.Terminal) error {
printer := ui.NewProgressPrinter(false, gopts.Verbosity, term)
printer := progress.NewTerminalPrinter(false, gopts.Verbosity, term)
if opts.ExtractPack && gopts.NoLock {
return fmt.Errorf("--extract-pack and --no-lock are mutually exclusive")
+2 -1
View File
@@ -12,6 +12,7 @@ import (
"github.com/restic/restic/internal/global"
"github.com/restic/restic/internal/restic"
"github.com/restic/restic/internal/ui"
"github.com/restic/restic/internal/ui/progress"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
)
@@ -358,7 +359,7 @@ func runDiff(ctx context.Context, opts DiffOptions, gopts global.Options, args [
return errors.Fatalf("specify two snapshot IDs")
}
printer := ui.NewProgressPrinter(gopts.JSON, gopts.Verbosity, term)
printer := progress.NewTerminalPrinter(gopts.JSON, gopts.Verbosity, term)
ctx, repo, unlock, err := openWithReadLock(ctx, gopts, gopts.NoLock, printer)
if err != nil {
+2 -1
View File
@@ -14,6 +14,7 @@ import (
"github.com/restic/restic/internal/global"
"github.com/restic/restic/internal/restic"
"github.com/restic/restic/internal/ui"
"github.com/restic/restic/internal/ui/progress"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
@@ -136,7 +137,7 @@ func runDump(ctx context.Context, opts DumpOptions, gopts global.Options, args [
return errors.Fatal("no file and no snapshot ID specified")
}
printer := ui.NewProgressPrinter(gopts.JSON, gopts.Verbosity, term)
printer := progress.NewTerminalPrinter(gopts.JSON, gopts.Verbosity, term)
switch opts.Archive {
case "tar", "zip":
+2 -1
View File
@@ -19,6 +19,7 @@ import (
"github.com/restic/restic/internal/global"
"github.com/restic/restic/internal/restic"
"github.com/restic/restic/internal/ui"
"github.com/restic/restic/internal/ui/progress"
"github.com/restic/restic/internal/walker"
)
@@ -599,7 +600,7 @@ func runFind(ctx context.Context, opts FindOptions, gopts global.Options, args [
return errors.Fatal("wrong number of arguments")
}
printer := ui.NewProgressPrinter(gopts.JSON, gopts.Verbosity, term)
printer := progress.NewTerminalPrinter(gopts.JSON, gopts.Verbosity, term)
var err error
pat := findPattern{pattern: args}
+3 -3
View File
@@ -12,7 +12,7 @@ import (
"github.com/restic/restic/internal/global"
"github.com/restic/restic/internal/restic"
rtest "github.com/restic/restic/internal/test"
"github.com/restic/restic/internal/ui"
"github.com/restic/restic/internal/ui/progress"
)
func testRunFind(t testing.TB, wantJSON bool, opts FindOptions, gopts global.Options, pattern string) []byte {
@@ -169,7 +169,7 @@ func TestFindPackfile(t *testing.T) {
// do all the testing wrapped inside withTermStatus()
err := withTermStatus(t, env.gopts, func(ctx context.Context, gopts global.Options) error {
printer := ui.NewProgressPrinter(gopts.JSON, gopts.Verbosity, gopts.Term)
printer := progress.NewTerminalPrinter(gopts.JSON, gopts.Verbosity, gopts.Term)
_, repo, unlock, err := openWithReadLock(ctx, gopts, false, printer)
rtest.OK(t, err)
defer unlock()
@@ -228,7 +228,7 @@ func TestFindPackID(t *testing.T) {
dataPackID := restic.ID{}
treePackID := restic.ID{}
err = withTermStatus(t, env.gopts, func(ctx context.Context, gopts global.Options) error {
printer := ui.NewProgressPrinter(gopts.JSON, gopts.Verbosity, gopts.Term)
printer := progress.NewTerminalPrinter(gopts.JSON, gopts.Verbosity, gopts.Term)
_, repo, unlock, err := openWithReadLock(ctx, gopts, false, printer)
rtest.OK(t, err)
defer unlock()
+2 -1
View File
@@ -12,6 +12,7 @@ import (
"github.com/restic/restic/internal/global"
"github.com/restic/restic/internal/restic"
"github.com/restic/restic/internal/ui"
"github.com/restic/restic/internal/ui/progress"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
)
@@ -189,7 +190,7 @@ func runForget(ctx context.Context, opts ForgetOptions, pruneOptions PruneOption
return errors.Fatal("--no-lock is only applicable in combination with --dry-run for forget command")
}
printer := ui.NewProgressPrinter(gopts.JSON, gopts.Verbosity, term)
printer := progress.NewTerminalPrinter(gopts.JSON, gopts.Verbosity, term)
ctx, repo, unlock, err := openWithExclusiveLock(ctx, gopts, opts.DryRun && gopts.NoLock, printer)
if err != nil {
return err
+1 -1
View File
@@ -116,7 +116,7 @@ func runGenerate(opts generateOptions, gopts global.Options, args []string, term
return errors.Fatal("the generate command expects no arguments, only options - please see `restic help generate` for usage and flags")
}
printer := ui.NewProgressPrinter(gopts.JSON, gopts.Verbosity, term)
printer := progress.NewTerminalPrinter(gopts.JSON, gopts.Verbosity, term)
cmdRoot := newRootCommand(&global.Options{})
if opts.ManDir != "" {
+1 -1
View File
@@ -60,7 +60,7 @@ func runInit(ctx context.Context, opts InitOptions, gopts global.Options, args [
return errors.Fatal("the init command expects no arguments, only options - please see `restic help init` for usage and flags")
}
printer := ui.NewProgressPrinter(gopts.JSON, gopts.Verbosity, term)
printer := progress.NewTerminalPrinter(gopts.JSON, gopts.Verbosity, term)
var version uint
switch opts.RepositoryVersion {
+2 -2
View File
@@ -57,14 +57,14 @@ func TestInitCopyChunkerParams(t *testing.T) {
var repo *repository.Repository
err = withTermStatus(t, env.gopts, func(ctx context.Context, gopts global.Options) error {
repo, err = global.OpenRepository(ctx, gopts, &progress.NoopPrinter{})
repo, err = global.OpenRepository(ctx, gopts, progress.NewNoopPrinter())
return err
})
rtest.OK(t, err)
var otherRepo *repository.Repository
err = withTermStatus(t, env2.gopts, func(ctx context.Context, gopts global.Options) error {
otherRepo, err = global.OpenRepository(ctx, gopts, &progress.NoopPrinter{})
otherRepo, err = global.OpenRepository(ctx, gopts, progress.NewNoopPrinter())
return err
})
rtest.OK(t, err)
+1 -1
View File
@@ -60,7 +60,7 @@ func runKeyAdd(ctx context.Context, gopts global.Options, opts KeyAddOptions, ar
return fmt.Errorf("the key add command expects no arguments, only options - please see `restic help key add` for usage and flags")
}
printer := ui.NewProgressPrinter(false, gopts.Verbosity, term)
printer := progress.NewTerminalPrinter(false, gopts.Verbosity, term)
ctx, repo, unlock, err := openWithAppendLock(ctx, gopts, false, printer)
if err != nil {
return err
+2 -2
View File
@@ -63,7 +63,7 @@ func testRunKeyAddNewKeyUserHost(t testing.TB, gopts global.Options) {
rtest.OK(t, err)
_ = withTermStatus(t, gopts, func(ctx context.Context, gopts global.Options) error {
repo, err := global.OpenRepository(ctx, gopts, &progress.NoopPrinter{})
repo, err := global.OpenRepository(ctx, gopts, progress.NewNoopPrinter())
rtest.OK(t, err)
key, err := repository.SearchKey(ctx, repo, testKeyNewPassword, 2, "")
rtest.OK(t, err)
@@ -105,7 +105,7 @@ func testRunKeyPasswdUserHost(t testing.TB, newPassword string, gopts global.Opt
gopts.Password = testKeyNewPassword
_ = withTermStatus(t, gopts, func(ctx context.Context, gopts global.Options) error {
repo, err := global.OpenRepository(ctx, gopts, &progress.NoopPrinter{})
repo, err := global.OpenRepository(ctx, gopts, progress.NewNoopPrinter())
rtest.OK(t, err)
key, err := repository.SearchKey(ctx, repo, testKeyNewPassword, 1, "")
rtest.OK(t, err)
+1 -1
View File
@@ -46,7 +46,7 @@ func runKeyList(ctx context.Context, gopts global.Options, args []string, term u
return fmt.Errorf("the key list command expects no arguments, only options - please see `restic help key list` for usage and flags")
}
printer := ui.NewProgressPrinter(gopts.JSON, gopts.Verbosity, term)
printer := progress.NewTerminalPrinter(gopts.JSON, gopts.Verbosity, term)
ctx, repo, unlock, err := openWithReadLock(ctx, gopts, gopts.NoLock, printer)
if err != nil {
return err
+1 -1
View File
@@ -55,7 +55,7 @@ func runKeyPasswd(ctx context.Context, gopts global.Options, opts KeyPasswdOptio
return fmt.Errorf("the key passwd command expects no arguments, only options - please see `restic help key passwd` for usage and flags")
}
printer := ui.NewProgressPrinter(false, gopts.Verbosity, term)
printer := progress.NewTerminalPrinter(false, gopts.Verbosity, term)
ctx, repo, unlock, err := openWithExclusiveLock(ctx, gopts, false, printer)
if err != nil {
return err
+1 -1
View File
@@ -43,7 +43,7 @@ func runKeyRemove(ctx context.Context, gopts global.Options, args []string, term
return fmt.Errorf("key remove expects one argument as the key id")
}
printer := ui.NewProgressPrinter(gopts.JSON, gopts.Verbosity, term)
printer := progress.NewTerminalPrinter(gopts.JSON, gopts.Verbosity, term)
ctx, repo, unlock, err := openWithExclusiveLock(ctx, gopts, false, printer)
if err != nil {
return err
+2 -1
View File
@@ -9,6 +9,7 @@ import (
"github.com/restic/restic/internal/repository"
"github.com/restic/restic/internal/restic"
"github.com/restic/restic/internal/ui"
"github.com/restic/restic/internal/ui/progress"
"github.com/spf13/cobra"
)
@@ -44,7 +45,7 @@ Exit status is 12 if the password is incorrect.
}
func runList(ctx context.Context, gopts global.Options, args []string, term ui.Terminal) error {
printer := ui.NewProgressPrinter(false, gopts.Verbosity, term)
printer := progress.NewTerminalPrinter(false, gopts.Verbosity, term)
if len(args) != 1 {
return errors.Fatal("type not specified")
+2 -2
View File
@@ -11,7 +11,7 @@ import (
"github.com/restic/restic/internal/global"
"github.com/restic/restic/internal/restic"
rtest "github.com/restic/restic/internal/test"
"github.com/restic/restic/internal/ui"
"github.com/restic/restic/internal/ui/progress"
)
func testRunList(t testing.TB, gopts global.Options, tpe string) restic.IDs {
@@ -61,7 +61,7 @@ func testListSnapshots(t testing.TB, gopts global.Options, expected int) restic.
// extract blob set from repository index
func testListBlobs(t testing.TB, gopts global.Options) (blobSetFromIndex restic.IDSet) {
err := withTermStatus(t, gopts, func(ctx context.Context, gopts global.Options) error {
printer := ui.NewProgressPrinter(gopts.JSON, gopts.Verbosity, gopts.Term)
printer := progress.NewTerminalPrinter(gopts.JSON, gopts.Verbosity, gopts.Term)
_, repo, unlock, err := openWithReadLock(ctx, gopts, false, printer)
rtest.OK(t, err)
defer unlock()
+2 -1
View File
@@ -21,6 +21,7 @@ import (
"github.com/restic/restic/internal/global"
"github.com/restic/restic/internal/restic"
"github.com/restic/restic/internal/ui"
"github.com/restic/restic/internal/ui/progress"
"github.com/restic/restic/internal/walker"
)
@@ -305,7 +306,7 @@ type toSortOutput struct {
}
func runLs(ctx context.Context, opts LsOptions, gopts global.Options, args []string, term ui.Terminal) error {
termPrinter := ui.NewProgressPrinter(gopts.JSON, gopts.Verbosity, term)
termPrinter := progress.NewTerminalPrinter(gopts.JSON, gopts.Verbosity, term)
if len(args) == 0 {
return errors.Fatal("no snapshot ID specified, specify snapshot ID or use special ID 'latest'")
+1 -1
View File
@@ -135,7 +135,7 @@ func applyMigrations(ctx context.Context, opts MigrateOptions, gopts global.Opti
}
func runMigrate(ctx context.Context, opts MigrateOptions, gopts global.Options, args []string, term ui.Terminal) error {
printer := ui.NewProgressPrinter(false, gopts.Verbosity, term)
printer := progress.NewTerminalPrinter(false, gopts.Verbosity, term)
ctx, repo, unlock, err := openWithExclusiveLock(ctx, gopts, false, printer)
if err != nil {
+2 -1
View File
@@ -21,6 +21,7 @@ import (
"github.com/restic/restic/internal/errors"
"github.com/restic/restic/internal/global"
"github.com/restic/restic/internal/ui"
"github.com/restic/restic/internal/ui/progress"
"github.com/restic/restic/internal/fuse"
@@ -119,7 +120,7 @@ func (opts *MountOptions) AddFlags(f *pflag.FlagSet) {
}
func runMount(ctx context.Context, opts MountOptions, gopts global.Options, args []string, term ui.Terminal) error {
printer := ui.NewProgressPrinter(false, gopts.Verbosity, term)
printer := progress.NewTerminalPrinter(false, gopts.Verbosity, term)
if opts.TimeTemplate == "" {
return errors.Fatal("time template string cannot be empty")
+2 -2
View File
@@ -18,7 +18,7 @@ import (
"github.com/restic/restic/internal/global"
"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"
)
const (
@@ -131,7 +131,7 @@ func checkSnapshots(t testing.TB, gopts global.Options, mountpoint string, snaps
}
err := withTermStatus(t, gopts, func(ctx context.Context, gopts global.Options) error {
printer := ui.NewProgressPrinter(gopts.JSON, gopts.Verbosity, gopts.Term)
printer := progress.NewTerminalPrinter(gopts.JSON, gopts.Verbosity, gopts.Term)
_, repo, unlock, err := openWithReadLock(ctx, gopts, false, printer)
if err != nil {
return err
+1 -1
View File
@@ -175,7 +175,7 @@ func runPrune(ctx context.Context, opts PruneOptions, gopts global.Options, term
return errors.Fatal("--no-lock is only applicable in combination with --dry-run for prune command")
}
printer := ui.NewProgressPrinter(gopts.JSON, gopts.Verbosity, term)
printer := progress.NewTerminalPrinter(gopts.JSON, gopts.Verbosity, term)
ctx, repo, unlock, err := openWithExclusiveLock(ctx, gopts, opts.DryRun && gopts.NoLock, printer)
if err != nil {
return err
+1 -1
View File
@@ -48,7 +48,7 @@ func runRecover(ctx context.Context, gopts global.Options, term ui.Terminal) err
return err
}
printer := ui.NewProgressPrinter(false, gopts.Verbosity, term)
printer := progress.NewTerminalPrinter(false, gopts.Verbosity, term)
ctx, repo, unlock, err := openWithExclusiveLock(ctx, gopts, false, printer)
if err != nil {
return err
+2 -1
View File
@@ -6,6 +6,7 @@ import (
"github.com/restic/restic/internal/global"
"github.com/restic/restic/internal/repository"
"github.com/restic/restic/internal/ui"
"github.com/restic/restic/internal/ui/progress"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
)
@@ -70,7 +71,7 @@ func newRebuildIndexCommand(globalOptions *global.Options) *cobra.Command {
}
func runRebuildIndex(ctx context.Context, opts RepairIndexOptions, gopts global.Options, term ui.Terminal) error {
printer := ui.NewProgressPrinter(false, gopts.Verbosity, term)
printer := progress.NewTerminalPrinter(false, gopts.Verbosity, term)
ctx, repo, unlock, err := openWithExclusiveLock(ctx, gopts, false, printer)
if err != nil {
+2 -1
View File
@@ -11,6 +11,7 @@ import (
"github.com/restic/restic/internal/repository"
"github.com/restic/restic/internal/restic"
"github.com/restic/restic/internal/ui"
"github.com/restic/restic/internal/ui/progress"
"github.com/spf13/cobra"
)
@@ -52,7 +53,7 @@ func runRepairPacks(ctx context.Context, gopts global.Options, term ui.Terminal,
return errors.Fatal("no ids specified")
}
printer := ui.NewProgressPrinter(false, gopts.Verbosity, term)
printer := progress.NewTerminalPrinter(false, gopts.Verbosity, term)
ctx, repo, unlock, err := openWithExclusiveLock(ctx, gopts, false, printer)
if err != nil {
+2 -1
View File
@@ -9,6 +9,7 @@ import (
"github.com/restic/restic/internal/global"
"github.com/restic/restic/internal/restic"
"github.com/restic/restic/internal/ui"
"github.com/restic/restic/internal/ui/progress"
"github.com/restic/restic/internal/walker"
"github.com/spf13/cobra"
@@ -78,7 +79,7 @@ func (opts *RepairOptions) AddFlags(f *pflag.FlagSet) {
}
func runRepairSnapshots(ctx context.Context, gopts global.Options, opts RepairOptions, args []string, term ui.Terminal) error {
printer := ui.NewProgressPrinter(false, gopts.Verbosity, term)
printer := progress.NewTerminalPrinter(false, gopts.Verbosity, term)
ctx, repo, unlock, err := openWithExclusiveLock(ctx, gopts, opts.DryRun, printer)
if err != nil {
+1 -1
View File
@@ -167,7 +167,7 @@ func runRestore(ctx context.Context, opts RestoreOptions, gopts global.Options,
return err
}
progress := restoreui.NewProgress(printer, ui.CalculateProgressInterval(!gopts.Quiet, gopts.JSON, term.CanUpdateStatus()))
progress := restoreui.NewProgress(printer, gopts.Quiet, gopts.JSON, term.CanUpdateStatus())
res := restorer.NewRestorer(repo, sn, restorer.Options{
DryRun: opts.DryRun,
Sparse: opts.Sparse,
+1 -1
View File
@@ -298,7 +298,7 @@ func runRewrite(ctx context.Context, opts RewriteOptions, gopts global.Options,
return errors.Fatal("exclude and include patterns are mutually exclusive")
}
printer := ui.NewProgressPrinter(false, gopts.Verbosity, term)
printer := progress.NewTerminalPrinter(false, gopts.Verbosity, term)
var (
repo *repository.Repository
+4 -3
View File
@@ -13,6 +13,7 @@ import (
"github.com/restic/restic/internal/restic"
rtest "github.com/restic/restic/internal/test"
"github.com/restic/restic/internal/ui"
"github.com/restic/restic/internal/ui/progress"
)
func testRunRewriteExclude(t testing.TB, gopts global.Options, excludes []string, forget bool, metadata snapshotMetadataArgs) {
@@ -81,7 +82,7 @@ func getSnapshot(t testing.TB, snapshotID restic.ID, env *testEnvironment) *data
var snapshots []*data.Snapshot
err := withTermStatus(t, env.gopts, func(ctx context.Context, gopts global.Options) error {
printer := ui.NewProgressPrinter(gopts.JSON, gopts.Verbosity, gopts.Term)
printer := progress.NewTerminalPrinter(gopts.JSON, gopts.Verbosity, gopts.Term)
ctx, repo, unlock, err := openWithReadLock(ctx, gopts, false, printer)
rtest.OK(t, err)
defer unlock()
@@ -157,7 +158,7 @@ func testRewriteMetadata(t *testing.T, metadata snapshotMetadataArgs) {
var snapshots []*data.Snapshot
err := withTermStatus(t, env.gopts, func(ctx context.Context, gopts global.Options) error {
printer := ui.NewProgressPrinter(gopts.JSON, gopts.Verbosity, gopts.Term)
printer := progress.NewTerminalPrinter(gopts.JSON, gopts.Verbosity, gopts.Term)
ctx, repo, unlock, err := openWithReadLock(ctx, gopts, false, printer)
rtest.OK(t, err)
defer unlock()
@@ -205,7 +206,7 @@ func TestRewriteSnaphotSummary(t *testing.T) {
// replace snapshot by one without a summary
var oldSummary *data.SnapshotSummary
err := withTermStatus(t, env.gopts, func(ctx context.Context, gopts global.Options) error {
printer := ui.NewProgressPrinter(gopts.JSON, gopts.Verbosity, gopts.Term)
printer := progress.NewTerminalPrinter(gopts.JSON, gopts.Verbosity, gopts.Term)
_, repo, unlock, err := openWithExclusiveLock(ctx, gopts, false, printer)
rtest.OK(t, err)
defer unlock()
+2 -1
View File
@@ -11,6 +11,7 @@ import (
"github.com/restic/restic/internal/global"
"github.com/restic/restic/internal/selfupdate"
"github.com/restic/restic/internal/ui"
"github.com/restic/restic/internal/ui/progress"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
)
@@ -84,7 +85,7 @@ func runSelfUpdate(ctx context.Context, opts SelfUpdateOptions, gopts global.Opt
}
}
printer := ui.NewProgressPrinter(false, gopts.Verbosity, term)
printer := progress.NewTerminalPrinter(false, gopts.Verbosity, term)
printer.P("writing restic to %v", opts.Output)
v, err := selfupdate.DownloadLatestStableRelease(ctx, opts.Output, global.Version, printer.P)
+2 -1
View File
@@ -13,6 +13,7 @@ import (
"github.com/restic/restic/internal/global"
"github.com/restic/restic/internal/restic"
"github.com/restic/restic/internal/ui"
"github.com/restic/restic/internal/ui/progress"
"github.com/restic/restic/internal/ui/table"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
@@ -71,7 +72,7 @@ func (opts *SnapshotOptions) AddFlags(f *pflag.FlagSet) {
}
func runSnapshots(ctx context.Context, opts SnapshotOptions, gopts global.Options, args []string, term ui.Terminal) error {
printer := ui.NewProgressPrinter(gopts.JSON, gopts.Verbosity, term)
printer := progress.NewTerminalPrinter(gopts.JSON, gopts.Verbosity, term)
ctx, repo, unlock, err := openWithReadLock(ctx, gopts, gopts.NoLock, printer)
if err != nil {
return err
+11 -87
View File
@@ -7,8 +7,6 @@ import (
"fmt"
"path/filepath"
"strings"
"sync"
"time"
"github.com/restic/chunker"
"github.com/restic/restic/internal/crypto"
@@ -19,6 +17,7 @@ import (
"github.com/restic/restic/internal/restorer"
"github.com/restic/restic/internal/ui"
"github.com/restic/restic/internal/ui/progress"
statsui "github.com/restic/restic/internal/ui/stats"
"github.com/restic/restic/internal/ui/table"
"github.com/restic/restic/internal/walker"
@@ -104,7 +103,7 @@ func runStats(ctx context.Context, opts StatsOptions, gopts global.Options, args
return err
}
printer := ui.NewProgressPrinter(gopts.JSON, gopts.Verbosity, term)
printer := progress.NewTerminalPrinter(gopts.JSON, gopts.Verbosity, term)
ctx, repo, unlock, err := openWithReadLock(ctx, gopts, gopts.NoLock, printer)
if err != nil {
@@ -141,13 +140,8 @@ func runStats(ctx context.Context, opts StatsOptions, gopts global.Options, args
snapshots = append(snapshots, sn)
}
statsProgress := newStatsProgress(term, !gopts.JSON, uint64(len(snapshots)))
updater := progress.NewUpdater(ui.CalculateProgressInterval(!gopts.Quiet, gopts.JSON, term.CanUpdateStatus()), func(runtime time.Duration, final bool) {
statsProgress.printProgress(runtime, final)
})
defer updater.Done()
statsProgress := statsui.NewProgress(term, gopts.Quiet, gopts.JSON, uint64(len(snapshots)))
defer statsProgress.Done()
for _, sn := range snapshots {
err = statsWalkSnapshot(ctx, sn, repo, opts, stats, statsProgress)
@@ -175,7 +169,7 @@ func runStats(ctx context.Context, opts StatsOptions, gopts global.Options, args
}
}
stats.TotalBlobCount++
statsProgress.update(0, 1, uint64(pbs[0].Length))
statsProgress.Update(0, 1, uint64(pbs[0].Length))
}
if stats.TotalCompressedBlobsSize > 0 {
stats.CompressionRatio = float64(stats.TotalCompressedBlobsUncompressedSize) / float64(stats.TotalCompressedBlobsSize)
@@ -186,7 +180,7 @@ func runStats(ctx context.Context, opts StatsOptions, gopts global.Options, args
}
}
// stop progress bar to prevent mangled output
updater.Done()
statsProgress.Done()
if gopts.JSON {
err = json.NewEncoder(gopts.Term.OutputWriter()).Encode(stats)
@@ -221,8 +215,8 @@ func runStats(ctx context.Context, opts StatsOptions, gopts global.Options, args
return nil
}
func statsWalkSnapshot(ctx context.Context, snapshot *data.Snapshot, repo restic.Loader, opts StatsOptions, stats *statsContainer, progress *statsProgress) error {
progress.processSnapshot()
func statsWalkSnapshot(ctx context.Context, snapshot *data.Snapshot, repo restic.Loader, opts StatsOptions, stats *statsContainer, progress *statsui.Progress) error {
progress.ProcessSnapshot()
if snapshot.Tree == nil {
return fmt.Errorf("snapshot %s has nil tree", snapshot.ID().Str())
}
@@ -246,7 +240,7 @@ func statsWalkSnapshot(ctx context.Context, snapshot *data.Snapshot, repo restic
return nil
}
func statsWalkTree(repo restic.Loader, opts StatsOptions, stats *statsContainer, hardLinkIndex *restorer.HardlinkIndex[struct{}], progress *statsProgress) walker.WalkFunc {
func statsWalkTree(repo restic.Loader, opts StatsOptions, stats *statsContainer, hardLinkIndex *restorer.HardlinkIndex[struct{}], progress *statsui.Progress) walker.WalkFunc {
return func(parentTreeID restic.ID, npath string, node *data.Node, nodeErr error) error {
if nodeErr != nil {
return nodeErr
@@ -254,7 +248,7 @@ func statsWalkTree(repo restic.Loader, opts StatsOptions, stats *statsContainer,
if node == nil {
return nil
}
progress.update(1, 0, uint64(node.Size))
progress.Update(1, 0, uint64(node.Size))
if opts.countMode == countModeUniqueFilesByContents || opts.countMode == countModeBlobsPerFile {
// only count this file if we haven't visited it before
fid := makeFileIDByContents(node)
@@ -274,7 +268,7 @@ func statsWalkTree(repo restic.Loader, opts StatsOptions, stats *statsContainer,
// ensure we have this file (by path) in our map; in this
// mode, a file is unique by both contents and path
nodePath := filepath.Join(npath, node.Name)
progress.update(0, 1, 0)
progress.Update(0, 1, 0)
if _, ok := stats.fileBlobs[nodePath]; !ok {
stats.fileBlobs[nodePath] = restic.NewIDSet()
stats.TotalFileCount++
@@ -371,76 +365,6 @@ type statsContainer struct {
// independent of references to files
blobs restic.AssociatedBlobSet
}
type statsProgress struct {
term ui.Terminal
m sync.Mutex
snapshotCount uint64
show bool
processedSnapshotCount uint64
processedFileCount uint64
processedBlobCount uint64
processedSize uint64
}
func newStatsProgress(term ui.Terminal, show bool, snapshotCount uint64) *statsProgress {
return &statsProgress{
term: term,
show: show,
snapshotCount: snapshotCount,
}
}
func (s *statsProgress) printProgress(runtime time.Duration, final bool) {
if !s.show {
return
}
s.m.Lock()
progressBase := s.processedSnapshotCount
if progressBase > 0 && !final {
progressBase--
}
status := fmt.Sprintf("[%s] %s %d / %d snapshots", ui.FormatDuration(runtime), ui.FormatPercent(progressBase, s.snapshotCount), s.processedSnapshotCount, s.snapshotCount)
if s.processedFileCount > 0 {
status += fmt.Sprintf(", %v files", s.processedFileCount)
}
if s.processedBlobCount > 0 {
status += fmt.Sprintf(", %d blobs", s.processedBlobCount)
}
status += fmt.Sprintf(", %s", ui.FormatBytes(s.processedSize))
s.m.Unlock()
if final {
s.term.SetStatus(nil)
s.term.Print(status)
} else {
s.term.SetStatus([]string{status})
}
}
func (s *statsProgress) update(fileCount uint64, blobCount uint64, size uint64) {
s.m.Lock()
defer s.m.Unlock()
s.processedFileCount += fileCount
s.processedBlobCount += blobCount
s.processedSize += size
}
func (s *statsProgress) processSnapshot() {
s.m.Lock()
defer s.m.Unlock()
s.processedSnapshotCount++
s.processedFileCount = 0
s.processedBlobCount = 0
s.processedSize = 0
}
// fileID is a 256-bit hash that distinguishes unique files.
type fileID [32]byte
-37
View File
@@ -2,10 +2,8 @@ package main
import (
"testing"
"time"
rtest "github.com/restic/restic/internal/test"
"github.com/restic/restic/internal/ui"
)
func TestSizeHistogramNew(t *testing.T) {
@@ -62,38 +60,3 @@ func TestSizeHistogramString(t *testing.T) {
rtest.Equals(t, "Count: 3\nTotal Size: 11 B\nSize Count\n-------------------\n 0 - 0 Byte 1\n 1 - 9 Byte 1\n10 - 42 Byte 1\n-------------------\n", h.String())
})
}
func TestStatsProgress(t *testing.T) {
term := &ui.MockTerminal{}
progress := newStatsProgress(term, true, 2)
progress.printProgress(0*time.Second, false)
rtest.Equals(t, []string{"[0:00] 0.00% 0 / 2 snapshots, 0 B"}, term.Output)
progress.processSnapshot()
progress.update(1, 2, 3)
progress.printProgress(5*time.Second, false)
// Output differs from the previous one because the progress is based on the number of processed snapshots,
// 1/2 snapshots means processing the snapshot 1 currently
rtest.Equals(t, []string{"[0:05] 0.00% 1 / 2 snapshots, 1 files, 2 blobs, 3 B"}, term.Output)
progress.processSnapshot()
progress.printProgress(10*time.Second, false)
rtest.Equals(t, []string{"[0:10] 50.00% 2 / 2 snapshots, 0 B"}, term.Output)
progress.update(4, 5, 6)
progress.printProgress(15*time.Second, false)
rtest.Equals(t, []string{"[0:15] 50.00% 2 / 2 snapshots, 4 files, 5 blobs, 6 B"}, term.Output)
progress.printProgress(20*time.Second, true)
rtest.Equals(t, []string{"[0:20] 100.00% 2 / 2 snapshots, 4 files, 5 blobs, 6 B"}, term.Output)
}
func TestStatsProgressJSON(t *testing.T) {
term := &ui.MockTerminal{}
progress := newStatsProgress(term, false, 2)
progress.printProgress(0*time.Second, false)
// JSON output is not available yet, so just make sure to not break normal json output
rtest.Equals(t, nil, term.Output)
}
+2 -1
View File
@@ -13,6 +13,7 @@ import (
"github.com/restic/restic/internal/repository"
"github.com/restic/restic/internal/restic"
"github.com/restic/restic/internal/ui"
"github.com/restic/restic/internal/ui/progress"
)
func newTagCommand(globalOptions *global.Options) *cobra.Command {
@@ -120,7 +121,7 @@ func changeTags(ctx context.Context, repo *repository.Repository, sn *data.Snaps
}
func runTag(ctx context.Context, opts TagOptions, gopts global.Options, term ui.Terminal, args []string) error {
printer := ui.NewProgressPrinter(gopts.JSON, gopts.Verbosity, term)
printer := progress.NewTerminalPrinter(gopts.JSON, gopts.Verbosity, term)
if len(opts.SetTags) == 0 && len(opts.AddTags) == 0 && len(opts.RemoveTags) == 0 {
return errors.Fatal("nothing to do!")
+2 -1
View File
@@ -6,6 +6,7 @@ import (
"github.com/restic/restic/internal/global"
"github.com/restic/restic/internal/repository"
"github.com/restic/restic/internal/ui"
"github.com/restic/restic/internal/ui/progress"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
)
@@ -47,7 +48,7 @@ func (opts *UnlockOptions) AddFlags(f *pflag.FlagSet) {
}
func runUnlock(ctx context.Context, opts UnlockOptions, gopts global.Options, term ui.Terminal) error {
printer := ui.NewProgressPrinter(gopts.JSON, gopts.Verbosity, term)
printer := progress.NewTerminalPrinter(gopts.JSON, gopts.Verbosity, term)
repo, err := global.OpenRepository(ctx, gopts, printer)
if err != nil {
return err
+2 -2
View File
@@ -5,7 +5,7 @@ import (
"runtime"
"github.com/restic/restic/internal/global"
"github.com/restic/restic/internal/ui"
"github.com/restic/restic/internal/ui/progress"
"github.com/spf13/cobra"
)
@@ -25,7 +25,7 @@ Exit status is 1 if there was any error.
`,
DisableAutoGenTag: true,
Run: func(_ *cobra.Command, _ []string) {
printer := ui.NewProgressPrinter(globalOptions.JSON, globalOptions.Verbosity, globalOptions.Term)
printer := progress.NewTerminalPrinter(globalOptions.JSON, globalOptions.Verbosity, globalOptions.Term)
if globalOptions.JSON {
type jsonVersion struct {
+9 -18
View File
@@ -10,7 +10,6 @@ import (
"path/filepath"
"runtime"
"strings"
"sync"
"testing"
"github.com/restic/restic/internal/backend"
@@ -23,7 +22,7 @@ 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"
"github.com/restic/restic/internal/ui/termstatus"
)
@@ -244,7 +243,7 @@ func testSetupBackupData(t testing.TB, env *testEnvironment) string {
func listPacks(gopts global.Options, t *testing.T) restic.IDSet {
var packs restic.IDSet
err := withTermStatus(t, gopts, func(ctx context.Context, gopts global.Options) error {
printer := ui.NewProgressPrinter(gopts.JSON, gopts.Verbosity, gopts.Term)
printer := progress.NewTerminalPrinter(gopts.JSON, gopts.Verbosity, gopts.Term)
ctx, r, unlock, err := openWithReadLock(ctx, gopts, false, printer)
rtest.OK(t, err)
defer unlock()
@@ -263,7 +262,7 @@ func listPacks(gopts global.Options, t *testing.T) restic.IDSet {
func listTreePacks(gopts global.Options, t *testing.T) restic.IDSet {
var treePacks restic.IDSet
err := withTermStatus(t, gopts, func(ctx context.Context, gopts global.Options) error {
printer := ui.NewProgressPrinter(gopts.JSON, gopts.Verbosity, gopts.Term)
printer := progress.NewTerminalPrinter(gopts.JSON, gopts.Verbosity, gopts.Term)
ctx, r, unlock, err := openWithReadLock(ctx, gopts, false, printer)
rtest.OK(t, err)
defer unlock()
@@ -294,7 +293,7 @@ func captureBackend(gopts *global.Options) func() backend.Backend {
func removePacks(gopts global.Options, t testing.TB, remove restic.IDSet) {
be := captureBackend(&gopts)
err := withTermStatus(t, gopts, func(ctx context.Context, gopts global.Options) error {
printer := ui.NewProgressPrinter(gopts.JSON, gopts.Verbosity, gopts.Term)
printer := progress.NewTerminalPrinter(gopts.JSON, gopts.Verbosity, gopts.Term)
ctx, _, unlock, err := openWithExclusiveLock(ctx, gopts, false, printer)
rtest.OK(t, err)
defer unlock()
@@ -310,7 +309,7 @@ func removePacks(gopts global.Options, t testing.TB, remove restic.IDSet) {
func removePacksExcept(gopts global.Options, t testing.TB, keep restic.IDSet, removeTreePacks bool) {
be := captureBackend(&gopts)
err := withTermStatus(t, gopts, func(ctx context.Context, gopts global.Options) error {
printer := ui.NewProgressPrinter(gopts.JSON, gopts.Verbosity, gopts.Term)
printer := progress.NewTerminalPrinter(gopts.JSON, gopts.Verbosity, gopts.Term)
ctx, r, unlock, err := openWithExclusiveLock(ctx, gopts, false, printer)
rtest.OK(t, err)
defer unlock()
@@ -371,7 +370,7 @@ func lastSnapshot(old, new map[string]struct{}) (map[string]struct{}, string) {
func testLoadSnapshot(t testing.TB, gopts global.Options, id restic.ID) *data.Snapshot {
var snapshot *data.Snapshot
err := withTermStatus(t, gopts, func(ctx context.Context, gopts global.Options) error {
printer := ui.NewProgressPrinter(gopts.JSON, gopts.Verbosity, gopts.Term)
printer := progress.NewTerminalPrinter(gopts.JSON, gopts.Verbosity, gopts.Term)
_, repo, unlock, err := openWithReadLock(ctx, gopts, false, printer)
rtest.OK(t, err)
defer unlock()
@@ -429,18 +428,10 @@ func withTermStatus(t testing.TB, gopts global.Options, callback func(ctx contex
}
func withTermStatusRaw(stdin io.ReadCloser, stdout, stderr io.Writer, gopts global.Options, callback func(ctx context.Context, gopts global.Options) error) error {
ctx, cancel := context.WithCancel(context.TODO())
var wg sync.WaitGroup
term := termstatus.New(stdin, stdout, stderr, gopts.Quiet)
term, cancel := termstatus.Setup(stdin, stdout, stderr, gopts.Quiet)
gopts.Term = term
wg.Add(1)
go func() {
defer wg.Done()
term.Run(ctx)
}()
defer wg.Wait()
defer cancel()
ctx, cancel := context.WithCancel(context.TODO())
defer cancel()
return callback(ctx, gopts)
+2 -2
View File
@@ -14,7 +14,7 @@ import (
"github.com/restic/restic/internal/global"
"github.com/restic/restic/internal/restic"
rtest "github.com/restic/restic/internal/test"
"github.com/restic/restic/internal/ui"
"github.com/restic/restic/internal/ui/progress"
)
func TestCheckRestoreNoLock(t *testing.T) {
@@ -165,7 +165,7 @@ func TestFindListOnce(t *testing.T) {
var snapshotIDs restic.IDSet
rtest.OK(t, withTermStatus(t, env.gopts, func(ctx context.Context, gopts global.Options) error {
printer := ui.NewProgressPrinter(gopts.JSON, gopts.Verbosity, gopts.Term)
printer := progress.NewTerminalPrinter(gopts.JSON, gopts.Verbosity, gopts.Term)
ctx, repo, unlock, err := openWithReadLock(ctx, gopts, false, printer)
rtest.OK(t, err)
defer unlock()
+2 -2
View File
@@ -70,10 +70,10 @@ func TestPruneMaxUnusedDuplicate(t *testing.T) {
usedBlobs.Insert(blob)
}
return nil
}, &progress.NoopPrinter{})
}, progress.NewNoopPrinter())
rtest.OK(t, err)
rtest.OK(t, plan.Execute(context.TODO(), &progress.NoopPrinter{}))
rtest.OK(t, plan.Execute(context.TODO(), progress.NewNoopPrinter()))
rsize := plan.Stats().Size
remainingUnusedSize := rsize.Duplicate + rsize.Unused - rsize.Remove - rsize.Repackrm
+4 -4
View File
@@ -41,10 +41,10 @@ func testPrune(t *testing.T, opts repository.PruneOptions, errOnUnused bool) {
usedBlobs.Insert(blob)
}
return nil
}, &progress.NoopPrinter{})
}, progress.NewNoopPrinter())
rtest.OK(t, err)
rtest.OK(t, plan.Execute(context.TODO(), &progress.NoopPrinter{}))
rtest.OK(t, plan.Execute(context.TODO(), progress.NewNoopPrinter()))
repo = repository.TestOpenBackend(t, be)
repository.TestCheckRepo(t, repo)
@@ -165,9 +165,9 @@ func TestPruneSmall(t *testing.T) {
usedBlobs.Insert(blob)
}
return nil
}, &progress.NoopPrinter{})
}, progress.NewNoopPrinter())
rtest.OK(t, err)
rtest.OK(t, plan.Execute(context.TODO(), &progress.NoopPrinter{}))
rtest.OK(t, plan.Execute(context.TODO(), progress.NewNoopPrinter()))
stats := plan.Stats()
rtest.Equals(t, stats.Size.Used/blobSize, uint64(numBlobsCreated), fmt.Sprintf("total size of blobs should be %d but is %d",
+1 -1
View File
@@ -162,7 +162,7 @@ func repack(t *testing.T, repo restic.Repository, be backend.Backend, packs rest
func rebuildAndReloadIndex(t *testing.T, repo *repository.Repository) {
rtest.OK(t, repository.RepairIndex(context.TODO(), repo, repository.RepairIndexOptions{
ReadAllPacks: true,
}, &progress.NoopPrinter{}))
}, progress.NewNoopPrinter()))
rtest.OK(t, repo.LoadIndex(context.TODO(), nil))
}
+1 -1
View File
@@ -33,7 +33,7 @@ func testRebuildIndex(t *testing.T, readAllPacks bool, damage func(t *testing.T,
repo = repository.TestOpenBackend(t, be)
rtest.OK(t, repository.RepairIndex(context.TODO(), repo, repository.RepairIndexOptions{
ReadAllPacks: readAllPacks,
}, &progress.NoopPrinter{}))
}, progress.NewNoopPrinter()))
repository.TestCheckRepo(t, repo)
}
+2 -2
View File
@@ -106,7 +106,7 @@ func testRepairBrokenPack(t *testing.T, version uint) {
buf, err := backendtest.LoadAll(context.TODO(), be, h)
rtest.OK(t, err)
rtest.OK(t, be.Remove(context.TODO(), h))
rtest.OK(t, repository.RepairIndex(context.TODO(), repo, repository.RepairIndexOptions{}, &progress.NoopPrinter{}))
rtest.OK(t, repository.RepairIndex(context.TODO(), repo, repository.RepairIndexOptions{}, progress.NewNoopPrinter()))
rtest.OK(t, be.Save(context.TODO(), h, backend.NewByteReader(buf, be.Hasher())))
@@ -130,7 +130,7 @@ func testRepairBrokenPack(t *testing.T, version uint) {
toRepair, damagedBlobs := test.damage(t, random, repo, be, packsBefore)
rtest.OK(t, repository.RepairPacks(context.TODO(), repo, toRepair, &progress.NoopPrinter{}))
rtest.OK(t, repository.RepairPacks(context.TODO(), repo, toRepair, progress.NewNoopPrinter()))
// reload index
rtest.OK(t, repo.LoadIndex(context.TODO(), nil))
+5 -5
View File
@@ -989,7 +989,7 @@ func TestRestorerSparseOverwrite(t *testing.T) {
type printerMock struct {
s restoreui.State
progress.NoopPrinter
progress.Printer
}
func (p *printerMock) Update(_ restoreui.State, _ time.Duration) {
@@ -1102,8 +1102,8 @@ func TestRestorerOverwriteBehavior(t *testing.T) {
for _, test := range tests {
t.Run("", func(t *testing.T) {
mock := &printerMock{}
progress := restoreui.NewProgress(mock, 0)
mock := &printerMock{Printer: progress.NewNoopPrinter()}
progress := restoreui.NewProgress(mock, true, false, true)
tempdir := saveSnapshotsAndOverwrite(t, baseSnapshot, overwriteSnapshot, Options{}, Options{Overwrite: test.Overwrite, Progress: progress})
for filename, content := range test.Files {
@@ -1154,8 +1154,8 @@ func TestRestorerOverwritePartial(t *testing.T) {
},
}
mock := &printerMock{}
progress := restoreui.NewProgress(mock, 0)
mock := &printerMock{Printer: progress.NewNoopPrinter()}
progress := restoreui.NewProgress(mock, true, false, true)
saveSnapshotsAndOverwrite(t, baseSnapshot, overwriteSnapshot, Options{}, Options{Overwrite: OverwriteAlways, Progress: progress})
progress.Finish()
rtest.Equals(t, restoreui.State{
+3 -2
View File
@@ -13,6 +13,7 @@ import (
"github.com/restic/restic/internal/repository"
rtest "github.com/restic/restic/internal/test"
"github.com/restic/restic/internal/ui/progress"
restoreui "github.com/restic/restic/internal/ui/restore"
)
@@ -87,8 +88,8 @@ func testRestorerProgressBar(t *testing.T, dryRun bool) {
},
}, noopGetGenericAttributes)
mock := &printerMock{}
progress := restoreui.NewProgress(mock, 0)
mock := &printerMock{Printer: progress.NewNoopPrinter()}
progress := restoreui.NewProgress(mock, true, false, true)
res := NewRestorer(repo, sn, Options{Progress: progress, DryRun: dryRun})
tempdir := rtest.TempDir(t)
+3 -3
View File
@@ -164,10 +164,10 @@ func isFile(fi os.FileInfo) bool {
return fi.Mode()&(os.ModeType|os.ModeCharDevice) == 0
}
// ResetReadOnly recursively resets the read-only flag recursively for dir.
// resetReadOnly recursively resets the read-only flag recursively for dir.
// This is mainly used for tests on Windows, which is unable to delete a file
// set read-only.
func ResetReadOnly(t testing.TB, dir string) {
func resetReadOnly(t testing.TB, dir string) {
t.Helper()
err := filepath.Walk(dir, func(path string, fi os.FileInfo, err error) error {
if fi == nil {
@@ -194,7 +194,7 @@ func ResetReadOnly(t testing.TB, dir string) {
// afterwards uses os.RemoveAll() to remove the path.
func RemoveAll(t testing.TB, path string) {
t.Helper()
ResetReadOnly(t, path)
resetReadOnly(t, path)
err := os.RemoveAll(path)
if errors.Is(err, os.ErrNotExist) {
err = nil
+2 -5
View File
@@ -14,11 +14,8 @@ var (
RunIntegrationTest = getBoolVar("RESTIC_TEST_INTEGRATION", true)
RunFuseTest = getBoolVar("RESTIC_TEST_FUSE", true)
TestSFTPPath = getStringVar("RESTIC_TEST_SFTPPATH", "/usr/lib/ssh:/usr/lib/openssh:/usr/libexec")
TestWalkerPath = getStringVar("RESTIC_TEST_PATH", ".")
BenchArchiveDirectory = getStringVar("RESTIC_BENCH_DIR", ".")
TestS3Server = getStringVar("RESTIC_TEST_S3_SERVER", "")
TestRESTServer = getStringVar("RESTIC_TEST_REST_SERVER", "")
TestIntegrationDisallowSkip = getStringVar("RESTIC_TEST_DISALLOW_SKIP", "")
testIntegrationDisallowSkip = getStringVar("RESTIC_TEST_DISALLOW_SKIP", "")
)
func getStringVar(name, defaultValue string) string {
@@ -49,7 +46,7 @@ func getBoolVar(name string, defaultValue bool) bool {
// names that must be run. If name is in this list, the test is marked as
// failed.
func SkipDisallowed(t testing.TB, name string) {
for _, s := range strings.Split(TestIntegrationDisallowSkip, ",") {
for _, s := range strings.Split(testIntegrationDisallowSkip, ",") {
if s == name {
t.Fatalf("test %v is in list of tests that need to run ($RESTIC_TEST_DISALLOW_SKIP)", name)
}
+15 -15
View File
@@ -10,8 +10,8 @@ import (
"github.com/restic/restic/internal/ui/progress"
)
// JSONProgress reports progress for the `backup` command in JSON.
type JSONProgress struct {
// jsonProgress reports progress for the `backup` command in JSON.
type jsonProgress struct {
progress.Printer
term ui.Terminal
@@ -19,27 +19,27 @@ type JSONProgress struct {
}
// assert that Backup implements the ProgressPrinter interface
var _ ProgressPrinter = &JSONProgress{}
var _ ProgressPrinter = &jsonProgress{}
// NewJSONProgress returns a new backup progress reporter.
func NewJSONProgress(term ui.Terminal, verbosity uint) *JSONProgress {
return &JSONProgress{
Printer: ui.NewProgressPrinter(true, verbosity, term),
func NewJSONProgress(term ui.Terminal, verbosity uint) ProgressPrinter {
return &jsonProgress{
Printer: progress.NewTerminalPrinter(true, verbosity, term),
term: term,
v: verbosity,
}
}
func (b *JSONProgress) print(status interface{}) {
func (b *jsonProgress) print(status interface{}) {
b.term.Print(ui.ToJSONString(status))
}
func (b *JSONProgress) error(status interface{}) {
func (b *jsonProgress) error(status interface{}) {
b.term.Error(ui.ToJSONString(status))
}
// Update updates the status lines.
func (b *JSONProgress) Update(total, processed Counter, errors uint, currentFiles map[string]struct{}, start time.Time, secs uint64) {
func (b *jsonProgress) Update(total, processed Counter, errors uint, currentFiles map[string]struct{}, start time.Time, secs uint64) {
status := statusUpdate{
MessageType: "status",
SecondsElapsed: uint64(time.Since(start) / time.Second),
@@ -65,7 +65,7 @@ func (b *JSONProgress) Update(total, processed Counter, errors uint, currentFile
// ScannerError is the error callback function for the scanner, it prints the
// error in verbose mode and returns nil.
func (b *JSONProgress) ScannerError(item string, err error) error {
func (b *jsonProgress) ScannerError(item string, err error) error {
b.error(errorUpdate{
MessageType: "error",
Error: errorObject{err.Error()},
@@ -76,7 +76,7 @@ func (b *JSONProgress) ScannerError(item string, err error) error {
}
// Error is the error callback function for the archiver, it prints the error and returns nil.
func (b *JSONProgress) Error(item string, err error) error {
func (b *jsonProgress) Error(item string, err error) error {
b.error(errorUpdate{
MessageType: "error",
Error: errorObject{err.Error()},
@@ -88,7 +88,7 @@ func (b *JSONProgress) Error(item string, err error) error {
// CompleteItem is the status callback function for the archiver when a
// file/dir has been saved successfully.
func (b *JSONProgress) CompleteItem(messageType, item string, s archiver.ItemStats, d time.Duration) {
func (b *jsonProgress) CompleteItem(messageType, item string, s archiver.ItemStats, d time.Duration) {
if b.v < 2 {
return
}
@@ -150,7 +150,7 @@ func (b *JSONProgress) CompleteItem(messageType, item string, s archiver.ItemSta
}
// ReportTotal sets the total stats up to now
func (b *JSONProgress) ReportTotal(start time.Time, s archiver.ScanStats) {
func (b *jsonProgress) ReportTotal(start time.Time, s archiver.ScanStats) {
if b.v >= 2 {
b.print(verboseUpdate{
MessageType: "verbose_status",
@@ -163,7 +163,7 @@ func (b *JSONProgress) ReportTotal(start time.Time, s archiver.ScanStats) {
}
// Finish prints the finishing messages.
func (b *JSONProgress) Finish(snapshotID restic.ID, summary *archiver.Summary, dryRun bool) {
func (b *jsonProgress) Finish(snapshotID restic.ID, summary *archiver.Summary, dryRun bool) {
id := ""
// empty if snapshot creation was skipped
if !snapshotID.IsNull() {
@@ -192,7 +192,7 @@ func (b *JSONProgress) Finish(snapshotID restic.ID, summary *archiver.Summary, d
}
// Reset no-op
func (b *JSONProgress) Reset() {
func (b *jsonProgress) Reset() {
}
type statusUpdate struct {
+5 -1
View File
@@ -45,7 +45,11 @@ type Progress struct {
printer ProgressPrinter
}
func NewProgress(printer ProgressPrinter, interval time.Duration) *Progress {
func NewProgress(printer ProgressPrinter, quiet, json, canUpdateStatus bool) *Progress {
return newProgress(printer, progress.CalculateProgressInterval(!quiet, json, canUpdateStatus))
}
func newProgress(printer ProgressPrinter, interval time.Duration) *Progress {
p := &Progress{
start: time.Now(),
currentFiles: make(map[string]struct{}),
+3 -3
View File
@@ -13,7 +13,7 @@ import (
type mockPrinter struct {
sync.Mutex
progress.NoopPrinter
progress.Printer
dirUnchanged, fileNew bool
id restic.ID
}
@@ -48,8 +48,8 @@ func (p *mockPrinter) Reset() {}
func TestProgress(t *testing.T) {
t.Parallel()
prnt := &mockPrinter{}
prog := NewProgress(prnt, time.Millisecond)
prnt := &mockPrinter{Printer: progress.NewNoopPrinter()}
prog := newProgress(prnt, time.Millisecond)
prog.StartFile("foo")
prog.CompleteBlob(1024)
+13 -13
View File
@@ -11,8 +11,8 @@ import (
"github.com/restic/restic/internal/ui/progress"
)
// TextProgress reports progress for the `backup` command.
type TextProgress struct {
// textProgress reports progress for the `backup` command.
type textProgress struct {
progress.Printer
term ui.Terminal
@@ -20,19 +20,19 @@ type TextProgress struct {
}
// assert that Backup implements the ProgressPrinter interface
var _ ProgressPrinter = &TextProgress{}
var _ ProgressPrinter = &textProgress{}
// NewTextProgress returns a new backup progress reporter.
func NewTextProgress(term ui.Terminal, verbosity uint) *TextProgress {
return &TextProgress{
Printer: ui.NewProgressPrinter(false, verbosity, term),
func NewTextProgress(term ui.Terminal, verbosity uint) ProgressPrinter {
return &textProgress{
Printer: progress.NewTerminalPrinter(false, verbosity, term),
term: term,
verbosity: verbosity,
}
}
// Update updates the status lines.
func (b *TextProgress) Update(total, processed Counter, errors uint, currentFiles map[string]struct{}, start time.Time, secs uint64) {
func (b *textProgress) Update(total, processed Counter, errors uint, currentFiles map[string]struct{}, start time.Time, secs uint64) {
var status string
if total.Files == 0 && total.Dirs == 0 {
// no total count available yet
@@ -74,7 +74,7 @@ func (b *TextProgress) Update(total, processed Counter, errors uint, currentFile
// ScannerError is the error callback function for the scanner, it prints the
// error in verbose mode and returns nil.
func (b *TextProgress) ScannerError(_ string, err error) error {
func (b *textProgress) ScannerError(_ string, err error) error {
if b.verbosity >= 2 {
b.E("scan: %v\n", err)
}
@@ -82,14 +82,14 @@ func (b *TextProgress) ScannerError(_ string, err error) error {
}
// Error is the error callback function for the archiver, it prints the error and returns nil.
func (b *TextProgress) Error(_ string, err error) error {
func (b *textProgress) Error(_ string, err error) error {
b.E("error: %v\n", err)
return nil
}
// CompleteItem is the status callback function for the archiver when a
// file/dir has been saved successfully.
func (b *TextProgress) CompleteItem(messageType, item string, s archiver.ItemStats, d time.Duration) {
func (b *textProgress) CompleteItem(messageType, item string, s archiver.ItemStats, d time.Duration) {
item = ui.Quote(item)
switch messageType {
@@ -115,7 +115,7 @@ func (b *TextProgress) CompleteItem(messageType, item string, s archiver.ItemSta
}
// ReportTotal sets the total stats up to now
func (b *TextProgress) ReportTotal(start time.Time, s archiver.ScanStats) {
func (b *textProgress) ReportTotal(start time.Time, s archiver.ScanStats) {
b.V("scan finished in %.3fs: %v files, %s",
time.Since(start).Seconds(),
s.Files, ui.FormatBytes(s.Bytes),
@@ -123,14 +123,14 @@ func (b *TextProgress) ReportTotal(start time.Time, s archiver.ScanStats) {
}
// Reset status
func (b *TextProgress) Reset() {
func (b *textProgress) Reset() {
if b.term.CanUpdateStatus() {
b.term.SetStatus(nil)
}
}
// Finish prints the finishing messages.
func (b *TextProgress) Finish(id restic.ID, summary *archiver.Summary, dryRun bool) {
func (b *textProgress) Finish(id restic.ID, summary *archiver.Summary, dryRun bool) {
b.P("\n")
b.P("Files: %5d new, %5d changed, %5d unmodified\n", summary.Files.New, summary.Files.Changed, summary.Files.Unchanged)
b.P("Dirs: %5d new, %5d changed, %5d unmodified\n", summary.Dirs.New, summary.Dirs.Changed, summary.Dirs.Unchanged)
-64
View File
@@ -1,64 +0,0 @@
package ui
import (
"fmt"
)
// Message reports progress with messages of different verbosity.
type Message struct {
term Terminal
v uint
}
// NewMessage returns a message progress reporter with underlying terminal
// term.
func NewMessage(term Terminal, verbosity uint) *Message {
return &Message{
term: term,
v: verbosity,
}
}
// E reports an error. This message is always printed to stderr.
func (m *Message) E(msg string, args ...interface{}) {
m.term.Error(fmt.Sprintf(msg, args...))
}
// S prints a message, this is should only be used for very important messages
// that are not errors. The message is even printed if --quiet is specified.
func (m *Message) S(msg string, args ...interface{}) {
m.term.Print(fmt.Sprintf(msg, args...))
}
// PT prints a message if verbosity >= 1 (neither --quiet nor --verbose is specified)
// and stdout points to a terminal.
// This is used for informational messages.
func (m *Message) PT(msg string, args ...interface{}) {
if m.term.OutputIsTerminal() && m.v >= 1 {
m.term.Print(fmt.Sprintf(msg, args...))
}
}
// P prints a message if verbosity >= 1 (neither --quiet nor --verbose is specified).
// This is used for normal messages which are not errors.
func (m *Message) P(msg string, args ...interface{}) {
if m.v >= 1 {
m.term.Print(fmt.Sprintf(msg, args...))
}
}
// V prints a message if verbosity >= 2 (equivalent to --verbose), this is used for
// verbose messages.
func (m *Message) V(msg string, args ...interface{}) {
if m.v >= 2 {
m.term.Print(fmt.Sprintf(msg, args...))
}
}
// VV prints a message if verbosity >= 3 (equivalent to --verbose=2), this is used for
// debug messages.
func (m *Message) VV(msg string, args ...interface{}) {
if m.v >= 3 {
m.term.Print(fmt.Sprintf(msg, args...))
}
}
-78
View File
@@ -1,78 +0,0 @@
package ui
import (
"fmt"
"os"
"strconv"
"time"
"github.com/restic/restic/internal/ui/progress"
)
// CalculateProgressInterval returns the interval configured via RESTIC_PROGRESS_FPS
// or if unset returns an interval for 60fps on interactive terminals and 0 (=disabled)
// for non-interactive terminals or when run using the --quiet flag
func CalculateProgressInterval(show bool, json bool, canUpdateStatus bool) time.Duration {
interval := time.Second / 10
fps, err := strconv.ParseFloat(os.Getenv("RESTIC_PROGRESS_FPS"), 64)
if err == nil && fps > 0 {
if fps > 60 {
fps = 60
}
interval = time.Duration(float64(time.Second) / fps)
} else if !json && !canUpdateStatus || !show {
interval = 0
}
return interval
}
// newProgressMax returns a progress.Counter that prints to terminal if provided.
func newProgressMax(show bool, max uint64, description string, term Terminal) *progress.Counter {
if !show {
return nil
}
interval := CalculateProgressInterval(show, false, term.CanUpdateStatus())
return progress.NewCounter(interval, max, func(v uint64, max uint64, d time.Duration, final bool) {
var status string
if max == 0 {
status = fmt.Sprintf("[%s] %d %s",
FormatDuration(d), v, description)
} else {
status = fmt.Sprintf("[%s] %s %d / %d %s",
FormatDuration(d), FormatPercent(v, max), v, max, description)
}
if final {
term.SetStatus(nil)
term.Print(status)
} else {
term.SetStatus([]string{status})
}
})
}
type progressPrinter struct {
term Terminal
Message
show bool
}
func (t *progressPrinter) NewCounter(description string) *progress.Counter {
return newProgressMax(t.show, 0, description, t.term)
}
func (t *progressPrinter) NewCounterTerminalOnly(description string) *progress.Counter {
return newProgressMax(t.show && t.term.OutputIsTerminal(), 0, description, t.term)
}
func NewProgressPrinter(json bool, verbosity uint, term Terminal) progress.Printer {
if json {
verbosity = 0
}
return &progressPrinter{
term: term,
Message: *NewMessage(term, verbosity),
show: verbosity > 0,
}
}
+16 -58
View File
@@ -1,7 +1,5 @@
package progress
import "testing"
// A Printer can can return a new counter or print messages
// at different log levels.
// It must be safe to call its methods from concurrent goroutines.
@@ -34,72 +32,32 @@ type Printer interface {
VV(msg string, args ...interface{})
}
// NoopPrinter discards all messages
type NoopPrinter struct{}
// noopPrinter discards all messages.
type noopPrinter struct{}
var _ Printer = (*NoopPrinter)(nil)
var _ Printer = (*noopPrinter)(nil)
func (*NoopPrinter) NewCounter(_ string) *Counter {
// NewNoopPrinter returns a Printer that discards all messages.
func NewNoopPrinter() Printer {
return &noopPrinter{}
}
func (*noopPrinter) NewCounter(_ string) *Counter {
return nil
}
func (*NoopPrinter) NewCounterTerminalOnly(_ string) *Counter {
func (*noopPrinter) NewCounterTerminalOnly(_ string) *Counter {
return nil
}
func (*NoopPrinter) E(_ string, _ ...interface{}) {}
func (*noopPrinter) E(_ string, _ ...interface{}) {}
func (*NoopPrinter) S(_ string, _ ...interface{}) {}
func (*noopPrinter) S(_ string, _ ...interface{}) {}
func (*NoopPrinter) PT(_ string, _ ...interface{}) {}
func (*noopPrinter) PT(_ string, _ ...interface{}) {}
func (*NoopPrinter) P(_ string, _ ...interface{}) {}
func (*noopPrinter) P(_ string, _ ...interface{}) {}
func (*NoopPrinter) V(_ string, _ ...interface{}) {}
func (*noopPrinter) V(_ string, _ ...interface{}) {}
func (*NoopPrinter) VV(_ string, _ ...interface{}) {}
// TestPrinter prints messages during testing
type TestPrinter struct {
t testing.TB
}
func NewTestPrinter(t testing.TB) *TestPrinter {
return &TestPrinter{
t: t,
}
}
var _ Printer = (*TestPrinter)(nil)
func (p *TestPrinter) NewCounter(_ string) *Counter {
return nil
}
func (p *TestPrinter) NewCounterTerminalOnly(_ string) *Counter {
return nil
}
func (p *TestPrinter) E(msg string, args ...interface{}) {
p.t.Logf("error: "+msg, args...)
}
func (p *TestPrinter) S(msg string, args ...interface{}) {
p.t.Logf("stdout: "+msg, args...)
}
func (p *TestPrinter) PT(msg string, args ...interface{}) {
p.t.Logf("stdout(terminal): "+msg, args...)
}
func (p *TestPrinter) P(msg string, args ...interface{}) {
p.t.Logf("print: "+msg, args...)
}
func (p *TestPrinter) V(msg string, args ...interface{}) {
p.t.Logf("verbose: "+msg, args...)
}
func (p *TestPrinter) VV(msg string, args ...interface{}) {
p.t.Logf("verbose2: "+msg, args...)
}
func (*noopPrinter) VV(_ string, _ ...interface{}) {}
+108
View File
@@ -0,0 +1,108 @@
package progress
import (
"fmt"
"os"
"strconv"
"time"
"github.com/restic/restic/internal/ui"
)
// CalculateProgressInterval returns the interval configured via RESTIC_PROGRESS_FPS
// or if unset returns an interval for 60fps on interactive terminals and 0 (=disabled)
// for non-interactive terminals or when run using the --quiet flag
func CalculateProgressInterval(show bool, json bool, canUpdateStatus bool) time.Duration {
interval := time.Second / 10
fps, err := strconv.ParseFloat(os.Getenv("RESTIC_PROGRESS_FPS"), 64)
if err == nil && fps > 0 {
if fps > 60 {
fps = 60
}
interval = time.Duration(float64(time.Second) / fps)
} else if !json && !canUpdateStatus || !show {
interval = 0
}
return interval
}
// newProgressMax returns a progress.Counter that prints to terminal if provided.
func newProgressMax(show bool, max uint64, description string, term ui.Terminal) *Counter {
if !show {
return nil
}
interval := CalculateProgressInterval(show, false, term.CanUpdateStatus())
return NewCounter(interval, max, func(v uint64, max uint64, d time.Duration, final bool) {
var status string
if max == 0 {
status = fmt.Sprintf("[%s] %d %s",
ui.FormatDuration(d), v, description)
} else {
status = fmt.Sprintf("[%s] %s %d / %d %s",
ui.FormatDuration(d), ui.FormatPercent(v, max), v, max, description)
}
if final {
term.SetStatus(nil)
term.Print(status)
} else {
term.SetStatus([]string{status})
}
})
}
type terminalPrinter struct {
term ui.Terminal
v uint
}
func (t *terminalPrinter) NewCounter(description string) *Counter {
return newProgressMax(t.v > 0, 0, description, t.term)
}
func (t *terminalPrinter) NewCounterTerminalOnly(description string) *Counter {
return newProgressMax(t.v > 0 && t.term.OutputIsTerminal(), 0, description, t.term)
}
func (t *terminalPrinter) E(msg string, args ...interface{}) {
t.term.Error(fmt.Sprintf(msg, args...))
}
func (t *terminalPrinter) S(msg string, args ...interface{}) {
t.term.Print(fmt.Sprintf(msg, args...))
}
func (t *terminalPrinter) PT(msg string, args ...interface{}) {
if t.term.OutputIsTerminal() && t.v >= 1 {
t.term.Print(fmt.Sprintf(msg, args...))
}
}
func (t *terminalPrinter) P(msg string, args ...interface{}) {
if t.v >= 1 {
t.term.Print(fmt.Sprintf(msg, args...))
}
}
func (t *terminalPrinter) V(msg string, args ...interface{}) {
if t.v >= 2 {
t.term.Print(fmt.Sprintf(msg, args...))
}
}
func (t *terminalPrinter) VV(msg string, args ...interface{}) {
if t.v >= 3 {
t.term.Print(fmt.Sprintf(msg, args...))
}
}
func NewTerminalPrinter(json bool, verbosity uint, term ui.Terminal) Printer {
if json {
verbosity = 0
}
return &terminalPrinter{
term: term,
v: verbosity,
}
}
+1 -1
View File
@@ -16,7 +16,7 @@ type jsonPrinter struct {
func NewJSONProgress(terminal ui.Terminal, verbosity uint) ProgressPrinter {
return &jsonPrinter{
Printer: ui.NewProgressPrinter(true, verbosity, terminal),
Printer: progress.NewTerminalPrinter(true, verbosity, terminal),
terminal: terminal,
verbosity: verbosity,
}
+5 -1
View File
@@ -53,7 +53,11 @@ const (
ActionDeleted ItemAction = "deleted"
)
func NewProgress(printer ProgressPrinter, interval time.Duration) *Progress {
func NewProgress(printer ProgressPrinter, quiet, json, canUpdateStatus bool) *Progress {
return newProgress(printer, progress.CalculateProgressInterval(!quiet, json, canUpdateStatus))
}
func newProgress(printer ProgressPrinter, interval time.Duration) *Progress {
p := &Progress{
progressInfoMap: make(map[string]progressInfoEntry),
started: time.Now(),
+3 -3
View File
@@ -37,7 +37,7 @@ type mockPrinter struct {
trace printerTrace
items itemTrace
errors errorTrace
progress.NoopPrinter
progress.Printer
}
const mockFinishDuration = 42 * time.Second
@@ -57,8 +57,8 @@ func (p *mockPrinter) Finish(progress State, _ time.Duration) {
}
func testProgress(fn func(progress *Progress) bool) (printerTrace, itemTrace, errorTrace) {
printer := &mockPrinter{}
progress := NewProgress(printer, 0)
printer := &mockPrinter{Printer: progress.NewNoopPrinter()}
progress := newProgress(printer, 0)
final := fn(progress)
progress.update(0, final)
trace := append(printerTrace{}, printer.trace...)
+1 -1
View File
@@ -16,7 +16,7 @@ type textPrinter struct {
func NewTextProgress(terminal ui.Terminal, verbosity uint) ProgressPrinter {
return &textPrinter{
Printer: ui.NewProgressPrinter(false, verbosity, terminal),
Printer: progress.NewTerminalPrinter(false, verbosity, terminal),
terminal: terminal,
}
}
+94
View File
@@ -0,0 +1,94 @@
package stats
import (
"fmt"
"sync"
"time"
"github.com/restic/restic/internal/ui"
"github.com/restic/restic/internal/ui/progress"
)
// Progress reports progress for the stats command.
type Progress struct {
progress.Updater
term ui.Terminal
m sync.Mutex
snapshotCount uint64
show bool
processedSnapshotCount uint64
processedFileCount uint64
processedBlobCount uint64
processedSize uint64
}
// NewProgress returns a new stats progress reporter.
func NewProgress(term ui.Terminal, quiet, json bool, snapshotCount uint64) *Progress {
p := newProgress(term, !json, snapshotCount)
p.Updater = *progress.NewUpdater(
progress.CalculateProgressInterval(!quiet, json, term.CanUpdateStatus()),
p.printProgress,
)
return p
}
func newProgress(term ui.Terminal, show bool, snapshotCount uint64) *Progress {
return &Progress{
term: term,
snapshotCount: snapshotCount,
show: show,
}
}
func (p *Progress) printProgress(runtime time.Duration, final bool) {
if !p.show {
return
}
p.m.Lock()
progressBase := p.processedSnapshotCount
if progressBase > 0 && !final {
progressBase--
}
status := fmt.Sprintf("[%s] %s %d / %d snapshots", ui.FormatDuration(runtime), ui.FormatPercent(progressBase, p.snapshotCount), p.processedSnapshotCount, p.snapshotCount)
if p.processedFileCount > 0 {
status += fmt.Sprintf(", %v files", p.processedFileCount)
}
if p.processedBlobCount > 0 {
status += fmt.Sprintf(", %d blobs", p.processedBlobCount)
}
status += fmt.Sprintf(", %s", ui.FormatBytes(p.processedSize))
p.m.Unlock()
if final {
p.term.SetStatus(nil)
p.term.Print(status)
} else {
p.term.SetStatus([]string{status})
}
}
func (p *Progress) Update(fileCount uint64, blobCount uint64, size uint64) {
p.m.Lock()
defer p.m.Unlock()
p.processedFileCount += fileCount
p.processedBlobCount += blobCount
p.processedSize += size
}
func (p *Progress) ProcessSnapshot() {
p.m.Lock()
defer p.m.Unlock()
p.processedSnapshotCount++
p.processedFileCount = 0
p.processedBlobCount = 0
p.processedSize = 0
}
+44
View File
@@ -0,0 +1,44 @@
package stats
import (
"testing"
"time"
rtest "github.com/restic/restic/internal/test"
"github.com/restic/restic/internal/ui"
)
func TestStatsProgress(t *testing.T) {
term := &ui.MockTerminal{}
progress := newProgress(term, true, 2)
progress.printProgress(0*time.Second, false)
rtest.Equals(t, []string{"[0:00] 0.00% 0 / 2 snapshots, 0 B"}, term.Output)
progress.ProcessSnapshot()
progress.Update(1, 2, 3)
progress.printProgress(5*time.Second, false)
// Output differs from the previous one because the progress is based on the number of processed snapshots,
// 1/2 snapshots means processing the snapshot 1 currently
rtest.Equals(t, []string{"[0:05] 0.00% 1 / 2 snapshots, 1 files, 2 blobs, 3 B"}, term.Output)
progress.ProcessSnapshot()
progress.printProgress(10*time.Second, false)
rtest.Equals(t, []string{"[0:10] 50.00% 2 / 2 snapshots, 0 B"}, term.Output)
progress.Update(4, 5, 6)
progress.printProgress(15*time.Second, false)
rtest.Equals(t, []string{"[0:15] 50.00% 2 / 2 snapshots, 4 files, 5 blobs, 6 B"}, term.Output)
progress.printProgress(20*time.Second, true)
rtest.Equals(t, []string{"[0:20] 100.00% 2 / 2 snapshots, 4 files, 5 blobs, 6 B"}, term.Output)
}
func TestStatsProgressJSON(t *testing.T) {
term := &ui.MockTerminal{}
progress := newProgress(term, false, 2)
progress.printProgress(0*time.Second, false)
// JSON output is not available yet, so just make sure to not break normal json output
rtest.Equals(t, nil, term.Output)
}
+1 -1
View File
@@ -6,7 +6,7 @@ import (
)
// Terminal is used to write messages and display status lines which can be
// updated. See termstatus.Terminal for a concrete implementation.
// updated. See termstatus.Setup for a concrete implementation.
type Terminal interface {
// Print writes a line to the terminal. Appends a newline if not present.
Print(line string)
+45 -44
View File
@@ -9,16 +9,16 @@ import (
"strings"
"sync"
"github.com/restic/restic/internal/terminal"
tty "github.com/restic/restic/internal/terminal"
"github.com/restic/restic/internal/ui"
)
var _ ui.Terminal = &Terminal{}
var _ ui.Terminal = &terminal{}
// Terminal is used to write messages and display status lines which can be
// terminal is used to write messages and display status lines which can be
// updated. When the output is redirected to a file, the status lines are not
// printed.
type Terminal struct {
type terminal struct {
rd io.ReadCloser
inFd uintptr
wr io.Writer
@@ -58,7 +58,15 @@ type fder interface {
}
// Setup creates a new termstatus.
// The returned function must be called to shut down the termstatus,
// The returned function must be called to shut down the termstatus.
//
// A goroutine is started to update the
// terminal. It is terminated when ctx is cancelled. When stdout is redirected to
// a file (e.g. via shell output redirection) or is just an io.Writer (not the
// open *os.File for stdout), no status lines are printed. The status lines and
// normal output (via Print/Printf) are written to stdout, error messages are
// written to stderr. If quiet is set to true, no status messages
// are printed even if the terminal supports it.
//
// Expected usage:
// ```
@@ -66,12 +74,12 @@ type fder interface {
// defer cancel()
// // do stuff
// ```
func Setup(stdin io.ReadCloser, stdout, stderr io.Writer, quiet bool) (*Terminal, func()) {
func Setup(stdin io.ReadCloser, stdout, stderr io.Writer, quiet bool) (ui.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(stdin, stdout, stderr, quiet)
term := new(stdin, stdout, stderr, quiet)
wg.Add(1)
go func() {
defer wg.Done()
@@ -89,15 +97,8 @@ func Setup(stdin io.ReadCloser, stdout, stderr io.Writer, quiet bool) (*Terminal
}
}
// 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
// open *os.File for stdout), no status lines are printed. The status lines and
// normal output (via Print/Printf) are written to wr, error messages are
// written to errWriter. If disableStatus is set to true, no status messages
// are printed even if the terminal supports it.
func New(rd io.ReadCloser, wr io.Writer, errWriter io.Writer, disableStatus bool) *Terminal {
t := &Terminal{
func new(rd io.ReadCloser, wr io.Writer, errWriter io.Writer, disableStatus bool) *terminal {
t := &terminal{
rd: rd,
wr: wr,
errWriter: errWriter,
@@ -111,22 +112,22 @@ func New(rd io.ReadCloser, wr io.Writer, errWriter io.Writer, disableStatus bool
}
if d, ok := rd.(fder); ok {
if terminal.InputIsTerminal(d.Fd()) {
if tty.InputIsTerminal(d.Fd()) {
t.inFd = d.Fd()
t.inputIsTerminal = true
}
}
if d, ok := wr.(fder); ok {
if terminal.CanUpdateStatus(d.Fd()) {
if tty.CanUpdateStatus(d.Fd()) {
// only use the fancy status code when we're running on a real terminal.
t.canUpdateStatus = true
t.fd = d.Fd()
t.clearCurrentLine = terminal.ClearCurrentLine(t.fd)
t.moveCursorUp = terminal.MoveCursorUp(t.fd)
t.moveCursorDown = terminal.MoveCursorDown(t.fd)
t.clearCurrentLine = tty.ClearCurrentLine(t.fd)
t.moveCursorUp = tty.MoveCursorUp(t.fd)
t.moveCursorDown = tty.MoveCursorDown(t.fd)
}
if terminal.OutputIsTerminal(d.Fd()) {
if tty.OutputIsTerminal(d.Fd()) {
t.outputIsTerminal = true
}
}
@@ -135,19 +136,19 @@ func New(rd io.ReadCloser, wr io.Writer, errWriter io.Writer, disableStatus bool
}
// InputIsTerminal returns whether the input is a terminal.
func (t *Terminal) InputIsTerminal() bool {
func (t *terminal) InputIsTerminal() bool {
return t.inputIsTerminal
}
// InputRaw returns the input reader.
func (t *Terminal) InputRaw() io.ReadCloser {
func (t *terminal) InputRaw() io.ReadCloser {
return t.rd
}
func (t *Terminal) ReadPassword(ctx context.Context, prompt string) (string, error) {
func (t *terminal) ReadPassword(ctx context.Context, prompt string) (string, error) {
if t.InputIsTerminal() {
t.Flush()
return terminal.ReadPassword(ctx, int(t.inFd), t.errWriter, prompt)
return tty.ReadPassword(ctx, int(t.inFd), t.errWriter, prompt)
}
if t.OutputIsTerminal() {
t.Print("reading repository password from stdin")
@@ -166,13 +167,13 @@ func readPassword(in io.Reader) (password string, err error) {
}
// CanUpdateStatus return whether the status output is updated in place.
func (t *Terminal) CanUpdateStatus() bool {
func (t *terminal) CanUpdateStatus() bool {
return t.canUpdateStatus
}
// OutputWriter returns a output writer that is safe for concurrent use with
// other output methods. Output is only shown after a line break.
func (t *Terminal) OutputWriter() io.Writer {
func (t *terminal) OutputWriter() io.Writer {
t.outputWriterOnce.Do(func() {
t.outputWriter = newLineWriter(t.Print)
})
@@ -182,19 +183,19 @@ func (t *Terminal) OutputWriter() io.Writer {
// OutputRaw returns the raw output writer. Should only be used if there is no
// other option. Must not be used in combination with Print, Error, SetStatus
// or any other method that writes to the terminal.
func (t *Terminal) OutputRaw() io.Writer {
func (t *terminal) OutputRaw() io.Writer {
t.Flush()
return t.wr
}
// OutputIsTerminal returns whether the output is a terminal.
func (t *Terminal) OutputIsTerminal() bool {
func (t *terminal) OutputIsTerminal() bool {
return t.outputIsTerminal
}
// Run updates the screen. It should be run in a separate goroutine. When
// ctx is cancelled, the status lines are cleanly removed.
func (t *Terminal) Run(ctx context.Context) {
func (t *terminal) Run(ctx context.Context) {
defer close(t.closed)
if t.canUpdateStatus {
t.run(ctx)
@@ -205,12 +206,12 @@ func (t *Terminal) Run(ctx context.Context) {
}
// run listens on the channels and updates the terminal screen.
func (t *Terminal) run(ctx context.Context) {
func (t *terminal) run(ctx context.Context) {
var status []string
for {
select {
case <-ctx.Done():
if !terminal.IsProcessBackground(t.fd) {
if !tty.IsProcessBackground(t.fd) {
t.writeStatus([]string{}, false)
}
@@ -221,7 +222,7 @@ func (t *Terminal) run(ctx context.Context) {
msg.barrier <- struct{}{}
continue
}
if terminal.IsProcessBackground(t.fd) {
if tty.IsProcessBackground(t.fd) {
// ignore all messages, do nothing, we are in the background process group
continue
}
@@ -246,7 +247,7 @@ func (t *Terminal) run(ctx context.Context) {
case stat := <-t.status:
status = append(status[:0], stat.lines...)
if terminal.IsProcessBackground(t.fd) {
if tty.IsProcessBackground(t.fd) {
// ignore all messages, do nothing, we are in the background process group
continue
}
@@ -256,13 +257,13 @@ func (t *Terminal) run(ctx context.Context) {
}
}
func (t *Terminal) logWriteErr(err error) {
func (t *terminal) logWriteErr(err error) {
if err != nil {
_, _ = fmt.Fprintf(t.errWriter, "write failed: %v\n", err)
}
}
func (t *Terminal) writeStatus(status []string, skipUnchanged bool) {
func (t *terminal) writeStatus(status []string, skipUnchanged bool) {
var unchanged []bool
if skipUnchanged {
if slices.Equal(status, t.lastStatus) {
@@ -321,7 +322,7 @@ func findUnchangedLines(curr, last []string) []bool {
// runWithoutStatus listens on the channels and just prints out the messages,
// without status lines.
func (t *Terminal) runWithoutStatus(ctx context.Context) {
func (t *terminal) runWithoutStatus(ctx context.Context) {
var lastStatus []string
for {
select {
@@ -358,7 +359,7 @@ func (t *Terminal) runWithoutStatus(ctx context.Context) {
}
// Flush waits for all pending messages to be printed.
func (t *Terminal) Flush() {
func (t *terminal) Flush() {
ch := make(chan struct{})
defer close(ch)
select {
@@ -371,7 +372,7 @@ func (t *Terminal) Flush() {
}
}
func (t *Terminal) print(line string, isErr bool) {
func (t *terminal) print(line string, isErr bool) {
// make sure the line ends with a line break
if len(line) == 0 || line[len(line)-1] != '\n' {
line += "\n"
@@ -384,12 +385,12 @@ func (t *Terminal) print(line string, isErr bool) {
}
// Print writes a line to the terminal.
func (t *Terminal) Print(line string) {
func (t *terminal) Print(line string) {
t.print(line, false)
}
// Error writes an error to the terminal.
func (t *Terminal) Error(line string) {
func (t *terminal) Error(line string) {
t.print(line, true)
}
@@ -409,11 +410,11 @@ func sanitizeLines(lines []string, width int) []string {
// SetStatus updates the status lines.
// The lines should not contain newlines; this method adds them.
// Pass nil or an empty array to remove the status lines.
func (t *Terminal) SetStatus(lines []string) {
func (t *terminal) SetStatus(lines []string) {
// only truncate interactive status output
var width int
if t.canUpdateStatus {
width = terminal.Width(t.fd)
width = tty.Width(t.fd)
if width <= 0 {
// use 80 columns by default
width = 80
+16 -16
View File
@@ -9,7 +9,7 @@ import (
"strings"
"testing"
"github.com/restic/restic/internal/terminal"
tty "github.com/restic/restic/internal/terminal"
rtest "github.com/restic/restic/internal/test"
)
@@ -17,9 +17,9 @@ func TestSetStatus(t *testing.T) {
buf, term, cancel := setupStatusTest()
const (
cl = terminal.PosixControlClearLine
home = terminal.PosixControlMoveCursorHome
up = terminal.PosixControlMoveCursorUp
cl = tty.PosixControlClearLine
home = tty.PosixControlMoveCursorHome
up = tty.PosixControlMoveCursorUp
clearLn = home + cl
)
@@ -55,10 +55,10 @@ func TestSetStatusUnchangedLines(t *testing.T) {
buf, term, cancel := setupStatusTest()
const (
cl = terminal.PosixControlClearLine
home = terminal.PosixControlMoveCursorHome
up = terminal.PosixControlMoveCursorUp
down = terminal.PosixControlMoveCursorDown
cl = tty.PosixControlClearLine
home = tty.PosixControlMoveCursorHome
up = tty.PosixControlMoveCursorUp
down = tty.PosixControlMoveCursorDown
clearLn = home + cl
stepDown = home + down
@@ -82,15 +82,15 @@ func TestSetStatusUnchangedLines(t *testing.T) {
rtest.Equals(t, exp, buf.String())
}
func setupStatusTest() (*bytes.Buffer, *Terminal, context.CancelFunc) {
func setupStatusTest() (*bytes.Buffer, *terminal, context.CancelFunc) {
buf := &bytes.Buffer{}
term := New(nil, buf, buf, false)
term := new(nil, buf, buf, false)
term.canUpdateStatus = true
term.fd = ^uintptr(0)
term.clearCurrentLine = terminal.PosixClearCurrentLine
term.moveCursorUp = terminal.PosixMoveCursorUp
term.moveCursorDown = terminal.PosixMoveCursorDown
term.clearCurrentLine = tty.PosixClearCurrentLine
term.moveCursorUp = tty.PosixMoveCursorUp
term.moveCursorDown = tty.PosixMoveCursorDown
ctx, cancel := context.WithCancel(context.Background())
go term.Run(ctx)
@@ -101,8 +101,8 @@ func TestPrint(t *testing.T) {
buf, term, cancel := setupStatusTest()
const (
cl = terminal.PosixControlClearLine
home = terminal.PosixControlMoveCursorHome
cl = tty.PosixControlClearLine
home = tty.PosixControlMoveCursorHome
)
term.Print("test")
@@ -148,7 +148,7 @@ func TestReadPassword(t *testing.T) {
func TestReadPasswordTerminal(t *testing.T) {
expected := "password"
term := New(io.NopCloser(strings.NewReader(expected)), io.Discard, io.Discard, false)
term := new(io.NopCloser(strings.NewReader(expected)), io.Discard, io.Discard, false)
pw, err := term.ReadPassword(context.Background(), "test")
rtest.OK(t, err)
rtest.Equals(t, expected, pw)
-1
View File
@@ -1 +0,0 @@
package walker