From 458518e9808b0ef4a018e6a027e1ad0549f94ed5 Mon Sep 17 00:00:00 2001 From: Jean-Hadrien Chabran Date: Fri, 15 Apr 2022 16:16:21 +0200 Subject: [PATCH 1/2] Enable usage as a normal go module. These changes enable to import codenotify as a go module and to use the now exported Subscribers function to compute subscribers from Go code rather than having to call the binary. Other than a slight code reorganization to allow that, behaviour remains unchanged. --- Dockerfile | 4 +- README.md | 2 - main.go => cmd/codenotify/main.go | 84 +++------------------ main_test.go => cmd/codenotify/main_test.go | 6 +- fs.go | 20 +++-- subcribers.go | 82 ++++++++++++++++++++ 6 files changed, 109 insertions(+), 89 deletions(-) rename main.go => cmd/codenotify/main.go (85%) rename main_test.go => cmd/codenotify/main_test.go (99%) create mode 100644 subcribers.go diff --git a/Dockerfile b/Dockerfile index ab0bd16..fb88101 100644 --- a/Dockerfile +++ b/Dockerfile @@ -3,9 +3,9 @@ FROM golang:1.15-alpine as builder #ENV CGO_ENABLED=0 WORKDIR /build COPY go.mod go.sum *.go ./ +COPY cmd/ ./cmd -RUN go build -o codenotify - +RUN go build -o codenotify ./cmd/codenotify FROM alpine:3.12 diff --git a/README.md b/README.md index 5ca135e..5b2678d 100644 --- a/README.md +++ b/README.md @@ -131,8 +131,6 @@ dir/**" @all-dir **/* @all ``` - - ## Why use Codenotify? GitHub projects can create a [CODEOWNERS](https://docs.github.com/en/github/creating-cloning-and-archiving-repositories/about-code-owners) file (inspired by [Chromium's use of OWNERS files](https://chromium.googlesource.com/chromium/src/+/master/docs/code_reviews.md#OWNERS-files)) and GitHub will automatically add reviewers. There are a few downsides to this approach: diff --git a/main.go b/cmd/codenotify/main.go similarity index 85% rename from main.go rename to cmd/codenotify/main.go index f6d4b9b..f4a954b 100644 --- a/main.go +++ b/cmd/codenotify/main.go @@ -12,11 +12,11 @@ import ( "net/http/httputil" "os" "os/exec" - "path/filepath" - "regexp" "sort" "strconv" "strings" + + "github.com/sourcegraph/codenotify" ) var verbose io.Writer = os.Stderr @@ -62,7 +62,7 @@ func testableMain(stdout io.Writer, args []string) error { return fmt.Errorf("error scanning lines from diff: %s\n%s", err, string(diff)) } - notifs, err := notifications(&gitfs{cwd: opts.cwd, rev: opts.baseRef}, paths, opts.filename) + notifs, err := notifications(codenotify.NewGitFS(opts.cwd, opts.baseRef), paths, opts.filename) if err != nil { return err } @@ -457,7 +457,12 @@ func readLines(b []byte) ([]string, error) { return lines, scanner.Err() } -func notifications(fs FS, paths []string, notifyFilename string) (map[string][]string, error) { +func subscribers(fs codenotify.FS, path string, notifyFilename string) ([]string, error) { + fmt.Fprintf(verbose, "analyzing subscribers in %s files\n", notifyFilename) + return codenotify.Subscribers(fs, path, notifyFilename) +} + +func notifications(fs codenotify.FS, paths []string, notifyFilename string) (map[string][]string, error) { notifications := map[string][]string{} for _, path := range paths { subs, err := subscribers(fs, path, notifyFilename) @@ -472,74 +477,3 @@ func notifications(fs FS, paths []string, notifyFilename string) (map[string][]s return notifications, nil } - -func subscribers(fs FS, path string, notifyFilename string) ([]string, error) { - fmt.Fprintf(verbose, "analyzing subscribers in %s files\n", notifyFilename) - subscribers := []string{} - - parts := strings.Split(path, string(os.PathSeparator)) - for i := range parts { - base := filepath.Join(parts[:i]...) - rulefilepath := filepath.Join(base, notifyFilename) - - rulefile, err := fs.Open(rulefilepath) - if err != nil { - if err == os.ErrNotExist { - continue - } - return nil, err - } - - scanner := bufio.NewScanner(rulefile) - for scanner.Scan() { - rule := scanner.Text() - if rule != "" && rule[0] == '#' { - // skip comment - continue - } - - fields := strings.Fields(rule) - switch len(fields) { - case 0: - // skip blank line - continue - case 1: - return nil, fmt.Errorf("expected at least two fields for rule in %s: %s", rulefilepath, rule) - } - - rel, err := filepath.Rel(base, path) - if err != nil { - return nil, err - } - - re, err := patternToRegexp(fields[0]) - if err != nil { - return nil, fmt.Errorf("invalid pattern in %s: %s: %w", rulefilepath, rule, err) - } - - if re.MatchString(rel) { - subscribers = append(subscribers, fields[1:]...) - } - } - - if err := scanner.Err(); err != nil { - return nil, err - } - } - - return subscribers, nil -} - -func patternToRegexp(pattern string) (*regexp.Regexp, error) { - if pattern[len(pattern)-1:] == "/" { - pattern += "**" - } - pattern = regexp.QuoteMeta(pattern) - pattern = strings.ReplaceAll(pattern, `/\*\*/`, "/([^/]*/)*") - pattern = strings.ReplaceAll(pattern, `\*\*/`, "([^/]+/)*") - pattern = strings.ReplaceAll(pattern, `/\*\*`, ".*") - pattern = strings.ReplaceAll(pattern, `\*\*`, ".*") - pattern = strings.ReplaceAll(pattern, `\*`, "[^/]*") - pattern = "^" + pattern + "$" - return regexp.Compile(pattern) -} diff --git a/main_test.go b/cmd/codenotify/main_test.go similarity index 99% rename from main_test.go rename to cmd/codenotify/main_test.go index 855f92d..c380d21 100644 --- a/main_test.go +++ b/cmd/codenotify/main_test.go @@ -11,6 +11,8 @@ import ( "sort" "strings" "testing" + + "github.com/sourcegraph/codenotify" ) func TestMain(t *testing.T) { @@ -682,13 +684,13 @@ func (m memfs) paths() []string { return paths } -func (m memfs) Open(name string) (File, error) { +func (m memfs) Open(name string) (codenotify.File, error) { content, ok := m[name] if !ok { return nil, os.ErrNotExist } - mf := memfile{ + mf := codenotify.Memfile{ Buffer: bytes.NewBufferString(content), } diff --git a/fs.go b/fs.go index 68c8e13..4d9d01f 100644 --- a/fs.go +++ b/fs.go @@ -1,4 +1,4 @@ -package main +package codenotify import ( "bytes" @@ -18,32 +18,36 @@ type File interface { } // memfile is an in-memory file -type memfile struct { +type Memfile struct { *bytes.Buffer } -func (m memfile) Close() error { +func (m Memfile) Close() error { m.Buffer = nil return nil } -func (m memfile) Stat() (os.FileInfo, error) { +func (m Memfile) Stat() (os.FileInfo, error) { return nil, errors.New("memfile does not support stat") } -// gitfs implements the FS interface for files at a specific git revision. -type gitfs struct { +// Gitfs implements the FS interface for files at a specific git revision. +type Gitfs struct { cwd string rev string } -func (g *gitfs) Open(name string) (File, error) { +func NewGitFS(cwd string, rev string) *Gitfs { + return &Gitfs{cwd: cwd, rev: rev} +} + +func (g *Gitfs) Open(name string) (File, error) { cmd := exec.Command("git", "-C", g.cwd, "show", g.rev+":"+name) buf, err := cmd.Output() if err != nil { return nil, os.ErrNotExist } - return memfile{ + return Memfile{ Buffer: bytes.NewBuffer(buf), }, nil } diff --git a/subcribers.go b/subcribers.go new file mode 100644 index 0000000..4e9f8fa --- /dev/null +++ b/subcribers.go @@ -0,0 +1,82 @@ +package codenotify + +import ( + "bufio" + "fmt" + "os" + "path/filepath" + "regexp" + "strings" +) + +// Subscribers returns the list of subscribers defined in the topmost notifyFilename +// found in the path. +func Subscribers(fs FS, path string, notifyFilename string) ([]string, error) { + subscribers := []string{} + + parts := strings.Split(path, string(os.PathSeparator)) + for i := range parts { + base := filepath.Join(parts[:i]...) + rulefilepath := filepath.Join(base, notifyFilename) + + rulefile, err := fs.Open(rulefilepath) + if err != nil { + if err == os.ErrNotExist { + continue + } + return nil, err + } + + scanner := bufio.NewScanner(rulefile) + for scanner.Scan() { + rule := scanner.Text() + if rule != "" && rule[0] == '#' { + // skip comment + continue + } + + fields := strings.Fields(rule) + switch len(fields) { + case 0: + // skip blank line + continue + case 1: + return nil, fmt.Errorf("expected at least two fields for rule in %s: %s", rulefilepath, rule) + } + + rel, err := filepath.Rel(base, path) + if err != nil { + return nil, err + } + + re, err := patternToRegexp(fields[0]) + if err != nil { + return nil, fmt.Errorf("invalid pattern in %s: %s: %w", rulefilepath, rule, err) + } + + if re.MatchString(rel) { + subscribers = append(subscribers, fields[1:]...) + } + } + + if err := scanner.Err(); err != nil { + return nil, err + } + } + + return subscribers, nil +} + +func patternToRegexp(pattern string) (*regexp.Regexp, error) { + if pattern[len(pattern)-1:] == "/" { + pattern += "**" + } + pattern = regexp.QuoteMeta(pattern) + pattern = strings.ReplaceAll(pattern, `/\*\*/`, "/([^/]*/)*") + pattern = strings.ReplaceAll(pattern, `\*\*/`, "([^/]+/)*") + pattern = strings.ReplaceAll(pattern, `/\*\*`, ".*") + pattern = strings.ReplaceAll(pattern, `\*\*`, ".*") + pattern = strings.ReplaceAll(pattern, `\*`, "[^/]*") + pattern = "^" + pattern + "$" + return regexp.Compile(pattern) +} From dcc28ac5aad499abe9404c37d2d277ef73e8c547 Mon Sep 17 00:00:00 2001 From: Jean-Hadrien Chabran Date: Wed, 26 Apr 2023 20:01:35 +0200 Subject: [PATCH 2/2] Update alpine and go version --- Dockerfile | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/Dockerfile b/Dockerfile index fb88101..3b01643 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,13 +1,11 @@ -FROM golang:1.15-alpine as builder +FROM golang:1.20-alpine as builder -#ENV CGO_ENABLED=0 WORKDIR /build -COPY go.mod go.sum *.go ./ -COPY cmd/ ./cmd +COPY . . RUN go build -o codenotify ./cmd/codenotify -FROM alpine:3.12 +FROM alpine:3 # hadolint ignore=DL3018 RUN apk add --no-cache git