Skip to content

Commit 3b6e4ee

Browse files
committed
Support GitLab syntax
1 parent fd957bb commit 3b6e4ee

File tree

5 files changed

+497
-12
lines changed

5 files changed

+497
-12
lines changed

codeowners.go

+24-11
Original file line numberDiff line numberDiff line change
@@ -5,27 +5,28 @@
55
// the CODEOWNERS file format into rulesets, which may then be used to determine
66
// the ownership of files.
77
//
8-
// Usage
8+
// # Usage
99
//
1010
// To find the owner of a given file, parse a CODEOWNERS file and call Match()
1111
// on the resulting ruleset.
12-
// ruleset, err := codeowners.ParseFile(file)
13-
// if err != nil {
14-
// log.Fatal(err)
15-
// }
1612
//
17-
// rule, err := ruleset.Match("path/to/file")
18-
// if err != nil {
19-
// log.Fatal(err)
20-
// }
13+
// ruleset, err := codeowners.ParseFile(file)
14+
// if err != nil {
15+
// log.Fatal(err)
16+
// }
2117
//
22-
// Command line interface
18+
// rule, err := ruleset.Match("path/to/file")
19+
// if err != nil {
20+
// log.Fatal(err)
21+
// }
22+
//
23+
// # Command line interface
2324
//
2425
// A command line interface is also available in the cmd/codeowners package.
2526
// When run, it will walk the directory tree showing the code owners for each
2627
// file encountered. The help flag lists available options.
2728
//
28-
// $ codeowners --help
29+
// $ codeowners --help
2930
package codeowners
3031

3132
import (
@@ -122,6 +123,14 @@ type Rule struct {
122123
pattern pattern
123124
}
124125

126+
type Section struct {
127+
Name string
128+
Owners []Owner
129+
Comment string
130+
ApprovalOptional bool
131+
ApprovalCount int
132+
}
133+
125134
// RawPattern returns the rule's gitignore-style path pattern.
126135
func (r Rule) RawPattern() string {
127136
return r.pattern.pattern
@@ -139,6 +148,10 @@ const (
139148
TeamOwner string = "team"
140149
// UsernameOwner is the owner type for GitHub usernames.
141150
UsernameOwner string = "username"
151+
// GroupOwner is the owner type for Gitlab groups.
152+
GroupOwner string = "group"
153+
// RoleOwner is the owner type for GitLab roles
154+
RoleOwner string = "role"
142155
)
143156

144157
// Owner represents an owner found in a rule.

example_test.go

+138
Original file line numberDiff line numberDiff line change
@@ -100,3 +100,141 @@ func ExampleRuleset_Match() {
100100
// src/foo/bar.go true
101101
// src/foo.rs false
102102
}
103+
104+
func ExampleRuleset_Match_section() {
105+
f := bytes.NewBufferString(`[SECTION] @the-a-team
106+
src
107+
src-b @user-b
108+
`)
109+
ruleset, _ := codeowners.ParseFile(f, codeowners.WithSectionSupport())
110+
match, _ := ruleset.Match("src")
111+
fmt.Println("src", match != nil)
112+
fmt.Println(ruleset[0].Owners[0].String())
113+
match, _ = ruleset.Match("src-b")
114+
fmt.Println("src-b", match != nil)
115+
fmt.Println(ruleset[1].Owners[0].String())
116+
// Output:
117+
// src true
118+
// @the-a-team
119+
// src-b true
120+
// @user-b
121+
}
122+
123+
func ExampleRuleset_Match_section_groups() {
124+
f := bytes.NewBufferString(`[SECTION] @the/a/group
125+
src
126+
src-b @user-b
127+
src-c @the/c/group
128+
`)
129+
ruleset, _ := codeowners.ParseFile(f, codeowners.WithSectionSupport(), codeowners.WithOwnerMatchers(codeowners.GitLabOwnerMatchers()))
130+
match, _ := ruleset.Match("src")
131+
fmt.Println("src", match != nil)
132+
fmt.Println(ruleset[0].Owners[0].String())
133+
match, _ = ruleset.Match("src-b")
134+
fmt.Println("src-b", match != nil)
135+
fmt.Println(ruleset[1].Owners[0].String())
136+
match, _ = ruleset.Match("src-c")
137+
fmt.Println("src-c", match != nil)
138+
fmt.Println(ruleset[2].Owners[0].String())
139+
// Output:
140+
// src true
141+
// @the/a/group
142+
// src-b true
143+
// @user-b
144+
// src-c true
145+
// @the/c/group
146+
}
147+
148+
func ExampleRuleset_Match_section_groups_multiple() {
149+
f := bytes.NewBufferString(`[SECTION] @the/a/group
150+
* @other
151+
[SECTION-B] @the/b/group
152+
b-src
153+
b-src-b @user-b
154+
b-src-c @the/c/group
155+
[SECTION-C]
156+
`)
157+
ruleset, _ := codeowners.ParseFile(f, codeowners.WithSectionSupport(), codeowners.WithOwnerMatchers(codeowners.GitLabOwnerMatchers()))
158+
match, _ := ruleset.Match("b-src")
159+
fmt.Println("b-src", match != nil)
160+
fmt.Println(ruleset[1].Owners[0].String())
161+
match, _ = ruleset.Match("b-src-b")
162+
fmt.Println("b-src-b", match != nil)
163+
fmt.Println(ruleset[2].Owners[0].String())
164+
match, _ = ruleset.Match("b-src-c")
165+
fmt.Println("b-src-c", match != nil)
166+
fmt.Println(ruleset[3].Owners[0].String())
167+
// Output:
168+
// b-src true
169+
// @the/b/group
170+
// b-src-b true
171+
// @user-b
172+
// b-src-c true
173+
// @the/c/group
174+
}
175+
176+
func ExampleRuleset_Match_gitlab_example() {
177+
f := bytes.NewBufferString(`
178+
# Specify a default Code Owner for all files with a wildcard:
179+
* @default-owner
180+
181+
# Specify multiple Code Owners to a specific file:
182+
README.md @@technical-writer @doc-team @tech-lead
183+
184+
# Specify a Code Owner to all files with a specific extension:
185+
*.rb @ruby-owner
186+
187+
# Specify Code Owners with usernames or email addresses:
188+
LICENSE @legal [email protected]
189+
190+
# Use group names to match groups and nested groups:
191+
README @group @group/with-nested/subgroup
192+
193+
# Specify a Code Owner to a directory and all its contents:
194+
/docs/ @all-docs
195+
/docs/* @root-docs
196+
/docs/**/*.md @root-docs
197+
198+
^[Optional] @everyone/all @everyone/any
199+
*.css
200+
*.html
201+
202+
# Use a section to group related rules:
203+
[Documentation][2] @@technical-writer
204+
ee/docs @docs
205+
docs @docs
206+
207+
[IAC] @ops
208+
.terraform
209+
charts
210+
.terraform/important @ops/admins
211+
212+
# Assign a role as a Code Owner:
213+
/config/ @@maintainer
214+
`)
215+
216+
ruleset, err := codeowners.ParseFile(f, codeowners.WithSectionSupport(), codeowners.WithOwnerMatchers(codeowners.GitLabOwnerMatchers()))
217+
fmt.Println("err: ", err)
218+
219+
for _, rule := range ruleset {
220+
fmt.Println(rule.RawPattern(), rule.Owners)
221+
}
222+
// Output:
223+
// err: <nil>
224+
// * [@default-owner]
225+
// README.md [@technical-writer @doc-team @tech-lead]
226+
// *.rb [@ruby-owner]
227+
// LICENSE [@legal [email protected]]
228+
// README [@group @group/with-nested/subgroup]
229+
// /docs/ [@all-docs]
230+
// /docs/* [@root-docs]
231+
// /docs/**/*.md [@root-docs]
232+
// *.css [@everyone/all @everyone/any]
233+
// *.html [@everyone/all @everyone/any]
234+
// ee/docs [@docs]
235+
// docs [@docs]
236+
// .terraform [@ops]
237+
// charts [@ops]
238+
// .terraform/important [@ops/admins]
239+
// /config/ [@maintainer]
240+
}

gitlab.go

+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package codeowners
2+
3+
import "regexp"
4+
5+
var (
6+
gitLabUsernameRegexp = regexp.MustCompile(`\A@(([a-zA-Z0-9\-_]+)([._][a-zA-Z0-9\-_]+)*)\z`)
7+
gitLabGroupRegexp = regexp.MustCompile(`\A@(([a-zA-Z0-9\-_]+)([/][a-zA-Z0-9\-_]+)+)\z`)
8+
gitLabRoleNameRegexp = regexp.MustCompile(`\A@@(([a-zA-Z0-9\-_]+)([._][a-zA-Z0-9\-_]+)*)\z`)
9+
)
10+
11+
func matchCustomOwner(s, t string, rgx *regexp.Regexp) (Owner, error) {
12+
match := rgx.FindStringSubmatch(s)
13+
if match == nil || len(match) < 2 {
14+
return Owner{}, ErrNoMatch
15+
}
16+
17+
return Owner{Value: match[1], Type: t}, nil
18+
}
19+
20+
func GitLabOwnerMatchers() []OwnerMatcher {
21+
return []OwnerMatcher{
22+
OwnerMatchFunc(func(s string) (Owner, error) {
23+
return matchCustomOwner(s, UsernameOwner, gitLabUsernameRegexp)
24+
}),
25+
OwnerMatchFunc(func(s string) (Owner, error) {
26+
return matchCustomOwner(s, GroupOwner, gitLabGroupRegexp)
27+
}),
28+
OwnerMatchFunc(func(s string) (Owner, error) {
29+
return matchCustomOwner(s, RoleOwner, gitLabRoleNameRegexp)
30+
}),
31+
OwnerMatchFunc(MatchEmailOwner),
32+
}
33+
}

0 commit comments

Comments
 (0)