Skip to content

Support GitLab syntax #39

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 24 additions & 11 deletions codeowners.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,27 +5,28 @@
// the CODEOWNERS file format into rulesets, which may then be used to determine
// the ownership of files.
//
// Usage
// # Usage
//
// To find the owner of a given file, parse a CODEOWNERS file and call Match()
// on the resulting ruleset.
// ruleset, err := codeowners.ParseFile(file)
// if err != nil {
// log.Fatal(err)
// }
//
// rule, err := ruleset.Match("path/to/file")
// if err != nil {
// log.Fatal(err)
// }
// ruleset, err := codeowners.ParseFile(file)
// if err != nil {
// log.Fatal(err)
// }
//
// Command line interface
// rule, err := ruleset.Match("path/to/file")
// if err != nil {
// log.Fatal(err)
// }
//
// # Command line interface
//
// A command line interface is also available in the cmd/codeowners package.
// When run, it will walk the directory tree showing the code owners for each
// file encountered. The help flag lists available options.
//
// $ codeowners --help
// $ codeowners --help
package codeowners

import (
Expand Down Expand Up @@ -122,6 +123,14 @@ type Rule struct {
pattern pattern
}

type Section struct {
Name string
Owners []Owner
Comment string
ApprovalOptional bool
ApprovalCount int
}

// RawPattern returns the rule's gitignore-style path pattern.
func (r Rule) RawPattern() string {
return r.pattern.pattern
Expand All @@ -139,6 +148,10 @@ const (
TeamOwner string = "team"
// UsernameOwner is the owner type for GitHub usernames.
UsernameOwner string = "username"
// GroupOwner is the owner type for Gitlab groups.
GroupOwner string = "group"
// RoleOwner is the owner type for GitLab roles
RoleOwner string = "role"
)

// Owner represents an owner found in a rule.
Expand Down
138 changes: 138 additions & 0 deletions example_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,3 +100,141 @@ func ExampleRuleset_Match() {
// src/foo/bar.go true
// src/foo.rs false
}

func ExampleRuleset_Match_section() {
f := bytes.NewBufferString(`[SECTION] @the-a-team
src
src-b @user-b
`)
ruleset, _ := codeowners.ParseFile(f, codeowners.WithSectionSupport())
match, _ := ruleset.Match("src")
fmt.Println("src", match != nil)
fmt.Println(ruleset[0].Owners[0].String())
match, _ = ruleset.Match("src-b")
fmt.Println("src-b", match != nil)
fmt.Println(ruleset[1].Owners[0].String())
// Output:
// src true
// @the-a-team
// src-b true
// @user-b
}

func ExampleRuleset_Match_section_groups() {
f := bytes.NewBufferString(`[SECTION] @the/a/group
src
src-b @user-b
src-c @the/c/group
`)
ruleset, _ := codeowners.ParseFile(f, codeowners.WithSectionSupport(), codeowners.WithOwnerMatchers(codeowners.GitLabOwnerMatchers()))
match, _ := ruleset.Match("src")
fmt.Println("src", match != nil)
fmt.Println(ruleset[0].Owners[0].String())
match, _ = ruleset.Match("src-b")
fmt.Println("src-b", match != nil)
fmt.Println(ruleset[1].Owners[0].String())
match, _ = ruleset.Match("src-c")
fmt.Println("src-c", match != nil)
fmt.Println(ruleset[2].Owners[0].String())
// Output:
// src true
// @the/a/group
// src-b true
// @user-b
// src-c true
// @the/c/group
}

func ExampleRuleset_Match_section_groups_multiple() {
f := bytes.NewBufferString(`[SECTION] @the/a/group
* @other
[SECTION-B] @the/b/group
b-src
b-src-b @user-b
b-src-c @the/c/group
[SECTION-C]
`)
ruleset, _ := codeowners.ParseFile(f, codeowners.WithSectionSupport(), codeowners.WithOwnerMatchers(codeowners.GitLabOwnerMatchers()))
match, _ := ruleset.Match("b-src")
fmt.Println("b-src", match != nil)
fmt.Println(ruleset[1].Owners[0].String())
match, _ = ruleset.Match("b-src-b")
fmt.Println("b-src-b", match != nil)
fmt.Println(ruleset[2].Owners[0].String())
match, _ = ruleset.Match("b-src-c")
fmt.Println("b-src-c", match != nil)
fmt.Println(ruleset[3].Owners[0].String())
// Output:
// b-src true
// @the/b/group
// b-src-b true
// @user-b
// b-src-c true
// @the/c/group
}

func ExampleRuleset_Match_gitlab_example() {
f := bytes.NewBufferString(`
# Specify a default Code Owner for all files with a wildcard:
* @default-owner

# Specify multiple Code Owners to a specific file:
README.md @@technical-writer @doc-team @tech-lead

# Specify a Code Owner to all files with a specific extension:
*.rb @ruby-owner

# Specify Code Owners with usernames or email addresses:
LICENSE @legal [email protected]

# Use group names to match groups and nested groups:
README @group @group/with-nested/subgroup

# Specify a Code Owner to a directory and all its contents:
/docs/ @all-docs
/docs/* @root-docs
/docs/**/*.md @root-docs

^[Optional] @everyone/all @everyone/any
*.css
*.html

# Use a section to group related rules:
[Documentation][2] @@technical-writer
ee/docs @docs
docs @docs

[IAC] @ops
.terraform
charts
.terraform/important @ops/admins

# Assign a role as a Code Owner:
/config/ @@maintainer
`)

ruleset, err := codeowners.ParseFile(f, codeowners.WithSectionSupport(), codeowners.WithOwnerMatchers(codeowners.GitLabOwnerMatchers()))
fmt.Println("err: ", err)

for _, rule := range ruleset {
fmt.Println(rule.RawPattern(), rule.Owners)
}
// Output:
// err: <nil>
// * [@default-owner]
// README.md [@technical-writer @doc-team @tech-lead]
// *.rb [@ruby-owner]
// LICENSE [@legal [email protected]]
// README [@group @group/with-nested/subgroup]
// /docs/ [@all-docs]
// /docs/* [@root-docs]
// /docs/**/*.md [@root-docs]
// *.css [@everyone/all @everyone/any]
// *.html [@everyone/all @everyone/any]
// ee/docs [@docs]
// docs [@docs]
// .terraform [@ops]
// charts [@ops]
// .terraform/important [@ops/admins]
// /config/ [@maintainer]
}
33 changes: 33 additions & 0 deletions gitlab.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package codeowners

import "regexp"

var (
gitLabUsernameRegexp = regexp.MustCompile(`\A@(([a-zA-Z0-9\-_]+)([._][a-zA-Z0-9\-_]+)*)\z`)
gitLabGroupRegexp = regexp.MustCompile(`\A@(([a-zA-Z0-9\-_]+)([/][a-zA-Z0-9\-_]+)+)\z`)
gitLabRoleNameRegexp = regexp.MustCompile(`\A@@(([a-zA-Z0-9\-_]+)([._][a-zA-Z0-9\-_]+)*)\z`)
)

func matchCustomOwner(s, t string, rgx *regexp.Regexp) (Owner, error) {
match := rgx.FindStringSubmatch(s)
if match == nil || len(match) < 2 {
return Owner{}, ErrNoMatch
}

return Owner{Value: match[1], Type: t}, nil
}

func GitLabOwnerMatchers() []OwnerMatcher {
return []OwnerMatcher{
OwnerMatchFunc(func(s string) (Owner, error) {
return matchCustomOwner(s, UsernameOwner, gitLabUsernameRegexp)
}),
OwnerMatchFunc(func(s string) (Owner, error) {
return matchCustomOwner(s, GroupOwner, gitLabGroupRegexp)
}),
OwnerMatchFunc(func(s string) (Owner, error) {
return matchCustomOwner(s, RoleOwner, gitLabRoleNameRegexp)
}),
OwnerMatchFunc(MatchEmailOwner),
}
}
Loading