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

Enable usage as a normal go module. #17

Open
wants to merge 2 commits 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
10 changes: 4 additions & 6 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -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 . .

RUN go build -o codenotify
RUN go build -o codenotify ./cmd/codenotify


FROM alpine:3.12
FROM alpine:3

# hadolint ignore=DL3018
RUN apk add --no-cache git
Expand Down
2 changes: 0 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
84 changes: 9 additions & 75 deletions main.go → cmd/codenotify/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
}
Expand Down Expand Up @@ -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)
Expand All @@ -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)
}
6 changes: 4 additions & 2 deletions main_test.go → cmd/codenotify/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import (
"sort"
"strings"
"testing"

"github.com/sourcegraph/codenotify"
)

func TestMain(t *testing.T) {
Expand Down Expand Up @@ -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),
}

Expand Down
20 changes: 12 additions & 8 deletions fs.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package main
package codenotify

import (
"bytes"
Expand All @@ -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
}
82 changes: 82 additions & 0 deletions subcribers.go
Original file line number Diff line number Diff line change
@@ -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)
}