mirror of
https://github.com/thegeeklab/git-sv.git
synced 2024-11-21 12:00:40 +00:00
refactor: merge CommitMessageProcessor and MessageProcessor
This commit is contained in:
parent
740f05b84a
commit
de23ff9638
@ -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)
|
||||
if err != nil {
|
||||
|
@ -19,19 +19,19 @@ func main() {
|
||||
// TODO: config using yaml
|
||||
commitMessageCfg := sv.CommitMessageConfig{
|
||||
Types: cfg.CommitMessageTypes,
|
||||
Scope: sv.ScopeConfig{},
|
||||
Footer: map[string]sv.FooterMetadataConfig{
|
||||
Scope: sv.CommitMessageScopeConfig{},
|
||||
Footer: map[string]sv.CommitMessageFooterConfig{
|
||||
"issue": {Key: cfg.IssueIDPrefixes[0], KeySynonyms: cfg.IssueIDPrefixes[1:], Regex: cfg.IssueRegex},
|
||||
"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)
|
||||
releasenotesProcessor := sv.NewReleaseNoteProcessor(cfg.ReleaseNotesTags)
|
||||
outputFormatter := sv.NewOutputFormatter()
|
||||
messageProcessor := sv.NewMessageProcessor(cfg.ValidateMessageSkipBranches, cfg.CommitMessageTypes, cfg.IssueKeyName, cfg.BranchIssueRegex, cfg.IssueRegex)
|
||||
|
||||
app := cli.NewApp()
|
||||
app.Name = "sv"
|
||||
|
38
sv/config.go
Normal file
38
sv/config.go
Normal 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
|
||||
}
|
@ -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]
|
||||
}
|
@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
@ -64,12 +64,12 @@ func NewLogRange(t LogRangeType, start, end string) LogRange {
|
||||
|
||||
// GitImpl git command implementation
|
||||
type GitImpl struct {
|
||||
messageProcessor CommitMessageProcessor
|
||||
messageProcessor MessageProcessor
|
||||
tagPattern string
|
||||
}
|
||||
|
||||
// NewGit constructor
|
||||
func NewGit(messageProcessor CommitMessageProcessor, tagPattern string) *GitImpl {
|
||||
func NewGit(messageProcessor MessageProcessor, tagPattern string) *GitImpl {
|
||||
return &GitImpl{
|
||||
messageProcessor: messageProcessor,
|
||||
tagPattern: tagPattern,
|
||||
@ -167,7 +167,7 @@ func parseTagsOutput(input string) ([]GitTag, error) {
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func parseLogOutput(messageProcessor CommitMessageProcessor, log string) []GitCommitLog {
|
||||
func parseLogOutput(messageProcessor MessageProcessor, log string) []GitCommitLog {
|
||||
scanner := bufio.NewScanner(strings.NewReader(log))
|
||||
scanner.Split(splitAt([]byte(endLine)))
|
||||
var logs []GitCommitLog
|
||||
@ -179,7 +179,7 @@ func parseLogOutput(messageProcessor CommitMessageProcessor, log string) []GitCo
|
||||
return logs
|
||||
}
|
||||
|
||||
func parseCommitLog(messageProcessor CommitMessageProcessor, commit string) GitCommitLog {
|
||||
func parseCommitLog(messageProcessor MessageProcessor, commit string) GitCommitLog {
|
||||
content := strings.Split(strings.Trim(commit, "\""), logSeparator)
|
||||
|
||||
return GitCommitLog{
|
||||
|
138
sv/message.go
138
sv/message.go
@ -7,7 +7,43 @@ import (
|
||||
"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.
|
||||
type MessageProcessor interface {
|
||||
@ -15,27 +51,24 @@ type MessageProcessor interface {
|
||||
Validate(message string) error
|
||||
Enhance(branch string, message 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
|
||||
func NewMessageProcessor(skipBranches, supportedTypes []string, issueKeyName, branchIssueRegex, issueRegex string) *MessageProcessorImpl {
|
||||
func NewMessageProcessor(cfg CommitMessageConfig, skipBranches []string, branchIssueRegex string) *MessageProcessorImpl {
|
||||
return &MessageProcessorImpl{
|
||||
cfg: cfg,
|
||||
skipBranches: skipBranches,
|
||||
supportedTypes: supportedTypes,
|
||||
issueKeyName: issueKeyName,
|
||||
branchIssueRegex: branchIssueRegex,
|
||||
issueRegex: issueRegex,
|
||||
}
|
||||
}
|
||||
|
||||
// MessageProcessorImpl process validate message hook.
|
||||
type MessageProcessorImpl struct {
|
||||
cfg CommitMessageConfig
|
||||
skipBranches []string
|
||||
supportedTypes []string
|
||||
issueKeyName string
|
||||
branchIssueRegex string
|
||||
issueRegex string
|
||||
}
|
||||
|
||||
// SkipBranch check if branch should be ignored.
|
||||
@ -45,19 +78,19 @@ func (p MessageProcessorImpl) SkipBranch(branch string) bool {
|
||||
|
||||
// Validate commit message.
|
||||
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 {
|
||||
return err
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
// Enhance add metadata on commit message.
|
||||
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
|
||||
}
|
||||
|
||||
@ -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")
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
@ -92,31 +125,84 @@ func (p MessageProcessorImpl) IssueID(branch string) (string, error) {
|
||||
return groups[2], nil
|
||||
}
|
||||
|
||||
// Format format commit message to header, body and footer
|
||||
func (p MessageProcessorImpl) Format(ctype, scope, subject, body, issue, breakingChanges string) (string, string, string) {
|
||||
// Format a commit message returning header, body and footer
|
||||
func (p MessageProcessorImpl) Format(msg CommitMessage) (string, string, string) {
|
||||
var header strings.Builder
|
||||
header.WriteString(ctype)
|
||||
if scope != "" {
|
||||
header.WriteString("(" + scope + ")")
|
||||
header.WriteString(msg.Type)
|
||||
if msg.Scope != "" {
|
||||
header.WriteString("(" + msg.Scope + ")")
|
||||
}
|
||||
header.WriteString(": ")
|
||||
header.WriteString(subject)
|
||||
header.WriteString(msg.Description)
|
||||
|
||||
var footer strings.Builder
|
||||
if breakingChanges != "" {
|
||||
footer.WriteString(fmt.Sprintf("%s: %s", breakingChangeKey, breakingChanges))
|
||||
if msg.BreakingMessage() != "" {
|
||||
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 {
|
||||
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 + ": .*")
|
||||
|
||||
scanner := bufio.NewScanner(strings.NewReader(message))
|
||||
|
@ -1,9 +1,20 @@
|
||||
package sv
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"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 (
|
||||
branchIssueRegex = "^([a-z]+\\/)?([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
|
||||
|
||||
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 {
|
||||
name string
|
||||
@ -79,7 +90,7 @@ func TestMessageProcessorImpl_Validate(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 {
|
||||
name string
|
||||
@ -112,7 +123,7 @@ func TestMessageProcessorImpl_Enhance(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 {
|
||||
name string
|
||||
@ -149,46 +160,6 @@ c`
|
||||
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) {
|
||||
tests := []struct {
|
||||
name string
|
||||
@ -252,9 +223,90 @@ func Test_hasFooter(t *testing.T) {
|
||||
}
|
||||
for _, tt := range tests {
|
||||
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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user