• 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

76.29
/internal/git/command_executor.go
1
package git
2

3
import (
4
        "bytes"
5
        "fmt"
6
        "path/filepath"
7
        "strings"
8

9
        "github.com/evilmartians/lefthook/internal/log"
10
        "github.com/evilmartians/lefthook/internal/system"
11
)
12

13
// CommandExecutor provides some methods that take some effect on execution and/or result data.
14
type CommandExecutor struct {
15
        cmd           system.Command
16
        root          string
17
        maxCmdLen     int
18
        onlyDebugLogs bool
19
        noTrimOut     bool
20
}
21

22
// NewExecutor returns an object that executes given commands in the OS.
23
func NewExecutor(cmd system.Command) *CommandExecutor {
3✔
24
        return &CommandExecutor{cmd: cmd, maxCmdLen: system.MaxCmdLen()}
3✔
25
}
3✔
26

27
func (c CommandExecutor) WithoutEnvs(envs ...string) CommandExecutor {
2✔
28
        c.cmd = c.cmd.WithoutEnvs(envs...)
2✔
29
        return c
2✔
30
}
2✔
31

32
func (c CommandExecutor) OnlyDebugLogs() CommandExecutor {
×
33
        c.onlyDebugLogs = true
×
34
        return c
×
35
}
×
36

37
func (c CommandExecutor) WithoutTrim() CommandExecutor {
6✔
38
        c.noTrimOut = true
6✔
39
        return c
6✔
40
}
6✔
41

42
// Cmd runs plain string command. Trims spaces around output.
43
func (c CommandExecutor) Cmd(cmd []string) (string, error) {
6✔
44
        out, err := c.execute(cmd, c.root)
6✔
45
        if err != nil {
6✔
UNCOV
46
                return "", err
×
UNCOV
47
        }
×
48

49
        if !c.noTrimOut {
12✔
50
                out = strings.TrimSpace(out)
6✔
51
        }
6✔
52

53
        return out, nil
6✔
54
}
55

56
// BatchedCmd runs the command with any number of appended arguments batched in chunks to match the OS limits.
57
func (c CommandExecutor) BatchedCmd(cmd []string, args []string) (string, error) {
6✔
58
        result := strings.Builder{}
6✔
59

6✔
60
        argsBatched := batchByLength(args, c.maxCmdLen-len(cmd))
6✔
61
        for i, batch := range argsBatched {
12✔
62
                out, err := c.Cmd(append(cmd, batch...))
6✔
63
                if err != nil {
6✔
64
                        return "", fmt.Errorf("error in batch %d: %w", i, err)
×
65
                }
×
66
                result.WriteString(strings.TrimRight(out, "\n"))
6✔
67
                result.WriteString("\n")
6✔
68
        }
69

70
        return result.String(), nil
6✔
71
}
72

73
// CmdLines runs plain string command, returns its output split by newline.
74
func (c CommandExecutor) CmdLines(cmd []string) ([]string, error) {
6✔
75
        out, err := c.execute(cmd, c.root)
6✔
76
        if err != nil {
6✔
77
                return nil, err
×
78
        }
×
79

80
        if !c.noTrimOut {
8✔
81
                out = strings.TrimSpace(out)
2✔
82
        }
2✔
83

84
        return strings.Split(out, "\n"), nil
6✔
85
}
86

87
// CmdLines runs plain string command, returns its output split by newline.
88
func (c CommandExecutor) CmdLinesWithinFolder(cmd []string, folder string) ([]string, error) {
3✔
89
        root := filepath.Join(c.root, folder)
3✔
90
        out, err := c.execute(cmd, root)
3✔
91
        if err != nil {
3✔
92
                return nil, err
×
93
        }
×
94

95
        if !c.noTrimOut {
6✔
96
                out = strings.TrimSpace(out)
3✔
97
        }
3✔
98

99
        return strings.Split(out, "\n"), nil
3✔
100
}
101

102
func (c CommandExecutor) execute(cmd []string, root string) (string, error) {
6✔
103
        stdout := new(bytes.Buffer)
6✔
104
        stderr := new(bytes.Buffer)
6✔
105
        err := c.cmd.Run(cmd, root, system.NullReader, stdout, stderr)
6✔
106
        outString := stdout.String()
6✔
107
        errString := stderr.String()
6✔
108

6✔
109
        log.Builder(log.DebugLevel, "[lefthook] ").
6✔
110
                Add("git: ", strings.Join(cmd, " ")).
6✔
111
                Add("out: ", outString).
6✔
112
                Log()
6✔
113

6✔
114
        if err != nil {
6✔
UNCOV
115
                if len(errString) > 0 {
×
UNCOV
116
                        logLevel := log.ErrorLevel
×
UNCOV
117
                        if c.onlyDebugLogs {
×
118
                                logLevel = log.DebugLevel
×
119
                        }
×
UNCOV
120
                        log.Builder(logLevel, "> ").
×
UNCOV
121
                                Add("", strings.Join(cmd, " ")).
×
UNCOV
122
                                Add("", errString).
×
UNCOV
123
                                Log()
×
124
                }
125
        }
126

127
        return outString, err
6✔
128
}
129

130
func batchByLength(s []string, length int) [][]string {
6✔
131
        batches := make([][]string, 0)
6✔
132

6✔
133
        var acc, prev int
6✔
134
        for i := range s {
12✔
135
                acc += len(s[i])
6✔
136
                if acc > length {
9✔
137
                        if i == prev {
3✔
138
                                batches = append(batches, s[prev:i+1])
×
139
                                prev = i + 1
×
140
                        } else {
3✔
141
                                batches = append(batches, s[prev:i])
3✔
142
                                prev = i
3✔
143
                        }
3✔
144
                        acc = len(s[i])
3✔
145
                }
146
        }
147
        if acc > 0 {
12✔
148
                batches = append(batches, s[prev:])
6✔
149
        }
6✔
150

151
        return batches
6✔
152
}
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