0
0
mirror of https://github.com/thegeeklab/git-sv.git synced 2024-11-22 08:20:39 +00:00

refactor: merge CommitMessageProcessor and MessageProcessor

This commit is contained in:
Beatriz Vieira 2021-02-14 01:04:32 -03:00
parent 740f05b84a
commit de23ff9638
8 changed files with 255 additions and 260 deletions

View File

@ -290,7 +290,7 @@ func commitHandler(cfg Config, git sv.Git, messageProcessor sv.MessageProcessor)
} }
} }
header, body, footer := messageProcessor.Format(ctype.Type, scope, subject, fullBody.String(), issue, breakingChanges) header, body, footer := messageProcessor.Format(sv.NewCommitMessage(ctype.Type, scope, subject, fullBody.String(), issue, breakingChanges))
err = git.Commit(header, body, footer) err = git.Commit(header, body, footer)
if err != nil { if err != nil {

View File

@ -19,19 +19,19 @@ func main() {
// TODO: config using yaml // TODO: config using yaml
commitMessageCfg := sv.CommitMessageConfig{ commitMessageCfg := sv.CommitMessageConfig{
Types: cfg.CommitMessageTypes, Types: cfg.CommitMessageTypes,
Scope: sv.ScopeConfig{}, Scope: sv.CommitMessageScopeConfig{},
Footer: map[string]sv.FooterMetadataConfig{ Footer: map[string]sv.CommitMessageFooterConfig{
"issue": {Key: cfg.IssueIDPrefixes[0], KeySynonyms: cfg.IssueIDPrefixes[1:], Regex: cfg.IssueRegex}, "issue": {Key: cfg.IssueIDPrefixes[0], KeySynonyms: cfg.IssueIDPrefixes[1:], Regex: cfg.IssueRegex},
"breaking-change": {Key: cfg.BreakingChangePrefixes[0], KeySynonyms: cfg.BreakingChangePrefixes[1:]}, "breaking-change": {Key: cfg.BreakingChangePrefixes[0], KeySynonyms: cfg.BreakingChangePrefixes[1:]},
}, },
} }
//// ////
git := sv.NewGit(sv.NewCommitMessageProcessor(commitMessageCfg), cfg.TagPattern) messageProcessor := sv.NewMessageProcessor(commitMessageCfg, cfg.ValidateMessageSkipBranches, cfg.BranchIssueRegex)
git := sv.NewGit(messageProcessor, cfg.TagPattern)
semverProcessor := sv.NewSemVerCommitsProcessor(cfg.IncludeUnknownTypeAsPatch, cfg.MajorVersionTypes, cfg.MinorVersionTypes, cfg.PatchVersionTypes) semverProcessor := sv.NewSemVerCommitsProcessor(cfg.IncludeUnknownTypeAsPatch, cfg.MajorVersionTypes, cfg.MinorVersionTypes, cfg.PatchVersionTypes)
releasenotesProcessor := sv.NewReleaseNoteProcessor(cfg.ReleaseNotesTags) releasenotesProcessor := sv.NewReleaseNoteProcessor(cfg.ReleaseNotesTags)
outputFormatter := sv.NewOutputFormatter() outputFormatter := sv.NewOutputFormatter()
messageProcessor := sv.NewMessageProcessor(cfg.ValidateMessageSkipBranches, cfg.CommitMessageTypes, cfg.IssueKeyName, cfg.BranchIssueRegex, cfg.IssueRegex)
app := cli.NewApp() app := cli.NewApp()
app.Name = "sv" app.Name = "sv"

38
sv/config.go Normal file
View File

@ -0,0 +1,38 @@
package sv
// CommitMessageConfig config a commit message.
type CommitMessageConfig struct {
Types []string
Scope CommitMessageScopeConfig
Footer map[string]CommitMessageFooterConfig
}
// IssueConfig config for issue.
func (c CommitMessageConfig) IssueConfig() CommitMessageFooterConfig {
if v, exists := c.Footer[issueKey]; exists {
return v
}
return CommitMessageFooterConfig{}
}
// BreakingChangeConfig config for breaking changes.
func (c CommitMessageConfig) BreakingChangeConfig() CommitMessageFooterConfig {
if v, exists := c.Footer[breakingKey]; exists {
return v
}
return CommitMessageFooterConfig{}
}
// CommitMessageScopeConfig config scope preferences.
type CommitMessageScopeConfig struct {
Mandatory bool
Values []string
}
// CommitMessageFooterConfig config footer metadata.
type CommitMessageFooterConfig struct {
Key string
KeySynonyms []string
Regex string
UseHash bool
}

View File

@ -1,121 +0,0 @@
package sv
import (
"regexp"
"strings"
)
const (
breakingKey = "breaking-change"
// IssueIDKey key to issue id metadata
issueKey = "issue"
)
// CommitMessageConfig config a commit message
type CommitMessageConfig struct {
Types []string
Scope ScopeConfig
Footer map[string]FooterMetadataConfig
}
// ScopeConfig config scope preferences
type ScopeConfig struct {
Mandatory bool
Values []string
}
// FooterMetadataConfig config footer metadata
type FooterMetadataConfig struct {
Key string
KeySynonyms []string
Regex string
UseHash bool
}
// CommitMessage is a message using conventional commits.
type CommitMessage struct {
Type string `json:"type,omitempty"`
Scope string `json:"scope,omitempty"`
Description string `json:"description,omitempty"`
Body string `json:"body,omitempty"`
IsBreakingChange bool `json:"isBreakingChange,omitempty"`
Metadata map[string]string `json:"metadata,omitempty"`
}
// Issue return issue from metadata.
func (m CommitMessage) Issue() string {
return m.Metadata[issueKey]
}
// BreakingMessage return breaking change message from metadata.
func (m CommitMessage) BreakingMessage() string {
return m.Metadata[breakingKey]
}
// CommitMessageProcessor parse commit messages.
type CommitMessageProcessor interface {
Parse(subject, body string) CommitMessage
}
// CommitMessageProcessorImpl commit message processor implementation
type CommitMessageProcessorImpl struct {
cfg CommitMessageConfig
}
// NewCommitMessageProcessor CommitMessageProcessorImpl constructor
func NewCommitMessageProcessor(cfg CommitMessageConfig) CommitMessageProcessor {
return &CommitMessageProcessorImpl{cfg: cfg}
}
// Parse parse a commit message
func (p CommitMessageProcessorImpl) Parse(subject, body string) CommitMessage {
commitType, scope, description, hasBreakingChange := parseSubjectMessage(subject)
metadata := make(map[string]string)
for key, mdCfg := range p.cfg.Footer {
prefixes := append([]string{mdCfg.Key}, mdCfg.KeySynonyms...)
for _, prefix := range prefixes {
if tagValue := extractFooterMetadata(prefix, body, mdCfg.UseHash); tagValue != "" {
metadata[key] = tagValue
break
}
}
}
if _, exists := metadata[breakingKey]; exists {
hasBreakingChange = true
}
return CommitMessage{
Type: commitType,
Scope: scope,
Description: description,
Body: body,
IsBreakingChange: hasBreakingChange,
Metadata: metadata,
}
}
func parseSubjectMessage(message string) (string, string, string, bool) {
regex := regexp.MustCompile("([a-z]+)(\\((.*)\\))?(!)?: (.*)")
result := regex.FindStringSubmatch(message)
if len(result) != 6 {
return "", "", message, false
}
return result[1], result[3], strings.TrimSpace(result[5]), result[4] == "!"
}
func extractFooterMetadata(key, text string, useHash bool) string {
var regex *regexp.Regexp
if useHash {
regex = regexp.MustCompile(key + " (#.*)")
} else {
regex = regexp.MustCompile(key + ": (.*)")
}
result := regex.FindStringSubmatch(text)
if len(result) < 2 {
return ""
}
return result[1]
}

View File

@ -1,60 +0,0 @@
package sv
import (
"reflect"
"testing"
)
var cfg = CommitMessageConfig{
Types: []string{"feat", "fix"},
Scope: ScopeConfig{},
Footer: map[string]FooterMetadataConfig{
"issue": {Key: "jira", KeySynonyms: []string{"Jira"}, Regex: "[A-Z]+-[0-9]+"},
"breaking-change": {Key: "BREAKING CHANGE", KeySynonyms: []string{"BREAKING CHANGES"}},
"refs": {Key: "Refs", UseHash: true},
},
}
var completeBody = `some descriptions
jira: JIRA-123
BREAKING CHANGE: this change breaks everything`
var issueOnlyBody = `some descriptions
jira: JIRA-456`
var issueSynonymsBody = `some descriptions
Jira: JIRA-789`
var hashMetadataBody = `some descriptions
Jira: JIRA-999
Refs #123`
func TestCommitMessageProcessorImpl_Parse(t *testing.T) {
tests := []struct {
name string
subject string
body string
want CommitMessage
}{
{"simple message", "feat: something awesome", "", CommitMessage{Type: "feat", Scope: "", Description: "something awesome", Body: "", IsBreakingChange: false, Metadata: map[string]string{}}},
{"message with scope", "feat(scope): something awesome", "", CommitMessage{Type: "feat", Scope: "scope", Description: "something awesome", Body: "", IsBreakingChange: false, Metadata: map[string]string{}}},
{"unmapped type", "unkn: something unknown", "", CommitMessage{Type: "unkn", Scope: "", Description: "something unknown", Body: "", IsBreakingChange: false, Metadata: map[string]string{}}},
{"jira and breaking change metadata", "feat: something new", completeBody, CommitMessage{Type: "feat", Scope: "", Description: "something new", Body: completeBody, IsBreakingChange: true, Metadata: map[string]string{issueKey: "JIRA-123", breakingKey: "this change breaks everything"}}},
{"jira only metadata", "feat: something new", issueOnlyBody, CommitMessage{Type: "feat", Scope: "", Description: "something new", Body: issueOnlyBody, IsBreakingChange: false, Metadata: map[string]string{issueKey: "JIRA-456"}}},
{"jira synonyms metadata", "feat: something new", issueSynonymsBody, CommitMessage{Type: "feat", Scope: "", Description: "something new", Body: issueSynonymsBody, IsBreakingChange: false, Metadata: map[string]string{issueKey: "JIRA-789"}}},
{"breaking change with exclamation mark", "feat!: something new", "", CommitMessage{Type: "feat", Scope: "", Description: "something new", Body: "", IsBreakingChange: true, Metadata: map[string]string{}}},
{"hash metadata", "feat: something new", hashMetadataBody, CommitMessage{Type: "feat", Scope: "", Description: "something new", Body: hashMetadataBody, IsBreakingChange: false, Metadata: map[string]string{issueKey: "JIRA-999", "refs": "#123"}}},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
p := NewCommitMessageProcessor(cfg)
if got := p.Parse(tt.subject, tt.body); !reflect.DeepEqual(got, tt.want) {
t.Errorf("CommitMessageProcessorImpl.Parse() = %v, want %v", got, tt.want)
}
})
}
}

View File

@ -64,12 +64,12 @@ func NewLogRange(t LogRangeType, start, end string) LogRange {
// GitImpl git command implementation // GitImpl git command implementation
type GitImpl struct { type GitImpl struct {
messageProcessor CommitMessageProcessor messageProcessor MessageProcessor
tagPattern string tagPattern string
} }
// NewGit constructor // NewGit constructor
func NewGit(messageProcessor CommitMessageProcessor, tagPattern string) *GitImpl { func NewGit(messageProcessor MessageProcessor, tagPattern string) *GitImpl {
return &GitImpl{ return &GitImpl{
messageProcessor: messageProcessor, messageProcessor: messageProcessor,
tagPattern: tagPattern, tagPattern: tagPattern,
@ -167,7 +167,7 @@ func parseTagsOutput(input string) ([]GitTag, error) {
return result, nil return result, nil
} }
func parseLogOutput(messageProcessor CommitMessageProcessor, log string) []GitCommitLog { func parseLogOutput(messageProcessor MessageProcessor, log string) []GitCommitLog {
scanner := bufio.NewScanner(strings.NewReader(log)) scanner := bufio.NewScanner(strings.NewReader(log))
scanner.Split(splitAt([]byte(endLine))) scanner.Split(splitAt([]byte(endLine)))
var logs []GitCommitLog var logs []GitCommitLog
@ -179,7 +179,7 @@ func parseLogOutput(messageProcessor CommitMessageProcessor, log string) []GitCo
return logs return logs
} }
func parseCommitLog(messageProcessor CommitMessageProcessor, commit string) GitCommitLog { func parseCommitLog(messageProcessor MessageProcessor, commit string) GitCommitLog {
content := strings.Split(strings.Trim(commit, "\""), logSeparator) content := strings.Split(strings.Trim(commit, "\""), logSeparator)
return GitCommitLog{ return GitCommitLog{

View File

@ -7,7 +7,43 @@ import (
"strings" "strings"
) )
const breakingChangeKey = "BREAKING CHANGE" const (
breakingKey = "breaking-change"
// IssueIDKey key to issue id metadata
issueKey = "issue"
)
// CommitMessage is a message using conventional commits.
type CommitMessage struct {
Type string `json:"type,omitempty"`
Scope string `json:"scope,omitempty"`
Description string `json:"description,omitempty"`
Body string `json:"body,omitempty"`
IsBreakingChange bool `json:"isBreakingChange,omitempty"`
Metadata map[string]string `json:"metadata,omitempty"`
}
// NewCommitMessage commit message constructor
func NewCommitMessage(ctype, scope, description, body, issue, breakingChanges string) CommitMessage {
metadata := make(map[string]string)
if issue != "" {
metadata[issueKey] = issue
}
if breakingChanges != "" {
metadata[breakingKey] = breakingChanges
}
return CommitMessage{Type: ctype, Scope: scope, Description: description, Body: body, IsBreakingChange: breakingChanges != "", Metadata: metadata}
}
// Issue return issue from metadata.
func (m CommitMessage) Issue() string {
return m.Metadata[issueKey]
}
// BreakingMessage return breaking change message from metadata.
func (m CommitMessage) BreakingMessage() string {
return m.Metadata[breakingKey]
}
// MessageProcessor interface. // MessageProcessor interface.
type MessageProcessor interface { type MessageProcessor interface {
@ -15,27 +51,24 @@ type MessageProcessor interface {
Validate(message string) error Validate(message string) error
Enhance(branch string, message string) (string, error) Enhance(branch string, message string) (string, error)
IssueID(branch string) (string, error) IssueID(branch string) (string, error)
Format(ctype, scope, subject, body, issue, breakingChanges string) (string, string, string) Format(msg CommitMessage) (string, string, string)
Parse(subject, body string) CommitMessage
} }
// NewMessageProcessor MessageProcessorImpl constructor // NewMessageProcessor MessageProcessorImpl constructor
func NewMessageProcessor(skipBranches, supportedTypes []string, issueKeyName, branchIssueRegex, issueRegex string) *MessageProcessorImpl { func NewMessageProcessor(cfg CommitMessageConfig, skipBranches []string, branchIssueRegex string) *MessageProcessorImpl {
return &MessageProcessorImpl{ return &MessageProcessorImpl{
cfg: cfg,
skipBranches: skipBranches, skipBranches: skipBranches,
supportedTypes: supportedTypes,
issueKeyName: issueKeyName,
branchIssueRegex: branchIssueRegex, branchIssueRegex: branchIssueRegex,
issueRegex: issueRegex,
} }
} }
// MessageProcessorImpl process validate message hook. // MessageProcessorImpl process validate message hook.
type MessageProcessorImpl struct { type MessageProcessorImpl struct {
cfg CommitMessageConfig
skipBranches []string skipBranches []string
supportedTypes []string
issueKeyName string
branchIssueRegex string branchIssueRegex string
issueRegex string
} }
// SkipBranch check if branch should be ignored. // SkipBranch check if branch should be ignored.
@ -45,19 +78,19 @@ 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.supportedTypes, "|")+")(\\(.+\\))?!?: .*$", firstLine(message)) valid, err := regexp.MatchString("^("+strings.Join(p.cfg.Types, "|")+")(\\(.+\\))?!?: .*$", firstLine(message))
if err != nil { if err != nil {
return err return err
} }
if !valid { if !valid {
return fmt.Errorf("message should contain type: %v, and should be valid according with conventional commits", p.supportedTypes) return fmt.Errorf("message should contain type: %v, and should be valid according with conventional commits", p.cfg.Types)
} }
return nil return nil
} }
// Enhance add metadata on commit message. // Enhance add metadata on commit message.
func (p MessageProcessorImpl) Enhance(branch string, message string) (string, error) { func (p MessageProcessorImpl) Enhance(branch string, message string) (string, error) {
if p.branchIssueRegex == "" || p.issueKeyName == "" || hasIssueID(message, p.issueKeyName) { if p.branchIssueRegex == "" || p.cfg.IssueConfig().Key == "" || hasIssueID(message, p.cfg.IssueConfig().Key) {
return "", nil //enhance disabled return "", nil //enhance disabled
} }
@ -69,9 +102,9 @@ func (p MessageProcessorImpl) Enhance(branch string, message string) (string, er
return "", fmt.Errorf("could not find issue id using configured regex") return "", fmt.Errorf("could not find issue id using configured regex")
} }
footer := fmt.Sprintf("%s: %s", p.issueKeyName, issue) footer := fmt.Sprintf("%s: %s", p.cfg.IssueConfig().Key, issue)
if !hasFooter(message) { if !hasFooter(message, p.cfg.Footer[breakingKey].Key) {
return "\n" + footer, nil return "\n" + footer, nil
} }
@ -92,31 +125,84 @@ func (p MessageProcessorImpl) IssueID(branch string) (string, error) {
return groups[2], nil return groups[2], nil
} }
// Format format commit message to header, body and footer // Format a commit message returning header, body and footer
func (p MessageProcessorImpl) Format(ctype, scope, subject, body, issue, breakingChanges string) (string, string, string) { func (p MessageProcessorImpl) Format(msg CommitMessage) (string, string, string) {
var header strings.Builder var header strings.Builder
header.WriteString(ctype) header.WriteString(msg.Type)
if scope != "" { if msg.Scope != "" {
header.WriteString("(" + scope + ")") header.WriteString("(" + msg.Scope + ")")
} }
header.WriteString(": ") header.WriteString(": ")
header.WriteString(subject) header.WriteString(msg.Description)
var footer strings.Builder var footer strings.Builder
if breakingChanges != "" { if msg.BreakingMessage() != "" {
footer.WriteString(fmt.Sprintf("%s: %s", breakingChangeKey, breakingChanges)) footer.WriteString(fmt.Sprintf("%s: %s", p.cfg.BreakingChangeConfig().Key, msg.BreakingMessage()))
} }
if issue != "" { if issue, exists := msg.Metadata[issueKey]; exists {
if footer.Len() > 0 { if footer.Len() > 0 {
footer.WriteString("\n") footer.WriteString("\n")
} }
footer.WriteString(fmt.Sprintf("%s: %s", p.issueKeyName, issue)) footer.WriteString(fmt.Sprintf("%s: %s", p.cfg.IssueConfig().Key, issue))
} }
return header.String(), body, footer.String() return header.String(), msg.Body, footer.String()
} }
func hasFooter(message string) bool { // Parse a commit message
func (p MessageProcessorImpl) Parse(subject, body string) CommitMessage {
commitType, scope, description, hasBreakingChange := parseSubjectMessage(subject)
metadata := make(map[string]string)
for key, mdCfg := range p.cfg.Footer {
prefixes := append([]string{mdCfg.Key}, mdCfg.KeySynonyms...)
for _, prefix := range prefixes {
if tagValue := extractFooterMetadata(prefix, body, mdCfg.UseHash); tagValue != "" {
metadata[key] = tagValue
break
}
}
}
if _, exists := metadata[breakingKey]; exists {
hasBreakingChange = true
}
return CommitMessage{
Type: commitType,
Scope: scope,
Description: description,
Body: body,
IsBreakingChange: hasBreakingChange,
Metadata: metadata,
}
}
func parseSubjectMessage(message string) (string, string, string, bool) {
regex := regexp.MustCompile("([a-z]+)(\\((.*)\\))?(!)?: (.*)")
result := regex.FindStringSubmatch(message)
if len(result) != 6 {
return "", "", message, false
}
return result[1], result[3], strings.TrimSpace(result[5]), result[4] == "!"
}
func extractFooterMetadata(key, text string, useHash bool) string {
var regex *regexp.Regexp
if useHash {
regex = regexp.MustCompile(key + " (#.*)")
} else {
regex = regexp.MustCompile(key + ": (.*)")
}
result := regex.FindStringSubmatch(text)
if len(result) < 2 {
return ""
}
return result[1]
}
func hasFooter(message, breakingChangeKey string) bool {
r := regexp.MustCompile("^[a-zA-Z-]+: .*|^[a-zA-Z-]+ #.*|^" + breakingChangeKey + ": .*") r := regexp.MustCompile("^[a-zA-Z-]+: .*|^[a-zA-Z-]+ #.*|^" + breakingChangeKey + ": .*")
scanner := bufio.NewScanner(strings.NewReader(message)) scanner := bufio.NewScanner(strings.NewReader(message))

View File

@ -1,9 +1,20 @@
package sv package sv
import ( import (
"reflect"
"testing" "testing"
) )
var cfg = CommitMessageConfig{
Types: []string{"feat", "fix"},
Scope: CommitMessageScopeConfig{},
Footer: map[string]CommitMessageFooterConfig{
"issue": {Key: "jira", KeySynonyms: []string{"Jira"}, Regex: "[A-Z]+-[0-9]+"},
"breaking-change": {Key: "BREAKING CHANGE", KeySynonyms: []string{"BREAKING CHANGES"}},
"refs": {Key: "Refs", UseHash: true},
},
}
const ( const (
branchIssueRegex = "^([a-z]+\\/)?([A-Z]+-[0-9]+)(-.*)?" branchIssueRegex = "^([a-z]+\\/)?([A-Z]+-[0-9]+)(-.*)?"
issueRegex = "[A-Z]+-[0-9]+" issueRegex = "[A-Z]+-[0-9]+"
@ -46,7 +57,7 @@ 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([]string{"develop", "master"}, []string{"feat", "fix"}, "jira", branchIssueRegex, issueRegex) p := NewMessageProcessor(cfg, []string{"develop", "master"}, branchIssueRegex)
tests := []struct { tests := []struct {
name string name string
@ -79,7 +90,7 @@ func TestMessageProcessorImpl_Validate(t *testing.T) {
} }
func TestMessageProcessorImpl_Enhance(t *testing.T) { func TestMessageProcessorImpl_Enhance(t *testing.T) {
p := NewMessageProcessor([]string{"develop", "master"}, []string{"feat", "fix"}, "jira", branchIssueRegex, issueRegex) p := NewMessageProcessor(cfg, []string{"develop", "master"}, branchIssueRegex)
tests := []struct { tests := []struct {
name string name string
@ -112,7 +123,7 @@ func TestMessageProcessorImpl_Enhance(t *testing.T) {
} }
func TestMessageProcessorImpl_IssueID(t *testing.T) { func TestMessageProcessorImpl_IssueID(t *testing.T) {
p := NewMessageProcessor([]string{"develop", "master"}, []string{"feat", "fix"}, "jira", branchIssueRegex, issueRegex) p := NewMessageProcessor(cfg, []string{"develop", "master"}, branchIssueRegex)
tests := []struct { tests := []struct {
name string name string
@ -149,46 +160,6 @@ c`
jira: JIRA-123` jira: JIRA-123`
) )
func TestMessageProcessorImpl_Format(t *testing.T) {
p := NewMessageProcessor([]string{"develop", "master"}, []string{"feat", "fix"}, "jira", branchIssueRegex, issueRegex)
type args struct {
ctype string
scope string
subject string
body string
issue string
breakingChanges string
}
tests := []struct {
name string
args args
wantHeader string
wantBody string
wantFooter string
}{
{"type and subject", args{"feat", "", "subject", "", "", ""}, "feat: subject", "", ""},
{"type, scope and subject", args{"feat", "scope", "subject", "", "", ""}, "feat(scope): subject", "", ""},
{"type, scope, subject and issue", args{"feat", "scope", "subject", "", "JIRA-123", ""}, "feat(scope): subject", "", "jira: JIRA-123"},
{"type, scope, subject and breaking change", args{"feat", "scope", "subject", "", "", "breaks"}, "feat(scope): subject", "", "BREAKING CHANGE: breaks"},
{"full message", args{"feat", "scope", "subject", multilineBody, "JIRA-123", "breaks"}, "feat(scope): subject", multilineBody, fullFooter},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
header, body, footer := p.Format(tt.args.ctype, tt.args.scope, tt.args.subject, tt.args.body, tt.args.issue, tt.args.breakingChanges)
if header != tt.wantHeader {
t.Errorf("MessageProcessorImpl.Format() header = %v, want %v", header, tt.wantHeader)
}
if body != tt.wantBody {
t.Errorf("MessageProcessorImpl.Format() body = %v, want %v", body, tt.wantBody)
}
if footer != tt.wantFooter {
t.Errorf("MessageProcessorImpl.Format() footer = %v, want %v", footer, tt.wantFooter)
}
})
}
}
func Test_firstLine(t *testing.T) { func Test_firstLine(t *testing.T) {
tests := []struct { tests := []struct {
name string name string
@ -252,9 +223,90 @@ func Test_hasFooter(t *testing.T) {
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
if got := hasFooter(tt.message); got != tt.want { if got := hasFooter(tt.message, "BREAKING CHANGE"); got != tt.want {
t.Errorf("hasFooter() = %v, want %v", got, tt.want) t.Errorf("hasFooter() = %v, want %v", got, tt.want)
} }
}) })
} }
} }
// conventional commit tests
var completeBody = `some descriptions
jira: JIRA-123
BREAKING CHANGE: this change breaks everything`
var issueOnlyBody = `some descriptions
jira: JIRA-456`
var issueSynonymsBody = `some descriptions
Jira: JIRA-789`
var hashMetadataBody = `some descriptions
Jira: JIRA-999
Refs #123`
func TestMessageProcessorImpl_Parse(t *testing.T) {
p := NewMessageProcessor(cfg, []string{"develop", "master"}, branchIssueRegex)
tests := []struct {
name string
subject string
body string
want CommitMessage
}{
{"simple message", "feat: something awesome", "", CommitMessage{Type: "feat", Scope: "", Description: "something awesome", Body: "", IsBreakingChange: false, Metadata: map[string]string{}}},
{"message with scope", "feat(scope): something awesome", "", CommitMessage{Type: "feat", Scope: "scope", Description: "something awesome", Body: "", IsBreakingChange: false, Metadata: map[string]string{}}},
{"unmapped type", "unkn: something unknown", "", CommitMessage{Type: "unkn", Scope: "", Description: "something unknown", Body: "", IsBreakingChange: false, Metadata: map[string]string{}}},
{"jira and breaking change metadata", "feat: something new", completeBody, CommitMessage{Type: "feat", Scope: "", Description: "something new", Body: completeBody, IsBreakingChange: true, Metadata: map[string]string{issueKey: "JIRA-123", breakingKey: "this change breaks everything"}}},
{"jira only metadata", "feat: something new", issueOnlyBody, CommitMessage{Type: "feat", Scope: "", Description: "something new", Body: issueOnlyBody, IsBreakingChange: false, Metadata: map[string]string{issueKey: "JIRA-456"}}},
{"jira synonyms metadata", "feat: something new", issueSynonymsBody, CommitMessage{Type: "feat", Scope: "", Description: "something new", Body: issueSynonymsBody, IsBreakingChange: false, Metadata: map[string]string{issueKey: "JIRA-789"}}},
{"breaking change with exclamation mark", "feat!: something new", "", CommitMessage{Type: "feat", Scope: "", Description: "something new", Body: "", IsBreakingChange: true, Metadata: map[string]string{}}},
{"hash metadata", "feat: something new", hashMetadataBody, CommitMessage{Type: "feat", Scope: "", Description: "something new", Body: hashMetadataBody, IsBreakingChange: false, Metadata: map[string]string{issueKey: "JIRA-999", "refs": "#123"}}},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := p.Parse(tt.subject, tt.body); !reflect.DeepEqual(got, tt.want) {
t.Errorf("MessageProcessorImpl.Parse() = %v, want %v", got, tt.want)
}
})
}
}
func TestMessageProcessorImpl_Format(t *testing.T) {
p := NewMessageProcessor(cfg, []string{"develop", "master"}, branchIssueRegex)
tests := []struct {
name string
msg CommitMessage
wantHeader string
wantBody string
wantFooter string
}{
{"simple message", NewCommitMessage("feat", "", "something", "", "", ""), "feat: something", "", ""},
{"with issue", NewCommitMessage("feat", "", "something", "", "JIRA-123", ""), "feat: something", "", "jira: JIRA-123"},
{"with breaking change", NewCommitMessage("feat", "", "something", "", "", "breaks"), "feat: something", "", "BREAKING CHANGE: breaks"},
{"with scope", NewCommitMessage("feat", "scope", "something", "", "", ""), "feat(scope): something", "", ""},
{"with body", NewCommitMessage("feat", "", "something", "body", "", ""), "feat: something", "body", ""},
{"with multiline body", NewCommitMessage("feat", "", "something", multilineBody, "", ""), "feat: something", multilineBody, ""},
{"full message", NewCommitMessage("feat", "scope", "something", multilineBody, "JIRA-123", "breaks"), "feat(scope): something", multilineBody, fullFooter},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, got1, got2 := p.Format(tt.msg)
if got != tt.wantHeader {
t.Errorf("MessageProcessorImpl.Format() header got = %v, want %v", got, tt.wantHeader)
}
if got1 != tt.wantBody {
t.Errorf("MessageProcessorImpl.Format() body got = %v, want %v", got1, tt.wantBody)
}
if got2 != tt.wantFooter {
t.Errorf("MessageProcessorImpl.Format() footer got = %v, want %v", got2, tt.wantFooter)
}
})
}
}