From dee28f5b0eb833056431820c63cd2b583c01118e Mon Sep 17 00:00:00 2001 From: Michael Eischer Date: Fri, 5 Jun 2026 14:18:21 +0200 Subject: [PATCH 01/11] ui/terminal: unexport New function --- cmd/restic/integration_helpers_test.go | 15 +++------------ internal/ui/termstatus/status.go | 23 ++++++++++++----------- internal/ui/termstatus/status_test.go | 4 ++-- 3 files changed, 17 insertions(+), 25 deletions(-) diff --git a/cmd/restic/integration_helpers_test.go b/cmd/restic/integration_helpers_test.go index b9a2db4c5..f574dbb13 100644 --- a/cmd/restic/integration_helpers_test.go +++ b/cmd/restic/integration_helpers_test.go @@ -10,7 +10,6 @@ import ( "path/filepath" "runtime" "strings" - "sync" "testing" "github.com/restic/restic/internal/backend" @@ -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) diff --git a/internal/ui/termstatus/status.go b/internal/ui/termstatus/status.go index 6f5f744e0..63f168472 100644 --- a/internal/ui/termstatus/status.go +++ b/internal/ui/termstatus/status.go @@ -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,14 +97,7 @@ 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 { +func new(rd io.ReadCloser, wr io.Writer, errWriter io.Writer, disableStatus bool) *Terminal { t := &Terminal{ rd: rd, wr: wr, diff --git a/internal/ui/termstatus/status_test.go b/internal/ui/termstatus/status_test.go index 989d2e782..78bc5ec6a 100644 --- a/internal/ui/termstatus/status_test.go +++ b/internal/ui/termstatus/status_test.go @@ -84,7 +84,7 @@ func TestSetStatusUnchangedLines(t *testing.T) { 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) @@ -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) From fc81a0b6add6ebc15fdea39769f61542bee2e3ec Mon Sep 17 00:00:00 2001 From: Michael Eischer Date: Fri, 5 Jun 2026 14:20:50 +0200 Subject: [PATCH 02/11] ui/terminal: unexport Terminal type --- internal/ui/terminal.go | 2 +- internal/ui/termstatus/status.go | 68 +++++++++++++-------------- internal/ui/termstatus/status_test.go | 28 +++++------ 3 files changed, 49 insertions(+), 49 deletions(-) diff --git a/internal/ui/terminal.go b/internal/ui/terminal.go index d4d5a10de..b100a27e2 100644 --- a/internal/ui/terminal.go +++ b/internal/ui/terminal.go @@ -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) diff --git a/internal/ui/termstatus/status.go b/internal/ui/termstatus/status.go index 63f168472..892e4c712 100644 --- a/internal/ui/termstatus/status.go +++ b/internal/ui/termstatus/status.go @@ -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 @@ -97,8 +97,8 @@ func Setup(stdin io.ReadCloser, stdout, stderr io.Writer, quiet bool) (ui.Termin } } -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, @@ -112,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 } } @@ -136,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") @@ -167,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) }) @@ -183,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) @@ -206,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) } @@ -222,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 } @@ -247,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 } @@ -257,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) { @@ -322,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 { @@ -359,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 { @@ -372,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" @@ -385,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) } @@ -410,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 diff --git a/internal/ui/termstatus/status_test.go b/internal/ui/termstatus/status_test.go index 78bc5ec6a..f8a7c4f5b 100644 --- a/internal/ui/termstatus/status_test.go +++ b/internal/ui/termstatus/status_test.go @@ -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.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") From e6d054d896758a40dd8da8756d6135e412c8d1cf Mon Sep 17 00:00:00 2001 From: Michael Eischer Date: Fri, 5 Jun 2026 14:21:04 +0200 Subject: [PATCH 03/11] walker: remove empty testing.go --- internal/walker/testing.go | 1 - 1 file changed, 1 deletion(-) delete mode 100644 internal/walker/testing.go diff --git a/internal/walker/testing.go b/internal/walker/testing.go deleted file mode 100644 index c06778242..000000000 --- a/internal/walker/testing.go +++ /dev/null @@ -1 +0,0 @@ -package walker From 47328afb2e59e5efb67441bf5e9809ee6af9c81e Mon Sep 17 00:00:00 2001 From: Michael Eischer Date: Fri, 5 Jun 2026 14:21:54 +0200 Subject: [PATCH 04/11] ui/progress: remove unused test helper --- internal/ui/progress/printer.go | 47 --------------------------------- 1 file changed, 47 deletions(-) diff --git a/internal/ui/progress/printer.go b/internal/ui/progress/printer.go index edcf7256b..5bf9d5017 100644 --- a/internal/ui/progress/printer.go +++ b/internal/ui/progress/printer.go @@ -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. @@ -58,48 +56,3 @@ func (*NoopPrinter) P(_ 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...) -} From 37fdbe5779752cbd6147e194283af5693d9401ef Mon Sep 17 00:00:00 2001 From: Michael Eischer Date: Fri, 5 Jun 2026 14:25:47 +0200 Subject: [PATCH 05/11] ui: inline Message into progressPrinter --- internal/ui/message.go | 64 ----------------------------------------- internal/ui/progress.go | 44 +++++++++++++++++++++++----- 2 files changed, 37 insertions(+), 71 deletions(-) delete mode 100644 internal/ui/message.go diff --git a/internal/ui/message.go b/internal/ui/message.go deleted file mode 100644 index d186c3859..000000000 --- a/internal/ui/message.go +++ /dev/null @@ -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...)) - } -} diff --git a/internal/ui/progress.go b/internal/ui/progress.go index 12388f844..4a9b432e7 100644 --- a/internal/ui/progress.go +++ b/internal/ui/progress.go @@ -54,16 +54,47 @@ func newProgressMax(show bool, max uint64, description string, term Terminal) *p type progressPrinter struct { term Terminal - Message - show bool + v uint } func (t *progressPrinter) NewCounter(description string) *progress.Counter { - return newProgressMax(t.show, 0, description, t.term) + return newProgressMax(t.v > 0, 0, description, t.term) } func (t *progressPrinter) NewCounterTerminalOnly(description string) *progress.Counter { - return newProgressMax(t.show && t.term.OutputIsTerminal(), 0, description, t.term) + return newProgressMax(t.v > 0 && t.term.OutputIsTerminal(), 0, description, t.term) +} + +func (t *progressPrinter) E(msg string, args ...interface{}) { + t.term.Error(fmt.Sprintf(msg, args...)) +} + +func (t *progressPrinter) S(msg string, args ...interface{}) { + t.term.Print(fmt.Sprintf(msg, args...)) +} + +func (t *progressPrinter) PT(msg string, args ...interface{}) { + if t.term.OutputIsTerminal() && t.v >= 1 { + t.term.Print(fmt.Sprintf(msg, args...)) + } +} + +func (t *progressPrinter) P(msg string, args ...interface{}) { + if t.v >= 1 { + t.term.Print(fmt.Sprintf(msg, args...)) + } +} + +func (t *progressPrinter) V(msg string, args ...interface{}) { + if t.v >= 2 { + t.term.Print(fmt.Sprintf(msg, args...)) + } +} + +func (t *progressPrinter) VV(msg string, args ...interface{}) { + if t.v >= 3 { + t.term.Print(fmt.Sprintf(msg, args...)) + } } func NewProgressPrinter(json bool, verbosity uint, term Terminal) progress.Printer { @@ -71,8 +102,7 @@ func NewProgressPrinter(json bool, verbosity uint, term Terminal) progress.Print verbosity = 0 } return &progressPrinter{ - term: term, - Message: *NewMessage(term, verbosity), - show: verbosity > 0, + term: term, + v: verbosity, } } From c04f3e9d6cd52b623537d487f33c460d167bf8e9 Mon Sep 17 00:00:00 2001 From: Michael Eischer Date: Fri, 5 Jun 2026 14:28:09 +0200 Subject: [PATCH 06/11] ui: unexport json and text progress --- internal/ui/backup/json.go | 28 ++++++++++++++-------------- internal/ui/backup/text.go | 24 ++++++++++++------------ 2 files changed, 26 insertions(+), 26 deletions(-) diff --git a/internal/ui/backup/json.go b/internal/ui/backup/json.go index b46bbdc5f..a678045ac 100644 --- a/internal/ui/backup/json.go +++ b/internal/ui/backup/json.go @@ -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{ +func NewJSONProgress(term ui.Terminal, verbosity uint) ProgressPrinter { + return &jsonProgress{ Printer: ui.NewProgressPrinter(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 { diff --git a/internal/ui/backup/text.go b/internal/ui/backup/text.go index 8b416da7d..0a0ccdbea 100644 --- a/internal/ui/backup/text.go +++ b/internal/ui/backup/text.go @@ -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,11 +20,11 @@ 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{ +func NewTextProgress(term ui.Terminal, verbosity uint) ProgressPrinter { + return &textProgress{ Printer: ui.NewProgressPrinter(false, verbosity, term), term: term, verbosity: verbosity, @@ -32,7 +32,7 @@ func NewTextProgress(term ui.Terminal, verbosity uint) *TextProgress { } // 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) From e8ed2434cd81b6ea8ea8fff8fe05ed658ab599d1 Mon Sep 17 00:00:00 2001 From: Michael Eischer Date: Fri, 5 Jun 2026 14:38:53 +0200 Subject: [PATCH 07/11] ui: move NewProgressPrinter to ui/progress.NewTerminalPrinter --- cmd/restic/cmd_backup.go | 3 +- cmd/restic/cmd_cache.go | 3 +- cmd/restic/cmd_cat.go | 3 +- cmd/restic/cmd_check.go | 2 +- cmd/restic/cmd_copy.go | 2 +- cmd/restic/cmd_copy_integration_test.go | 4 +-- cmd/restic/cmd_debug.go | 5 +-- cmd/restic/cmd_diff.go | 3 +- cmd/restic/cmd_dump.go | 3 +- cmd/restic/cmd_find.go | 3 +- cmd/restic/cmd_find_integration_test.go | 6 ++-- cmd/restic/cmd_forget.go | 3 +- cmd/restic/cmd_generate.go | 2 +- cmd/restic/cmd_init.go | 2 +- cmd/restic/cmd_key_add.go | 2 +- cmd/restic/cmd_key_list.go | 2 +- cmd/restic/cmd_key_passwd.go | 2 +- cmd/restic/cmd_key_remove.go | 2 +- cmd/restic/cmd_list.go | 3 +- cmd/restic/cmd_list_integration_test.go | 4 +-- cmd/restic/cmd_ls.go | 3 +- cmd/restic/cmd_migrate.go | 2 +- cmd/restic/cmd_mount.go | 3 +- cmd/restic/cmd_mount_integration_test.go | 4 +-- cmd/restic/cmd_prune.go | 2 +- cmd/restic/cmd_recover.go | 2 +- cmd/restic/cmd_repair_index.go | 3 +- cmd/restic/cmd_repair_packs.go | 3 +- cmd/restic/cmd_repair_snapshots.go | 3 +- cmd/restic/cmd_restore.go | 2 +- cmd/restic/cmd_rewrite.go | 2 +- cmd/restic/cmd_rewrite_integration_test.go | 7 ++-- cmd/restic/cmd_self_update.go | 3 +- cmd/restic/cmd_snapshots.go | 3 +- cmd/restic/cmd_stats.go | 4 +-- cmd/restic/cmd_tag.go | 3 +- cmd/restic/cmd_unlock.go | 3 +- cmd/restic/cmd_version.go | 4 +-- cmd/restic/integration_helpers_test.go | 12 +++---- cmd/restic/integration_test.go | 4 +-- internal/ui/backup/json.go | 2 +- internal/ui/backup/text.go | 2 +- .../ui/{progress.go => progress/terminal.go} | 36 +++++++++---------- internal/ui/restore/json.go | 2 +- internal/ui/restore/text.go | 2 +- 45 files changed, 97 insertions(+), 78 deletions(-) rename internal/ui/{progress.go => progress/terminal.go} (64%) diff --git a/cmd/restic/cmd_backup.go b/cmd/restic/cmd_backup.go index e0f901738..e4de65752 100644 --- a/cmd/restic/cmd_backup.go +++ b/cmd/restic/cmd_backup.go @@ -29,6 +29,7 @@ import ( "github.com/restic/restic/internal/restic" "github.com/restic/restic/internal/textfile" "github.com/restic/restic/internal/ui" + "github.com/restic/restic/internal/ui/progress" "github.com/restic/restic/internal/ui/backup" ) @@ -534,7 +535,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())) + progress.CalculateProgressInterval(!gopts.Quiet, gopts.JSON, term.CanUpdateStatus())) defer progressReporter.Done() // rejectByNameFuncs collect functions that can reject items from the backup based on path only diff --git a/cmd/restic/cmd_cache.go b/cmd/restic/cmd_cache.go index 7a571ae85..5c61ab115 100644 --- a/cmd/restic/cmd_cache.go +++ b/cmd/restic/cmd_cache.go @@ -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") diff --git a/cmd/restic/cmd_cat.go b/cmd/restic/cmd_cat.go index 0d64dbd87..437c4437b 100644 --- a/cmd/restic/cmd_cat.go +++ b/cmd/restic/cmd_cat.go @@ -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 diff --git a/cmd/restic/cmd_check.go b/cmd/restic/cmd_check.go index 4a5a54694..83d673193 100644 --- a/cmd/restic/cmd_check.go +++ b/cmd/restic/cmd_check.go @@ -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) } diff --git a/cmd/restic/cmd_copy.go b/cmd/restic/cmd_copy.go index d17ded7c9..7728af1a2 100644 --- a/cmd/restic/cmd_copy.go +++ b/cmd/restic/cmd_copy.go @@ -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 diff --git a/cmd/restic/cmd_copy_integration_test.go b/cmd/restic/cmd_copy_integration_test.go index 252b2fdfe..29e269578 100644 --- a/cmd/restic/cmd_copy_integration_test.go +++ b/cmd/restic/cmd_copy_integration_test.go @@ -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() diff --git a/cmd/restic/cmd_debug.go b/cmd/restic/cmd_debug.go index efc022941..a92160d26 100644 --- a/cmd/restic/cmd_debug.go +++ b/cmd/restic/cmd_debug.go @@ -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") diff --git a/cmd/restic/cmd_diff.go b/cmd/restic/cmd_diff.go index 500e38ff5..883216bbe 100644 --- a/cmd/restic/cmd_diff.go +++ b/cmd/restic/cmd_diff.go @@ -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 { diff --git a/cmd/restic/cmd_dump.go b/cmd/restic/cmd_dump.go index 87ca88f2c..981cd95ff 100644 --- a/cmd/restic/cmd_dump.go +++ b/cmd/restic/cmd_dump.go @@ -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": diff --git a/cmd/restic/cmd_find.go b/cmd/restic/cmd_find.go index 5254b58a0..baa47dc1f 100644 --- a/cmd/restic/cmd_find.go +++ b/cmd/restic/cmd_find.go @@ -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} diff --git a/cmd/restic/cmd_find_integration_test.go b/cmd/restic/cmd_find_integration_test.go index f46d63cc5..e5e35348f 100644 --- a/cmd/restic/cmd_find_integration_test.go +++ b/cmd/restic/cmd_find_integration_test.go @@ -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() diff --git a/cmd/restic/cmd_forget.go b/cmd/restic/cmd_forget.go index f1521f1c2..8dabb7feb 100644 --- a/cmd/restic/cmd_forget.go +++ b/cmd/restic/cmd_forget.go @@ -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 diff --git a/cmd/restic/cmd_generate.go b/cmd/restic/cmd_generate.go index 9f7d5611f..0a491518d 100644 --- a/cmd/restic/cmd_generate.go +++ b/cmd/restic/cmd_generate.go @@ -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 != "" { diff --git a/cmd/restic/cmd_init.go b/cmd/restic/cmd_init.go index 50d8810d8..9bf765353 100644 --- a/cmd/restic/cmd_init.go +++ b/cmd/restic/cmd_init.go @@ -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 { diff --git a/cmd/restic/cmd_key_add.go b/cmd/restic/cmd_key_add.go index f83a048ab..c0d156189 100644 --- a/cmd/restic/cmd_key_add.go +++ b/cmd/restic/cmd_key_add.go @@ -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 diff --git a/cmd/restic/cmd_key_list.go b/cmd/restic/cmd_key_list.go index a7828b44f..77da22e34 100644 --- a/cmd/restic/cmd_key_list.go +++ b/cmd/restic/cmd_key_list.go @@ -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 diff --git a/cmd/restic/cmd_key_passwd.go b/cmd/restic/cmd_key_passwd.go index 2f4a9507f..b46a12db2 100644 --- a/cmd/restic/cmd_key_passwd.go +++ b/cmd/restic/cmd_key_passwd.go @@ -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 diff --git a/cmd/restic/cmd_key_remove.go b/cmd/restic/cmd_key_remove.go index 2d85c0122..03eb8f313 100644 --- a/cmd/restic/cmd_key_remove.go +++ b/cmd/restic/cmd_key_remove.go @@ -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 diff --git a/cmd/restic/cmd_list.go b/cmd/restic/cmd_list.go index 11482fab1..7a8df5aed 100644 --- a/cmd/restic/cmd_list.go +++ b/cmd/restic/cmd_list.go @@ -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") diff --git a/cmd/restic/cmd_list_integration_test.go b/cmd/restic/cmd_list_integration_test.go index 655d6da27..6b2358d47 100644 --- a/cmd/restic/cmd_list_integration_test.go +++ b/cmd/restic/cmd_list_integration_test.go @@ -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() diff --git a/cmd/restic/cmd_ls.go b/cmd/restic/cmd_ls.go index 92e3abfcc..9672805f3 100644 --- a/cmd/restic/cmd_ls.go +++ b/cmd/restic/cmd_ls.go @@ -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'") diff --git a/cmd/restic/cmd_migrate.go b/cmd/restic/cmd_migrate.go index 8ad3541eb..1832b8b12 100644 --- a/cmd/restic/cmd_migrate.go +++ b/cmd/restic/cmd_migrate.go @@ -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 { diff --git a/cmd/restic/cmd_mount.go b/cmd/restic/cmd_mount.go index cb88a70e2..2322e6769 100644 --- a/cmd/restic/cmd_mount.go +++ b/cmd/restic/cmd_mount.go @@ -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") diff --git a/cmd/restic/cmd_mount_integration_test.go b/cmd/restic/cmd_mount_integration_test.go index 6f5557778..6aa3b8650 100644 --- a/cmd/restic/cmd_mount_integration_test.go +++ b/cmd/restic/cmd_mount_integration_test.go @@ -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 diff --git a/cmd/restic/cmd_prune.go b/cmd/restic/cmd_prune.go index c0d148990..eab66b839 100644 --- a/cmd/restic/cmd_prune.go +++ b/cmd/restic/cmd_prune.go @@ -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 diff --git a/cmd/restic/cmd_recover.go b/cmd/restic/cmd_recover.go index bbb71972f..72e8f94f8 100644 --- a/cmd/restic/cmd_recover.go +++ b/cmd/restic/cmd_recover.go @@ -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 diff --git a/cmd/restic/cmd_repair_index.go b/cmd/restic/cmd_repair_index.go index 32e38fc7b..de5f43ad6 100644 --- a/cmd/restic/cmd_repair_index.go +++ b/cmd/restic/cmd_repair_index.go @@ -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 { diff --git a/cmd/restic/cmd_repair_packs.go b/cmd/restic/cmd_repair_packs.go index f0f4cccbf..f32e57706 100644 --- a/cmd/restic/cmd_repair_packs.go +++ b/cmd/restic/cmd_repair_packs.go @@ -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 { diff --git a/cmd/restic/cmd_repair_snapshots.go b/cmd/restic/cmd_repair_snapshots.go index b392bf017..b6ffbef8b 100644 --- a/cmd/restic/cmd_repair_snapshots.go +++ b/cmd/restic/cmd_repair_snapshots.go @@ -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 { diff --git a/cmd/restic/cmd_restore.go b/cmd/restic/cmd_restore.go index d797d1b68..effa27c06 100644 --- a/cmd/restic/cmd_restore.go +++ b/cmd/restic/cmd_restore.go @@ -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, progress.CalculateProgressInterval(!gopts.Quiet, gopts.JSON, term.CanUpdateStatus())) res := restorer.NewRestorer(repo, sn, restorer.Options{ DryRun: opts.DryRun, Sparse: opts.Sparse, diff --git a/cmd/restic/cmd_rewrite.go b/cmd/restic/cmd_rewrite.go index 976fe4f73..1050730d2 100644 --- a/cmd/restic/cmd_rewrite.go +++ b/cmd/restic/cmd_rewrite.go @@ -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 diff --git a/cmd/restic/cmd_rewrite_integration_test.go b/cmd/restic/cmd_rewrite_integration_test.go index acef0dd4d..e30f437b2 100644 --- a/cmd/restic/cmd_rewrite_integration_test.go +++ b/cmd/restic/cmd_rewrite_integration_test.go @@ -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() diff --git a/cmd/restic/cmd_self_update.go b/cmd/restic/cmd_self_update.go index 387c9b1d7..946924057 100644 --- a/cmd/restic/cmd_self_update.go +++ b/cmd/restic/cmd_self_update.go @@ -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) diff --git a/cmd/restic/cmd_snapshots.go b/cmd/restic/cmd_snapshots.go index 5f938fbe8..12fbc7bf6 100644 --- a/cmd/restic/cmd_snapshots.go +++ b/cmd/restic/cmd_snapshots.go @@ -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 diff --git a/cmd/restic/cmd_stats.go b/cmd/restic/cmd_stats.go index d09f97ecb..14424fdcc 100644 --- a/cmd/restic/cmd_stats.go +++ b/cmd/restic/cmd_stats.go @@ -104,7 +104,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 { @@ -143,7 +143,7 @@ func runStats(ctx context.Context, opts StatsOptions, gopts global.Options, args 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) { + updater := progress.NewUpdater(progress.CalculateProgressInterval(!gopts.Quiet, gopts.JSON, term.CanUpdateStatus()), func(runtime time.Duration, final bool) { statsProgress.printProgress(runtime, final) }) diff --git a/cmd/restic/cmd_tag.go b/cmd/restic/cmd_tag.go index 6b1503f96..bda249f25 100644 --- a/cmd/restic/cmd_tag.go +++ b/cmd/restic/cmd_tag.go @@ -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!") diff --git a/cmd/restic/cmd_unlock.go b/cmd/restic/cmd_unlock.go index 48f2e593d..55452b9fc 100644 --- a/cmd/restic/cmd_unlock.go +++ b/cmd/restic/cmd_unlock.go @@ -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 diff --git a/cmd/restic/cmd_version.go b/cmd/restic/cmd_version.go index 1232ea779..a43fe1d0f 100644 --- a/cmd/restic/cmd_version.go +++ b/cmd/restic/cmd_version.go @@ -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 { diff --git a/cmd/restic/integration_helpers_test.go b/cmd/restic/integration_helpers_test.go index f574dbb13..d64447f7d 100644 --- a/cmd/restic/integration_helpers_test.go +++ b/cmd/restic/integration_helpers_test.go @@ -22,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" ) @@ -243,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() @@ -262,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() @@ -293,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() @@ -309,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() @@ -370,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() diff --git a/cmd/restic/integration_test.go b/cmd/restic/integration_test.go index 8c7ac6ccd..de3000527 100644 --- a/cmd/restic/integration_test.go +++ b/cmd/restic/integration_test.go @@ -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() diff --git a/internal/ui/backup/json.go b/internal/ui/backup/json.go index a678045ac..de91eff50 100644 --- a/internal/ui/backup/json.go +++ b/internal/ui/backup/json.go @@ -24,7 +24,7 @@ var _ ProgressPrinter = &jsonProgress{} // NewJSONProgress returns a new backup progress reporter. func NewJSONProgress(term ui.Terminal, verbosity uint) ProgressPrinter { return &jsonProgress{ - Printer: ui.NewProgressPrinter(true, verbosity, term), + Printer: progress.NewTerminalPrinter(true, verbosity, term), term: term, v: verbosity, } diff --git a/internal/ui/backup/text.go b/internal/ui/backup/text.go index 0a0ccdbea..b402e211a 100644 --- a/internal/ui/backup/text.go +++ b/internal/ui/backup/text.go @@ -25,7 +25,7 @@ var _ ProgressPrinter = &textProgress{} // NewTextProgress returns a new backup progress reporter. func NewTextProgress(term ui.Terminal, verbosity uint) ProgressPrinter { return &textProgress{ - Printer: ui.NewProgressPrinter(false, verbosity, term), + Printer: progress.NewTerminalPrinter(false, verbosity, term), term: term, verbosity: verbosity, } diff --git a/internal/ui/progress.go b/internal/ui/progress/terminal.go similarity index 64% rename from internal/ui/progress.go rename to internal/ui/progress/terminal.go index 4a9b432e7..9b0ab111e 100644 --- a/internal/ui/progress.go +++ b/internal/ui/progress/terminal.go @@ -1,4 +1,4 @@ -package ui +package progress import ( "fmt" @@ -6,7 +6,7 @@ import ( "strconv" "time" - "github.com/restic/restic/internal/ui/progress" + "github.com/restic/restic/internal/ui" ) // CalculateProgressInterval returns the interval configured via RESTIC_PROGRESS_FPS @@ -27,20 +27,20 @@ func CalculateProgressInterval(show bool, json bool, canUpdateStatus bool) time. } // newProgressMax returns a progress.Counter that prints to terminal if provided. -func newProgressMax(show bool, max uint64, description string, term Terminal) *progress.Counter { +func newProgressMax(show bool, max uint64, description string, term ui.Terminal) *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) { + 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", - FormatDuration(d), v, description) + ui.FormatDuration(d), v, description) } else { status = fmt.Sprintf("[%s] %s %d / %d %s", - FormatDuration(d), FormatPercent(v, max), v, max, description) + ui.FormatDuration(d), ui.FormatPercent(v, max), v, max, description) } if final { @@ -52,56 +52,56 @@ func newProgressMax(show bool, max uint64, description string, term Terminal) *p }) } -type progressPrinter struct { - term Terminal +type terminalPrinter struct { + term ui.Terminal v uint } -func (t *progressPrinter) NewCounter(description string) *progress.Counter { +func (t *terminalPrinter) NewCounter(description string) *Counter { return newProgressMax(t.v > 0, 0, description, t.term) } -func (t *progressPrinter) NewCounterTerminalOnly(description string) *progress.Counter { +func (t *terminalPrinter) NewCounterTerminalOnly(description string) *Counter { return newProgressMax(t.v > 0 && t.term.OutputIsTerminal(), 0, description, t.term) } -func (t *progressPrinter) E(msg string, args ...interface{}) { +func (t *terminalPrinter) E(msg string, args ...interface{}) { t.term.Error(fmt.Sprintf(msg, args...)) } -func (t *progressPrinter) S(msg string, args ...interface{}) { +func (t *terminalPrinter) S(msg string, args ...interface{}) { t.term.Print(fmt.Sprintf(msg, args...)) } -func (t *progressPrinter) PT(msg string, args ...interface{}) { +func (t *terminalPrinter) PT(msg string, args ...interface{}) { if t.term.OutputIsTerminal() && t.v >= 1 { t.term.Print(fmt.Sprintf(msg, args...)) } } -func (t *progressPrinter) P(msg string, args ...interface{}) { +func (t *terminalPrinter) P(msg string, args ...interface{}) { if t.v >= 1 { t.term.Print(fmt.Sprintf(msg, args...)) } } -func (t *progressPrinter) V(msg string, args ...interface{}) { +func (t *terminalPrinter) V(msg string, args ...interface{}) { if t.v >= 2 { t.term.Print(fmt.Sprintf(msg, args...)) } } -func (t *progressPrinter) VV(msg string, args ...interface{}) { +func (t *terminalPrinter) VV(msg string, args ...interface{}) { if t.v >= 3 { t.term.Print(fmt.Sprintf(msg, args...)) } } -func NewProgressPrinter(json bool, verbosity uint, term Terminal) progress.Printer { +func NewTerminalPrinter(json bool, verbosity uint, term ui.Terminal) Printer { if json { verbosity = 0 } - return &progressPrinter{ + return &terminalPrinter{ term: term, v: verbosity, } diff --git a/internal/ui/restore/json.go b/internal/ui/restore/json.go index 8e6e3cfd4..86fc492d4 100644 --- a/internal/ui/restore/json.go +++ b/internal/ui/restore/json.go @@ -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, } diff --git a/internal/ui/restore/text.go b/internal/ui/restore/text.go index 464615ecb..5eee78bfd 100644 --- a/internal/ui/restore/text.go +++ b/internal/ui/restore/text.go @@ -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, } } From 0bbfb072afac77f4e2d26b0ab426e8d75e10ed95 Mon Sep 17 00:00:00 2001 From: Michael Eischer Date: Fri, 5 Jun 2026 14:48:44 +0200 Subject: [PATCH 08/11] ui/progress: unexport NoopPrinter and add New* function --- cmd/restic/cmd_check_test.go | 4 ++-- cmd/restic/cmd_init_integration_test.go | 4 ++-- cmd/restic/cmd_key_integration_test.go | 4 ++-- internal/repository/prune_internal_test.go | 4 ++-- internal/repository/prune_test.go | 8 +++---- internal/repository/repack_test.go | 2 +- internal/repository/repair_index_test.go | 2 +- internal/repository/repair_pack_test.go | 4 ++-- internal/restorer/restorer_test.go | 6 ++--- internal/restorer/restorer_unix_test.go | 3 ++- internal/ui/backup/progress_test.go | 4 ++-- internal/ui/progress/printer.go | 27 +++++++++++++--------- internal/ui/restore/progress_test.go | 4 ++-- 13 files changed, 41 insertions(+), 35 deletions(-) diff --git a/cmd/restic/cmd_check_test.go b/cmd/restic/cmd_check_test.go index 58b50dae3..db05051f7 100644 --- a/cmd/restic/cmd_check_test.go +++ b/cmd/restic/cmd_check_test.go @@ -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) diff --git a/cmd/restic/cmd_init_integration_test.go b/cmd/restic/cmd_init_integration_test.go index ef08eabe5..5d8a8c64c 100644 --- a/cmd/restic/cmd_init_integration_test.go +++ b/cmd/restic/cmd_init_integration_test.go @@ -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) diff --git a/cmd/restic/cmd_key_integration_test.go b/cmd/restic/cmd_key_integration_test.go index abc5d96c7..35e949020 100644 --- a/cmd/restic/cmd_key_integration_test.go +++ b/cmd/restic/cmd_key_integration_test.go @@ -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) diff --git a/internal/repository/prune_internal_test.go b/internal/repository/prune_internal_test.go index 640ab061b..94fcdad41 100644 --- a/internal/repository/prune_internal_test.go +++ b/internal/repository/prune_internal_test.go @@ -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 diff --git a/internal/repository/prune_test.go b/internal/repository/prune_test.go index fc3c4dbbe..609935aa0 100644 --- a/internal/repository/prune_test.go +++ b/internal/repository/prune_test.go @@ -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", diff --git a/internal/repository/repack_test.go b/internal/repository/repack_test.go index fc7d86900..c490759ba 100644 --- a/internal/repository/repack_test.go +++ b/internal/repository/repack_test.go @@ -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)) } diff --git a/internal/repository/repair_index_test.go b/internal/repository/repair_index_test.go index c6b095696..3a39f4fa5 100644 --- a/internal/repository/repair_index_test.go +++ b/internal/repository/repair_index_test.go @@ -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) } diff --git a/internal/repository/repair_pack_test.go b/internal/repository/repair_pack_test.go index 54f0ca02a..558889e0d 100644 --- a/internal/repository/repair_pack_test.go +++ b/internal/repository/repair_pack_test.go @@ -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)) diff --git a/internal/restorer/restorer_test.go b/internal/restorer/restorer_test.go index 337a99918..25e4fc071 100644 --- a/internal/restorer/restorer_test.go +++ b/internal/restorer/restorer_test.go @@ -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,7 +1102,7 @@ func TestRestorerOverwriteBehavior(t *testing.T) { for _, test := range tests { t.Run("", func(t *testing.T) { - mock := &printerMock{} + mock := &printerMock{Printer: progress.NewNoopPrinter()} progress := restoreui.NewProgress(mock, 0) tempdir := saveSnapshotsAndOverwrite(t, baseSnapshot, overwriteSnapshot, Options{}, Options{Overwrite: test.Overwrite, Progress: progress}) @@ -1154,7 +1154,7 @@ func TestRestorerOverwritePartial(t *testing.T) { }, } - mock := &printerMock{} + mock := &printerMock{Printer: progress.NewNoopPrinter()} progress := restoreui.NewProgress(mock, 0) saveSnapshotsAndOverwrite(t, baseSnapshot, overwriteSnapshot, Options{}, Options{Overwrite: OverwriteAlways, Progress: progress}) progress.Finish() diff --git a/internal/restorer/restorer_unix_test.go b/internal/restorer/restorer_unix_test.go index a7912ea03..cad4976b4 100644 --- a/internal/restorer/restorer_unix_test.go +++ b/internal/restorer/restorer_unix_test.go @@ -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,7 +88,7 @@ func testRestorerProgressBar(t *testing.T, dryRun bool) { }, }, noopGetGenericAttributes) - mock := &printerMock{} + mock := &printerMock{Printer: progress.NewNoopPrinter()} progress := restoreui.NewProgress(mock, 0) res := NewRestorer(repo, sn, Options{Progress: progress, DryRun: dryRun}) diff --git a/internal/ui/backup/progress_test.go b/internal/ui/backup/progress_test.go index 7b53f2116..0afd4e85d 100644 --- a/internal/ui/backup/progress_test.go +++ b/internal/ui/backup/progress_test.go @@ -13,7 +13,7 @@ import ( type mockPrinter struct { sync.Mutex - progress.NoopPrinter + progress.Printer dirUnchanged, fileNew bool id restic.ID } @@ -48,7 +48,7 @@ func (p *mockPrinter) Reset() {} func TestProgress(t *testing.T) { t.Parallel() - prnt := &mockPrinter{} + prnt := &mockPrinter{Printer: progress.NewNoopPrinter()} prog := NewProgress(prnt, time.Millisecond) prog.StartFile("foo") diff --git a/internal/ui/progress/printer.go b/internal/ui/progress/printer.go index 5bf9d5017..f9b37808e 100644 --- a/internal/ui/progress/printer.go +++ b/internal/ui/progress/printer.go @@ -32,27 +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{}) {} +func (*noopPrinter) VV(_ string, _ ...interface{}) {} diff --git a/internal/ui/restore/progress_test.go b/internal/ui/restore/progress_test.go index 4c59d11ee..85fe8f553 100644 --- a/internal/ui/restore/progress_test.go +++ b/internal/ui/restore/progress_test.go @@ -37,7 +37,7 @@ type mockPrinter struct { trace printerTrace items itemTrace errors errorTrace - progress.NoopPrinter + progress.Printer } const mockFinishDuration = 42 * time.Second @@ -57,7 +57,7 @@ func (p *mockPrinter) Finish(progress State, _ time.Duration) { } func testProgress(fn func(progress *Progress) bool) (printerTrace, itemTrace, errorTrace) { - printer := &mockPrinter{} + printer := &mockPrinter{Printer: progress.NewNoopPrinter()} progress := NewProgress(printer, 0) final := fn(progress) progress.update(0, final) From 3b8b77e54df8cca99c1ea7daf7b672279791a889 Mon Sep 17 00:00:00 2001 From: Michael Eischer Date: Fri, 5 Jun 2026 14:54:07 +0200 Subject: [PATCH 09/11] test: remove unused exports --- internal/test/helpers.go | 6 +++--- internal/test/vars.go | 7 ++----- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/internal/test/helpers.go b/internal/test/helpers.go index e3fded66e..99d7c49b8 100644 --- a/internal/test/helpers.go +++ b/internal/test/helpers.go @@ -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 diff --git a/internal/test/vars.go b/internal/test/vars.go index b6b76541e..f3c740d46 100644 --- a/internal/test/vars.go +++ b/internal/test/vars.go @@ -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) } From 825d67ba4b2e2975684cf20317106c68468a01cd Mon Sep 17 00:00:00 2001 From: Michael Eischer Date: Fri, 5 Jun 2026 15:02:56 +0200 Subject: [PATCH 10/11] backup,restore: move CalculateProgressInterval call to their ui package --- cmd/restic/cmd_backup.go | 4 +--- cmd/restic/cmd_restore.go | 2 +- internal/restorer/restorer_test.go | 4 ++-- internal/restorer/restorer_unix_test.go | 2 +- internal/ui/backup/progress.go | 6 +++++- internal/ui/backup/progress_test.go | 2 +- internal/ui/restore/progress.go | 6 +++++- internal/ui/restore/progress_test.go | 2 +- 8 files changed, 17 insertions(+), 11 deletions(-) diff --git a/cmd/restic/cmd_backup.go b/cmd/restic/cmd_backup.go index e4de65752..94c0dd30d 100644 --- a/cmd/restic/cmd_backup.go +++ b/cmd/restic/cmd_backup.go @@ -29,7 +29,6 @@ import ( "github.com/restic/restic/internal/restic" "github.com/restic/restic/internal/textfile" "github.com/restic/restic/internal/ui" - "github.com/restic/restic/internal/ui/progress" "github.com/restic/restic/internal/ui/backup" ) @@ -534,8 +533,7 @@ func runBackup(ctx context.Context, opts BackupOptions, gopts global.Options, te } defer unlock() - progressReporter := backup.NewProgress(printer, - progress.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 diff --git a/cmd/restic/cmd_restore.go b/cmd/restic/cmd_restore.go index effa27c06..9648ac7f7 100644 --- a/cmd/restic/cmd_restore.go +++ b/cmd/restic/cmd_restore.go @@ -167,7 +167,7 @@ func runRestore(ctx context.Context, opts RestoreOptions, gopts global.Options, return err } - progress := restoreui.NewProgress(printer, progress.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, diff --git a/internal/restorer/restorer_test.go b/internal/restorer/restorer_test.go index 25e4fc071..ef4533578 100644 --- a/internal/restorer/restorer_test.go +++ b/internal/restorer/restorer_test.go @@ -1103,7 +1103,7 @@ func TestRestorerOverwriteBehavior(t *testing.T) { for _, test := range tests { t.Run("", func(t *testing.T) { mock := &printerMock{Printer: progress.NewNoopPrinter()} - progress := restoreui.NewProgress(mock, 0) + 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 { @@ -1155,7 +1155,7 @@ func TestRestorerOverwritePartial(t *testing.T) { } mock := &printerMock{Printer: progress.NewNoopPrinter()} - progress := restoreui.NewProgress(mock, 0) + progress := restoreui.NewProgress(mock, true, false, true) saveSnapshotsAndOverwrite(t, baseSnapshot, overwriteSnapshot, Options{}, Options{Overwrite: OverwriteAlways, Progress: progress}) progress.Finish() rtest.Equals(t, restoreui.State{ diff --git a/internal/restorer/restorer_unix_test.go b/internal/restorer/restorer_unix_test.go index cad4976b4..0ee6b885a 100644 --- a/internal/restorer/restorer_unix_test.go +++ b/internal/restorer/restorer_unix_test.go @@ -89,7 +89,7 @@ func testRestorerProgressBar(t *testing.T, dryRun bool) { }, noopGetGenericAttributes) mock := &printerMock{Printer: progress.NewNoopPrinter()} - progress := restoreui.NewProgress(mock, 0) + progress := restoreui.NewProgress(mock, true, false, true) res := NewRestorer(repo, sn, Options{Progress: progress, DryRun: dryRun}) tempdir := rtest.TempDir(t) diff --git a/internal/ui/backup/progress.go b/internal/ui/backup/progress.go index 17ccfa8f4..3675a9d49 100644 --- a/internal/ui/backup/progress.go +++ b/internal/ui/backup/progress.go @@ -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{}), diff --git a/internal/ui/backup/progress_test.go b/internal/ui/backup/progress_test.go index 0afd4e85d..6fa8a7ce9 100644 --- a/internal/ui/backup/progress_test.go +++ b/internal/ui/backup/progress_test.go @@ -49,7 +49,7 @@ func TestProgress(t *testing.T) { t.Parallel() prnt := &mockPrinter{Printer: progress.NewNoopPrinter()} - prog := NewProgress(prnt, time.Millisecond) + prog := newProgress(prnt, time.Millisecond) prog.StartFile("foo") prog.CompleteBlob(1024) diff --git a/internal/ui/restore/progress.go b/internal/ui/restore/progress.go index 575c7e27d..5a79d3825 100644 --- a/internal/ui/restore/progress.go +++ b/internal/ui/restore/progress.go @@ -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(), diff --git a/internal/ui/restore/progress_test.go b/internal/ui/restore/progress_test.go index 85fe8f553..407d2dfba 100644 --- a/internal/ui/restore/progress_test.go +++ b/internal/ui/restore/progress_test.go @@ -58,7 +58,7 @@ func (p *mockPrinter) Finish(progress State, _ time.Duration) { func testProgress(fn func(progress *Progress) bool) (printerTrace, itemTrace, errorTrace) { printer := &mockPrinter{Printer: progress.NewNoopPrinter()} - progress := NewProgress(printer, 0) + progress := newProgress(printer, 0) final := fn(progress) progress.update(0, final) trace := append(printerTrace{}, printer.trace...) From 14f86a462a18904928e2937d4bca9c58add4e5e0 Mon Sep 17 00:00:00 2001 From: Michael Eischer Date: Fri, 5 Jun 2026 15:00:16 +0200 Subject: [PATCH 11/11] stats: refactor ui progress printer into ui/stats --- cmd/restic/cmd_stats.go | 96 ++++-------------------------- cmd/restic/cmd_stats_test.go | 37 ------------ internal/ui/stats/progress.go | 94 +++++++++++++++++++++++++++++ internal/ui/stats/progress_test.go | 44 ++++++++++++++ 4 files changed, 148 insertions(+), 123 deletions(-) create mode 100644 internal/ui/stats/progress.go create mode 100644 internal/ui/stats/progress_test.go diff --git a/cmd/restic/cmd_stats.go b/cmd/restic/cmd_stats.go index 14424fdcc..83d2a2954 100644 --- a/cmd/restic/cmd_stats.go +++ b/cmd/restic/cmd_stats.go @@ -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" @@ -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(progress.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 diff --git a/cmd/restic/cmd_stats_test.go b/cmd/restic/cmd_stats_test.go index a2068de44..02d37acd9 100644 --- a/cmd/restic/cmd_stats_test.go +++ b/cmd/restic/cmd_stats_test.go @@ -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) -} diff --git a/internal/ui/stats/progress.go b/internal/ui/stats/progress.go new file mode 100644 index 000000000..5f568070e --- /dev/null +++ b/internal/ui/stats/progress.go @@ -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 +} diff --git a/internal/ui/stats/progress_test.go b/internal/ui/stats/progress_test.go new file mode 100644 index 000000000..2508d0056 --- /dev/null +++ b/internal/ui/stats/progress_test.go @@ -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) +}