-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathgit.go
141 lines (132 loc) · 3.18 KB
/
git.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
package main
import (
"bytes"
"fmt"
"io"
"os/exec"
"path/filepath"
"strings"
)
type changeMode byte
var validChangeTypes = "ACDMRTUXB"
const (
modeAdded changeMode = 'A'
modeCopied changeMode = 'C'
modeDeleted changeMode = 'D'
modeModified changeMode = 'M'
modeRenamed changeMode = 'R'
modeTypeChanged changeMode = 'T'
modeUnmerged changeMode = 'U'
modeUnknown changeMode = 'X'
modeBroken changeMode = 'B'
)
func (t changeMode) String() string {
switch t {
case modeAdded:
return "added"
case modeCopied:
return "copied"
case modeDeleted:
return "deleted"
case modeModified:
return "modified"
case modeRenamed:
return "renamed"
case modeUnknown:
return "unknown"
case modeBroken:
return "broken"
default:
return fmt.Sprintf("%s-invalid", string(t))
}
}
type fileStat struct {
mode changeMode
path string
oldPath string // for renames
}
func gitStats(ref string) ([]*fileStat, error) {
var buf, errbuf strings.Builder
cmd := exec.Command("git", "diff", "--numstat", "--name-status", "--relative", ref)
cmd.Stdout = &buf
cmd.Stderr = &errbuf
if err := cmd.Run(); err != nil {
return nil, fmt.Errorf("git diff: %s", errbuf.String())
}
lines := strings.Split(buf.String(), "\n")
changes := make([]*fileStat, 0, len(lines))
for _, line := range lines {
if len(line) == 0 {
continue
}
chops := strings.SplitN(line, "\t", 3)
if len(chops) < 2 || len(chops[0]) == 0 {
return nil, fmt.Errorf("git diff: unexpected line: %q", line)
}
mod := chops[0][0]
if strings.IndexByte(validChangeTypes, mod) == -1 {
return nil, fmt.Errorf("invalid change type: %q", line)
}
change := &fileStat{
mode: changeMode(mod),
path: chops[1],
oldPath: chops[1],
}
if changeMode(mod) == modeRenamed {
if len(chops) != 3 {
return nil, fmt.Errorf("git diff: unexpected line: %q", line)
}
change.path = chops[2]
}
changes = append(changes, change)
}
return changes, nil
}
func gitBlob(ref, path string) (io.Reader, error) {
var (
buf bytes.Buffer
errbuf strings.Builder
)
cmd := exec.Command("git", "cat-file", "blob", ref+":./"+path)
cmd.Stdout = &buf
cmd.Stderr = &errbuf
if err := cmd.Run(); err != nil {
return nil, fmt.Errorf("git cat-file: %s", errbuf.String())
}
return &buf, nil
}
// gitLsTreeGoBlobs lists all .go files at the ref:path
func gitLsTreeGoBlobs(ref, path string) ([]string, error) {
var buf, errbuf strings.Builder
if !strings.HasSuffix(path, "/") {
path += "/"
}
cmd := exec.Command("git", "ls-tree", ref, path)
cmd.Stdout = &buf
cmd.Stderr = &errbuf
if err := cmd.Run(); err != nil {
return nil, fmt.Errorf("git ls-tree %s:%s: %s", ref, path, errbuf.String())
}
lines := strings.Split(buf.String(), "\n")
blobs := make([]string, 0, len(lines))
for _, line := range lines {
if len(line) == 0 {
continue
}
chops := strings.Fields(line)
if n := len(chops); n != 4 {
return nil, fmt.Errorf("git ls-tree: unexpected line: %s", line)
}
if chops[1] != "blob" {
continue
}
if filepath.Ext(chops[3]) != ".go" {
continue
}
if strings.Contains(chops[3], "_test.") {
continue
}
blobs = append(blobs, chops[3])
}
return blobs, nil
}