mirror of
https://github.com/thegeeklab/git-sv.git
synced 2024-11-21 22:10:39 +00:00
feat: validate scope at validate-commit-message command
This commit is contained in:
parent
221d7cd8a7
commit
df26b50096
@ -2,6 +2,7 @@ package sv
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
@ -76,13 +77,21 @@ func (p MessageProcessorImpl) SkipBranch(branch string) bool {
|
|||||||
|
|
||||||
// Validate commit message.
|
// Validate commit message.
|
||||||
func (p MessageProcessorImpl) Validate(message string) error {
|
func (p MessageProcessorImpl) Validate(message string) error {
|
||||||
valid, err := regexp.MatchString("^("+strings.Join(p.messageCfg.Types, "|")+")(\\(.+\\))?!?: .*$", firstLine(message))
|
subject, body := splitCommitMessageContent(message)
|
||||||
if err != nil {
|
msg := p.Parse(subject, body)
|
||||||
return err
|
|
||||||
|
if !regexp.MustCompile("^[a-z+]+(\\(.+\\))?!?: .+$").MatchString(subject) {
|
||||||
|
return errors.New("message should be valid according with conventional commits")
|
||||||
}
|
}
|
||||||
if !valid {
|
|
||||||
return fmt.Errorf("message should contain type: %v, and should be valid according with conventional commits", p.messageCfg.Types)
|
if msg.Type == "" || !contains(msg.Type, p.messageCfg.Types) {
|
||||||
|
return fmt.Errorf("message type should be one of [%v]", strings.Join(p.messageCfg.Types, ", "))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if len(p.messageCfg.Scope.Values) > 0 && !contains(msg.Scope, p.messageCfg.Scope.Values) {
|
||||||
|
return fmt.Errorf("message scope should one of [%v]", strings.Join(p.messageCfg.Scope.Values, ", "))
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -230,6 +239,21 @@ func contains(value string, content []string) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func firstLine(value string) string {
|
func splitCommitMessageContent(content string) (string, string) {
|
||||||
return strings.Split(value, "\n")[0]
|
scanner := bufio.NewScanner(strings.NewReader(content))
|
||||||
|
|
||||||
|
scanner.Scan()
|
||||||
|
subject := scanner.Text()
|
||||||
|
|
||||||
|
var body strings.Builder
|
||||||
|
first := true
|
||||||
|
for scanner.Scan() {
|
||||||
|
if !first {
|
||||||
|
body.WriteString("\n")
|
||||||
|
}
|
||||||
|
body.WriteString(scanner.Text())
|
||||||
|
first = false
|
||||||
|
}
|
||||||
|
|
||||||
|
return subject, body.String()
|
||||||
}
|
}
|
||||||
|
@ -16,6 +16,17 @@ var ccfg = CommitMessageConfig{
|
|||||||
Issue: CommitMessageIssueConfig{Regex: "[A-Z]+-[0-9]+"},
|
Issue: CommitMessageIssueConfig{Regex: "[A-Z]+-[0-9]+"},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var ccfgWithScope = CommitMessageConfig{
|
||||||
|
Types: []string{"feat", "fix"},
|
||||||
|
Scope: CommitMessageScopeConfig{Values: []string{"", "scope"}},
|
||||||
|
Footer: map[string]CommitMessageFooterConfig{
|
||||||
|
"issue": {Key: "jira", KeySynonyms: []string{"Jira"}},
|
||||||
|
"breaking-change": {Key: "BREAKING CHANGE", KeySynonyms: []string{"BREAKING CHANGES"}},
|
||||||
|
"refs": {Key: "Refs", UseHash: true},
|
||||||
|
},
|
||||||
|
Issue: CommitMessageIssueConfig{Regex: "[A-Z]+-[0-9]+"},
|
||||||
|
}
|
||||||
|
|
||||||
var bcfg = BranchesConfig{
|
var bcfg = BranchesConfig{
|
||||||
PrefixRegex: "([a-z]+\\/)?",
|
PrefixRegex: "([a-z]+\\/)?",
|
||||||
SuffixRegex: "(-.*)?",
|
SuffixRegex: "(-.*)?",
|
||||||
@ -59,31 +70,33 @@ BREAKING CHANGE: refactor to use JavaScript features not available in Node 6.`
|
|||||||
// multiline samples end
|
// multiline samples end
|
||||||
|
|
||||||
func TestMessageProcessorImpl_Validate(t *testing.T) {
|
func TestMessageProcessorImpl_Validate(t *testing.T) {
|
||||||
p := NewMessageProcessor(ccfg, bcfg)
|
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
|
cfg CommitMessageConfig
|
||||||
message string
|
message string
|
||||||
wantErr bool
|
wantErr bool
|
||||||
}{
|
}{
|
||||||
{"single line valid message", "feat: add something", false},
|
{"single line valid message", ccfg, "feat: add something", false},
|
||||||
{"single line valid message with scope", "feat(scope): add something", false},
|
{"single line valid message with scope", ccfg, "feat(scope): add something", false},
|
||||||
{"single line invalid type message", "something: add something", true},
|
{"single line valid scope from list", ccfgWithScope, "feat(scope): add something", false},
|
||||||
{"single line invalid type message", "feat?: add something", true},
|
{"single line invalid scope from list", ccfgWithScope, "feat(invalid): add something", true},
|
||||||
|
{"single line invalid type message", ccfg, "something: add something", true},
|
||||||
|
{"single line invalid type message", ccfg, "feat?: add something", true},
|
||||||
|
|
||||||
{"multi line valid message", `feat: add something
|
{"multi line valid message", ccfg, `feat: add something
|
||||||
|
|
||||||
team: x`, false},
|
team: x`, false},
|
||||||
|
|
||||||
{"multi line invalid message", `feat add something
|
{"multi line invalid message", ccfg, `feat add something
|
||||||
|
|
||||||
team: x`, true},
|
team: x`, true},
|
||||||
|
|
||||||
{"support ! for breaking change", "feat!: add something", false},
|
{"support ! for breaking change", ccfg, "feat!: add something", false},
|
||||||
{"support ! with scope for breaking change", "feat(scope)!: add something", false},
|
{"support ! with scope for breaking change", ccfg, "feat(scope)!: add something", false},
|
||||||
}
|
}
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
p := NewMessageProcessor(tt.cfg, bcfg)
|
||||||
if err := p.Validate(tt.message); (err != nil) != tt.wantErr {
|
if err := p.Validate(tt.message); (err != nil) != tt.wantErr {
|
||||||
t.Errorf("MessageProcessorImpl.Validate() error = %v, wantErr %v", err, tt.wantErr)
|
t.Errorf("MessageProcessorImpl.Validate() error = %v, wantErr %v", err, tt.wantErr)
|
||||||
}
|
}
|
||||||
@ -162,28 +175,6 @@ c`
|
|||||||
jira: JIRA-123`
|
jira: JIRA-123`
|
||||||
)
|
)
|
||||||
|
|
||||||
func Test_firstLine(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
value string
|
|
||||||
want string
|
|
||||||
}{
|
|
||||||
{"empty string", "", ""},
|
|
||||||
|
|
||||||
{"single line string", "single line", "single line"},
|
|
||||||
|
|
||||||
{"multi line string", `first line
|
|
||||||
last line`, "first line"},
|
|
||||||
}
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
if got := firstLine(tt.value); got != tt.want {
|
|
||||||
t.Errorf("firstLine() = %v, want %v", got, tt.want)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func Test_hasIssueID(t *testing.T) {
|
func Test_hasIssueID(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
@ -312,3 +303,68 @@ func TestMessageProcessorImpl_Format(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var expectedBodyFullMessage = `
|
||||||
|
see the issue for details
|
||||||
|
|
||||||
|
on typos fixed.
|
||||||
|
|
||||||
|
Reviewed-by: Z
|
||||||
|
Refs #133`
|
||||||
|
|
||||||
|
func Test_splitCommitMessageContent(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
content string
|
||||||
|
wantSubject string
|
||||||
|
wantBody string
|
||||||
|
}{
|
||||||
|
{"single line commit", "feat: something", "feat: something", ""},
|
||||||
|
{"multi line commit", fullMessage, "fix: correct minor typos in code", expectedBodyFullMessage},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
got, got1 := splitCommitMessageContent(tt.content)
|
||||||
|
if got != tt.wantSubject {
|
||||||
|
t.Errorf("splitCommitMessageContent() subject got = %v, want %v", got, tt.wantSubject)
|
||||||
|
}
|
||||||
|
if got1 != tt.wantBody {
|
||||||
|
t.Errorf("splitCommitMessageContent() body got1 = [%v], want [%v]", got1, tt.wantBody)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//commitType, scope, description, hasBreakingChange
|
||||||
|
func Test_parseSubjectMessage(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
message string
|
||||||
|
wantType string
|
||||||
|
wantScope string
|
||||||
|
wantDescription string
|
||||||
|
wantHasBreakingChange bool
|
||||||
|
}{
|
||||||
|
{"valid commit", "feat: something", "feat", "", "something", false},
|
||||||
|
{"valid commit with scope", "feat(scope): something", "feat", "scope", "something", false},
|
||||||
|
{"valid commit with breaking change", "feat(scope)!: something", "feat", "scope", "something", true},
|
||||||
|
{"missing description", "feat: ", "feat", "", "", false},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
ctype, scope, description, hasBreakingChange := parseSubjectMessage(tt.message)
|
||||||
|
if ctype != tt.wantType {
|
||||||
|
t.Errorf("parseSubjectMessage() type got = %v, want %v", ctype, tt.wantType)
|
||||||
|
}
|
||||||
|
if scope != tt.wantScope {
|
||||||
|
t.Errorf("parseSubjectMessage() scope got = %v, want %v", scope, tt.wantScope)
|
||||||
|
}
|
||||||
|
if description != tt.wantDescription {
|
||||||
|
t.Errorf("parseSubjectMessage() description got = %v, want %v", description, tt.wantDescription)
|
||||||
|
}
|
||||||
|
if hasBreakingChange != tt.wantHasBreakingChange {
|
||||||
|
t.Errorf("parseSubjectMessage() hasBreakingChange got = %v, want %v", hasBreakingChange, tt.wantHasBreakingChange)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user