cloudflare/pint

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
v0.57.3

Branches

Tags

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

Clone

HTTPS

Download ZIP

internal/git/git.go

149lines · modecode

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