mirror of
https://github.com/thegeeklab/git-sv.git
synced 2024-11-21 12:00:40 +00:00
feat: add support for current-version, next-version, commit-log and release-notes
BREAKING CHANGE: first release
This commit is contained in:
parent
d101a63bdb
commit
9374b3addc
43
Makefile
Normal file
43
Makefile
Normal file
@ -0,0 +1,43 @@
|
||||
.PHONY: usage build run test
|
||||
|
||||
OK_COLOR=\033[32;01m
|
||||
NO_COLOR=\033[0m
|
||||
ERROR_COLOR=\033[31;01m
|
||||
WARN_COLOR=\033[33;01m
|
||||
|
||||
PKGS = $(shell go list ./...)
|
||||
BIN = git-sv
|
||||
|
||||
ECHOFLAGS ?=
|
||||
|
||||
BUILDOS ?= linux
|
||||
BUILDARCH ?= amd64
|
||||
BUILDENVS ?= CGO_ENABLED=0 GOOS=$(BUILDOS) GOARCH=$(BUILDARCH)
|
||||
BUILDFLAGS ?= -a -installsuffix cgo --ldflags '-extldflags "-lm -lstdc++ -static"'
|
||||
|
||||
usage: Makefile
|
||||
@echo $(ECHOFLAGS) "to use make call:"
|
||||
@echo $(ECHOFLAGS) " make <action>"
|
||||
@echo $(ECHOFLAGS) ""
|
||||
@echo $(ECHOFLAGS) "list of available actions:"
|
||||
@sed -n 's/^##//p' $< | column -t -s ':' | sed -e 's/^/ /'
|
||||
|
||||
## build: build git-sv
|
||||
build: test
|
||||
@echo $(ECHOFLAGS) "$(OK_COLOR)==> Building binary ($(BUILDOS)/$(BUILDARCH)/$(BIN))...$(NO_COLOR)"
|
||||
@$(BUILDENVS) go build -v $(BUILDFLAGS) -o bin/$(BUILDOS)_$(BUILDARCH)/$(BIN) ./cmd/git-sv
|
||||
|
||||
## test: run unit tests
|
||||
test:
|
||||
@echo $(ECHOFLAGS) "$(OK_COLOR)==> Running tests...$(NO_COLOR)"
|
||||
@go test $(PKGS)
|
||||
|
||||
## run: run gitlabels-cli
|
||||
run:
|
||||
@echo $(ECHOFLAGS) "$(OK_COLOR)==> Running bin/$(BUILDOS)_$(BUILDARCH)/$(BIN)...$(NO_COLOR)"
|
||||
@./bin/$(BUILDOS)_$(BUILDARCH)/$(BIN) $(args)
|
||||
|
||||
## tidy: execute go mod tidy
|
||||
tidy:
|
||||
@echo $(ECHOFLAGS) "$(OK_COLOR)==> runing tidy"
|
||||
@go mod tidy
|
27
cmd/git-sv/config.go
Normal file
27
cmd/git-sv/config.go
Normal file
@ -0,0 +1,27 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
"github.com/kelseyhightower/envconfig"
|
||||
)
|
||||
|
||||
// Config env vars for cli configuration
|
||||
type Config struct {
|
||||
MajorVersionTypes []string `envconfig:"MAJOR_VERSION_TYPES" default:""`
|
||||
MinorVersionTypes []string `envconfig:"MINOR_VERSION_TYPES" default:"feat"`
|
||||
PatchVersionTypes []string `envconfig:"PATCH_VERSION_TYPES" default:"build,ci,docs,fix,perf,refactor,style,test"`
|
||||
IncludeUnknownTypeAsPatch bool `envconfig:"INCLUDE_UNKNOWN_TYPE_AS_PATCH" default:"true"`
|
||||
CommitMessageMetadata map[string]string `envconfig:"COMMIT_MESSAGE_METADATA" default:"breakingchange:BREAKING CHANGE,issueid:jira"`
|
||||
TagPattern string `envconfig:"TAG_PATTERN" default:"v%d.%d.%d"`
|
||||
ReleaseNotesTags map[string]string `envconfig:"RELEASE_NOTES_TAGS" default:"fix:Bug Fixes,feat:Features"`
|
||||
}
|
||||
|
||||
func loadConfig() Config {
|
||||
var c Config
|
||||
err := envconfig.Process("SV", &c)
|
||||
if err != nil {
|
||||
log.Fatal(err.Error())
|
||||
}
|
||||
return c
|
||||
}
|
85
cmd/git-sv/handlers.go
Normal file
85
cmd/git-sv/handlers.go
Normal file
@ -0,0 +1,85 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"sv4git/sv"
|
||||
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
func currentVersionHandler(git sv.Git) func(c *cli.Context) error {
|
||||
return func(c *cli.Context) error {
|
||||
describe := git.Describe()
|
||||
|
||||
currentVer, err := sv.ToVersion(describe)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Printf("%d.%d.%d\n", currentVer.Major(), currentVer.Minor(), currentVer.Patch())
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func nextVersionHandler(git sv.Git, semverProcessor sv.SemVerCommitsProcessor) func(c *cli.Context) error {
|
||||
return func(c *cli.Context) error {
|
||||
describe := git.Describe()
|
||||
|
||||
currentVer, err := sv.ToVersion(describe)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error parsing version: %s from describe, message: %v", describe, err)
|
||||
}
|
||||
|
||||
commits, err := git.Log(describe)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error getting git log, message: %v", err)
|
||||
}
|
||||
|
||||
nextVer := semverProcessor.NexVersion(currentVer, commits)
|
||||
fmt.Printf("%d.%d.%d\n", nextVer.Major(), nextVer.Minor(), nextVer.Patch())
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func commitLogHandler(git sv.Git, semverProcessor sv.SemVerCommitsProcessor) func(c *cli.Context) error {
|
||||
return func(c *cli.Context) error {
|
||||
describe := git.Describe()
|
||||
|
||||
commits, err := git.Log(describe)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error getting git log, message: %v", err)
|
||||
}
|
||||
|
||||
for _, commit := range commits {
|
||||
content, err := json.Marshal(commit)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Println(string(content))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func releaseNotesHandler(git sv.Git, semverProcessor sv.SemVerCommitsProcessor, rnProcessor sv.ReleaseNoteProcessor) func(c *cli.Context) error {
|
||||
return func(c *cli.Context) error {
|
||||
|
||||
describe := git.Describe()
|
||||
|
||||
currentVer, err := sv.ToVersion(describe)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error parsing version: %s from describe, message: %v", describe, err)
|
||||
}
|
||||
|
||||
commits, err := git.Log(describe)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error getting git log, message: %v", err)
|
||||
}
|
||||
|
||||
nextVer := semverProcessor.NexVersion(currentVer, commits)
|
||||
|
||||
releasenote := rnProcessor.Get(commits)
|
||||
fmt.Println(rnProcessor.Format(releasenote, nextVer))
|
||||
return nil
|
||||
}
|
||||
}
|
52
cmd/git-sv/main.go
Normal file
52
cmd/git-sv/main.go
Normal file
@ -0,0 +1,52 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
"os"
|
||||
"sv4git/sv"
|
||||
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
func main() {
|
||||
cfg := loadConfig()
|
||||
|
||||
git := sv.NewGit(cfg.CommitMessageMetadata, cfg.TagPattern)
|
||||
semverProcessor := sv.NewSemVerCommitsProcessor(cfg.IncludeUnknownTypeAsPatch, cfg.MajorVersionTypes, cfg.MinorVersionTypes, cfg.PatchVersionTypes)
|
||||
releasenotesProcessor := sv.NewReleaseNoteProcessor(cfg.ReleaseNotesTags)
|
||||
|
||||
app := cli.NewApp()
|
||||
app.Name = "sv"
|
||||
app.Usage = "semantic version for git"
|
||||
app.Commands = []cli.Command{
|
||||
{
|
||||
Name: "current-version",
|
||||
Aliases: []string{"cv"},
|
||||
Usage: "get last released version from git",
|
||||
Action: currentVersionHandler(git),
|
||||
},
|
||||
{
|
||||
Name: "next-version",
|
||||
Aliases: []string{"nv"},
|
||||
Usage: "generate the next version based on git commit messages",
|
||||
Action: nextVersionHandler(git, semverProcessor),
|
||||
},
|
||||
{
|
||||
Name: "commit-log",
|
||||
Aliases: []string{"cl"},
|
||||
Usage: "list all commit logs since last version as jsons",
|
||||
Action: commitLogHandler(git, semverProcessor),
|
||||
},
|
||||
{
|
||||
Name: "release-notes",
|
||||
Aliases: []string{"rn"},
|
||||
Usage: "generate release notes",
|
||||
Action: releaseNotesHandler(git, semverProcessor, releasenotesProcessor),
|
||||
},
|
||||
}
|
||||
|
||||
apperr := app.Run(os.Args)
|
||||
if apperr != nil {
|
||||
log.Fatal(apperr)
|
||||
}
|
||||
}
|
10
go.mod
Normal file
10
go.mod
Normal file
@ -0,0 +1,10 @@
|
||||
module sv4git
|
||||
|
||||
go 1.13
|
||||
|
||||
require (
|
||||
github.com/Masterminds/semver v1.5.0
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0 // indirect
|
||||
github.com/kelseyhightower/envconfig v1.4.0
|
||||
github.com/urfave/cli v1.22.1
|
||||
)
|
19
go.sum
Normal file
19
go.sum
Normal file
@ -0,0 +1,19 @@
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww=
|
||||
github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSYgptZMwQh2aRr3LuazLJIa+Pg3Kc1ylSYVY=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0 h1:EoUDS0afbrsXAZ9YQ9jdu/mZ2sXgT1/2yyNng4PGlyM=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
||||
github.com/kelseyhightower/envconfig v1.4.0 h1:Im6hONhd3pLkfDFsbRgu68RDNkGF1r3dvMUtDTo2cv8=
|
||||
github.com/kelseyhightower/envconfig v1.4.0/go.mod h1:cccZRl6mQpaq41TPp5QxidR+Sa3axMbJDNb//FQX6Gg=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q=
|
||||
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo=
|
||||
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
||||
github.com/urfave/cli v1.22.1 h1:+mkCCcOFKPnCmVYVcURKps1Xe+3zP90gSYGNfRkjoIY=
|
||||
github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
150
sv/git.go
Normal file
150
sv/git.go
Normal file
@ -0,0 +1,150 @@
|
||||
package sv
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"os/exec"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/Masterminds/semver"
|
||||
)
|
||||
|
||||
const (
|
||||
logSeparator = "##"
|
||||
endLine = "~~"
|
||||
breakingChangesTag = "BREAKING CHANGE:"
|
||||
issueIDTag = "jira:"
|
||||
)
|
||||
|
||||
// Git commands
|
||||
type Git interface {
|
||||
Describe() string
|
||||
Log(lastTag string) ([]GitCommitLog, error)
|
||||
Tag(version semver.Version) error
|
||||
}
|
||||
|
||||
// GitCommitLog description of a single commit log
|
||||
type GitCommitLog struct {
|
||||
Hash string `json:"hash,omitempty"`
|
||||
Type string `json:"type,omitempty"`
|
||||
Scope string `json:"scope,omitempty"`
|
||||
Subject string `json:"subject,omitempty"`
|
||||
Body string `json:"body,omitempty"`
|
||||
Metadata map[string]string `json:"metadata,omitempty"`
|
||||
}
|
||||
|
||||
// GitImpl git command implementation
|
||||
type GitImpl struct {
|
||||
messageMetadata map[string]string
|
||||
tagPattern string
|
||||
}
|
||||
|
||||
// NewGit constructor
|
||||
func NewGit(messageMetadata map[string]string, tagPattern string) *GitImpl {
|
||||
return &GitImpl{messageMetadata: messageMetadata, tagPattern: tagPattern}
|
||||
}
|
||||
|
||||
// Describe runs git describe, it no tag found, return empty
|
||||
func (GitImpl) Describe() string {
|
||||
cmd := exec.Command("git", "describe", "--abbrev=0")
|
||||
out, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
return strings.TrimSpace(strings.Trim(string(out), "\n"))
|
||||
}
|
||||
|
||||
// Log return git log
|
||||
func (g GitImpl) Log(lastTag string) ([]GitCommitLog, error) {
|
||||
format := "--pretty=format:\"%h" + logSeparator + "%s" + logSeparator + "%b" + endLine + "\""
|
||||
cmd := exec.Command("git", "log", format)
|
||||
if lastTag != "" {
|
||||
cmd = exec.Command("git", "log", lastTag+"..HEAD", format)
|
||||
}
|
||||
|
||||
out, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return parseLogOutput(g.messageMetadata, string(out)), nil
|
||||
}
|
||||
|
||||
// Tag create a git tag
|
||||
func (g GitImpl) Tag(version semver.Version) error {
|
||||
tagMsg := fmt.Sprintf("-v \"Version %d.%d.%d\"", version.Major(), version.Minor(), version.Patch())
|
||||
cmd := exec.Command("git", "tag", "-a "+fmt.Sprintf(g.tagPattern, version.Major(), version.Minor(), version.Patch()), tagMsg)
|
||||
return cmd.Run()
|
||||
}
|
||||
|
||||
func parseLogOutput(messageMetadata map[string]string, log string) []GitCommitLog {
|
||||
scanner := bufio.NewScanner(strings.NewReader(log))
|
||||
scanner.Split(splitAt([]byte(endLine)))
|
||||
var logs []GitCommitLog
|
||||
for scanner.Scan() {
|
||||
if text := strings.TrimSpace(strings.Trim(scanner.Text(), "\"")); text != "" {
|
||||
logs = append(logs, parseCommitLog(messageMetadata, text))
|
||||
}
|
||||
}
|
||||
return logs
|
||||
}
|
||||
|
||||
func parseCommitLog(messageMetadata map[string]string, commit string) GitCommitLog {
|
||||
content := strings.Split(strings.Trim(commit, "\""), logSeparator)
|
||||
commitType, scope, subject := parseCommitLogMessage(content[1])
|
||||
|
||||
metadata := make(map[string]string)
|
||||
for k, v := range messageMetadata {
|
||||
if tagValue := extractTag(v, content[2]); tagValue != "" {
|
||||
metadata[k] = tagValue
|
||||
}
|
||||
}
|
||||
|
||||
return GitCommitLog{
|
||||
Hash: content[0],
|
||||
Type: commitType,
|
||||
Scope: scope,
|
||||
Subject: subject,
|
||||
Body: content[2],
|
||||
Metadata: metadata,
|
||||
}
|
||||
}
|
||||
|
||||
func parseCommitLogMessage(message string) (string, string, string) {
|
||||
regex := regexp.MustCompile("([a-z]+)(\\((.*)\\))?: (.*)")
|
||||
result := regex.FindStringSubmatch(message)
|
||||
if len(result) != 5 {
|
||||
return "", "", message
|
||||
}
|
||||
return result[1], result[3], strings.TrimSpace(result[4])
|
||||
}
|
||||
|
||||
func extractTag(tag, text string) string {
|
||||
regex := regexp.MustCompile(tag + ": (.*)")
|
||||
result := regex.FindStringSubmatch(text)
|
||||
if len(result) < 2 {
|
||||
return ""
|
||||
}
|
||||
return result[1]
|
||||
}
|
||||
|
||||
func splitAt(b []byte) func(data []byte, atEOF bool) (advance int, token []byte, err error) {
|
||||
return func(data []byte, atEOF bool) (advance int, token []byte, err error) {
|
||||
dataLen := len(data)
|
||||
|
||||
if atEOF && dataLen == 0 {
|
||||
return 0, nil, nil
|
||||
}
|
||||
|
||||
if i := bytes.Index(data, b); i >= 0 {
|
||||
return i + len(b), data[0:i], nil
|
||||
}
|
||||
|
||||
if atEOF {
|
||||
return dataLen, data, nil
|
||||
}
|
||||
|
||||
return 0, nil, nil
|
||||
}
|
||||
}
|
98
sv/releasenotes.go
Normal file
98
sv/releasenotes.go
Normal file
@ -0,0 +1,98 @@
|
||||
package sv
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"text/template"
|
||||
"time"
|
||||
|
||||
"github.com/Masterminds/semver"
|
||||
)
|
||||
|
||||
type releaseNoteTemplate struct {
|
||||
Version string
|
||||
Date string
|
||||
Sections map[string]ReleaseNoteSection
|
||||
BreakingChanges []string
|
||||
}
|
||||
|
||||
const markdownTemplate = `# v{{.Version}} ({{.Date}})
|
||||
|
||||
{{if .Sections.feat}}## {{.Sections.feat.Name}}
|
||||
{{range $k,$v := .Sections.feat.Items}}
|
||||
- {{if $v.Scope}}**{{$v.Scope}}:** {{end}}{{$v.Subject}} ({{$v.Hash}}) {{if $v.Metadata.issueid}}({{$v.Metadata.issueid}}){{end}}{{end}}{{end}}
|
||||
|
||||
{{if .Sections.fix}}## {{.Sections.fix.Name}}
|
||||
{{range $k,$v := .Sections.fix.Items}}
|
||||
- {{if $v.Scope}}**{{$v.Scope}}:** {{end}}{{$v.Subject}} ({{$v.Hash}}) {{if $v.Metadata.issueid}}({{$v.Metadata.issueid}}){{end}}{{end}}{{end}}
|
||||
|
||||
{{if .BreakingChanges}}## Breaking Changes
|
||||
{{range $k,$v := .BreakingChanges}}
|
||||
- {{$v}}{{end}}
|
||||
{{end}}`
|
||||
|
||||
// ReleaseNoteProcessor release note processor interface.
|
||||
type ReleaseNoteProcessor interface {
|
||||
Get(commits []GitCommitLog) ReleaseNote
|
||||
Format(releasenote ReleaseNote, version semver.Version) string
|
||||
}
|
||||
|
||||
// ReleaseNoteProcessorImpl release note based on commit log.
|
||||
type ReleaseNoteProcessorImpl struct {
|
||||
tags map[string]string
|
||||
template *template.Template
|
||||
}
|
||||
|
||||
// NewReleaseNoteProcessor ReleaseNoteProcessor constructor.
|
||||
func NewReleaseNoteProcessor(tags map[string]string) *ReleaseNoteProcessorImpl {
|
||||
template := template.Must(template.New("markdown").Parse(markdownTemplate))
|
||||
return &ReleaseNoteProcessorImpl{tags: tags, template: template}
|
||||
}
|
||||
|
||||
// Get generate a release note based on commits.
|
||||
func (p ReleaseNoteProcessorImpl) Get(commits []GitCommitLog) ReleaseNote {
|
||||
sections := make(map[string]ReleaseNoteSection)
|
||||
var breakingChanges []string
|
||||
for _, commit := range commits {
|
||||
if name, exists := p.tags[commit.Type]; exists {
|
||||
section, sexists := sections[commit.Type]
|
||||
if !sexists {
|
||||
section = ReleaseNoteSection{Name: name}
|
||||
}
|
||||
section.Items = append(section.Items, commit)
|
||||
sections[commit.Type] = section
|
||||
}
|
||||
if value, exists := commit.Metadata[BreakingChangeTag]; exists {
|
||||
breakingChanges = append(breakingChanges, value)
|
||||
}
|
||||
}
|
||||
|
||||
return ReleaseNote{Date: time.Now(), Sections: sections, BreakingChanges: breakingChanges}
|
||||
}
|
||||
|
||||
// Format format a release note.
|
||||
func (p ReleaseNoteProcessorImpl) Format(releasenote ReleaseNote, version semver.Version) string {
|
||||
templateVars := releaseNoteTemplate{
|
||||
Version: fmt.Sprintf("%d.%d.%d", version.Major(), version.Minor(), version.Patch()),
|
||||
Date: releasenote.Date.Format("2006-01-02"),
|
||||
Sections: releasenote.Sections,
|
||||
BreakingChanges: releasenote.BreakingChanges,
|
||||
}
|
||||
|
||||
var b bytes.Buffer
|
||||
p.template.Execute(&b, templateVars)
|
||||
return b.String()
|
||||
}
|
||||
|
||||
// ReleaseNote release note.
|
||||
type ReleaseNote struct {
|
||||
Date time.Time
|
||||
Sections map[string]ReleaseNoteSection
|
||||
BreakingChanges []string
|
||||
}
|
||||
|
||||
// ReleaseNoteSection release note section.
|
||||
type ReleaseNoteSection struct {
|
||||
Name string
|
||||
Items []GitCommitLog
|
||||
}
|
96
sv/semver.go
Normal file
96
sv/semver.go
Normal file
@ -0,0 +1,96 @@
|
||||
package sv
|
||||
|
||||
import "github.com/Masterminds/semver"
|
||||
|
||||
type versionType int
|
||||
|
||||
const (
|
||||
none versionType = iota
|
||||
patch
|
||||
minor
|
||||
major
|
||||
)
|
||||
|
||||
// ToVersion parse string to semver.Version
|
||||
func ToVersion(value string) (semver.Version, error) {
|
||||
version := value
|
||||
if version == "" {
|
||||
version = "0.0.0"
|
||||
}
|
||||
v, err := semver.NewVersion(version)
|
||||
return *v, err
|
||||
}
|
||||
|
||||
// BreakingChangeTag breaking change tag from commit metadata
|
||||
const BreakingChangeTag string = "breakingchange"
|
||||
|
||||
// SemVerCommitsProcessor interface
|
||||
type SemVerCommitsProcessor interface {
|
||||
NexVersion(version semver.Version, commits []GitCommitLog) semver.Version
|
||||
}
|
||||
|
||||
// SemVerCommitsProcessorImpl process versions using commit log
|
||||
type SemVerCommitsProcessorImpl struct {
|
||||
MajorVersionTypes map[string]struct{}
|
||||
MinorVersionTypes map[string]struct{}
|
||||
PatchVersionTypes map[string]struct{}
|
||||
IncludeUnknownTypeAsPatch bool
|
||||
}
|
||||
|
||||
// NewSemVerCommitsProcessor SemanticVersionCommitsProcessorImpl constructor
|
||||
func NewSemVerCommitsProcessor(unknownAsPatch bool, majorTypes, minorTypes, patchTypes []string) *SemVerCommitsProcessorImpl {
|
||||
return &SemVerCommitsProcessorImpl{
|
||||
IncludeUnknownTypeAsPatch: unknownAsPatch,
|
||||
MajorVersionTypes: toMap(majorTypes),
|
||||
MinorVersionTypes: toMap(minorTypes),
|
||||
PatchVersionTypes: toMap(patchTypes),
|
||||
}
|
||||
}
|
||||
|
||||
// NexVersion calculates next version based on commit log
|
||||
func (p SemVerCommitsProcessorImpl) NexVersion(version semver.Version, commits []GitCommitLog) semver.Version {
|
||||
var versionToUpdate = none
|
||||
for _, commit := range commits {
|
||||
if v := p.versionTypeToUpdate(commit); v > versionToUpdate {
|
||||
versionToUpdate = v
|
||||
}
|
||||
}
|
||||
|
||||
switch versionToUpdate {
|
||||
case major:
|
||||
return version.IncMajor()
|
||||
case minor:
|
||||
return version.IncMinor()
|
||||
case patch:
|
||||
return version.IncPatch()
|
||||
default:
|
||||
return version
|
||||
}
|
||||
}
|
||||
|
||||
func (p SemVerCommitsProcessorImpl) versionTypeToUpdate(commit GitCommitLog) versionType {
|
||||
if _, exists := commit.Metadata[BreakingChangeTag]; exists {
|
||||
return major
|
||||
}
|
||||
if _, exists := p.MajorVersionTypes[commit.Type]; exists {
|
||||
return major
|
||||
}
|
||||
if _, exists := p.MinorVersionTypes[commit.Type]; exists {
|
||||
return minor
|
||||
}
|
||||
if _, exists := p.PatchVersionTypes[commit.Type]; exists {
|
||||
return patch
|
||||
}
|
||||
if p.IncludeUnknownTypeAsPatch {
|
||||
return patch
|
||||
}
|
||||
return none
|
||||
}
|
||||
|
||||
func toMap(values []string) map[string]struct{} {
|
||||
result := make(map[string]struct{})
|
||||
for _, v := range values {
|
||||
result[v] = struct{}{}
|
||||
}
|
||||
return result
|
||||
}
|
Loading…
Reference in New Issue
Block a user