0
0
mirror of https://github.com/thegeeklab/git-sv.git synced 2024-09-20 00:02:46 +02:00
git-sv/sv/validatemessage.go

151 lines
4.0 KiB
Go
Raw Normal View History

package sv
import (
"bufio"
"fmt"
"regexp"
"strings"
)
2020-12-02 03:15:51 +01:00
const breakingChangeKey = "BREAKING CHANGE"
// ValidateMessageProcessor interface.
type ValidateMessageProcessor interface {
SkipBranch(branch string) bool
Validate(message string) error
Enhance(branch string, message string) (string, error)
2020-12-02 03:15:51 +01:00
IssueID(branch string) (string, error)
Format(ctype, scope, subject, body, issue, breakingChanges string) (string, string, string)
}
// NewValidateMessageProcessor ValidateMessageProcessorImpl constructor
2020-12-02 03:15:51 +01:00
func NewValidateMessageProcessor(skipBranches, supportedTypes []string, issueKeyName, branchIssueRegex, issueRegex string) *ValidateMessageProcessorImpl {
return &ValidateMessageProcessorImpl{
skipBranches: skipBranches,
supportedTypes: supportedTypes,
issueKeyName: issueKeyName,
branchIssueRegex: branchIssueRegex,
2020-12-02 03:15:51 +01:00
issueRegex: issueRegex,
}
}
// ValidateMessageProcessorImpl process validate message hook.
type ValidateMessageProcessorImpl struct {
skipBranches []string
supportedTypes []string
issueKeyName string
branchIssueRegex string
2020-12-02 03:15:51 +01:00
issueRegex string
}
// SkipBranch check if branch should be ignored.
func (p ValidateMessageProcessorImpl) SkipBranch(branch string) bool {
return contains(branch, p.skipBranches)
}
// Validate commit message.
func (p ValidateMessageProcessorImpl) Validate(message string) error {
valid, err := regexp.MatchString("^("+strings.Join(p.supportedTypes, "|")+")(\\(.+\\))?!?: .*$", 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 nil
}
// Enhance add metadata on commit message.
func (p ValidateMessageProcessorImpl) Enhance(branch string, message string) (string, error) {
if p.branchIssueRegex == "" || p.issueKeyName == "" || hasIssueID(message, p.issueKeyName) {
return "", nil //enhance disabled
}
2020-12-02 03:15:51 +01:00
issue, err := p.IssueID(branch)
if err != nil {
return "", err
}
if issue == "" {
return "", fmt.Errorf("could not find issue id using configured regex")
}
footer := fmt.Sprintf("%s: %s", p.issueKeyName, issue)
if !hasFooter(message) {
return "\n" + footer, nil
}
return footer, nil
}
// IssueID try to extract issue id from branch, return empty if not found
func (p ValidateMessageProcessorImpl) IssueID(branch string) (string, error) {
r, err := regexp.Compile(p.branchIssueRegex)
if err != nil {
return "", fmt.Errorf("could not compile issue regex: %s, error: %v", p.branchIssueRegex, err.Error())
}
groups := r.FindStringSubmatch(branch)
if len(groups) != 4 {
2020-12-02 03:15:51 +01:00
return "", nil
}
2020-12-02 03:15:51 +01:00
return groups[2], nil
}
2020-12-02 03:15:51 +01:00
// Format format commit message to header, body and footer
func (p ValidateMessageProcessorImpl) Format(ctype, scope, subject, body, issue, breakingChanges string) (string, string, string) {
var header strings.Builder
header.WriteString(ctype)
if scope != "" {
header.WriteString("(" + scope + ")")
}
header.WriteString(": ")
header.WriteString(subject)
2020-12-02 03:15:51 +01:00
var footer strings.Builder
if breakingChanges != "" {
footer.WriteString(fmt.Sprintf("%s: %s", breakingChangeKey, breakingChanges))
}
if issue != "" {
if footer.Len() > 0 {
footer.WriteString("\n")
}
footer.WriteString(fmt.Sprintf("%s: %s", p.issueKeyName, issue))
}
2020-12-02 03:15:51 +01:00
return header.String(), body, footer.String()
}
func hasFooter(message string) bool {
2020-12-02 03:15:51 +01:00
r := regexp.MustCompile("^[a-zA-Z-]+: .*|^[a-zA-Z-]+ #.*|^" + breakingChangeKey + ": .*")
scanner := bufio.NewScanner(strings.NewReader(message))
lines := 0
for scanner.Scan() {
if lines > 0 && r.MatchString(scanner.Text()) {
return true
}
lines++
}
return false
}
func hasIssueID(message, issueKeyName string) bool {
r := regexp.MustCompile(fmt.Sprintf("(?m)^%s: .+$", issueKeyName))
return r.MatchString(message)
}
func contains(value string, content []string) bool {
for _, v := range content {
if value == v {
return true
}
}
return false
}
func firstLine(value string) string {
return strings.Split(value, "\n")[0]
}