• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

evilmartians / lefthook / 17761174587

16 Sep 2025 09:24AM UTC coverage: 73.952% (-1.0%) from 74.989%
17761174587

push

github

web-flow
refactor: reduce the amount of code in a single file (#1118)

* Split controller.go and run_jobs.go 
* Add utils subpackage for common functions
* Add Name to a config.Hook and set it on config load

385 of 505 new or added lines in 14 files covered. (76.24%)

33 existing lines in 4 files now uncovered.

3370 of 4557 relevant lines covered (73.95%)

3.28 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

92.0
/internal/run/controller/controller.go
1
// controller handles ordering, filtering, substitutions while running
2
// jobs for a given hook.
3
package controller
4

5
import (
6
        "context"
7
        "io"
8
        "os"
9
        "strconv"
10
        "sync"
11

12
        "github.com/evilmartians/lefthook/internal/config"
13
        "github.com/evilmartians/lefthook/internal/git"
14
        "github.com/evilmartians/lefthook/internal/log"
15
        "github.com/evilmartians/lefthook/internal/run/controller/utils"
16
        "github.com/evilmartians/lefthook/internal/run/exec"
17
        "github.com/evilmartians/lefthook/internal/run/result"
18
        "github.com/evilmartians/lefthook/internal/system"
19
)
20

21
type Controller struct {
22
        git         *git.Repository
23
        cachedStdin io.Reader
24
        executor    exec.Executor
25
        cmd         system.CommandWithContext
26
}
27

28
type Options struct {
29
        GitArgs       []string
30
        ExcludeFiles  []string
31
        Files         []string
32
        RunOnlyJobs   []string
33
        RunOnlyTags   []string
34
        SourceDirs    []string
35
        Templates     map[string]string
36
        DisableTTY    bool
37
        FailOnChanges bool
38
        Force         bool
39
        SkipLFS       bool
40
}
41

42
func NewController(repo *git.Repository) *Controller {
3✔
43
        return &Controller{
3✔
44
                git: repo,
3✔
45

3✔
46
                // Some hooks use STDIN for parsing data from Git. To allow multiple commands
3✔
47
                // and scripts access the same Git data STDIN is cached via CachedReadec.
3✔
48
                cachedStdin: utils.NewCachedReader(os.Stdin),
3✔
49

3✔
50
                // Executor interface for jobs
3✔
51
                executor: exec.CommandExecutor{},
3✔
52

3✔
53
                // Command interface (for LFS hooks)
3✔
54
                cmd: system.Cmd,
3✔
55
        }
3✔
56
}
3✔
57

58
func (c *Controller) RunHook(ctx context.Context, opts Options, hook *config.Hook) ([]result.Result, error) {
6✔
59
        results := make([]result.Result, 0, len(hook.Jobs))
6✔
60

6✔
61
        if config.NewSkipChecker(system.Cmd).Check(c.git.State, hook.Skip, hook.Only) {
12✔
62
                log.Skip(hook.Name, "hook setting")
6✔
63
                return results, nil
6✔
64
        }
6✔
65

66
        if !opts.SkipLFS {
12✔
67
                if err := c.runLFSHook(ctx, hook.Name, opts.GitArgs); err != nil {
6✔
NEW
68
                        return results, err
×
NEW
69
                }
×
70
        }
71

72
        if !opts.DisableTTY && !hook.Follow {
12✔
73
                log.StartSpinner()
6✔
74
                defer log.StopSpinner()
6✔
75
        }
6✔
76

77
        guard := newGuard(c, config.HookUsesStagedFiles(hook.Name), opts.FailOnChanges)
6✔
78
        scope := newScope(hook, opts)
6✔
79
        err := guard.wrap(func() {
12✔
80
                if hook.Parallel {
9✔
81
                        results = c.concurrently(ctx, scope, hook.Jobs)
3✔
82
                } else {
9✔
83
                        results = c.sequentially(ctx, scope, hook.Jobs, hook.Piped)
6✔
84
                }
6✔
85
        })
86

87
        return results, err
6✔
88
}
89

90
func (c *Controller) concurrently(ctx context.Context, scope *scope, jobs []*config.Job) []result.Result {
3✔
91
        var wg sync.WaitGroup
3✔
92

3✔
93
        results := make([]result.Result, 0, len(jobs))
3✔
94
        resultsChan := make(chan result.Result, len(jobs))
3✔
95

3✔
96
        for i, job := range jobs {
6✔
97
                id := strconv.Itoa(i)
3✔
98

3✔
99
                wg.Add(1)
3✔
100
                go func(job *config.Job) {
6✔
101
                        defer wg.Done()
3✔
102
                        resultsChan <- c.runJob(ctx, scope, id, job)
3✔
103
                }(job)
3✔
104
        }
105

106
        wg.Wait()
3✔
107
        close(resultsChan)
3✔
108
        for result := range resultsChan {
6✔
109
                results = append(results, result)
3✔
110
        }
3✔
111

112
        return results
3✔
113
}
114

115
func (c *Controller) sequentially(ctx context.Context, scope *scope, jobs []*config.Job, piped bool) []result.Result {
6✔
116
        results := make([]result.Result, 0, len(jobs))
6✔
117
        var failPipe bool
6✔
118

6✔
119
        for i, job := range jobs {
12✔
120
                id := strconv.Itoa(i)
6✔
121

6✔
122
                if piped && failPipe {
6✔
NEW
123
                        log.Skip(job.PrintableName(id), "broken pipe")
×
NEW
124
                        continue
×
125
                }
126

127
                result := c.runJob(ctx, scope, id, job)
6✔
128
                if piped && result.Failure() {
6✔
NEW
129
                        failPipe = true
×
NEW
130
                }
×
131

132
                results = append(results, result)
6✔
133
        }
134

135
        return results
6✔
136
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc