diff --git a/commands/command_checkout.go b/commands/command_checkout.go index a854a107..2d9860da 100644 --- a/commands/command_checkout.go +++ b/commands/command_checkout.go @@ -47,7 +47,9 @@ func checkoutCommand(cmd *cobra.Command, args []string) { var totalBytes int64 var pointers []*lfs.WrappedPointer - logger := tasklog.NewLogger(os.Stdout) + logger := tasklog.NewLogger(os.Stdout, + tasklog.ForceProgress(cfg.ForceProgress()), + ) meter := tq.NewMeter() meter.Direction = tq.Checkout meter.Logger = meter.LoggerFromEnv(cfg.Os) diff --git a/commands/command_fetch.go b/commands/command_fetch.go index 86e1e9e3..08ef2631 100644 --- a/commands/command_fetch.go +++ b/commands/command_fetch.go @@ -230,7 +230,9 @@ func scanAll() []*lfs.WrappedPointer { task := tasklog.NewSimpleTask() defer task.Complete() - logger := tasklog.NewLogger(OutputWriter) + logger := tasklog.NewLogger(OutputWriter, + tasklog.ForceProgress(cfg.ForceProgress()), + ) logger.Enqueue(task) var numObjs int64 @@ -324,7 +326,9 @@ func fetchAndReportToChan(allpointers []*lfs.WrappedPointer, filter *filepathfil } func readyAndMissingPointers(allpointers []*lfs.WrappedPointer, filter *filepathfilter.Filter) ([]*lfs.WrappedPointer, []*lfs.WrappedPointer, *tq.Meter) { - logger := tasklog.NewLogger(os.Stdout) + logger := tasklog.NewLogger(os.Stdout, + tasklog.ForceProgress(cfg.ForceProgress()), + ) meter := buildProgressMeter(false, tq.Download) logger.Enqueue(meter) diff --git a/commands/command_migrate_export.go b/commands/command_migrate_export.go index aba64c64..790bf211 100644 --- a/commands/command_migrate_export.go +++ b/commands/command_migrate_export.go @@ -19,7 +19,9 @@ import ( func migrateExportCommand(cmd *cobra.Command, args []string) { ensureWorkingCopyClean(os.Stdin, os.Stderr) - l := tasklog.NewLogger(os.Stderr) + l := tasklog.NewLogger(os.Stderr, + tasklog.ForceProgress(cfg.ForceProgress()), + ) defer l.Close() db, err := getObjectDatabase() diff --git a/commands/command_migrate_import.go b/commands/command_migrate_import.go index 04cc9733..4fbeef8e 100644 --- a/commands/command_migrate_import.go +++ b/commands/command_migrate_import.go @@ -24,7 +24,9 @@ import ( func migrateImportCommand(cmd *cobra.Command, args []string) { ensureWorkingCopyClean(os.Stdin, os.Stderr) - l := tasklog.NewLogger(os.Stderr) + l := tasklog.NewLogger(os.Stderr, + tasklog.ForceProgress(cfg.ForceProgress()), + ) defer l.Close() db, err := getObjectDatabase() diff --git a/commands/command_migrate_info.go b/commands/command_migrate_info.go index ef4956f9..2e1b8d23 100644 --- a/commands/command_migrate_info.go +++ b/commands/command_migrate_info.go @@ -40,7 +40,9 @@ var ( ) func migrateInfoCommand(cmd *cobra.Command, args []string) { - l := tasklog.NewLogger(os.Stderr) + l := tasklog.NewLogger(os.Stderr, + tasklog.ForceProgress(cfg.ForceProgress()), + ) db, err := getObjectDatabase() if err != nil { diff --git a/commands/command_prune.go b/commands/command_prune.go index 06b2993b..5b54ac5d 100644 --- a/commands/command_prune.go +++ b/commands/command_prune.go @@ -57,7 +57,9 @@ func prune(fetchPruneConfig lfs.FetchPruneConfig, verifyRemote, dryRun, verbose localObjects := make([]fs.Object, 0, 100) retainedObjects := tools.NewStringSetWithCapacity(100) - logger := tasklog.NewLogger(OutputWriter) + logger := tasklog.NewLogger(OutputWriter, + tasklog.ForceProgress(cfg.ForceProgress()), + ) defer logger.Close() var reachableObjects tools.StringSet diff --git a/commands/command_pull.go b/commands/command_pull.go index 352b89f0..091643a3 100644 --- a/commands/command_pull.go +++ b/commands/command_pull.go @@ -38,7 +38,9 @@ func pull(filter *filepathfilter.Filter) { } pointers := newPointerMap() - logger := tasklog.NewLogger(os.Stdout) + logger := tasklog.NewLogger(os.Stdout, + tasklog.ForceProgress(cfg.ForceProgress()), + ) meter := tq.NewMeter() meter.Logger = meter.LoggerFromEnv(cfg.Os) logger.Enqueue(meter) diff --git a/commands/commands.go b/commands/commands.go index e7e351de..7505224e 100644 --- a/commands/commands.go +++ b/commands/commands.go @@ -30,8 +30,8 @@ import ( var ( Debugging = false ErrorBuffer = &bytes.Buffer{} - ErrorWriter = io.MultiWriter(os.Stderr, ErrorBuffer) - OutputWriter = io.MultiWriter(os.Stdout, ErrorBuffer) + ErrorWriter = newMultiWriter(os.Stderr, ErrorBuffer) + OutputWriter = newMultiWriter(os.Stdout, ErrorBuffer) ManPages = make(map[string]string, 20) tqManifest = make(map[string]*tq.Manifest) diff --git a/commands/multiwriter.go b/commands/multiwriter.go new file mode 100644 index 00000000..ea92b825 --- /dev/null +++ b/commands/multiwriter.go @@ -0,0 +1,26 @@ +package commands + +import ( + "io" + "os" +) + +type multiWriter struct { + writer io.Writer + fd uintptr +} + +func newMultiWriter(f *os.File, writers ...io.Writer) *multiWriter { + return &multiWriter{ + writer: io.MultiWriter(append([]io.Writer{f}, writers...)...), + fd: f.Fd(), + } +} + +func (w *multiWriter) Write(p []byte) (n int, err error) { + return w.writer.Write(p) +} + +func (w *multiWriter) Fd() uintptr { + return w.fd +} diff --git a/commands/uploader.go b/commands/uploader.go index c08ae16e..8f7c28a9 100644 --- a/commands/uploader.go +++ b/commands/uploader.go @@ -111,7 +111,9 @@ func newUploadContext(dryRun bool) *uploadContext { sink = ioutil.Discard } - ctx.logger = tasklog.NewLogger(sink) + ctx.logger = tasklog.NewLogger(sink, + tasklog.ForceProgress(cfg.ForceProgress()), + ) ctx.meter = buildProgressMeter(ctx.DryRun, tq.Upload) ctx.logger.Enqueue(ctx.meter) ctx.committerName, ctx.committerEmail = cfg.CurrentCommitter() diff --git a/config/config.go b/config/config.go index 96d69517..7a3e0755 100644 --- a/config/config.go +++ b/config/config.go @@ -308,6 +308,10 @@ func (c *Configuration) SetLockableFilesReadOnly() bool { return c.Os.Bool("GIT_LFS_SET_LOCKABLE_READONLY", true) && c.Git.Bool("lfs.setlockablereadonly", true) } +func (c *Configuration) ForceProgress() bool { + return c.Os.Bool("GIT_LFS_FORCE_PROGRESS", false) || c.Git.Bool("lfs.forceprogress", false) +} + // HookDir returns the location of the hooks owned by this repository. If the // core.hooksPath configuration variable is supported, we prefer that and expand // paths appropriately. diff --git a/docs/man/git-lfs-config.5.ronn b/docs/man/git-lfs-config.5.ronn index 8b71b224..52ea81f2 100644 --- a/docs/man/git-lfs-config.5.ronn +++ b/docs/man/git-lfs-config.5.ronn @@ -306,6 +306,16 @@ be scoped inside the configuration for a remote. * `total` The entire size of the file, in bytes. * `name` The name of the file. +* `GIT_LFS_FORCE_PROGRESS` + `lfs.forceprogress` + + Controls whether Git LFS will suppress progress status when the standard + output stream is not attached to a terminal. The default is `false` which + makes Git LFS detect whether stdout is a terminal and suppress progress when + it's not; you can disable this behaviour and force progress status even when + standard output stream is not a terminal by setting either variable to 1, + 'yes' or 'true'. + * `GIT_LFS_SET_LOCKABLE_READONLY` `lfs.setlockablereadonly` diff --git a/git/githistory/rewriter.go b/git/githistory/rewriter.go index 2283b4b4..f4854351 100644 --- a/git/githistory/rewriter.go +++ b/git/githistory/rewriter.go @@ -158,10 +158,12 @@ var ( } } - // WithLoggerto logs updates caused by the *git/githistory.Rewriter to + // WithLoggerTo logs updates caused by the *git/githistory.Rewriter to // the given io.Writer "sink". - WithLoggerTo = func(sink io.Writer) rewriterOption { - return WithLogger(tasklog.NewLogger(sink)) + WithLoggerTo = func(sink io.Writer, forceProgress bool) rewriterOption { + return WithLogger(tasklog.NewLogger(sink, + tasklog.ForceProgress(forceProgress), + )) } // WithLogger logs updates caused by the *git/githistory.Rewriter to the diff --git a/go.mod b/go.mod index 7f11ff32..2ebfbf73 100644 --- a/go.mod +++ b/go.mod @@ -8,6 +8,7 @@ require ( github.com/git-lfs/wildmatch v1.0.0 github.com/inconshreveable/mousetrap v1.0.0 // indirect github.com/kr/pty v0.0.0-20150511174710-5cf931ef8f76 + github.com/mattn/go-isatty v0.0.4 github.com/olekukonko/ts v0.0.0-20171002115256-78ecb04241c0 github.com/pkg/errors v0.0.0-20170505043639-c605e284fe17 github.com/rubyist/tracerx v0.0.0-20170927163412-787959303086 diff --git a/go.sum b/go.sum index 15f3909b..6476aa58 100644 --- a/go.sum +++ b/go.sum @@ -14,6 +14,8 @@ github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NH github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/kr/pty v0.0.0-20150511174710-5cf931ef8f76 h1:i5TIRQpbCg4aJMUtVHIhkQnSw++Z405Z5pzqHqeNkdU= github.com/kr/pty v0.0.0-20150511174710-5cf931ef8f76/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/mattn/go-isatty v0.0.4 h1:bnP0vzxcAdeI1zdubAl5PjU6zsERjGZb7raWodagDYs= +github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/olekukonko/ts v0.0.0-20171002115256-78ecb04241c0 h1:LiZB1h0GIcudcDci2bxbqI6DXV8bF8POAnArqvRrIyw= github.com/olekukonko/ts v0.0.0-20171002115256-78ecb04241c0/go.mod h1:F/7q8/HZz+TXjlsoZQQKVYvXTZaFH4QRa3y+j1p7MS0= github.com/pkg/errors v0.0.0-20170505043639-c605e284fe17 h1:chPfVn+gpAM5CTpTyVU9j8J+xgRGwmoDlNDLjKnJiYo= diff --git a/t/git-lfs-test-server-api/main.go b/t/git-lfs-test-server-api/main.go index 63f8e6c0..bdba2dd1 100644 --- a/t/git-lfs-test-server-api/main.go +++ b/t/git-lfs-test-server-api/main.go @@ -182,7 +182,9 @@ func buildTestData(repo *t.Repo, manifest *tq.Manifest) (oidsExist, oidsMissing oidsMissing = make([]TestObject, 0, oidCount) // just one commit - logger := tasklog.NewLogger(os.Stdout) + logger := tasklog.NewLogger(os.Stdout, + tasklog.ForceProgress(false), + ) meter := tq.NewMeter() meter.Logger = meter.LoggerFromEnv(repo.OSEnv()) logger.Enqueue(meter) diff --git a/t/testenv.sh b/t/testenv.sh index ecdf8ff5..86eef9cb 100644 --- a/t/testenv.sh +++ b/t/testenv.sh @@ -123,12 +123,14 @@ LFS_CLIENT_KEY_FILE_ENCRYPTED="$REMOTEDIR/client.enc.key" # the fake home dir used for the initial setup TESTHOME="$REMOTEDIR/home" +GIT_LFS_FORCE_PROGRESS=1 GIT_CONFIG_NOSYSTEM=1 GIT_TERMINAL_PROMPT=0 GIT_SSH=lfs-ssh-echo APPVEYOR_REPO_COMMIT_MESSAGE="test: env test should look for GIT_SSH too" export CREDSDIR +export GIT_LFS_FORCE_PROGRESS export GIT_CONFIG_NOSYSTEM export GIT_SSH export APPVEYOR_REPO_COMMIT_MESSAGE diff --git a/tasklog/log.go b/tasklog/log.go index ac2ab72c..c320c344 100644 --- a/tasklog/log.go +++ b/tasklog/log.go @@ -4,10 +4,12 @@ import ( "fmt" "io" "io/ioutil" + "os" "strings" "sync" "time" + isatty "github.com/mattn/go-isatty" "github.com/olekukonko/ts" ) @@ -25,6 +27,12 @@ type Logger struct { // this logger is running within. widthFn func() int + // tty is true if sink is connected to a terminal + tty bool + + // forceProgress forces progress status even when stdout is not a tty + forceProgress bool + // throttle is the minimum amount of time that must pass between each // instant data is logged. throttle time.Duration @@ -38,9 +46,21 @@ type Logger struct { wg *sync.WaitGroup } +// Option is the type for +type Option func(*Logger) + +// ForceProgress returns an options function that configures forced progress status +// on the logger. +func ForceProgress(v bool) Option { + return func(l *Logger) { + l.forceProgress = v + } +} + // NewLogger retuns a new *Logger instance that logs to "sink" and uses the -// current terminal width as the width of the line. -func NewLogger(sink io.Writer) *Logger { +// current terminal width as the width of the line. Will log progress status if +// stdout is a terminal or if forceProgress is true +func NewLogger(sink io.Writer, options ...Option) *Logger { if sink == nil { sink = ioutil.Discard } @@ -60,11 +80,29 @@ func NewLogger(sink io.Writer) *Logger { wg: new(sync.WaitGroup), } + for _, option := range options { + option(l) + } + + l.tty = tty(sink) + go l.consume() return l } +type hasFd interface { + Fd() uintptr +} + +// tty returns true if the writer is connected to a tty +func tty(writer io.Writer) bool { + if v, ok := writer.(hasFd); ok { + return isatty.IsTerminal(v.Fd()) + } + return false +} + // Close closes the queue and does not allow new Tasks to be `enqueue()`'d. It // waits until the currently running Task has completed. func (l *Logger) Close() { @@ -214,6 +252,9 @@ func (l *Logger) logTask(task Task) { var update *Update for update = range task.Updates() { + if !isatty.IsTerminal(os.Stdout.Fd()) && !l.forceProgress { + continue + } if logAll || l.throttle == 0 || !update.Throttled(last.Add(l.throttle)) { l.logLine(update.S) last = update.At diff --git a/tasklog/log_test.go b/tasklog/log_test.go index 7d0ac3c7..a087a6dc 100644 --- a/tasklog/log_test.go +++ b/tasklog/log_test.go @@ -31,7 +31,7 @@ func TestLoggerLogsTasks(t *testing.T) { close(task) }() - l := NewLogger(&buf) + l := NewLogger(&buf, ForceProgress(true)) l.throttle = 0 l.widthFn = func() int { return 0 } l.Enqueue(ChanTask(task)) @@ -40,6 +40,25 @@ func TestLoggerLogsTasks(t *testing.T) { assert.Equal(t, "first\rsecond\rsecond, done\n", buf.String()) } +func TestLoggerLogsSuppressesProgress(t *testing.T) { + var buf bytes.Buffer + + task := make(chan *Update) + go func() { + task <- &Update{"first", time.Now(), false} + task <- &Update{"second", time.Now(), false} + close(task) + }() + + l := NewLogger(&buf, ForceProgress(false)) + l.throttle = 0 + l.widthFn = func() int { return 0 } + l.Enqueue(ChanTask(task)) + l.Close() + + assert.Equal(t, "second, done\n", buf.String()) +} + func TestLoggerLogsMultipleTasksInOrder(t *testing.T) { var buf bytes.Buffer @@ -56,7 +75,7 @@ func TestLoggerLogsMultipleTasksInOrder(t *testing.T) { close(t2) }() - l := NewLogger(&buf) + l := NewLogger(&buf, ForceProgress(true)) l.throttle = 0 l.widthFn = func() int { return 0 } l.Enqueue(ChanTask(t1), ChanTask(t2)) @@ -75,7 +94,7 @@ func TestLoggerLogsMultipleTasksInOrder(t *testing.T) { func TestLoggerLogsMultipleTasksWithoutBlocking(t *testing.T) { var buf bytes.Buffer - l := NewLogger(&buf) + l := NewLogger(&buf, ForceProgress(true)) l.throttle = 0 t1, t2 := make(chan *Update), make(chan *Update) @@ -112,7 +131,7 @@ func TestLoggerThrottlesWrites(t *testing.T) { close(t1) // t = 20+2ε ms, throttle is closed }() - l := NewLogger(&buf) + l := NewLogger(&buf, ForceProgress(true)) l.widthFn = func() int { return 0 } l.throttle = 15 * time.Millisecond @@ -139,7 +158,7 @@ func TestLoggerThrottlesLastWrite(t *testing.T) { close(t1) // t = 10+2ε ms, throttle is closed }() - l := NewLogger(&buf) + l := NewLogger(&buf, ForceProgress(true)) l.widthFn = func() int { return 0 } l.throttle = 15 * time.Millisecond @@ -155,7 +174,7 @@ func TestLoggerThrottlesLastWrite(t *testing.T) { func TestLoggerLogsAllDurableUpdates(t *testing.T) { var buf bytes.Buffer - l := NewLogger(&buf) + l := NewLogger(&buf, ForceProgress(true)) l.widthFn = func() int { return 0 } l.throttle = 15 * time.Minute @@ -182,7 +201,7 @@ func TestLoggerHandlesSilentTasks(t *testing.T) { task := make(chan *Update) close(task) - l := NewLogger(&buf) + l := NewLogger(&buf, ForceProgress(true)) l.Enqueue(ChanTask(task)) l.Close() diff --git a/vendor/github.com/mattn/go-isatty/.travis.yml b/vendor/github.com/mattn/go-isatty/.travis.yml new file mode 100644 index 00000000..5597e026 --- /dev/null +++ b/vendor/github.com/mattn/go-isatty/.travis.yml @@ -0,0 +1,13 @@ +language: go +go: + - tip + +os: + - linux + - osx + +before_install: + - go get github.com/mattn/goveralls + - go get golang.org/x/tools/cmd/cover +script: + - $HOME/gopath/bin/goveralls -repotoken 3gHdORO5k5ziZcWMBxnd9LrMZaJs8m9x5 diff --git a/vendor/github.com/mattn/go-isatty/LICENSE b/vendor/github.com/mattn/go-isatty/LICENSE new file mode 100644 index 00000000..65dc692b --- /dev/null +++ b/vendor/github.com/mattn/go-isatty/LICENSE @@ -0,0 +1,9 @@ +Copyright (c) Yasuhiro MATSUMOTO + +MIT License (Expat) + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/vendor/github.com/mattn/go-isatty/README.md b/vendor/github.com/mattn/go-isatty/README.md new file mode 100644 index 00000000..1e69004b --- /dev/null +++ b/vendor/github.com/mattn/go-isatty/README.md @@ -0,0 +1,50 @@ +# go-isatty + +[![Godoc Reference](https://godoc.org/github.com/mattn/go-isatty?status.svg)](http://godoc.org/github.com/mattn/go-isatty) +[![Build Status](https://travis-ci.org/mattn/go-isatty.svg?branch=master)](https://travis-ci.org/mattn/go-isatty) +[![Coverage Status](https://coveralls.io/repos/github/mattn/go-isatty/badge.svg?branch=master)](https://coveralls.io/github/mattn/go-isatty?branch=master) +[![Go Report Card](https://goreportcard.com/badge/mattn/go-isatty)](https://goreportcard.com/report/mattn/go-isatty) + +isatty for golang + +## Usage + +```go +package main + +import ( + "fmt" + "github.com/mattn/go-isatty" + "os" +) + +func main() { + if isatty.IsTerminal(os.Stdout.Fd()) { + fmt.Println("Is Terminal") + } else if isatty.IsCygwinTerminal(os.Stdout.Fd()) { + fmt.Println("Is Cygwin/MSYS2 Terminal") + } else { + fmt.Println("Is Not Terminal") + } +} +``` + +## Installation + +``` +$ go get github.com/mattn/go-isatty +``` + +## License + +MIT + +## Author + +Yasuhiro Matsumoto (a.k.a mattn) + +## Thanks + +* k-takata: base idea for IsCygwinTerminal + + https://github.com/k-takata/go-iscygpty diff --git a/vendor/github.com/mattn/go-isatty/doc.go b/vendor/github.com/mattn/go-isatty/doc.go new file mode 100644 index 00000000..17d4f90e --- /dev/null +++ b/vendor/github.com/mattn/go-isatty/doc.go @@ -0,0 +1,2 @@ +// Package isatty implements interface to isatty +package isatty diff --git a/vendor/github.com/mattn/go-isatty/isatty_appengine.go b/vendor/github.com/mattn/go-isatty/isatty_appengine.go new file mode 100644 index 00000000..9584a988 --- /dev/null +++ b/vendor/github.com/mattn/go-isatty/isatty_appengine.go @@ -0,0 +1,15 @@ +// +build appengine + +package isatty + +// IsTerminal returns true if the file descriptor is terminal which +// is always false on on appengine classic which is a sandboxed PaaS. +func IsTerminal(fd uintptr) bool { + return false +} + +// IsCygwinTerminal() return true if the file descriptor is a cygwin or msys2 +// terminal. This is also always false on this environment. +func IsCygwinTerminal(fd uintptr) bool { + return false +} diff --git a/vendor/github.com/mattn/go-isatty/isatty_bsd.go b/vendor/github.com/mattn/go-isatty/isatty_bsd.go new file mode 100644 index 00000000..42f2514d --- /dev/null +++ b/vendor/github.com/mattn/go-isatty/isatty_bsd.go @@ -0,0 +1,18 @@ +// +build darwin freebsd openbsd netbsd dragonfly +// +build !appengine + +package isatty + +import ( + "syscall" + "unsafe" +) + +const ioctlReadTermios = syscall.TIOCGETA + +// IsTerminal return true if the file descriptor is terminal. +func IsTerminal(fd uintptr) bool { + var termios syscall.Termios + _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, fd, ioctlReadTermios, uintptr(unsafe.Pointer(&termios)), 0, 0, 0) + return err == 0 +} diff --git a/vendor/github.com/mattn/go-isatty/isatty_linux.go b/vendor/github.com/mattn/go-isatty/isatty_linux.go new file mode 100644 index 00000000..7384cf99 --- /dev/null +++ b/vendor/github.com/mattn/go-isatty/isatty_linux.go @@ -0,0 +1,18 @@ +// +build linux +// +build !appengine,!ppc64,!ppc64le + +package isatty + +import ( + "syscall" + "unsafe" +) + +const ioctlReadTermios = syscall.TCGETS + +// IsTerminal return true if the file descriptor is terminal. +func IsTerminal(fd uintptr) bool { + var termios syscall.Termios + _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, fd, ioctlReadTermios, uintptr(unsafe.Pointer(&termios)), 0, 0, 0) + return err == 0 +} diff --git a/vendor/github.com/mattn/go-isatty/isatty_linux_ppc64x.go b/vendor/github.com/mattn/go-isatty/isatty_linux_ppc64x.go new file mode 100644 index 00000000..44e5d213 --- /dev/null +++ b/vendor/github.com/mattn/go-isatty/isatty_linux_ppc64x.go @@ -0,0 +1,19 @@ +// +build linux +// +build ppc64 ppc64le + +package isatty + +import ( + "unsafe" + + syscall "golang.org/x/sys/unix" +) + +const ioctlReadTermios = syscall.TCGETS + +// IsTerminal return true if the file descriptor is terminal. +func IsTerminal(fd uintptr) bool { + var termios syscall.Termios + _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, fd, ioctlReadTermios, uintptr(unsafe.Pointer(&termios)), 0, 0, 0) + return err == 0 +} diff --git a/vendor/github.com/mattn/go-isatty/isatty_others.go b/vendor/github.com/mattn/go-isatty/isatty_others.go new file mode 100644 index 00000000..9d8b4a59 --- /dev/null +++ b/vendor/github.com/mattn/go-isatty/isatty_others.go @@ -0,0 +1,10 @@ +// +build !windows +// +build !appengine + +package isatty + +// IsCygwinTerminal return true if the file descriptor is a cygwin or msys2 +// terminal. This is also always false on this environment. +func IsCygwinTerminal(fd uintptr) bool { + return false +} diff --git a/vendor/github.com/mattn/go-isatty/isatty_solaris.go b/vendor/github.com/mattn/go-isatty/isatty_solaris.go new file mode 100644 index 00000000..1f0c6bf5 --- /dev/null +++ b/vendor/github.com/mattn/go-isatty/isatty_solaris.go @@ -0,0 +1,16 @@ +// +build solaris +// +build !appengine + +package isatty + +import ( + "golang.org/x/sys/unix" +) + +// IsTerminal returns true if the given file descriptor is a terminal. +// see: http://src.illumos.org/source/xref/illumos-gate/usr/src/lib/libbc/libc/gen/common/isatty.c +func IsTerminal(fd uintptr) bool { + var termio unix.Termio + err := unix.IoctlSetTermio(int(fd), unix.TCGETA, &termio) + return err == nil +} diff --git a/vendor/github.com/mattn/go-isatty/isatty_windows.go b/vendor/github.com/mattn/go-isatty/isatty_windows.go new file mode 100644 index 00000000..af51cbca --- /dev/null +++ b/vendor/github.com/mattn/go-isatty/isatty_windows.go @@ -0,0 +1,94 @@ +// +build windows +// +build !appengine + +package isatty + +import ( + "strings" + "syscall" + "unicode/utf16" + "unsafe" +) + +const ( + fileNameInfo uintptr = 2 + fileTypePipe = 3 +) + +var ( + kernel32 = syscall.NewLazyDLL("kernel32.dll") + procGetConsoleMode = kernel32.NewProc("GetConsoleMode") + procGetFileInformationByHandleEx = kernel32.NewProc("GetFileInformationByHandleEx") + procGetFileType = kernel32.NewProc("GetFileType") +) + +func init() { + // Check if GetFileInformationByHandleEx is available. + if procGetFileInformationByHandleEx.Find() != nil { + procGetFileInformationByHandleEx = nil + } +} + +// IsTerminal return true if the file descriptor is terminal. +func IsTerminal(fd uintptr) bool { + var st uint32 + r, _, e := syscall.Syscall(procGetConsoleMode.Addr(), 2, fd, uintptr(unsafe.Pointer(&st)), 0) + return r != 0 && e == 0 +} + +// Check pipe name is used for cygwin/msys2 pty. +// Cygwin/MSYS2 PTY has a name like: +// \{cygwin,msys}-XXXXXXXXXXXXXXXX-ptyN-{from,to}-master +func isCygwinPipeName(name string) bool { + token := strings.Split(name, "-") + if len(token) < 5 { + return false + } + + if token[0] != `\msys` && token[0] != `\cygwin` { + return false + } + + if token[1] == "" { + return false + } + + if !strings.HasPrefix(token[2], "pty") { + return false + } + + if token[3] != `from` && token[3] != `to` { + return false + } + + if token[4] != "master" { + return false + } + + return true +} + +// IsCygwinTerminal() return true if the file descriptor is a cygwin or msys2 +// terminal. +func IsCygwinTerminal(fd uintptr) bool { + if procGetFileInformationByHandleEx == nil { + return false + } + + // Cygwin/msys's pty is a pipe. + ft, _, e := syscall.Syscall(procGetFileType.Addr(), 1, fd, 0, 0) + if ft != fileTypePipe || e != 0 { + return false + } + + var buf [2 + syscall.MAX_PATH]uint16 + r, _, e := syscall.Syscall6(procGetFileInformationByHandleEx.Addr(), + 4, fd, fileNameInfo, uintptr(unsafe.Pointer(&buf)), + uintptr(len(buf)*2), 0, 0) + if r == 0 || e != 0 { + return false + } + + l := *(*uint32)(unsafe.Pointer(&buf)) + return isCygwinPipeName(string(utf16.Decode(buf[2 : 2+l/2]))) +} diff --git a/vendor/modules.txt b/vendor/modules.txt index 1119d6e5..b2c9289d 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -19,6 +19,8 @@ github.com/git-lfs/wildmatch github.com/inconshreveable/mousetrap # github.com/kr/pty v0.0.0-20150511174710-5cf931ef8f76 github.com/kr/pty +# github.com/mattn/go-isatty v0.0.4 +github.com/mattn/go-isatty # github.com/olekukonko/ts v0.0.0-20171002115256-78ecb04241c0 github.com/olekukonko/ts # github.com/pkg/errors v0.0.0-20170505043639-c605e284fe17