cloudflare/pint

Public

mirrored from https://github.com/cloudflare/pintAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
v0.49.2

Branches

Tags

  • No tags available.
0Branches0Tags
Go to file
Add file
Code

Clone

HTTPS

Download ZIP

internal/discovery/git_branch.go

283lines · modecode

1package discovery
2
3import (
4 "bytes"
5 "fmt"
6 "log/slog"
7 "regexp"
8 "strings"
9
10 "golang.org/x/exp/slices"
11
12 "github.com/cloudflare/pint/internal/git"
13 "github.com/cloudflare/pint/internal/output"
14 "github.com/cloudflare/pint/internal/parser"
15)
16
17func NewGitBranchFinder(
18 gitCmd git.CommandRunner,
19 include []*regexp.Regexp,
20 exclude []*regexp.Regexp,
21 baseBranch string,
22 maxCommits int,
23 relaxed []*regexp.Regexp,
24) GitBranchFinder {
25 return GitBranchFinder{
26 gitCmd: gitCmd,
27 include: include,
28 exclude: exclude,
29 baseBranch: baseBranch,
30 maxCommits: maxCommits,
31 relaxed: relaxed,
32 }
33}
34
35type GitBranchFinder struct {
36 gitCmd git.CommandRunner
37 include []*regexp.Regexp
38 exclude []*regexp.Regexp
39 baseBranch string
40 maxCommits int
41 relaxed []*regexp.Regexp
42}
43
44func (f GitBranchFinder) Find() (entries []Entry, err error) {
45 slog.Info("Finding all rules to check on current git branch using logical diff", slog.String("base", f.baseBranch))
46
47 cr, err := git.CommitRange(f.gitCmd, f.baseBranch)
48 if err != nil {
49 return nil, fmt.Errorf("failed to get the list of commits to scan: %w", err)
50 }
51 slog.Debug("Got commit range from git", slog.String("from", cr.From), slog.String("to", cr.To))
52
53 if len(cr.Commits) > f.maxCommits {
54 return nil, fmt.Errorf("number of commits to check (%d) is higher than maxCommits (%d), exiting", len(cr.Commits), f.maxCommits)
55 }
56
57 changes, err := git.Changes(f.gitCmd, cr)
58 if err != nil {
59 return nil, err
60 }
61
62 shouldSkip, err := f.shouldSkipAllChecks(changes)
63 if err != nil {
64 return nil, err
65 }
66 if shouldSkip {
67 return nil, nil
68 }
69
70 for _, change := range changes {
71 if !f.isPathAllowed(change.Path.After.Name) {
72 slog.Debug("Skipping file due to include/exclude rules", slog.String("path", change.Path.After.Name))
73 continue
74 }
75
76 var entriesBefore, entriesAfter []Entry
77 entriesBefore, _ = readRules(
78 change.Path.Before.EffectivePath(),
79 change.Path.Before.Name,
80 bytes.NewReader(change.Body.Before),
81 !matchesAny(f.relaxed, change.Path.Before.Name),
82 )
83 entriesAfter, err = readRules(
84 change.Path.After.EffectivePath(),
85 change.Path.After.Name,
86 bytes.NewReader(change.Body.After),
87 !matchesAny(f.relaxed, change.Path.After.Name),
88 )
89 if err != nil {
90 return nil, fmt.Errorf("invalid file syntax: %w", err)
91 }
92
93 for _, me := range matchEntries(entriesBefore, entriesAfter) {
94 switch {
95 case me.before == nil && me.after != nil:
96 me.after.State = Added
97 me.after.ModifiedLines = commonLines(change.Body.ModifiedLines, me.after.ModifiedLines)
98 slog.Debug(
99 "Rule added on HEAD branch",
100 slog.String("name", me.after.Rule.Name()),
101 slog.String("state", me.after.State.String()),
102 slog.String("path", me.after.SourcePath),
103 slog.String("ruleLines", output.FormatLineRangeString(me.after.Rule.Lines())),
104 slog.String("modifiedLines", output.FormatLineRangeString(me.after.ModifiedLines)),
105 )
106 entries = append(entries, *me.after)
107 case me.before != nil && me.after != nil:
108 if me.isIdentical {
109 slog.Debug(
110 "Rule content was not modified on HEAD, identical rule present before",
111 slog.String("name", me.after.Rule.Name()),
112 slog.String("lines", output.FormatLineRangeString(me.after.Rule.Lines())),
113 )
114 continue
115 }
116 me.after.State = Modified
117 me.after.ModifiedLines = commonLines(change.Body.ModifiedLines, me.after.ModifiedLines)
118 slog.Debug(
119 "Rule modified on HEAD branch",
120 slog.String("name", me.after.Rule.Name()),
121 slog.String("state", me.after.State.String()),
122 slog.String("path", me.after.SourcePath),
123 slog.String("ruleLines", output.FormatLineRangeString(me.after.Rule.Lines())),
124 slog.String("modifiedLines", output.FormatLineRangeString(me.after.ModifiedLines)),
125 )
126 entries = append(entries, *me.after)
127 case me.before != nil && me.after == nil:
128 me.before.State = Removed
129 slog.Debug(
130 "Rule removed on HEAD branch",
131 slog.String("name", me.before.Rule.Name()),
132 slog.String("state", me.before.State.String()),
133 slog.String("path", me.before.SourcePath),
134 slog.String("ruleLines", output.FormatLineRangeString(me.before.Rule.Lines())),
135 slog.String("modifiedLines", output.FormatLineRangeString(me.before.ModifiedLines)),
136 )
137 entries = append(entries, *me.before)
138 default:
139 slog.Debug(
140 "Unknown rule",
141 slog.String("state", me.before.State.String()),
142 slog.String("path", me.before.SourcePath),
143 slog.String("modifiedLines", output.FormatLineRangeString(me.before.ModifiedLines)),
144 )
145 entries = append(entries, *me.after)
146 }
147 }
148 }
149
150 symlinks, err := addSymlinkedEntries(entries)
151 if err != nil {
152 return nil, err
153 }
154
155 for _, entry := range symlinks {
156 if f.isPathAllowed(entry.SourcePath) {
157 entries = append(entries, entry)
158 }
159 }
160
161 return entries, nil
162}
163
164func (f GitBranchFinder) isPathAllowed(path string) bool {
165 if len(f.include) == 0 && len(f.exclude) == 0 {
166 return true
167 }
168
169 for _, pattern := range f.exclude {
170 if pattern.MatchString(path) {
171 return false
172 }
173 }
174
175 for _, pattern := range f.include {
176 if pattern.MatchString(path) {
177 return true
178 }
179 }
180
181 return false
182}
183
184func (f GitBranchFinder) shouldSkipAllChecks(changes []*git.FileChange) (bool, error) {
185 commits := map[string]struct{}{}
186 for _, change := range changes {
187 for _, commit := range change.Commits {
188 commits[commit] = struct{}{}
189 }
190 }
191
192 for commit := range commits {
193 msg, err := git.CommitMessage(f.gitCmd, commit)
194 if err != nil {
195 return false, fmt.Errorf("failed to get commit message for %s: %w", commit, err)
196 }
197 for _, comment := range []string{"[skip ci]", "[no ci]"} {
198 if strings.Contains(msg, comment) {
199 slog.Info(
200 fmt.Sprintf("Found a commit with '%s', skipping all checks", comment),
201 slog.String("commit", commit))
202 return true, nil
203 }
204 }
205 }
206
207 return false, nil
208}
209
210func commonLines(a, b []int) (common []int) {
211 for _, ai := range a {
212 if slices.Contains(b, ai) {
213 common = append(common, ai)
214 }
215 }
216 for _, bi := range b {
217 if slices.Contains(a, bi) && !slices.Contains(common, bi) {
218 common = append(common, bi)
219 }
220 }
221 return common
222}
223
224type matchedEntry struct {
225 before *Entry
226 after *Entry
227 isIdentical bool
228}
229
230func matchEntries(before, after []Entry) (ml []matchedEntry) {
231 for _, a := range after {
232 a := a
233
234 m := matchedEntry{after: &a}
235 beforeSwap := make([]Entry, 0, len(before))
236 var matches []Entry
237 var matched bool
238
239 for _, b := range before {
240 b := b
241
242 if !matched && a.Rule.Name() != "" && a.Rule.ToYAML() == b.Rule.ToYAML() {
243 m.before = &b
244 m.isIdentical = true
245 matched = true
246 } else {
247 beforeSwap = append(beforeSwap, b)
248 }
249 }
250 before = beforeSwap
251
252 if !matched {
253 before, matches = findRulesByName(before, a.Rule.Name(), a.Rule.Type())
254 switch len(matches) {
255 case 0:
256 case 1:
257 m.before = &matches[0]
258 default:
259 before = append(before, matches...)
260 }
261 }
262
263 ml = append(ml, m)
264 }
265
266 for _, b := range before {
267 b := b
268 ml = append(ml, matchedEntry{before: &b})
269 }
270
271 return ml
272}
273
274func findRulesByName(entries []Entry, name string, typ parser.RuleType) (nomatch, match []Entry) {
275 for _, entry := range entries {
276 if entry.PathError == nil && entry.Rule.Type() == typ && entry.Rule.Name() == name {
277 match = append(match, entry)
278 } else {
279 nomatch = append(nomatch, entry)
280 }
281 }
282 return nomatch, match
283}
284