cloudflare/pint

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
v0.30.2

Branches

Tags

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

Clone

HTTPS

Download ZIP

internal/discovery/git_branch.go

190lines · modecode

1package discovery
2
3import (
4 "fmt"
5 "regexp"
6 "strings"
7
8 "github.com/cloudflare/pint/internal/git"
9
10 "github.com/rs/zerolog/log"
11)
12
13func NewGitBranchFinder(
14 gitCmd git.CommandRunner,
15 include []*regexp.Regexp,
16 baseBranch string,
17 maxCommits int,
18 relaxed []*regexp.Regexp,
19) GitBranchFinder {
20 return GitBranchFinder{
21 gitCmd: gitCmd,
22 include: include,
23 baseBranch: baseBranch,
24 maxCommits: maxCommits,
25 relaxed: relaxed,
26 }
27}
28
29type GitBranchFinder struct {
30 gitCmd git.CommandRunner
31 include []*regexp.Regexp
32 baseBranch string
33 maxCommits int
34 relaxed []*regexp.Regexp
35}
36
37func (f GitBranchFinder) Find() (entries []Entry, err error) {
38 cr, err := git.CommitRange(f.gitCmd, f.baseBranch)
39 if err != nil {
40 return nil, fmt.Errorf("failed to get the list of commits to scan: %w", err)
41 }
42
43 log.Debug().Str("from", cr.From).Str("to", cr.To).Msg("Got commit range from git")
44
45 if f.maxCommits > 0 && len(cr.Commits) > f.maxCommits {
46 return nil, fmt.Errorf("number of commits to check (%d) is higher than maxCommits (%d), exiting", len(cr.Commits), f.maxCommits)
47 }
48
49 out, err := f.gitCmd("log", "--reverse", "--no-merges", "--pretty=format:%H", "--name-status", cr.String())
50 if err != nil {
51 return nil, fmt.Errorf("failed to get the list of modified files from git: %w", err)
52 }
53
54 pathCommits := map[string]map[string]struct{}{}
55 var commit string
56
57 for _, line := range strings.Split(string(out), "\n") {
58 parts := strings.Split(removeRedundantSpaces(line), " ")
59 if len(parts) == 1 && parts[0] != "" {
60 commit = parts[0]
61 } else if len(parts) >= 2 {
62 op := parts[0]
63 srcPath := parts[1]
64 dstPath := parts[len(parts)-1]
65 log.Debug().
66 Str("path", dstPath).
67 Str("commit", commit).
68 Bool("allowed", f.isPathAllowed(dstPath)).
69 Msg("Git file change")
70 if !f.isPathAllowed(dstPath) {
71 continue
72 }
73
74 msg, err := git.CommitMessage(f.gitCmd, commit)
75 if err != nil {
76 return nil, fmt.Errorf("failed to get commit message for %s: %w", commit, err)
77 }
78 if strings.Contains(msg, "[skip ci]") {
79 log.Info().Str("commit", commit).Msg("Found a commit with '[skip ci]', skipping all checks")
80 return []Entry{}, nil
81 }
82 if strings.Contains(msg, "[no ci]") {
83 log.Info().Str("commit", commit).Msg("Found a commit with '[no ci]', skipping all checks")
84 return []Entry{}, nil
85 }
86
87 if _, ok := pathCommits[dstPath]; !ok {
88 pathCommits[dstPath] = map[string]struct{}{}
89 }
90 // check if we're dealing with a rename and if so we need to
91 // rename results in pathCommits
92 if strings.HasPrefix(op, "R") {
93 if commits, ok := pathCommits[srcPath]; ok {
94 for c := range commits {
95 pathCommits[dstPath][c] = struct{}{}
96 }
97 delete(pathCommits, srcPath)
98 }
99 }
100 // check if file is being removed, if so drop it from the results
101 if strings.HasPrefix(op, "D") {
102 delete(pathCommits, srcPath)
103 continue
104 }
105 pathCommits[dstPath][commit] = struct{}{}
106 }
107 }
108
109 for path, commits := range pathCommits {
110 lbs, err := git.Blame(path, f.gitCmd)
111 if err != nil {
112 return nil, fmt.Errorf("failed to run git blame for %s: %w", path, err)
113 }
114
115 allowedLines := []int{}
116 for _, lb := range lbs {
117 // skip commits that are not part of our diff
118 if _, ok := commits[lb.Commit]; !ok {
119 continue
120 }
121 allowedLines = append(allowedLines, lb.Line)
122 }
123
124 els, err := readFile(path, !matchesAny(f.relaxed, path))
125 if err != nil {
126 return nil, err
127 }
128 for _, e := range els {
129 e.ModifiedLines = getOverlap(e.Rule.Lines(), allowedLines)
130 if len(e.ModifiedLines) == 0 && e.PathError != nil {
131 e.ModifiedLines = allowedLines
132 }
133 if isOverlap(allowedLines, e.Rule.Lines()) || isOverlap(allowedLines, e.ModifiedLines) {
134 entries = append(entries, e)
135 }
136 }
137 }
138
139 symlinks, err := addSymlinkedEntries(entries)
140 if err != nil {
141 return nil, err
142 }
143
144 for _, entry := range symlinks {
145 if f.isPathAllowed(entry.SourcePath) {
146 entries = append(entries, entry)
147 }
148 }
149
150 return entries, nil
151}
152
153func (f GitBranchFinder) isPathAllowed(path string) bool {
154 if len(f.include) == 0 {
155 return true
156 }
157
158 for _, pattern := range f.include {
159 if pattern.MatchString(path) {
160 return true
161 }
162 }
163 return false
164}
165
166func removeRedundantSpaces(line string) string {
167 return strings.Join(strings.Fields(line), " ")
168}
169
170func isOverlap(a, b []int) bool {
171 for _, i := range a {
172 for _, j := range b {
173 if i == j {
174 return true
175 }
176 }
177 }
178 return false
179}
180
181func getOverlap(a, b []int) (o []int) {
182 for _, i := range a {
183 for _, j := range b {
184 if i == j {
185 o = append(o, i)
186 }
187 }
188 }
189 return
190}
191