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

evilmartians / lefthook / 17643285980

11 Sep 2025 11:36AM UTC coverage: 74.989%. First build
17643285980

push

github

web-flow
fix: use batched cmd for calculating git hashes (#1116)

11 of 13 new or added lines in 2 files covered. (84.62%)

3406 of 4542 relevant lines covered (74.99%)

3.32 hits per line

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

85.57
/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 {
×
NEW
33
        c.onlyDebugLogs = true
×
NEW
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 {
8✔
46
                return "", err
2✔
47
        }
2✔
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 {
8✔
115
                if len(errString) > 0 {
4✔
116
                        logLevel := log.ErrorLevel
2✔
117
                        if c.onlyDebugLogs {
2✔
118
                                logLevel = log.DebugLevel
×
119
                        }
×
120
                        log.Builder(logLevel, "> ").
2✔
121
                                Add("", strings.Join(cmd, " ")).
2✔
122
                                Add("", errString).
2✔
123
                                Log()
2✔
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