diff --git a/internal/config/do.go b/internal/config/do.go index 0fc6eec3..bfd4008b 100644 --- a/internal/config/do.go +++ b/internal/config/do.go @@ -5,13 +5,14 @@ import "errors" var ( errAmbiguousDo = errors.New("Ambiguous command") errEmptyDo = errors.New("Empty command") + errEmptyGroup = errors.New("Empty group") ) type Do struct { Name string `json:"name,omitempty" mapstructure:"name" toml:"name,omitempty" yaml:",omitempty"` Run string `json:"run,omitempty" mapstructure:"run" toml:"run,omitempty" yaml:",omitempty"` Script string `json:"script,omitempty" mapstructure:"script" toml:"script,omitempty" yaml:",omitempty"` - Runner string `json:"runner,omitempty" mapstructure:"runner" toml:"runner,omitempty" yaml:",runner"` + Runner string `json:"runner,omitempty" mapstructure:"runner" toml:"runner,omitempty" yaml:",omitempty"` Glob string `json:"glob,omitempty" mapstructure:"glob" toml:"glob,omitempty" yaml:",omitempty"` Root string `json:"root,omitempty" mapstructure:"root" toml:"root,omitempty" yaml:",omitempty"` @@ -53,4 +54,10 @@ func (do *Do) Validate() error { return nil } -// TODO: properly merge `do` options by index/name +func (g *Group) Validate() error { + if len(g.Do) == 0 { + return errEmptyGroup + } + + return nil +} diff --git a/internal/lefthook/run.go b/internal/lefthook/run.go index 08560552..102800e4 100644 --- a/internal/lefthook/run.go +++ b/internal/lefthook/run.go @@ -165,11 +165,12 @@ func (l *Lefthook) Run(hookName string, args RunArgs, gitArgs []string) error { Files: args.Files, Force: args.Force, RunOnlyCommands: args.RunOnlyCommands, + SourceDirs: sourceDirs, }, ) startTime := time.Now() - results, runErr := r.RunAll(ctx, sourceDirs) + results, runErr := r.RunAll(ctx) if runErr != nil { return fmt.Errorf("failed to run the hook: %w", runErr) } diff --git a/internal/lefthook/runner/do.go b/internal/lefthook/runner/do.go new file mode 100644 index 00000000..5f92d59a --- /dev/null +++ b/internal/lefthook/runner/do.go @@ -0,0 +1,109 @@ +package runner + +import ( + "context" + "fmt" + "path/filepath" + "strconv" + "sync" + + "github.com/evilmartians/lefthook/internal/config" + "github.com/evilmartians/lefthook/internal/lefthook/runner/exec" +) + +func (r *Runner) do(ctx context.Context) []Result { + var wg sync.WaitGroup + + results := make([]Result, 0, len(r.Hook.Do)) + resultsChan := make(chan Result, len(r.Hook.Do)) + for i, do := range r.Hook.Do { + id := strconv.Itoa(i) + if r.Hook.Parallel { + wg.Add(1) + go func(id string, do *config.Do) { + defer wg.Done() + resultsChan <- r.runDo(ctx, id, do) + }(id, do) + } else { + results = append(results, r.runDo(ctx, id, do)) + } + } + + wg.Wait() + close(resultsChan) + for result := range resultsChan { + results = append(results, result) + } + + return results +} + +func (r *Runner) runDo(ctx context.Context, id string, do *config.Do) Result { + name := do.Name + if len(name) == 0 { + name = do.Run + } + if len(name) == 0 { + name = do.Script + } + + err := do.Validate() + if err != nil { + return failed(name, "validation error") + } + + if len(do.Run) != 0 { + ok := r.run(ctx, exec.Options{ + Name: name, + Root: filepath.Join(r.Repo.RootPath, do.Root), + Commands: []string{do.Run}, + Interactive: do.Interactive && !r.DisableTTY, + UseStdin: do.UseStdin, + Env: do.Env, + }, r.Hook.Follow) + + if !ok { + r.failed.Store(true) + return failed(name, do.FailText) + } + } + + // if len(do.Script) != 0 { + // // run a script + // } + + if do.Group == nil { + return succeeded(name) + } + + if len(name) == 0 { + name = do.Group.Name + } + if len(name) == 0 { + name = fmt.Sprintf("group #%s", id) + } + + err = do.Group.Validate() + if err != nil { + return failed(name, "group validation error") + } + + var wg sync.WaitGroup + + for idx, subdo := range do.Group.Do { + subid := fmt.Sprintf("%s.%d", id, idx) + if do.Group.Parallel { + wg.Add(1) + go func(id string, do *config.Do) { + defer wg.Done() + r.runDo(ctx, id, do) + }(subid, subdo) + } else { + r.runDo(ctx, subid, subdo) + } + } + + wg.Wait() + + return succeeded(name) +} diff --git a/internal/lefthook/runner/runner.go b/internal/lefthook/runner/runner.go index d7a60413..a09b3aa7 100644 --- a/internal/lefthook/runner/runner.go +++ b/internal/lefthook/runner/runner.go @@ -46,6 +46,7 @@ type Options struct { Force bool Files []string RunOnlyCommands []string + SourceDirs []string } // Runner responds for actual execution and handling the results. @@ -87,7 +88,7 @@ type executable interface { // RunAll runs scripts and commands. // LFS hook is executed at first if needed. -func (r *Runner) RunAll(ctx context.Context, sourceDirs []string) ([]Result, error) { +func (r *Runner) RunAll(ctx context.Context) ([]Result, error) { results := make([]Result, 0, len(r.Hook.Commands)+len(r.Hook.Scripts)) if r.Hook.DoSkip(r.Repo.State) { @@ -104,8 +105,10 @@ func (r *Runner) RunAll(ctx context.Context, sourceDirs []string) ([]Result, err defer log.StopSpinner() } - scriptDirs := make([]string, 0, len(sourceDirs)) - for _, sourceDir := range sourceDirs { + results = append(results, r.do(ctx)...) + + scriptDirs := make([]string, 0, len(r.SourceDirs)) + for _, sourceDir := range r.SourceDirs { scriptDirs = append(scriptDirs, filepath.Join( sourceDir, r.HookName, )) diff --git a/internal/lefthook/runner/runner_test.go b/internal/lefthook/runner/runner_test.go index d4330aa1..4f1fe1a4 100644 --- a/internal/lefthook/runner/runner_test.go +++ b/internal/lefthook/runner/runner_test.go @@ -727,6 +727,7 @@ func TestRunAll(t *testing.T) { LogSettings: log.NewSettings(), GitArgs: tt.args, Force: tt.force, + SourceDirs: tt.sourceDirs, }, executor: executor{}, cmd: cmd{}, @@ -745,7 +746,7 @@ func TestRunAll(t *testing.T) { t.Run(name, func(t *testing.T) { assert := assert.New(t) git.ResetState() - results, err := runner.RunAll(context.Background(), tt.sourceDirs) + results, err := runner.RunAll(context.Background()) assert.NoError(err) var success, fail []Result diff --git a/testdata/dump.txt b/testdata/dump.txt index a57cfb25..a13d3b45 100644 --- a/testdata/dump.txt +++ b/testdata/dump.txt @@ -42,6 +42,7 @@ colors: red: '#FF1493' yellow: '#F0E68C' pre-commit: + follow: true commands: lint: run: yarn lint {staged_files} @@ -54,7 +55,6 @@ pre-commit: run: yarn test skip: merge glob: '*.js' - follow: true -- lefthook-dumped.json -- { "colors": { @@ -65,6 +65,7 @@ pre-commit: "yellow": "#F0E68C" }, "pre-commit": { + "follow": true, "commands": { "lint": { "run": "yarn lint {staged_files}", @@ -82,8 +83,7 @@ pre-commit: "skip": "merge", "glob": "*.js" } - }, - "follow": true + } } } -- lefthook-dumped.toml -- diff --git a/testdata/remotes.txt b/testdata/remotes.txt index 9836ba77..a9262a7e 100644 --- a/testdata/remotes.txt +++ b/testdata/remotes.txt @@ -19,6 +19,7 @@ remotes: -- lefthook-dump.yml -- DEPRECATED: "remotes"."config" option is deprecated and will be omitted in the next major release, use "configs" option instead pre-commit: + parallel: true commands: js-lint: run: npx eslint --fix {staged_files} && git add {staged_files} @@ -38,7 +39,6 @@ pre-commit: scripts: good_job.js: runner: node - parallel: true pre-push: commands: spelling: