Skip to content

Commit

Permalink
feat: add support for requiring a Jira issue in the header
Browse files Browse the repository at this point in the history
This is meant to fix #180
Signed-off-by: Per Abich <per@abich.com>
  • Loading branch information
Per Abich authored and talos-bot committed Nov 13, 2020
1 parent 5a75e96 commit 001de56
Show file tree
Hide file tree
Showing 7 changed files with 203 additions and 1 deletion.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
build
cache
vendor
.idea/
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ Some of the policies included are:
- Spell check
- Maximum of one commit ahead of `master`
- Require a commit body
- Jira issue check
- **License Headers**: Enforce license headers on source code files.

## Getting Started
Expand All @@ -48,6 +49,10 @@ policies:
imperative: true
case: lower
invalidLastCharacters: .
jira:
keys:
- PROJ
- JIRA
body:
required: true
dco: true
Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ require (
github.com/pkg/errors v0.8.1
github.com/spf13/cobra v0.0.3
github.com/spf13/pflag v1.0.5 // indirect
golang.org/x/net v0.0.0-20201021035429-f5854403a974 // indirect
gonum.org/v1/gonum v0.6.1 // indirect
gopkg.in/jdkato/prose.v2 v2.0.0-20180825173540-767a23049b9e
gopkg.in/neurosnap/sentences.v1 v1.0.6 // indirect
Expand Down
8 changes: 8 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,8 @@ golang.org/x/crypto v0.0.0-20190219172222-a4c6cb3142f2/go.mod h1:6SG95UA2DQfeDnf
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073 h1:xMPOj6Pz6UipU1wXLkrtqpHbR0AVFnyPEQq/wRWz9lM=
golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/exp v0.0.0-20180321215751-8460e604b9de h1:xSjD6HQTqT0H/k60N5yYBtnN1OEkVy7WIo/DYyxKRO0=
golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
Expand All @@ -98,14 +100,20 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3 h1:0GoQqolDA55aaLxZyTzK/Y2eP
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20200301022130-244492dfa37a h1:GuSPYbZzB5/dcLNCwLQLsg3obCJtX9IJhpXkvY7kzk0=
golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20201021035429-f5854403a974 h1:IX6qOQeG5uLjB/hjjwjedwfjND0hgjPMMyO1RoIXQNI=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190221075227-b4e8571b14e0/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527 h1:uYVVQ9WP/Ds2ROhcaGPeIdVq0RIXVLwsHlnvJ+cT1So=
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f h1:+Nyd8tzPX9R7BWHguqsrbFdRx3WQ/1ib8I44HXV5yTA=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190206041539-40960b6deb8e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
Expand Down
65 changes: 65 additions & 0 deletions internal/policy/commit/check_jira.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */

package commit

import (
"regexp"

"github.com/pkg/errors"
"github.com/talos-systems/conform/internal/policy"
)

// JiraCheck enforces that a Jira issue is mentioned in the header
type JiraCheck struct {
errors []error
}

// Name returns the name of the check.
func (j *JiraCheck) Name() string {
return "Jira issues"
}

// Message returns to check message.
func (j *JiraCheck) Message() string {
if len(j.errors) != 0 {
return j.errors[0].Error()
}

return "Jira issues are invalid"
}

// Errors returns any violations of the check.
func (j *JiraCheck) Errors() []error {
return j.errors
}

// ValidateJiraCheck validates if a Jira issue is mentioned in the header
func (c Commit) ValidateJiraCheck() policy.Check {
check := &JiraCheck{}
compile := regexp.MustCompile(`^(\w+)( \w+)?: \[(\w+)-\d+\] .*`)

if compile.MatchString(c.msg) {
submatch := compile.FindStringSubmatch(c.msg)
jiraProject := submatch[3]

if !find(c.Header.Jira.Keys, jiraProject) {
check.errors = append(check.errors, errors.Errorf("Jira project %s is not a valid jira project", jiraProject))
}
} else {
check.errors = append(check.errors, errors.Errorf("No Jira issue tag found in %q", c.msg))
}

return check
}

func find(slice []string, value string) bool {
for _, elem := range slice {
if elem == value {
return true
}
}

return false
}
112 changes: 112 additions & 0 deletions internal/policy/commit/check_jira_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */

package commit

import (
"testing"
)

func TestCommit_ValidateJiraCheck(t *testing.T) {
type fields struct {
SpellCheck *SpellCheck
Conventional *Conventional
Header *HeaderChecks
Body *BodyChecks
DCO bool
GPG bool
MaximumOfOneCommit bool
msg string
}

type want struct {
errorCount int
}

tests := []struct {
name string
fields fields
want want
}{
{
name: "Missing jira issue no type",
fields: fields{
Header: &HeaderChecks{
Jira: &JiraChecks{
Keys: []string{"JIRA", "PROJ"},
},
},
msg: "invalid commit",
},
want: want{errorCount: 1},
},
{
name: "Missing jira issue with type",
fields: fields{
Header: &HeaderChecks{
Jira: &JiraChecks{
Keys: []string{"JIRA", "PROJ"},
},
},
msg: "fix: invalid commit",
},
want: want{errorCount: 1},
},
{
name: "Valid commit",
fields: fields{
Header: &HeaderChecks{
Jira: &JiraChecks{
Keys: []string{"JIRA", "PROJ"},
},
},
msg: "fix: [JIRA-1234] valid commit",
},
want: want{errorCount: 0},
},
{
name: "Valid commit 2",
fields: fields{
Header: &HeaderChecks{
Jira: &JiraChecks{
Keys: []string{"JIRA", "PROJ"},
},
},
msg: "fix: [PROJ-1234] valid commit",
},
want: want{errorCount: 0},
},
{
name: "invalid jira project",
fields: fields{
Header: &HeaderChecks{
Jira: &JiraChecks{
Keys: []string{"JIRA", "PROJ"},
},
},
msg: "fix: [FALSE-1234] valid commit",
},
want: want{errorCount: 1},
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
c := Commit{
SpellCheck: tt.fields.SpellCheck,
Conventional: tt.fields.Conventional,
Header: tt.fields.Header,
Body: tt.fields.Body,
DCO: tt.fields.DCO,
GPG: tt.fields.GPG,
MaximumOfOneCommit: tt.fields.MaximumOfOneCommit,
msg: tt.fields.msg,
}
got := c.ValidateJiraCheck()
if len(got.Errors()) != tt.want.errorCount {
t.Errorf("Wanted %d errors but got %d errors: %v", tt.want.errorCount, len(got.Errors()), got.Errors())
}
})
}
}
12 changes: 11 additions & 1 deletion internal/policy/commit/commit.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,13 @@ type HeaderChecks struct {
// HeaderCase is the case that the first word of the header must have ("upper" or "lower").
Case string `mapstructure:"case"`
// HeaderInvalidLastCharacters is a string containing all invalid last characters for the header.
InvalidLastCharacters string `mapstructure:"invalidLastCharacters"`
InvalidLastCharacters string `mapstructure:"invalidLastCharacters"`
Jira *JiraChecks `mapstructure:"jira"`
}

// JiraChecks is the configuration for checks for Jira issues
type JiraChecks struct {
Keys []string `mapstructure:"jiraKeys"`
}

// BodyChecks is the configuration for checks on the body of a commit.
Expand Down Expand Up @@ -107,6 +113,10 @@ func (c *Commit) Compliance(options *policy.Options) (*policy.Report, error) {
if c.Header.InvalidLastCharacters != "" {
report.AddCheck(c.ValidateHeaderLastCharacter())
}

if c.Header.Jira != nil {
report.AddCheck(c.ValidateJiraCheck())
}
}

if c.DCO {
Expand Down

0 comments on commit 001de56

Please sign in to comment.