mirror of
https://github.com/thegeeklab/git-sv.git
synced 2024-11-13 21:30:40 +00:00
151 lines
3.9 KiB
Go
151 lines
3.9 KiB
Go
package sv
|
|
|
|
import (
|
|
"bufio"
|
|
"fmt"
|
|
"regexp"
|
|
"strings"
|
|
)
|
|
|
|
const breakingChangeKey = "BREAKING CHANGE"
|
|
|
|
// MessageProcessor interface.
|
|
type MessageProcessor interface {
|
|
SkipBranch(branch string) bool
|
|
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)
|
|
}
|
|
|
|
// NewMessageProcessor MessageProcessorImpl constructor
|
|
func NewMessageProcessor(skipBranches, supportedTypes []string, issueKeyName, branchIssueRegex, issueRegex string) *MessageProcessorImpl {
|
|
return &MessageProcessorImpl{
|
|
skipBranches: skipBranches,
|
|
supportedTypes: supportedTypes,
|
|
issueKeyName: issueKeyName,
|
|
branchIssueRegex: branchIssueRegex,
|
|
issueRegex: issueRegex,
|
|
}
|
|
}
|
|
|
|
// MessageProcessorImpl process validate message hook.
|
|
type MessageProcessorImpl struct {
|
|
skipBranches []string
|
|
supportedTypes []string
|
|
issueKeyName string
|
|
branchIssueRegex string
|
|
issueRegex string
|
|
}
|
|
|
|
// SkipBranch check if branch should be ignored.
|
|
func (p MessageProcessorImpl) SkipBranch(branch string) bool {
|
|
return contains(branch, p.skipBranches)
|
|
}
|
|
|
|
// Validate commit message.
|
|
func (p MessageProcessorImpl) 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 MessageProcessorImpl) Enhance(branch string, message string) (string, error) {
|
|
if p.branchIssueRegex == "" || p.issueKeyName == "" || hasIssueID(message, p.issueKeyName) {
|
|
return "", nil //enhance disabled
|
|
}
|
|
|
|
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 MessageProcessorImpl) 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 {
|
|
return "", nil
|
|
}
|
|
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) {
|
|
var header strings.Builder
|
|
header.WriteString(ctype)
|
|
if scope != "" {
|
|
header.WriteString("(" + scope + ")")
|
|
}
|
|
header.WriteString(": ")
|
|
header.WriteString(subject)
|
|
|
|
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))
|
|
}
|
|
|
|
return header.String(), body, footer.String()
|
|
}
|
|
|
|
func hasFooter(message string) bool {
|
|
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]
|
|
}
|