From 23678ffb95ab687b11dd266fa2f9ebb421a21285 Mon Sep 17 00:00:00 2001 From: Timo Beckers Date: Wed, 24 Jul 2024 09:26:00 +0200 Subject: [PATCH 1/2] cmd: remove special case for dirs in dir walker This looks like a leftover from using another library for walking files. Signed-off-by: Timo Beckers --- cmd/codeowners/main.go | 33 ++++++++------------------------- 1 file changed, 8 insertions(+), 25 deletions(-) diff --git a/cmd/codeowners/main.go b/cmd/codeowners/main.go index 8a9e7e1..8723216 100644 --- a/cmd/codeowners/main.go +++ b/cmd/codeowners/main.go @@ -55,25 +55,17 @@ func main() { defer out.Flush() 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 { - fmt.Fprintf(os.Stderr, "error: %v", err) - os.Exit(1) - } - continue - } - err = filepath.WalkDir(startPath, func(path string, d os.DirEntry, err error) error { - if path == ".git" { - return filepath.SkipDir - } + if d.IsDir() { + if path == ".git" { + return filepath.SkipDir + } - // Only show code owners for files, not directories - if !d.IsDir() { - return printFileOwners(out, ruleset, path, ownerFilters, showUnowned) + // Don't show code owners for directories. + return nil } - return nil + + return printFileOwners(out, ruleset, path, ownerFilters, showUnowned) }) if err != nil { @@ -125,12 +117,3 @@ func loadCodeowners(path string) (codeowners.Ruleset, error) { } return codeowners.LoadFile(path) } - -// isDir checks if there's a directory at the path specified. -func isDir(path string) bool { - info, err := os.Stat(path) - if os.IsNotExist(err) { - return false - } - return info.IsDir() -} From c59230fc4e3889096cfc03c4784f85d194a466bf Mon Sep 17 00:00:00 2001 From: Timo Beckers Date: Wed, 24 Jul 2024 10:09:24 +0200 Subject: [PATCH 2/2] cmd: hide files excluded by .gitignore Fixes #14 This commit adds support for hiding files from output that have been excluded by .gitignore and friends. I've implemented a few version of this and settled on just `git ls-files` since it's likely going to be the most correct and maintainable solution. I've tried github.com/boyter/gocodewalker but it's a complex piece of machinery and was much slower on a repo of 15k files (Cilium) than just git ls-files. I also tried out github.com/ianlewis/go-gitignore directly, but it doesn't pick up .gitignore files in subdirs automatically, nor the system-wide gitignore. I figured since the overwhelming majority of users will be running this in CI where Git will always be present, relying on the canonical .gitignore implementation (git itself) is the safest option. Signed-off-by: Timo Beckers --- cmd/codeowners/main.go | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/cmd/codeowners/main.go b/cmd/codeowners/main.go index 8723216..d708b76 100644 --- a/cmd/codeowners/main.go +++ b/cmd/codeowners/main.go @@ -5,6 +5,7 @@ import ( "fmt" "io" "os" + "os/exec" "path/filepath" "strings" @@ -55,6 +56,8 @@ func main() { defer out.Flush() for _, startPath := range paths { + files := gitFiles(startPath) + err = filepath.WalkDir(startPath, func(path string, d os.DirEntry, err error) error { if d.IsDir() { if path == ".git" { @@ -65,6 +68,14 @@ func main() { return nil } + if files != nil { + // Skip displaying code owners for files that are not managed by git, + // e.g. untracked files or files excluded by .gitignore. + if _, ok := files[path]; !ok { + return nil + } + } + return printFileOwners(out, ruleset, path, ownerFilters, showUnowned) }) @@ -117,3 +128,25 @@ func loadCodeowners(path string) (codeowners.Ruleset, error) { } return codeowners.LoadFile(path) } + +// gitFiles returns a map of files in the git repository at the given path. +// Notably, this omits files that have been excluded by .gitignore, +// .git/info/exclude and system-wide gitignore. See +// https://git-scm.com/docs/gitignore for more details. +// +// Returns nil if anything goes wrong, such as the path not being a git repo or +// git not being installed. +func gitFiles(path string) map[string]struct{} { + cmd := exec.Command("git", "ls-files", "-z", "--", path) + out, err := cmd.Output() + if err != nil { + return nil + } + + files := make(map[string]struct{}) + for _, file := range strings.Split(string(out), "\x00") { + files[file] = struct{}{} + } + + return files +}