cloudflare/pint

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
v0.28.0

Branches

Tags

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

Clone

HTTPS

Download ZIP

internal/git/git.go

150lines · modecode

1package git
2
3import (
4 "bufio"
5 "bytes"
6 "errors"
7 "fmt"
8 "os/exec"
9 "strconv"
10 "strings"
11
12 "github.com/rs/zerolog/log"
13)
14
15type LineBlame struct {
16 Filename string
17 Line int
18 Commit string
19}
20
21type LineBlames []LineBlame
22
23type FileBlames map[string]LineBlames
24
25type CommandRunner func(args ...string) ([]byte, error)
26
27func RunGit(args ...string) (content []byte, err error) {
28 log.Debug().Strs("args", args).Msg("Running git command")
29 cmd := exec.Command("git", args...)
30 var stdout, stderr bytes.Buffer
31 cmd.Stdout = &stdout
32 cmd.Stderr = &stderr
33 if err = cmd.Run(); err != nil {
34 if stderr.Len() > 0 {
35 return nil, errors.New(stderr.String())
36 }
37 return nil, err
38 }
39 return stdout.Bytes(), nil
40}
41
42func Blame(path string, cmd CommandRunner) (lines LineBlames, err error) {
43 log.Debug().Str("path", path).Msg("Running git blame")
44 output, err := cmd("blame", "--line-porcelain", "--", path)
45 if err != nil {
46 return nil, err
47 }
48
49 buf := bytes.NewReader(output)
50 scanner := bufio.NewScanner(buf)
51 var line string
52 var cl LineBlame
53 for scanner.Scan() {
54 line = scanner.Text()
55
56 switch {
57 case strings.HasPrefix(line, "author"):
58 continue
59 case strings.HasPrefix(line, "committer"):
60 continue
61 case strings.HasPrefix(line, "summary"):
62 continue
63 case strings.HasPrefix(line, "filename"):
64 cl.Filename = strings.Split(line, " ")[1]
65 case strings.HasPrefix(line, "previous"):
66 continue
67 case strings.HasPrefix(line, "boundary"):
68 continue
69 case strings.HasPrefix(line, "\t"):
70 if cl.Filename == path {
71 lines = append(lines, cl)
72 }
73 default:
74 parts := strings.Split(line, " ")
75 if len(parts) < 3 {
76 return nil, fmt.Errorf("failed to parse line number from line: %q", line)
77 }
78 cl.Commit = parts[0]
79 cl.Line, err = strconv.Atoi(parts[2])
80 if err != nil {
81 return nil, fmt.Errorf("failed to parse line number from %q: %w", line, err)
82 }
83 }
84 }
85 if err = scanner.Err(); err != nil {
86 return nil, err
87 }
88
89 return lines, nil
90}
91
92func HeadCommit(cmd CommandRunner) (string, error) {
93 commit, err := cmd("rev-parse", "--verify", "HEAD")
94 if err != nil {
95 return "", err
96 }
97 return strings.Trim(string(commit), "\n"), nil
98}
99
100type CommitRangeResults struct {
101 From string
102 To string
103 Commits []string
104}
105
106func (gcr CommitRangeResults) String() string {
107 return fmt.Sprintf("%s^..%s", gcr.From, gcr.To)
108}
109
110func CommitRange(cmd CommandRunner, baseBranch string) (cr CommitRangeResults, err error) {
111 cr.Commits = []string{}
112
113 out, err := cmd("log", "--format=%H", "--no-abbrev-commit", "--reverse", fmt.Sprintf("%s..HEAD", baseBranch))
114 if err != nil {
115 return cr, err
116 }
117
118 for _, line := range strings.Split(strings.TrimSuffix(string(out), "\n"), "\n") {
119 if line != "" {
120 cr.Commits = append(cr.Commits, line)
121 if cr.From == "" {
122 cr.From = line
123 }
124 cr.To = line
125 log.Debug().Str("commit", line).Msg("Found commit to scan")
126 }
127 }
128
129 if len(cr.Commits) == 0 {
130 return cr, fmt.Errorf("empty commit range")
131 }
132
133 return
134}
135
136func CurrentBranch(cmd CommandRunner) (string, error) {
137 commit, err := cmd("rev-parse", "--abbrev-ref", "HEAD")
138 if err != nil {
139 return "", err
140 }
141 return strings.Trim(string(commit), "\n"), nil
142}
143
144func CommitMessage(cmd CommandRunner, sha string) (string, error) {
145 msg, err := cmd("show", "-s", "--format=%B", sha)
146 if err != nil {
147 return "", err
148 }
149 return string(msg), err
150}
151