diff --git a/internal/config/command.go b/internal/config/command.go index c671589c..a0412305 100644 --- a/internal/config/command.go +++ b/internal/config/command.go @@ -26,6 +26,7 @@ type Command struct { FailText string `mapstructure:"fail_text" yaml:"fail_text,omitempty" json:"fail_text,omitempty" toml:"fail_text,omitempty"` Interactive bool `mapstructure:"interactive" yaml:",omitempty" json:"interactive,omitempty" toml:"interactive,omitempty"` + UseStdin bool `mapstructure:"use_stdin" yaml:",omitempty" json:"use_stdin,omitempty" toml:"use_stdin,omitempty"` StageFixed bool `mapstructure:"stage_fixed" yaml:"stage_fixed,omitempty" json:"stage_fixed,omitempty" toml:"stage_fixed,omitempty"` } diff --git a/internal/config/script.go b/internal/config/script.go index b7eb3c12..a2070dbe 100644 --- a/internal/config/script.go +++ b/internal/config/script.go @@ -19,6 +19,7 @@ type Script struct { FailText string `mapstructure:"fail_text" yaml:"fail_text,omitempty" json:"fail_text,omitempty" toml:"fail_text,omitempty"` Interactive bool `mapstructure:"interactive" yaml:",omitempty" json:"interactive,omitempty" toml:"interactive,omitempty"` + UseStdin bool `mapstructure:"use_stdin" yaml:",omitempty" json:"use_stdin,omitempty" toml:"use_stdin,omitempty"` StageFixed bool `mapstructure:"stage_fixed" yaml:"stage_fixed,omitempty" json:"stage_fixed,omitempty" toml:"stage_fixed,omitempty"` } diff --git a/internal/lefthook/run/exec/execute_unix.go b/internal/lefthook/run/exec/execute_unix.go index 0aaa93a6..ad0ceff2 100644 --- a/internal/lefthook/run/exec/execute_unix.go +++ b/internal/lefthook/run/exec/execute_unix.go @@ -19,6 +19,14 @@ import ( type CommandExecutor struct{} +type executeArgs struct { + in io.Reader + out io.Writer + envs []string + root string + interactive, useStdin bool +} + func (e CommandExecutor) Execute(opts Options, out io.Writer) error { in := os.Stdin if opts.Interactive && !isatty.IsTerminal(os.Stdin.Fd()) { @@ -40,10 +48,19 @@ func (e CommandExecutor) Execute(opts Options, out io.Writer) error { ) } + args := &executeArgs{ + in: in, + out: out, + envs: envs, + root: root, + interactive: opts.Interactive, + useStdin: opts.UseStdin, + } + // We can have one command split into separate to fit into shell command max length. // In this case we execute those commands one by one. for _, command := range opts.Commands { - if err := e.executeOne(command, root, envs, opts.Interactive, in, out); err != nil { + if err := e.execute(command, args); err != nil { return err } } @@ -60,14 +77,14 @@ func (e CommandExecutor) RawExecute(command []string, out io.Writer) error { return cmd.Run() } -func (e CommandExecutor) executeOne(cmdstr string, root string, envs []string, interactive bool, in io.Reader, out io.Writer) error { +func (e CommandExecutor) execute(cmdstr string, args *executeArgs) error { command := exec.Command("sh", "-c", cmdstr) - command.Dir = root - command.Env = append(os.Environ(), envs...) + command.Dir = args.root + command.Env = append(os.Environ(), args.envs...) - if interactive { - command.Stdout = out - command.Stdin = in + if args.interactive || args.useStdin { + command.Stdout = args.out + command.Stdin = args.in command.Stderr = os.Stderr err := command.Start() if err != nil { @@ -81,9 +98,9 @@ func (e CommandExecutor) executeOne(cmdstr string, root string, envs []string, i defer func() { _ = p.Close() }() - go func() { _, _ = io.Copy(p, in) }() + go func() { _, _ = io.Copy(p, args.in) }() - _, _ = io.Copy(out, p) + _, _ = io.Copy(args.out, p) } defer func() { _ = command.Process.Kill() }() diff --git a/internal/lefthook/run/exec/execute_windows.go b/internal/lefthook/run/exec/execute_windows.go index 3b6c66f4..d8cf2b89 100644 --- a/internal/lefthook/run/exec/execute_windows.go +++ b/internal/lefthook/run/exec/execute_windows.go @@ -11,6 +11,12 @@ import ( ) type CommandExecutor struct{} +type executeArgs struct { + in io.Reader + out io.Writer + envs []string + root string +} func (e CommandExecutor) Execute(opts Options, out io.Writer) error { root, _ := filepath.Abs(opts.Root) @@ -22,8 +28,16 @@ func (e CommandExecutor) Execute(opts Options, out io.Writer) error { ) } +args: + &executeArgs{ + in: os.Stdin, + out: out, + envs: envs, + root: root, + } + for _, command := range opts.Commands { - if err := e.executeOne(command, root, envs, opts.Interactive, os.Stdin, out); err != nil { + if err := e.execute(command, args); err != nil { return err } } @@ -40,22 +54,17 @@ func (e CommandExecutor) RawExecute(command []string, out io.Writer) error { return cmd.Run() } -func (e CommandExecutor) executeOne(cmdstr string, root string, envs []string, interactive bool, in io.Reader, out io.Writer) error { +func (e CommandExecutor) execute(cmdstr string, args *executeArgs) error { cmdargs := strings.Split(cmdstr, " ") command := exec.Command(cmdargs[0]) command.SysProcAttr = &syscall.SysProcAttr{ CmdLine: strings.Join(cmdargs, " "), } - command.Dir = root - command.Env = append(os.Environ(), envs...) - - if interactive { - command.Stdout = os.Stdout - } else { - command.Stdout = out - } + command.Dir = args.root + command.Env = append(os.Environ(), args.envs...) - command.Stdin = in + command.Stdout = args.out + command.Stdin = args.in command.Stderr = os.Stderr err := command.Start() if err != nil { diff --git a/internal/lefthook/run/exec/executor.go b/internal/lefthook/run/exec/executor.go index daa90830..96cf4099 100644 --- a/internal/lefthook/run/exec/executor.go +++ b/internal/lefthook/run/exec/executor.go @@ -6,10 +6,10 @@ import ( // Options contains the data that controls the execution. type Options struct { - Name, Root, FailText string - Commands []string - Env map[string]string - Interactive bool + Name, Root, FailText string + Commands []string + Env map[string]string + Interactive, UseStdin bool } // Executor provides an interface for command execution. diff --git a/internal/lefthook/run/runner.go b/internal/lefthook/run/runner.go index 078dee27..6ca98b1f 100644 --- a/internal/lefthook/run/runner.go +++ b/internal/lefthook/run/runner.go @@ -295,6 +295,7 @@ func (r *Runner) runScript(script *config.Script, path string, file os.FileInfo) Commands: []string{command}, FailText: script.FailText, Interactive: script.Interactive && !r.DisableTTY, + UseStdin: script.UseStdin, Env: script.Env, }, r.Hook.Follow) @@ -374,6 +375,7 @@ func (r *Runner) runCommand(name string, command *config.Command) { Commands: run.commands, FailText: command.FailText, Interactive: command.Interactive && !r.DisableTTY, + UseStdin: command.UseStdin, Env: command.Env, }, r.Hook.Follow)