-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathcodeowners.go
153 lines (134 loc) · 4.05 KB
/
codeowners.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
142
143
144
145
146
147
148
149
150
151
152
153
// Package codeowners needs documentation.
package codeowners
import (
"bytes"
"fmt"
"os"
"os/exec"
"path/filepath"
"strings"
)
// LoadFileFromStandardLocation loads and parses a CODEOWNERS file at one of the
// standard locations for CODEOWNERS files (./, .github/, docs/). If run from a
// git repository, all paths are relative to the repository root.
func LoadFileFromStandardLocation() ([]Rule, error) {
path := findFileAtStandardLocation()
if path == "" {
return nil, fmt.Errorf("could not find CODEOWNERS file at any of the standard locations")
}
return LoadFile(path)
}
// LoadFile loads and parses a CODEOWNERS file at the path specified.
func LoadFile(path string) ([]Rule, error) {
f, err := os.Open(path)
if err != nil {
return nil, err
}
return ParseFile(f)
}
func LsFiles(ref string) ([]string, error) {
var (
files []byte
err error
)
if ref == "" {
files, err = exec.Command("git", "ls-files").Output()
} else {
files, err = exec.Command("git", "ls-tree", "-r", "--name-only", ref).Output()
}
if err != nil {
return nil, err
}
return strings.Split(strings.TrimSpace(string(files)), "\n"), nil
}
func LoadFileFromStandardLocationAtRef(ref string) ([]Rule, error) {
if ref == "" {
return LoadFileFromStandardLocation()
}
files, err := LsFiles(ref)
if err != nil {
return nil, err
}
standards := []string{"CODEOWNERS", ".github/CODEOWNERS", ".gitlab/CODEOWNERS", "docs/CODEOWNERS"}
for _, file := range files {
for _, known := range standards {
if file == known {
return LoadFileAtRef(ref, file)
}
}
}
return nil, fmt.Errorf("could not find CODEOWNERS file at any of the standard locations (ref: %s)", ref)
}
// LoadFileAtRef loads and parses a CODEOWNERS file from a historical commit. If ref is an empty string,
// file will be read from disk.
func LoadFileAtRef(ref, path string) ([]Rule, error) {
if ref == "" {
return LoadFile(path)
}
spec := fmt.Sprintf("%s:%s", ref, path)
f, err := exec.Command("git", "show", spec).Output()
if err != nil {
return nil, fmt.Errorf("%s: could not load codeowners at %s", err, spec)
}
return ParseFile(bytes.NewReader(f))
}
// findFileAtStandardLocation loops through the standard locations for
// CODEOWNERS files (./, .github/, docs/), and returns the first place a
// CODEOWNERS file is found. If run from a git repository, all paths are
// relative to the repository root.
func findFileAtStandardLocation() string {
pathPrefix := ""
repoRoot, inRepo := findRepositoryRoot()
if inRepo {
pathPrefix = repoRoot
}
for _, path := range []string{"CODEOWNERS", ".github/CODEOWNERS", ".gitlab/CODEOWNERS", "docs/CODEOWNERS"} {
fullPath := filepath.Join(pathPrefix, path)
if fileExists(fullPath) {
return fullPath
}
}
return ""
}
// fileExist checks if a normal file exists at the path specified.
func fileExists(path string) bool {
info, err := os.Stat(path)
if os.IsNotExist(err) {
return false
}
return !info.IsDir()
}
// findRepositoryRoot returns the path to the root of the git repository, if
// we're currently in one. If we're not in a git repository, the boolean return
// value is false.
func findRepositoryRoot() (string, bool) {
output, err := exec.Command("git", "rev-parse", "--show-toplevel").Output()
if err != nil {
return "", false
}
return strings.TrimSpace(string(output)), true
}
const (
// EmailOwner is the owner type for email addresses.
EmailOwner string = "email"
// TeamOwner is the owner type for GitHub teams.
TeamOwner string = "team"
// UsernameOwner is the owner type for GitHub usernames.
UsernameOwner string = "username"
)
// Owner represents an owner found in a rule.
type Owner struct {
// Value is the name of the owner: the email addres, team name, or username.
Value string
// Type will be one of 'email', 'team', or 'username'.
Type string
}
// String returns a string representation of the owner. For email owners, it
// simply returns the email address. For user and team owners it prepends an '@'
// to the owner.
func (o Owner) String() string {
if o.Type == EmailOwner {
return o.Value
}
return "@" + o.Value
}