Skip to content
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

fixing multiple issues #28

Closed
wants to merge 21 commits into from
Closed
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
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ usage: codeowners <path>...
-h, --help show this help message
-o, --owner strings filter results by owner
-u, --unowned only show unowned files (can be combined with -o)
-c, --check exit with a non-zero status code if unowned files exist

$ ls
CODEOWNERS DOCUMENTATION.md README.md example.go example_test.go
Expand Down
98 changes: 78 additions & 20 deletions cmd/codeowners/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package main

import (
"bufio"
"errors"
"fmt"
"io"
"os"
Expand All @@ -12,17 +13,29 @@ import (
flag "github.com/spf13/pflag"
)

var ErrCheck = errors.New("unowned files exist")

type Codeowners struct {
ownerFilters []string
showUnowned bool
checkMode bool
codeownersPath string
helpFlag bool
sections bool
}

func main() {
var (
ownerFilters []string
showUnowned bool
codeownersPath string
helpFlag bool
c Codeowners
helpFlag bool
)
flag.StringSliceVarP(&ownerFilters, "owner", "o", nil, "filter results by owner")
flag.BoolVarP(&showUnowned, "unowned", "u", false, "only show unowned files (can be combined with -o)")
flag.StringVarP(&codeownersPath, "file", "f", "", "CODEOWNERS file path")

flag.StringSliceVarP(&c.ownerFilters, "owner", "o", nil, "filter results by owner")
flag.BoolVarP(&c.showUnowned, "unowned", "u", false, "only show unowned files (can be combined with -o)")
flag.StringVarP(&c.codeownersPath, "file", "f", "", "CODEOWNERS file path")
flag.BoolVarP(&helpFlag, "help", "h", false, "show this help message")
flag.BoolVarP(&c.checkMode, "check", "c", false, "exit with a non-zero status code if unowned files exist")
flag.BoolVarP(&c.sections, "sections", "", false, "support sections and inheritance")

flag.Usage = func() {
fmt.Fprintf(os.Stderr, "usage: codeowners <path>...\n")
Expand All @@ -35,7 +48,7 @@ func main() {
os.Exit(0)
}

ruleset, err := loadCodeowners(codeownersPath)
ruleset, err := c.loadCodeowners()
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
Expand All @@ -47,17 +60,23 @@ func main() {
}

// Make the @ optional for GitHub teams and usernames
for i := range ownerFilters {
ownerFilters[i] = strings.TrimLeft(ownerFilters[i], "@")
for i := range c.ownerFilters {
c.ownerFilters[i] = strings.TrimLeft(c.ownerFilters[i], "@")
}

out := bufio.NewWriter(os.Stdout)
defer out.Flush()

var checkError bool
for _, startPath := range paths {
// godirwalk only accepts directories, so we need to handle files separately
if !isDir(startPath) {
if err := printFileOwners(out, ruleset, startPath, ownerFilters, showUnowned); err != nil {
if err := c.printFileOwners(out, ruleset, startPath); err != nil {
if errors.Is(err, ErrCheck) {
checkError = true
continue
}

fmt.Fprintf(os.Stderr, "error: %v", err)
os.Exit(1)
}
Expand All @@ -71,38 +90,72 @@ func main() {

// Only show code owners for files, not directories
if !d.IsDir() {
return printFileOwners(out, ruleset, path, ownerFilters, showUnowned)
err := c.printFileOwners(out, ruleset, path)
if err != nil {
if errors.Is(err, ErrCheck) {
checkError = true
return nil
}
}

return err
}
return nil
})

if err != nil {
if errors.Is(err, ErrCheck) {
checkError = true
continue
}

fmt.Fprintf(os.Stderr, "error: %v", err)
os.Exit(1)
}
}

if checkError {
if c.showUnowned {
out.Flush()
}

fmt.Fprintf(os.Stderr, "error: %v\n", ErrCheck.Error())

os.Exit(1)
}
}

func printFileOwners(out io.Writer, ruleset codeowners.Ruleset, path string, ownerFilters []string, showUnowned bool) error {
func (c Codeowners) printFileOwners(out io.Writer, ruleset codeowners.Ruleset, path string) error {
hasUnowned := false

rule, err := ruleset.Match(path)
if err != nil {
return err
}
// If we didn't get a match, the file is unowned
if rule == nil || rule.Owners == nil {
// Unless explicitly requested, don't show unowned files if we're filtering by owner
if len(ownerFilters) == 0 || showUnowned {
if len(c.ownerFilters) == 0 || c.showUnowned || c.checkMode {
fmt.Fprintf(out, "%-70s (unowned)\n", path)

if c.checkMode {
hasUnowned = true
}
}

if hasUnowned {
return ErrCheck
}

return nil
}

// Figure out which of the owners we need to show according to the --owner filters
ownersToShow := make([]string, 0, len(rule.Owners))
for _, o := range rule.Owners {
// If there are no filters, show all owners
filterMatch := len(ownerFilters) == 0 && !showUnowned
for _, filter := range ownerFilters {
filterMatch := len(c.ownerFilters) == 0 && !c.showUnowned
for _, filter := range c.ownerFilters {
if filter == o.Value {
filterMatch = true
}
Expand All @@ -119,11 +172,16 @@ func printFileOwners(out io.Writer, ruleset codeowners.Ruleset, path string, own
return nil
}

func loadCodeowners(path string) (codeowners.Ruleset, error) {
if path == "" {
return codeowners.LoadFileFromStandardLocation()
func (c Codeowners) loadCodeowners() (codeowners.Ruleset, error) {
var parseOptions []codeowners.ParseOption
if c.sections {
parseOptions = append(parseOptions, codeowners.WithSectionSupport())
}

if c.codeownersPath == "" {
return codeowners.LoadFileFromStandardLocation(parseOptions...)
}
return codeowners.LoadFile(path)
return codeowners.LoadFile(c.codeownersPath, parseOptions...)
}

// isDir checks if there's a directory at the path specified.
Expand Down
41 changes: 26 additions & 15 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 All @@ -39,21 +40,21 @@ import (
// 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() (Ruleset, error) {
func LoadFileFromStandardLocation(options ...ParseOption) (Ruleset, error) {
path := findFileAtStandardLocation()
if path == "" {
return nil, fmt.Errorf("could not find CODEOWNERS file at any of the standard locations")
}
return LoadFile(path)
return LoadFile(path, options...)
}

// LoadFile loads and parses a CODEOWNERS file at the path specified.
func LoadFile(path string) (Ruleset, error) {
func LoadFile(path string, options ...ParseOption) (Ruleset, error) {
f, err := os.Open(path)
if err != nil {
return nil, err
}
return ParseFile(f)
return ParseFile(f, options...)
}

// findFileAtStandardLocation loops through the standard locations for
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,8 @@ 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"
)

// Owner represents an owner found in a rule.
Expand Down
Loading