Skip to content

Commit

Permalink
feature: Pass stdin by default, add interactive option (#324)
Browse files Browse the repository at this point in the history
* fix: Pass STDIN by default

Signed-off-by: Valentin Kiselev <mrexox@evilmartians.com>

* fix: Copy STDIN to PTY when there is something

Signed-off-by: Valentin Kiselev <mrexox@evilmartians.com>

* fix: Linter complains

Signed-off-by: Valentin Kiselev <mrexox@evilmartians.com>

* fix: Remove tty hack, add interactive option

Signed-off-by: Valentin Kiselev <mrexox@evilmartians.com>
  • Loading branch information
mrexox authored Sep 19, 2022
1 parent c7ef982 commit 6548f66
Show file tree
Hide file tree
Showing 8 changed files with 71 additions and 27 deletions.
3 changes: 2 additions & 1 deletion internal/config/command.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ type Command struct {
Root string `mapstructure:"root"`
Exclude string `mapstructure:"exclude"`

FailText string `mapstructure:"fail_text"`
FailText string `mapstructure:"fail_text"`
Interactive bool `mapstructure:"interactive"`

// DEPRECATED, will be deleted in 1.2.0
Runner string `mapstructure:"runner"`
Expand Down
17 changes: 11 additions & 6 deletions internal/config/script.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ type Script struct {
Skip interface{} `mapstructure:"skip"`
Tags []string `mapstructure:"tags"`

FailText string `mapstructure:"fail_text"`
FailText string `mapstructure:"fail_text"`
Interactive bool `mapstructure:"interactive"`

// DEPRECATED
Run string `mapstructure:"run"`
Expand Down Expand Up @@ -116,17 +117,21 @@ func unmarshalScripts(s map[string]interface{}) (map[string]*Script, error) {
//
// ```yaml
// scripts:
// "example.sh":
// runner: bash
//
// "example.sh":
// runner: bash
//
// ```
//
// Unmarshals into this:
//
// ```yaml
// scripts:
// example:
// sh:
// runner: bash
//
// example:
// sh:
// runner: bash
//
// ```
//
// This is not an expected behavior and cannot be controlled yet
Expand Down
26 changes: 22 additions & 4 deletions internal/lefthook/runner/execute_unix.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,22 +12,40 @@ import (
"strings"

"github.com/creack/pty"
"github.com/mattn/go-isatty"

"github.com/evilmartians/lefthook/internal/log"
)

type CommandExecutor struct{}

func (e CommandExecutor) Execute(root string, args []string) (*bytes.Buffer, error) {
func (e CommandExecutor) Execute(root string, args []string, interactive bool) (*bytes.Buffer, error) {
stdin := os.Stdin
if interactive && !isatty.IsTerminal(os.Stdin.Fd()) {
tty, err := os.Open("/dev/tty")
if err == nil {
defer tty.Close()
stdin = tty
} else {
log.Errorf("Couldn't enable TTY input: %s\n", err)
}
}
command := exec.Command("sh", "-c", strings.Join(args, " "))
rootDir, _ := filepath.Abs(root)
command.Dir = rootDir

ptyOut, err := pty.Start(command)
p, err := pty.Start(command)
if err != nil {
return nil, err
}
defer func() { _ = ptyOut.Close() }()

defer func() { _ = p.Close() }()
defer func() { _ = command.Process.Kill() }()

go func() { _, _ = io.Copy(p, stdin) }()

out := bytes.NewBuffer(make([]byte, 0))
_, _ = io.Copy(out, ptyOut)
_, _ = io.Copy(out, p)

return out, command.Wait()
}
Expand Down
5 changes: 4 additions & 1 deletion internal/lefthook/runner/execute_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import (

type CommandExecutor struct{}

func (e CommandExecutor) Execute(root string, args []string) (*bytes.Buffer, error) {
func (e CommandExecutor) Execute(root string, args []string, _ bool) (*bytes.Buffer, error) {
command := exec.Command(args[0])
command.SysProcAttr = &syscall.SysProcAttr{
CmdLine: strings.Join(args, " "),
Expand All @@ -29,6 +29,9 @@ func (e CommandExecutor) Execute(root string, args []string) (*bytes.Buffer, err
if err != nil {
return nil, err
}

defer command.Process.Kill()

return &out, command.Wait()
}

Expand Down
2 changes: 1 addition & 1 deletion internal/lefthook/runner/executor.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,6 @@ import (
// Executor provides an interface for command execution.
// It is used here for testing purpose mostly.
type Executor interface {
Execute(root string, args []string) (*bytes.Buffer, error)
Execute(root string, args []string, interactive bool) (*bytes.Buffer, error)
RawExecute(command string, args ...string) error
}
39 changes: 30 additions & 9 deletions internal/lefthook/runner/runner.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,14 @@ const (

var surroundingQuotesRegexp = regexp.MustCompile(`^'(.*)'$`)

// RunOptions contains the options that control the execution.
type RunOptions struct {
name, root, failText string
args []string
interactive bool
}

// Runner responds for actual execution and handling the results.
type Runner struct {
fs afero.Fs
repo *git.Repository
Expand Down Expand Up @@ -57,6 +65,8 @@ func NewRunner(
}
}

// RunAll runs scripts and commands.
// LFS hook is executed at first if needed.
func (r *Runner) RunAll(hookName string, sourceDirs []string) {
if err := r.runLFSHook(hookName); err != nil {
log.Error(err)
Expand Down Expand Up @@ -201,7 +211,13 @@ func (r *Runner) runScript(script *config.Script, path string, file os.FileInfo)
args = append(args, path)
args = append(args, r.args[:]...)

r.run(file.Name(), r.repo.RootPath, script.FailText, args)
r.run(RunOptions{
name: file.Name(),
root: r.repo.RootPath,
args: args,
failText: script.FailText,
interactive: script.Interactive,
})
}

func (r *Runner) runCommands() {
Expand Down Expand Up @@ -260,8 +276,13 @@ func (r *Runner) runCommand(name string, command *config.Command) {
return
}

root := filepath.Join(r.repo.RootPath, command.Root)
r.run(name, root, command.FailText, args)
r.run(RunOptions{
name: name,
root: filepath.Join(r.repo.RootPath, command.Root),
args: args,
failText: command.FailText,
interactive: command.Interactive,
})
}

func (r *Runner) buildCommandArgs(command *config.Command) ([]string, error) {
Expand Down Expand Up @@ -369,16 +390,16 @@ func replaceQuoted(source, substitution string, files []string) string {
return source
}

func (r *Runner) run(name, root, failText string, args []string) {
out, err := r.exec.Execute(root, args)
func (r *Runner) run(opts RunOptions) {
out, err := r.exec.Execute(opts.root, opts.args, opts.interactive)

var execName string
if err != nil {
r.fail(name, failText)
execName = fmt.Sprint(log.Red("\n EXECUTE >"), log.Bold(name))
r.fail(opts.name, opts.failText)
execName = fmt.Sprint(log.Red("\n EXECUTE >"), log.Bold(opts.name))
} else {
r.success(name)
execName = fmt.Sprint(log.Cyan("\n EXECUTE >"), log.Bold(name))
r.success(opts.name)
execName = fmt.Sprint(log.Cyan("\n EXECUTE >"), log.Bold(opts.name))
}

if out != nil {
Expand Down
2 changes: 1 addition & 1 deletion internal/lefthook/runner/runner_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import (

type TestExecutor struct{}

func (e TestExecutor) Execute(root string, args []string) (out *bytes.Buffer, err error) {
func (e TestExecutor) Execute(root string, args []string, interactive bool) (out *bytes.Buffer, err error) {
out = bytes.NewBuffer(make([]byte, 0))

if args[0] == "success" {
Expand Down
4 changes: 0 additions & 4 deletions internal/templates/hook.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,6 @@ if [ "$LEFTHOOK" = "0" ]; then
exit 0
fi

if [ -t 1 ] ; then
exec < /dev/tty ; # <- enables interactive shell
fi

call_lefthook()
{
dir="$(git rev-parse --show-toplevel)"
Expand Down

0 comments on commit 6548f66

Please sign in to comment.