Skip to content

Commit

Permalink
fix: split command with file templates into chunks (#541)
Browse files Browse the repository at this point in the history
* set the maximum line length for Windows, macOS and Linux
* split the command if it uses file templates in run string
* execute the split command sequentially
  • Loading branch information
mrexox committed Sep 4, 2023
1 parent ac918cb commit 78069b2
Show file tree
Hide file tree
Showing 19 changed files with 765 additions and 379 deletions.
3 changes: 0 additions & 3 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
.vscode/
.idea/
.lefthook-local/
.lefthook/
/lefthook-local.yml
/lefthook.yml
/lefthook

tmp/
Expand Down
15 changes: 15 additions & 0 deletions .lefthook.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
[pre-commit]
parallel = true

[pre-commit.commands.lint]
run = "make lint"
glob = "*.go"
stage_fixed = true

[pre-commit.commands.test]
run = "make test"
glob = "*.go"

[pre-commit.commands.lychee]
glob = "*.md"
run = "lychee {staged_files}"
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ bin/golangci-lint:
curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $$(go env GOPATH)/bin v1.54.1

lint: bin/golangci-lint
$$(go env GOPATH)/bin/golangci-lint run
$$(go env GOPATH)/bin/golangci-lint run --fix

.ONESHELL:
version:
Expand Down
2 changes: 1 addition & 1 deletion docs/usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -219,7 +219,7 @@ When you try to commit `git commit -m "haha bad commit text"` script `commitlint
### Parallel execution

You can enable parallel execution if you want to speed up your checks.
Lets get example from [discourse](https://github.com/discourse/discourse/blob/master/.travis.yml#L77-L83) project.
Lets imagine we have the following rules to lint the whole project:

```
bundle exec rubocop --parallel && \
Expand Down
21 changes: 10 additions & 11 deletions internal/lefthook/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import (
"time"

"github.com/evilmartians/lefthook/internal/config"
"github.com/evilmartians/lefthook/internal/lefthook/runner"
"github.com/evilmartians/lefthook/internal/lefthook/run"
"github.com/evilmartians/lefthook/internal/log"
"github.com/evilmartians/lefthook/internal/version"
)
Expand Down Expand Up @@ -105,11 +105,10 @@ Run 'lefthook install' manually.`,
}

startTime := time.Now()
resultChan := make(chan runner.Result, len(hook.Commands)+len(hook.Scripts))
resultChan := make(chan run.Result, len(hook.Commands)+len(hook.Scripts))

run := runner.NewRunner(
runner.Opts{
Fs: l.Fs,
runner := run.NewRunner(
run.Options{
Repo: l.repo,
Hook: hook,
HookName: hookName,
Expand Down Expand Up @@ -140,11 +139,11 @@ Run 'lefthook install' manually.`,
}

go func() {
run.RunAll(sourceDirs)
runner.RunAll(sourceDirs)
close(resultChan)
}()

var results []runner.Result
var results []run.Result
for res := range resultChan {
results = append(results, res)
}
Expand All @@ -154,7 +153,7 @@ Run 'lefthook install' manually.`,
}

for _, result := range results {
if result.Status == runner.StatusErr {
if result.Status == run.StatusErr {
return errors.New("") // No error should be printed
}
}
Expand All @@ -164,7 +163,7 @@ Run 'lefthook install' manually.`,

func printSummary(
duration time.Duration,
results []runner.Result,
results []run.Result,
logSettings log.SkipSettings,
) {
if len(results) == 0 {
Expand All @@ -180,7 +179,7 @@ func printSummary(

if !logSettings.SkipSuccess() {
for _, result := range results {
if result.Status != runner.StatusOk {
if result.Status != run.StatusOk {
continue
}

Expand All @@ -190,7 +189,7 @@ func printSummary(

if !logSettings.SkipFailure() {
for _, result := range results {
if result.Status != runner.StatusErr {
if result.Status != run.StatusErr {
continue
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
//go:build !windows
// +build !windows

package runner
package exec

import (
"fmt"
Expand All @@ -19,36 +19,55 @@ import (

type CommandExecutor struct{}

func (e CommandExecutor) Execute(opts ExecuteOptions, out io.Writer) error {
stdin := os.Stdin
if opts.interactive && !isatty.IsTerminal(os.Stdin.Fd()) {
func (e CommandExecutor) Execute(opts Options, out io.Writer) error {
in := os.Stdin
if opts.Interactive && !isatty.IsTerminal(os.Stdin.Fd()) {
tty, err := os.Open("/dev/tty")
if err == nil {
defer tty.Close()
stdin = tty
in = tty
} else {
log.Errorf("Couldn't enable TTY input: %s\n", err)
}
}

command := exec.Command("sh", "-c", strings.Join(opts.args, " "))

rootDir, _ := filepath.Abs(opts.root)
command.Dir = rootDir

envList := make([]string, len(opts.env))
for name, value := range opts.env {
envList = append(
envList,
root, _ := filepath.Abs(opts.Root)
envs := make([]string, len(opts.Env))
for name, value := range opts.Env {
envs = append(
envs,
fmt.Sprintf("%s=%s", strings.ToUpper(name), os.ExpandEnv(value)),
)
}

command.Env = append(os.Environ(), envList...)
// 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 _, args := range opts.Commands {
if err := e.executeOne(args, root, envs, opts.Interactive, in, out); err != nil {
return err
}
}

return nil
}

func (e CommandExecutor) RawExecute(command []string, out io.Writer) error {
cmd := exec.Command(command[0], command[1:]...)

cmd.Stdout = out
cmd.Stderr = os.Stderr

return cmd.Run()
}

func (e CommandExecutor) executeOne(args []string, root string, envs []string, interactive bool, in io.Reader, out io.Writer) error {
command := exec.Command("sh", "-c", strings.Join(args, " "))
command.Dir = root
command.Env = append(os.Environ(), envs...)

if opts.interactive {
if interactive {
command.Stdout = out
command.Stdin = stdin
command.Stdin = in
command.Stderr = os.Stderr
err := command.Start()
if err != nil {
Expand All @@ -62,7 +81,7 @@ func (e CommandExecutor) Execute(opts ExecuteOptions, out io.Writer) error {

defer func() { _ = p.Close() }()

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

_, _ = io.Copy(out, p)
}
Expand All @@ -71,12 +90,3 @@ func (e CommandExecutor) Execute(opts ExecuteOptions, out io.Writer) error {

return command.Wait()
}

func (e CommandExecutor) RawExecute(command []string, out io.Writer) error {
cmd := exec.Command(command[0], command[1:]...)

cmd.Stdout = out
cmd.Stderr = os.Stderr

return cmd.Run()
}
67 changes: 67 additions & 0 deletions internal/lefthook/run/exec/execute_windows.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package exec

import (
"fmt"
"io"
"os"
"os/exec"
"path/filepath"
"strings"
"syscall"
)

type CommandExecutor struct{}

func (e CommandExecutor) Execute(opts Options, out io.Writer) error {
root, _ := filepath.Abs(opts.Root)
envs := make([]string, len(opts.Env))
for name, value := range opts.Env {
envs = append(
envs,
fmt.Sprintf("%s=%s", strings.ToUpper(name), os.ExpandEnv(value)),
)
}

for _, args := range opts.Commands {
if err := e.executeOne(args, root, envs, opts.Interactive, os.Stdin, out); err != nil {
return err
}
}

return nil
}

func (e CommandExecutor) RawExecute(command []string, out io.Writer) error {
cmd := exec.Command(command[0], command[1:]...)

cmd.Stdout = out
cmd.Stderr = os.Stderr

return cmd.Run()
}

func (e CommandExecutor) executeOne(args []string, root string, envs []string, interactive bool, in io.Reader, out io.Writer) error {
command := exec.Command(args[0])
command.SysProcAttr = &syscall.SysProcAttr{
CmdLine: strings.Join(args, " "),
}
command.Dir = root
command.Env = append(os.Environ(), envs...)

if interactive {
command.Stdout = os.Stdout
} else {
command.Stdout = out
}

command.Stdin = in
command.Stderr = os.Stderr
err := command.Start()
if err != nil {
return err
}

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

return command.Wait()
}
20 changes: 20 additions & 0 deletions internal/lefthook/run/exec/executor.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package exec

import (
"io"
)

// Options contains the data that controls the execution.
type Options struct {
Name, Root, FailText string
Commands [][]string
Env map[string]string
Interactive bool
}

// Executor provides an interface for command execution.
// It is used here for testing purpose mostly.
type Executor interface {
Execute(opts Options, out io.Writer) error
RawExecute(command []string, out io.Writer) error
}
Original file line number Diff line number Diff line change
@@ -1,13 +1,32 @@
package runner
package filter

import (
"regexp"
"strings"

"github.com/gobwas/glob"

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

func filterGlob(vs []string, matcher string) []string {
func Apply(command *config.Command, files []string) []string {
if len(files) == 0 {
return nil
}

log.Debug("[lefthook] files before filters:\n", files)

files = byGlob(files, command.Glob)
files = byExclude(files, command.Exclude)
files = byRoot(files, command.Root)

log.Debug("[lefthook] files after filters:\n", files)

return files
}

func byGlob(vs []string, matcher string) []string {
if matcher == "" {
return vs
}
Expand All @@ -23,7 +42,7 @@ func filterGlob(vs []string, matcher string) []string {
return vsf
}

func filterExclude(vs []string, matcher string) []string {
func byExclude(vs []string, matcher string) []string {
if matcher == "" {
return vs
}
Expand All @@ -37,7 +56,7 @@ func filterExclude(vs []string, matcher string) []string {
return vsf
}

func filterRelative(vs []string, matcher string) []string {
func byRoot(vs []string, matcher string) []string {
if matcher == "" {
return vs
}
Expand Down
Loading

0 comments on commit 78069b2

Please sign in to comment.