Skip to content

Commit

Permalink
Add initial version
Browse files Browse the repository at this point in the history
  • Loading branch information
chefe committed Dec 31, 2023
0 parents commit 5c3c177
Show file tree
Hide file tree
Showing 22 changed files with 2,232 additions and 0 deletions.
7 changes: 7 additions & 0 deletions .envrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
curpath=$_

if [ "${BASH_SOURCE}" != "" ]; then
curpath=${BASH_SOURCE}
fi

export PATH=$(dirname $(readlink -f $curpath))/build/ext/bin:${PATH}
18 changes: 18 additions & 0 deletions .github/workflows/default.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
name: default

on:
push:
branches:
- main
pull_request:
branches:
- main

jobs:
default:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Run all CI checks
run: make ci
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
build/
92 changes: 92 additions & 0 deletions .golangci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
linters-settings:
depguard:
rules:
main:
list-mode: strict
files:
- $all
- "!$test"
- "!**/pattern.go"
- "!**/testhelper/helper.go"
allow:
- $gostd

tests:
list-mode: strict
files:
- $test
allow:
- $gostd
- github.com/chefe/gitlabcodeowners/testhelper

pattern:
list-mode: strict
files:
- "**/pattern.go"
allow:
- $gostd
- github.com/bmatcuk/doublestar/v4

testhelper:
list-mode: strict
files:
- "**/testhelper/helper.go"
allow:
- $gostd
- github.com/go-test/deep

gocritic:
enabled-tags:
- diagnostic
- style
- performance
- experimental
- opinionated

misspell:
locale: US

nolintlint:
allow-unused: false
require-explanation: true
require-specific: true

linters:
enable-all: true
disable:
# disable deprecated linters
- nosnakecase
- ifshort
- interfacer
- golint
- deadcode
- exhaustivestruct
- scopelint
- structcheck
- varcheck
- maligned

# disable `testpackage` linter because white-box tests are used
- testpackage

issues:
exclude-use-default: false

exclude-rules:
# disable funlen linter for test funcs
- source: ^func\ Test.*$
path: _test\.go
linters:
- funlen

# disable lll linter for long strings in test funcs
- source: ^.*strings\.NewReader\(.*$
path: _test\.go
linters:
- lll

# disable varnamelen linter for tt variable in test funcs
- source: ^\s*tt := tt$
path: _test\.go
linters:
- varnamelen
9 changes: 9 additions & 0 deletions .gomarkdoc.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
output: "{{.Dir}}/README.md"
header: |-
[![Go Report Card](https://goreportcard.com/badge/github.com/chefe/gitlabcodeowners)](https://goreportcard.com/report/github.com/chefe/gitlabcodeowners)
[![GitHub Actions](https://github.com/chefe/gitlabcodeowners/workflows/default/badge.svg)](https://github.com/chefe/gitlabcodeowners/actions/workflows/default.yml?query=branch%3Amain)
[![Go Reference](https://pkg.go.dev/badge/github.com/chefe/gitlabcodeowners.svg)](https://pkg.go.dev/github.com/chefe/gitlabcodeowners)
repository:
url: https://github.com/chefe/gitlabcodeowners
defaultBranch: main
path: /
22 changes: 22 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
MIT License

Copyright (c) 2023 chefe

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

47 changes: 47 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
MKFILE_PATH=$(abspath $(lastword $(MAKEFILE_LIST)))
PROJECT_PATH=$(patsubst %/,%,$(dir $(MKFILE_PATH)))
GOBIN_PATH=$(PROJECT_PATH)/build/bin
PROJECTSEARCHPATH=${SELF_DIR}/build/ext/bin
SEARCH_PATH := $(GOBIN_PATH):$(PROJECTSEARCHPATH):$(PATH)

export GOBIN=${GOBIN_PATH}
export PATH=${SEARCH_PATH}

all: ci

.PHONY: all ci lint shellcheck golangci-lint test setup clean

## ci: run all CI steps
ci: setup lint test

# lint: run all linting checks
lint: shellcheck
golangci-lint run ./...

## shellscript: check all scripts with shellcheck
shellcheck: scripts/*
shellcheck $<

## check: run checks
check: check-docs

## check-doc: check if the documentation is up to date
check-docs:
gomarkdoc --check

## doc: update the documentation
doc:
gomarkdoc

## test: run all tests
test:
gotestsum ./...

## setup: download all external dependencies for a build on linux
setup:
./scripts/setup_toolchain
touch build/go.mod

## clean: delete all build artefacts
clean:
rm -rf build
85 changes: 85 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
<!-- Code generated by gomarkdoc. DO NOT EDIT -->

[![Go Report Card](https://goreportcard.com/badge/github.com/chefe/gitlabcodeowners)](https://goreportcard.com/report/github.com/chefe/gitlabcodeowners)
[![GitHub Actions](https://github.com/chefe/gitlabcodeowners/workflows/default/badge.svg)](https://github.com/chefe/gitlabcodeowners/actions/workflows/default.yml?query=branch%3Amain)
[![Go Reference](https://pkg.go.dev/badge/github.com/chefe/gitlabcodeowners.svg)](https://pkg.go.dev/github.com/chefe/gitlabcodeowners)

# gitlabcodeowners

```go
import "github.com/chefe/gitlabcodeowners"
```

Package gitlabcodeowners provides parsing and querying function to work with \`CODEOWNERS\` file from Gitlab. \(see https://docs.gitlab.com/ee/user/project/codeowners\)

## Index

- [func GetPossibleCodeOwnersLocations\(\) \[\]string](<#GetPossibleCodeOwnersLocations>)
- [type Approval](<#Approval>)
- [type File](<#File>)
- [func NewCodeOwnersFile\(reader io.Reader\) \(File, error\)](<#NewCodeOwnersFile>)
- [func \(f File\) GetRequiredApprovalsForFile\(path string\) map\[string\]Approval](<#File.GetRequiredApprovalsForFile>)
- [func \(f File\) GetRequiredApprovalsForFiles\(paths \[\]string\) map\[string\]\[\]Approval](<#File.GetRequiredApprovalsForFiles>)


<a name="GetPossibleCodeOwnersLocations"></a>
## func [GetPossibleCodeOwnersLocations](<https://github.com/chefe/gitlabcodeowners/blob/main/file.go#L24>)

```go
func GetPossibleCodeOwnersLocations() []string
```

GetPossibleCodeOwnersLocations returns a list of possible locations where a \`CODEOWNERS\` file can be located according to Gitlab.

<a name="Approval"></a>
## type [Approval](<https://github.com/chefe/gitlabcodeowners/blob/main/file.go#L16-L20>)

Approval describes an approval required by a rule in the \`CODEOWNERS\` file.

```go
type Approval struct {
Pattern string
Approvals int
Owners []string
}
```

<a name="File"></a>
## type [File](<https://github.com/chefe/gitlabcodeowners/blob/main/file.go#L11-L13>)

File is a representation of a parsed \`CODEOWNERS\` file.

```go
type File struct {
// contains filtered or unexported fields
}
```

<a name="NewCodeOwnersFile"></a>
### func [NewCodeOwnersFile](<https://github.com/chefe/gitlabcodeowners/blob/main/file.go#L30>)

```go
func NewCodeOwnersFile(reader io.Reader) (File, error)
```

NewCodeOwnersFile tries to parse the given description and returns a \`File\` instance if parsing succeeded otherwise it return an error.

<a name="File.GetRequiredApprovalsForFile"></a>
### func \(File\) [GetRequiredApprovalsForFile](<https://github.com/chefe/gitlabcodeowners/blob/main/file.go#L42>)

```go
func (f File) GetRequiredApprovalsForFile(path string) map[string]Approval
```

GetRequiredApprovalsForFile returns a map of all approvals which apply to the file given by it's path. All path need to start with a \`/\` which represents the root folder of the repository.

<a name="File.GetRequiredApprovalsForFiles"></a>
### func \(File\) [GetRequiredApprovalsForFiles](<https://github.com/chefe/gitlabcodeowners/blob/main/file.go#L76>)

```go
func (f File) GetRequiredApprovalsForFiles(paths []string) map[string][]Approval
```

GetRequiredApprovalsForFiles returns a map of all approvals which apply to the files given by their path. All paths need to start with a \`/\` which represents the root folder of the repository.

Generated by [gomarkdoc](<https://github.com/princjef/gomarkdoc>)
110 changes: 110 additions & 0 deletions file.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
// Package gitlabcodeowners provides parsing and querying
// function to work with `CODEOWNERS` file from Gitlab.
// (see https://docs.gitlab.com/ee/user/project/codeowners)
package gitlabcodeowners

import (
"io"
)

// File is a representation of a parsed `CODEOWNERS` file.
type File struct {
sections []section
}

// Approval describes an approval required by a rule in the `CODEOWNERS` file.
type Approval struct {
Pattern string
Approvals int
Owners []string
}

// GetPossibleCodeOwnersLocations returns a list of possible locations
// where a `CODEOWNERS` file can be located according to Gitlab.
func GetPossibleCodeOwnersLocations() []string {
return []string{"/CODEOWNERS", "/docs/CODEOWNERS", "/.gitlab/CODEOWNERS"}
}

// NewCodeOwnersFile tries to parse the given description and returns a `File`
// instance if parsing succeeded otherwise it return an error.
func NewCodeOwnersFile(reader io.Reader) (File, error) {
sections, err := parseFile(reader)
if err != nil {
return File{}, err
}

return File{sections: sections}, nil
}

// GetRequiredApprovalsForFile returns a map of all approvals which
// apply to the file given by it's path. All path need to start with
// a `/` which represents the root folder of the repository.
func (f File) GetRequiredApprovalsForFile(path string) map[string]Approval {
requiredApprovals := map[string]Approval{}

for _, sec := range f.sections {
found := false
rule := rule{} //nolint:exhaustruct // used as placeholder if no rule is found

for _, r := range sec.rules {
if isValidRule(r, sec.owners) && r.pattern.match(path) {
rule = r
found = true
}
}

if found {
owners := sec.owners
if len(rule.owners) > 0 {
owners = rule.owners
}

requiredApprovals[sec.name] = Approval{
Pattern: rule.pattern.value,
Approvals: sec.approvals,
Owners: owners,
}
}
}

return requiredApprovals
}

// GetRequiredApprovalsForFiles returns a map of all approvals which
// apply to the files given by their path. All paths need to start with
// a `/` which represents the root folder of the repository.
func (f File) GetRequiredApprovalsForFiles(paths []string) map[string][]Approval {
requiredApprovals := map[string][]Approval{}

for _, path := range paths {
for section, approval := range f.GetRequiredApprovalsForFile(path) {
existingApprovals := requiredApprovals[section]
requiredApprovals[section] = append(existingApprovals, approval)
}
}

for section, approvals := range requiredApprovals {
requiredApprovals[section] = removeDuplicatedApprovals(approvals)
}

return requiredApprovals
}

func isValidRule(rule rule, defaultOwners []string) bool {
return (len(rule.owners) + len(defaultOwners)) > 0
}

func removeDuplicatedApprovals(approvals []Approval) []Approval {
result := []Approval{}
patterns := map[string]bool{}

for _, approval := range approvals {
if _, existing := patterns[approval.Pattern]; !existing {
patterns[approval.Pattern] = true

result = append(result, approval)
}
}

return result
}
Loading

0 comments on commit 5c3c177

Please sign in to comment.