From 58c0b8cef0e5450895bb184feaacc2b393ba2145 Mon Sep 17 00:00:00 2001 From: George Aristy Date: Sat, 14 Sep 2019 11:58:41 -0400 Subject: [PATCH] (#52) Ignore authors by name and email (#56) --- .dependency_license | 1 + .gitignore | 1 + README.md | 2 + cmd/go-gitlint/main.go | 38 +++++++----- internal/commits/commits.go | 99 +++++++++++++++++++++++++------- internal/commits/commits_test.go | 30 ++++++++++ 6 files changed, 135 insertions(+), 36 deletions(-) diff --git a/.dependency_license b/.dependency_license index f2fde0f..358b1cc 100644 --- a/.dependency_license +++ b/.dependency_license @@ -12,4 +12,5 @@ go\.sum, Ignore \.pdd, Ignore \.travis\.yml, Ignore \.idea, Ignore +\.vscode, Ignore download-gitlint.sh, Ignore diff --git a/.gitignore b/.gitignore index 2642c36..ba87118 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ .idea/ +.vscode/ " goreleaser dist/ diff --git a/README.md b/README.md index 57e9947..a371a9c 100644 --- a/README.md +++ b/README.md @@ -28,6 +28,8 @@ Flags: --since="1970-01-01" A date in "yyyy-MM-dd" format starting from which commits will be analyzed (default: "1970-01-01"). --msg-file="" Only analyze the commit message found in this file (default: ""). --max-parents=1 Max number of parents a commit can have in order to be analyzed (default: 1). Useful for excluding merge commits. + --excl-author-names="$a" Don't lint commits with authors whose names match these comma-separated regular expressions (default: '$a'). + --excl-author-emails="$a" Don't lint commits with authors whose emails match these comma-separated regular expressions (default: '$a'). ``` Additionally, it will look for configurations in a file `.gitlint` in the current directory if it exists. This file's format is just the same command line flags but each on a separate line. *Flags passed through the command line take precedence.* diff --git a/cmd/go-gitlint/main.go b/cmd/go-gitlint/main.go index c257f96..34ecded 100644 --- a/cmd/go-gitlint/main.go +++ b/cmd/go-gitlint/main.go @@ -31,15 +31,17 @@ import ( // Figure out a way to remove these global variables. Whatever command line // parser we choose should be able to auto-generate usage. var ( - path = kingpin.Flag("path", `Path to the git repo (default: ".").`).Default(".").String() //nolint[gochecknoglobals] - subjectRegex = kingpin.Flag("subject-regex", `Commit subject line must conform to this regular expression (default: ".*").`).Default(".*").String() //nolint[gochecknoglobals] - subjectMaxLength = kingpin.Flag("subject-maxlen", "Max length for commit subject line (default: math.MaxInt32 - 1).").Default(strconv.Itoa(math.MaxInt32 - 1)).Int() //nolint[gochecknoglobals] - subjectMinLength = kingpin.Flag("subject-minlen", "Min length for commit subject line (default: 0).").Default("0").Int() //nolint[gochecknoglobals] - bodyRegex = kingpin.Flag("body-regex", `Commit message body must conform to this regular expression (default: ".*").`).Default(".*").String() //nolint[gochecknoglobals] - bodyMaxLength = kingpin.Flag("body-maxlen", `Max length for commit body (default: math.MaxInt32 - 1)`).Default(strconv.Itoa(math.MaxInt32 - 1)).Int() //nolint[gochecknoglobals] - since = kingpin.Flag("since", `A date in "yyyy-MM-dd" format starting from which commits will be analyzed (default: "1970-01-01").`).Default("1970-01-01").String() //nolint[gochecknoglobals] - msgFile = kingpin.Flag("msg-file", `Only analyze the commit message found in this file (default: "").`).Default("").String() //nolint[gochecknoglobals] - maxParents = kingpin.Flag("max-parents", `Max number of parents a commit can have in order to be analyzed (default: 1). Useful for excluding merge commits.`).Default("1").Int() //nolint[gochecknoglobals] + path = kingpin.Flag("path", `Path to the git repo (default: ".").`).Default(".").String() //nolint[gochecknoglobals] + subjectRegex = kingpin.Flag("subject-regex", `Commit subject line must conform to this regular expression (default: ".*").`).Default(".*").String() //nolint[gochecknoglobals] + subjectMaxLength = kingpin.Flag("subject-maxlen", "Max length for commit subject line (default: math.MaxInt32 - 1).").Default(strconv.Itoa(math.MaxInt32 - 1)).Int() //nolint[gochecknoglobals] + subjectMinLength = kingpin.Flag("subject-minlen", "Min length for commit subject line (default: 0).").Default("0").Int() //nolint[gochecknoglobals] + bodyRegex = kingpin.Flag("body-regex", `Commit message body must conform to this regular expression (default: ".*").`).Default(".*").String() //nolint[gochecknoglobals] + bodyMaxLength = kingpin.Flag("body-maxlen", `Max length for commit body (default: math.MaxInt32 - 1)`).Default(strconv.Itoa(math.MaxInt32 - 1)).Int() //nolint[gochecknoglobals] + since = kingpin.Flag("since", `A date in "yyyy-MM-dd" format starting from which commits will be analyzed (default: "1970-01-01").`).Default("1970-01-01").String() //nolint[gochecknoglobals] + msgFile = kingpin.Flag("msg-file", `Only analyze the commit message found in this file (default: "").`).Default("").String() //nolint[gochecknoglobals] + maxParents = kingpin.Flag("max-parents", `Max number of parents a commit can have in order to be analyzed (default: 1). Useful for excluding merge commits.`).Default("1").Int() //nolint[gochecknoglobals] + authorNames = kingpin.Flag("excl-author-names", "Don't lint commits with authors whose names match these comma-separated regular expressions (default: '$a').").Default("$a").String() //nolint[gochecknoglobals] + authorEmails = kingpin.Flag("excl-author-emails", "Don't lint commits with authors whose emails match these comma-separated regular expressions (default: '$a').").Default("$a").String() //nolint[gochecknoglobals] ) func main() { @@ -66,12 +68,18 @@ func main() { return commits.MsgIn(file) }, func() commits.Commits { - return commits.WithMaxParents( - *maxParents, - commits.Since( - *since, - commits.In( - repo.Filesystem(*path), + return commits.NotAuthoredByNames( + strings.Split(*authorNames, ","), + commits.NotAuthoredByEmails( + strings.Split(*authorEmails, ","), + commits.WithMaxParents( + *maxParents, + commits.Since( + *since, + commits.In( + repo.Filesystem(*path), + ), + ), ), ), ) diff --git a/internal/commits/commits.go b/internal/commits/commits.go index 08661b4..ddee19b 100644 --- a/internal/commits/commits.go +++ b/internal/commits/commits.go @@ -17,6 +17,7 @@ package commits import ( "io" "io/ioutil" + "regexp" "strings" "time" @@ -39,6 +40,13 @@ type Commit struct { Message string Date time.Time NumParents int + Author *Author +} + +// Author is the author of a commit. +type Author struct { + Name string + Email string } // ID is the commit's hash. @@ -90,6 +98,10 @@ func In(repository repo.Repo) Commits { Message: c.Message, Date: c.Author.When, NumParents: len(c.ParentHashes), + Author: &Author{ + Name: c.Author.Name, + Email: c.Author.Email, + }, }, ) return nil @@ -100,33 +112,66 @@ func In(repository repo.Repo) Commits { // Since returns commits authored since time t (format: yyyy-MM-dd). func Since(t string, cmts Commits) Commits { - return func() []*Commit { - start, err := time.Parse("2006-01-02", t) - if err != nil { - panic(err) - } - filtered := make([]*Commit, 0) - for _, c := range cmts() { - if !c.Date.Before(start) { - filtered = append(filtered, c) + return filtered( + func(c *Commit) bool { + start, err := time.Parse("2006-01-02", t) + if err != nil { + panic(err) } - } - return filtered - } + return !c.Date.Before(start) + }, + cmts, + ) +} + +// NotAuthoredByNames filters out commits with authors whose names match any of the given patterns. +func NotAuthoredByNames(patterns []string, cmts Commits) Commits { + return filtered( + func(c *Commit) bool { + for _, p := range patterns { + match, err := regexp.MatchString(p, c.Author.Name) + if err != nil { + panic(err) + } + if match { + return false + } + } + return true + }, + cmts, + ) +} + +// NotAuthoredByEmails filters out commits with authors whose emails match any +// of the given patterns. +func NotAuthoredByEmails(patterns []string, cmts Commits) Commits { + return filtered( + func(c *Commit) bool { + for _, p := range patterns { + match, err := regexp.MatchString(p, c.Author.Email) + if err != nil { + panic(err) + } + if match { + return false + } + } + return true + }, + cmts, + ) } // WithMaxParents returns commits that have at most n number of parents. // Useful for excluding merge commits. func WithMaxParents(n int, cmts Commits) Commits { - return func() []*Commit { - filtered := make([]*Commit, 0) - for _, c := range cmts() { - if c.NumParents <= n { - filtered = append(filtered, c) - } - } - return filtered - } + return filtered( + func(c *Commit) bool { + return c.NumParents <= n + }, + cmts, + ) } // MsgIn returns a single fake commit with the message read from this reader. @@ -144,3 +189,15 @@ func MsgIn(reader io.Reader) Commits { }} } } + +func filtered(filter func(*Commit) bool, in Commits) (out Commits) { + return func() []*Commit { + f := make([]*Commit, 0) + for _, c := range in() { + if filter(c) { + f = append(f, c) + } + } + return f + } +} diff --git a/internal/commits/commits_test.go b/internal/commits/commits_test.go index e3ae7b2..f78cb31 100644 --- a/internal/commits/commits_test.go +++ b/internal/commits/commits_test.go @@ -119,6 +119,36 @@ func TestWithMaxParents(t *testing.T) { assert.Equal(t, commits[0].NumParents, max) } +func TestNotAuthoredByNames(t *testing.T) { //nolint:dupl + filtered := &Commit{Author: &Author{Name: uuid.New().String()}} + expected := []*Commit{ + {Author: &Author{Name: uuid.New().String()}}, + {Author: &Author{Name: uuid.New().String()}}, + {Author: &Author{Name: uuid.New().String()}}, + {Author: &Author{Name: uuid.New().String()}}, + } + actual := NotAuthoredByNames( + []string{filtered.Author.Name}, + func() []*Commit { return append(expected, filtered) }, + )() + assert.Equal(t, expected, actual) +} + +func TestNotAuthoredByEmails(t *testing.T) { //nolint:dupl + filtered := &Commit{Author: &Author{Email: uuid.New().String()}} + expected := []*Commit{ + {Author: &Author{Email: uuid.New().String()}}, + {Author: &Author{Email: uuid.New().String()}}, + {Author: &Author{Email: uuid.New().String()}}, + {Author: &Author{Email: uuid.New().String()}}, + } + actual := NotAuthoredByEmails( + []string{filtered.Author.Email}, + func() []*Commit { return append(expected, filtered) }, + )() + assert.Equal(t, expected, actual) +} + // A git repo initialized and with one commit per each of the messages provided. // This repo is created in a temporary directory; use the cleanup function // to delete it afterwards.