diff --git a/cmd/git-sv/config.go b/cmd/git-sv/config.go index 3f8d454..4e4ae97 100644 --- a/cmd/git-sv/config.go +++ b/cmd/git-sv/config.go @@ -20,7 +20,8 @@ type Config struct { CommitMessageTypes []string `envconfig:"COMMIT_MESSAGE_TYPES" default:"build,ci,chore,docs,feat,fix,perf,refactor,revert,style,test"` IssueKeyName string `envconfig:"ISSUE_KEY_NAME" default:"jira"` IssueRegex string `envconfig:"ISSUE_REGEX" default:"[A-Z]+-[0-9]+"` - BranchIssueRegex string `envconfig:"BRANCH_ISSUE_REGEX" default:"^([a-z]+\\/)?([A-Z]+-[0-9]+)(-.*)?"` //TODO breaking change: use issue regex instead of duplicating issue regex + BranchIssuePrefixRegex string `envconfig:"BRANCH_ISSUE_PREFIX_REGEX" default:"([a-z]+\\/)?"` + BranchIssueSuffixRegex string `envconfig:"BRANCH_ISSUE_SUFFIX_REGEX" default:"(-.*)?"` } func loadConfig() Config { diff --git a/cmd/git-sv/main.go b/cmd/git-sv/main.go index 1102e1c..5819bfa 100644 --- a/cmd/git-sv/main.go +++ b/cmd/git-sv/main.go @@ -21,13 +21,20 @@ func main() { Types: cfg.CommitMessageTypes, Scope: sv.CommitMessageScopeConfig{}, 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:]}, "breaking-change": {Key: cfg.BreakingChangePrefixes[0], KeySynonyms: cfg.BreakingChangePrefixes[1:]}, }, + Issue: sv.CommitMessageIssueConfig{Regex: cfg.IssueRegex}, + } + branchesConfig := sv.BranchesConfig{ + Skip: cfg.ValidateMessageSkipBranches, + ExpectIssue: true, + PrefixRegex: cfg.BranchIssuePrefixRegex, + SuffixRegex: cfg.BranchIssueSuffixRegex, } //// - messageProcessor := sv.NewMessageProcessor(commitMessageCfg, cfg.ValidateMessageSkipBranches, cfg.BranchIssueRegex) + messageProcessor := sv.NewMessageProcessor(commitMessageCfg, branchesConfig) git := sv.NewGit(messageProcessor, cfg.TagPattern) semverProcessor := sv.NewSemVerCommitsProcessor(cfg.IncludeUnknownTypeAsPatch, cfg.MajorVersionTypes, cfg.MinorVersionTypes, cfg.PatchVersionTypes) releasenotesProcessor := sv.NewReleaseNoteProcessor(cfg.ReleaseNotesTags) diff --git a/sv/config.go b/sv/config.go index 5074461..9f3fb03 100644 --- a/sv/config.go +++ b/sv/config.go @@ -5,6 +5,7 @@ type CommitMessageConfig struct { Types []string Scope CommitMessageScopeConfig Footer map[string]CommitMessageFooterConfig + Issue CommitMessageIssueConfig } // IssueConfig config for issue. @@ -33,6 +34,18 @@ type CommitMessageScopeConfig struct { type CommitMessageFooterConfig struct { Key string KeySynonyms []string - Regex string UseHash bool } + +// CommitMessageIssueConfig issue preferences. +type CommitMessageIssueConfig struct { + Regex string +} + +// BranchesConfig branches preferences. +type BranchesConfig struct { + PrefixRegex string + SuffixRegex string + ExpectIssue bool + Skip []string +} diff --git a/sv/message.go b/sv/message.go index b34af01..0d74508 100644 --- a/sv/message.go +++ b/sv/message.go @@ -56,41 +56,39 @@ type MessageProcessor interface { } // NewMessageProcessor MessageProcessorImpl constructor -func NewMessageProcessor(cfg CommitMessageConfig, skipBranches []string, branchIssueRegex string) *MessageProcessorImpl { +func NewMessageProcessor(mcfg CommitMessageConfig, bcfg BranchesConfig) *MessageProcessorImpl { return &MessageProcessorImpl{ - cfg: cfg, - skipBranches: skipBranches, - branchIssueRegex: branchIssueRegex, + messageCfg: mcfg, + branchesCfg: bcfg, } } // MessageProcessorImpl process validate message hook. type MessageProcessorImpl struct { - cfg CommitMessageConfig - skipBranches []string - branchIssueRegex string + messageCfg CommitMessageConfig + branchesCfg BranchesConfig } // SkipBranch check if branch should be ignored. func (p MessageProcessorImpl) SkipBranch(branch string) bool { - return contains(branch, p.skipBranches) + return contains(branch, p.branchesCfg.Skip) } // Validate commit message. func (p MessageProcessorImpl) Validate(message string) error { - valid, err := regexp.MatchString("^("+strings.Join(p.cfg.Types, "|")+")(\\(.+\\))?!?: .*$", firstLine(message)) + valid, err := regexp.MatchString("^("+strings.Join(p.messageCfg.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.cfg.Types) + return fmt.Errorf("message should contain type: %v, and should be valid according with conventional commits", p.messageCfg.Types) } return nil } // Enhance add metadata on commit message. func (p MessageProcessorImpl) Enhance(branch string, message string) (string, error) { - if p.branchIssueRegex == "" || p.cfg.IssueConfig().Key == "" || hasIssueID(message, p.cfg.IssueConfig().Key) { + if !p.branchesCfg.ExpectIssue || p.messageCfg.IssueConfig().Key == "" || hasIssueID(message, p.messageCfg.IssueConfig().Key) { return "", nil //enhance disabled } @@ -102,9 +100,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.cfg.IssueConfig().Key, issue) + footer := fmt.Sprintf("%s: %s", p.messageCfg.IssueConfig().Key, issue) - if !hasFooter(message, p.cfg.Footer[breakingKey].Key) { + if !hasFooter(message, p.messageCfg.Footer[breakingKey].Key) { return "\n" + footer, nil } @@ -113,9 +111,10 @@ func (p MessageProcessorImpl) Enhance(branch string, message string) (string, er // IssueID try to extract issue id from branch, return empty if not found. func (p MessageProcessorImpl) IssueID(branch string) (string, error) { - r, err := regexp.Compile(p.branchIssueRegex) + rstr := fmt.Sprintf("^%s(%s)%s$", p.branchesCfg.PrefixRegex, p.messageCfg.Issue.Regex, p.branchesCfg.SuffixRegex) + r, err := regexp.Compile(rstr) if err != nil { - return "", fmt.Errorf("could not compile issue regex: %s, error: %v", p.branchIssueRegex, err.Error()) + return "", fmt.Errorf("could not compile issue regex: %s, error: %v", rstr, err.Error()) } groups := r.FindStringSubmatch(branch) @@ -137,13 +136,13 @@ func (p MessageProcessorImpl) Format(msg CommitMessage) (string, string, string) var footer strings.Builder if msg.BreakingMessage() != "" { - footer.WriteString(fmt.Sprintf("%s: %s", p.cfg.BreakingChangeConfig().Key, msg.BreakingMessage())) + footer.WriteString(fmt.Sprintf("%s: %s", p.messageCfg.BreakingChangeConfig().Key, msg.BreakingMessage())) } if issue, exists := msg.Metadata[issueKey]; exists { if footer.Len() > 0 { footer.WriteString("\n") } - footer.WriteString(fmt.Sprintf("%s: %s", p.cfg.IssueConfig().Key, issue)) + footer.WriteString(fmt.Sprintf("%s: %s", p.messageCfg.IssueConfig().Key, issue)) } return header.String(), msg.Body, footer.String() @@ -154,7 +153,7 @@ 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 { + for key, mdCfg := range p.messageCfg.Footer { prefixes := append([]string{mdCfg.Key}, mdCfg.KeySynonyms...) for _, prefix := range prefixes { if tagValue := extractFooterMetadata(prefix, body, mdCfg.UseHash); tagValue != "" { diff --git a/sv/message_test.go b/sv/message_test.go index 32e01ad..6491f67 100644 --- a/sv/message_test.go +++ b/sv/message_test.go @@ -5,20 +5,23 @@ import ( "testing" ) -var cfg = CommitMessageConfig{ +var ccfg = CommitMessageConfig{ Types: []string{"feat", "fix"}, Scope: CommitMessageScopeConfig{}, Footer: map[string]CommitMessageFooterConfig{ - "issue": {Key: "jira", KeySynonyms: []string{"Jira"}, Regex: "[A-Z]+-[0-9]+"}, + "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]+"}, } -const ( - branchIssueRegex = "^([a-z]+\\/)?([A-Z]+-[0-9]+)(-.*)?" - issueRegex = "[A-Z]+-[0-9]+" -) +var bcfg = BranchesConfig{ + ExpectIssue: true, + PrefixRegex: "([a-z]+\\/)?", + SuffixRegex: "(-.*)?", + Skip: []string{"develop", "master"}, +} // messages samples start var fullMessage = `fix: correct minor typos in code @@ -57,7 +60,7 @@ BREAKING CHANGE: refactor to use JavaScript features not available in Node 6.` // multiline samples end func TestMessageProcessorImpl_Validate(t *testing.T) { - p := NewMessageProcessor(cfg, []string{"develop", "master"}, branchIssueRegex) + p := NewMessageProcessor(ccfg, bcfg) tests := []struct { name string @@ -90,7 +93,7 @@ func TestMessageProcessorImpl_Validate(t *testing.T) { } func TestMessageProcessorImpl_Enhance(t *testing.T) { - p := NewMessageProcessor(cfg, []string{"develop", "master"}, branchIssueRegex) + p := NewMessageProcessor(ccfg, bcfg) tests := []struct { name string @@ -123,7 +126,7 @@ func TestMessageProcessorImpl_Enhance(t *testing.T) { } func TestMessageProcessorImpl_IssueID(t *testing.T) { - p := NewMessageProcessor(cfg, []string{"develop", "master"}, branchIssueRegex) + p := NewMessageProcessor(ccfg, bcfg) tests := []struct { name string @@ -251,7 +254,7 @@ Jira: JIRA-999 Refs #123` func TestMessageProcessorImpl_Parse(t *testing.T) { - p := NewMessageProcessor(cfg, []string{"develop", "master"}, branchIssueRegex) + p := NewMessageProcessor(ccfg, bcfg) tests := []struct { name string @@ -278,7 +281,7 @@ func TestMessageProcessorImpl_Parse(t *testing.T) { } func TestMessageProcessorImpl_Format(t *testing.T) { - p := NewMessageProcessor(cfg, []string{"develop", "master"}, branchIssueRegex) + p := NewMessageProcessor(ccfg, bcfg) tests := []struct { name string