Unify interactive terminal detection code

Previously the progress bar / status update interval used
stdoutIsTerminal to determine whether it is possible to update the
progress bar or not. However, its implementation differed from the
detection within the backup command which included additional checks to
detect the presence of mintty on Windows. mintty behaves like a terminal
but uses pipes for communication.

This adds stdoutCanUpdateStatus() which calls the same terminal detection
code used by backup. This ensures that all commands consistently switch
between interactive and non-interactive terminal mode.

stdoutIsTerminal() now also returns true whenever stdoutCanUpdateStatus()
does so. This is required to properly handle the special case of mintty.
This commit is contained in:
Michael Eischer 2021-03-07 22:50:52 +01:00
parent cc254dfefe
commit 5e6af77b7a
5 changed files with 17 additions and 10 deletions

View File

@ -31,6 +31,7 @@ import (
"github.com/restic/restic/internal/repository"
"github.com/restic/restic/internal/restic"
"github.com/restic/restic/internal/textfile"
"github.com/restic/restic/internal/ui/termstatus"
"github.com/restic/restic/internal/errors"
@ -142,7 +143,13 @@ func stdinIsTerminal() bool {
}
func stdoutIsTerminal() bool {
return terminal.IsTerminal(int(os.Stdout.Fd()))
// mintty on windows can use pipes which behave like a posix terminal,
// but which are not a terminal handle
return terminal.IsTerminal(int(os.Stdout.Fd())) || stdoutCanUpdateStatus()
}
func stdoutCanUpdateStatus() bool {
return termstatus.CanUpdateStatus(os.Stdout.Fd())
}
func stdoutTerminalWidth() int {
@ -159,7 +166,7 @@ func stdoutTerminalWidth() int {
// program execution must revert changes to the terminal configuration itself.
// The terminal configuration is only restored while reading a password.
func restoreTerminal() {
if !stdoutIsTerminal() {
if !terminal.IsTerminal(int(os.Stdout.Fd())) {
return
}
@ -248,7 +255,7 @@ func PrintProgress(format string, args ...interface{}) {
message = fmt.Sprintf(format, args...)
if !(strings.HasSuffix(message, "\r") || strings.HasSuffix(message, "\n")) {
if stdoutIsTerminal() {
if stdoutCanUpdateStatus() {
carriageControl = "\r"
} else {
carriageControl = "\n"
@ -256,7 +263,7 @@ func PrintProgress(format string, args ...interface{}) {
message = fmt.Sprintf("%s%s", message, carriageControl)
}
if stdoutIsTerminal() {
if stdoutCanUpdateStatus() {
message = fmt.Sprintf("%s%s", ClearLine(), message)
}

View File

@ -20,7 +20,7 @@ func calculateProgressInterval(show bool) time.Duration {
fps = 60
}
interval = time.Duration(float64(time.Second) / fps)
} else if !stdoutIsTerminal() || !show {
} else if !stdoutCanUpdateStatus() || !show {
interval = 0
}
return interval

View File

@ -67,7 +67,7 @@ func New(wr io.Writer, errWriter io.Writer, disableStatus bool) *Terminal {
return t
}
if d, ok := wr.(fder); ok && canUpdateStatus(d.Fd()) {
if d, ok := wr.(fder); ok && CanUpdateStatus(d.Fd()) {
// only use the fancy status code when we're running on a real terminal.
t.canUpdateStatus = true
t.fd = d.Fd()

View File

@ -20,9 +20,9 @@ func moveCursorUp(wr io.Writer, fd uintptr) func(io.Writer, uintptr, int) {
return posixMoveCursorUp
}
// canUpdateStatus returns true if status lines can be printed, the process
// CanUpdateStatus returns true if status lines can be printed, the process
// output is not redirected to a file or pipe.
func canUpdateStatus(fd uintptr) bool {
func CanUpdateStatus(fd uintptr) bool {
if !terminal.IsTerminal(int(fd)) {
return false
}

View File

@ -80,9 +80,9 @@ func isPipe(fd uintptr) bool {
return err == nil && typ == windows.FILE_TYPE_PIPE
}
// canUpdateStatus returns true if status lines can be printed, the process
// CanUpdateStatus returns true if status lines can be printed, the process
// output is not redirected to a file or pipe.
func canUpdateStatus(fd uintptr) bool {
func CanUpdateStatus(fd uintptr) bool {
// easy case, the terminal is cmd or psh, without redirection
if isWindowsTerminal(fd) {
return true