You've already forked go-semantic-release
Compare commits
15 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
47a54436f5 | ||
|
|
deed3a630e | ||
|
|
df058a927f | ||
|
|
5a58d039fb | ||
|
|
08ab3af547 | ||
|
|
7208daed1f | ||
|
|
a20992af14 | ||
|
|
dc4d1c581a | ||
|
|
81bdb68ee4 | ||
|
|
c485c3ee85 | ||
|
|
86c9512479 | ||
|
|
4574d00c28 | ||
|
|
0c4310d60b | ||
|
|
3a37a5e1db | ||
|
|
9594f39caa |
21
.github/workflows/main.yml
vendored
21
.github/workflows/main.yml
vendored
@@ -1,9 +1,9 @@
|
||||
name: Go
|
||||
on:
|
||||
pull_request:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
pull_request:
|
||||
jobs:
|
||||
build:
|
||||
strategy:
|
||||
@@ -21,11 +21,10 @@ jobs:
|
||||
- name: Check out code into the Go module directory
|
||||
uses: actions/checkout@v1
|
||||
|
||||
- name: Lint
|
||||
run: |
|
||||
export PATH=$PATH:$(go env GOPATH)/bin
|
||||
curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | sh -s -- -b $(go env GOPATH)/bin v1.35.2
|
||||
golangci-lint run ./...
|
||||
- name: golangci-lint
|
||||
uses: golangci/golangci-lint-action@v2
|
||||
with:
|
||||
version: v1.29
|
||||
|
||||
- name: Run tests
|
||||
run: go test ./...
|
||||
@@ -41,7 +40,7 @@ jobs:
|
||||
GOOS=windows GOARCH=amd64 CGO_ENABLED=0 go build -o build/go-semantic-release.windows_x86_64.exe -ldflags "-w -s -X main.version=`./build/go-semantic-release-temp next`" ./cmd/go-semantic-release/
|
||||
GOOS=darwin GOARCH=amd64 CGO_ENABLED=0 go build -o build/go-semantic-release.darwin_x86_64 -ldflags "-w -s -X main.version=`./build/go-semantic-release-temp next`" ./cmd/go-semantic-release/
|
||||
- name: Build Docker image
|
||||
if: matrix.go == '1.15'
|
||||
if: matrix.go == '1.15' && github.repository == 'Nightapes/go-semantic-release'
|
||||
run: |
|
||||
docker login -u nightapes -p ${{ secrets.DOCKER_PASSWORD }}
|
||||
docker login -u nightapes -p ${{ secrets.GITHUB_TOKEN }} docker.pkg.github.com
|
||||
@@ -66,6 +65,7 @@ jobs:
|
||||
name: build
|
||||
path: build
|
||||
- name: Release
|
||||
if: github.repository == 'Nightapes/go-semantic-release'
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
@@ -73,3 +73,10 @@ jobs:
|
||||
docker login -u nightapes -p ${{ secrets.DOCKER_PASSWORD }}
|
||||
docker login -u nightapes -p $GITHUB_TOKEN docker.pkg.github.com
|
||||
./build/go-semantic-release-temp release --loglevel trace
|
||||
- name: Release fork
|
||||
if: github.repository != 'Nightapes/go-semantic-release'
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
chmod -R +x build
|
||||
./build/go-semantic-release-temp release --loglevel trace
|
||||
|
||||
60
README.md
60
README.md
@@ -160,14 +160,58 @@ Hooks will run when calling `release`. Hooks run only if a release will be trigg
|
||||
|
||||
#### Changelog
|
||||
|
||||
Following variables can be used for templates:
|
||||
* `Commits` string
|
||||
* `Version` string
|
||||
* `Now` time.Time
|
||||
* `Backtick` string
|
||||
* `HasDocker` bool
|
||||
* `HasDockerLatest` bool
|
||||
* `DockerRepository` string
|
||||
Following variables and objects can be used for templates:
|
||||
|
||||
__Top level__
|
||||
|
||||
| Field | Type | Description |
|
||||
| -------- | ------ | ----- |
|
||||
| `Commits` | string | Fully rendered commit messages. This is left for backward compatibility. |
|
||||
| `CommitsContent` | commitsContent | Raw parsed commit data. Use this if you want to customize the output. |
|
||||
| `Version` | string | Next release version |
|
||||
| `Now` | time.Time | Current time of generating changelog |
|
||||
| `Backtick` | string | Backtick character |
|
||||
| `HasDocker` | bool | If a docker repository is set in the config. |
|
||||
| `HasDockerLatest` | bool | If `latest` image was uploaded |
|
||||
| `DockerRepository` | string | Docker repository |
|
||||
|
||||
__commitsContent__
|
||||
|
||||
| Field | Type | Description |
|
||||
| -------- | ------ | ----- |
|
||||
| `Commits` | map[string][]AnalyzedCommit | Commits grouped by commit type |
|
||||
| `BreakingChanges` | []AnalyzedCommit | Analyzed commit structure |
|
||||
| `Order` | []string | Ordered list of types |
|
||||
| `HasURL` | bool | If a URL is available for commits |
|
||||
| `URL` | string | URL for to the commit with {{hash}} suffix |
|
||||
|
||||
__AnalyzedCommit__
|
||||
|
||||
| Field | Type | Description |
|
||||
| -------- | ------ | ----- |
|
||||
| `Commit` | Commit | Original GIT commit |
|
||||
| `Tag` | string | Type of commit (e.g. feat, fix, ...) |
|
||||
| `TagString` | string | Full name of the type |
|
||||
| `Scope` | bool | Scope value from the commit |
|
||||
| `Subject` | string | URL for to the commit with {{hash}} suffix |
|
||||
| `MessageBlocks` | map[string][]MessageBlock | Different sections of a message (e.g. body, footer etc.) |
|
||||
| `IsBreaking` | bool | If this commit contains a breaking change |
|
||||
| `Print` | bool | Should this commit be included in Changelog output |
|
||||
|
||||
__Commit__
|
||||
|
||||
| Field | Type | Description |
|
||||
| -------- | ------ | ----- |
|
||||
| `Message` | string | Original git commit message |
|
||||
| `Author` | string | Name of the author |
|
||||
| `Hash` | string | Commit hash value "|
|
||||
|
||||
__MessageBlock__
|
||||
|
||||
| Field | Type | Description |
|
||||
| -------- | ------ | ----- |
|
||||
| `Label` | string | Label for a block (optional). This will usually be a token used in a footer |
|
||||
| `Content` | string | The parsed content of a block |
|
||||
|
||||
```yml
|
||||
changelog:
|
||||
|
||||
@@ -1,5 +1,26 @@
|
||||
{{ define "commitList" }}
|
||||
{{ range $index,$commit := .BreakingChanges -}}
|
||||
{{ if eq $index 0 -}}
|
||||
## BREAKING CHANGES
|
||||
{{ end -}}
|
||||
* {{ if $commit.Scope }}**{{$.Backtick}}{{$commit.Scope}}{{$.Backtick}}**{{ end }} {{$commit.ParsedBreakingChangeMessage}}
|
||||
introduced by commit:
|
||||
{{$commit.ParsedMessage}} {{if $.HasURL}} ([{{ printf "%.7s" $commit.Commit.Hash}}]({{ replace $.URL "{{hash}}" $commit.Commit.Hash}})){{end}}
|
||||
{{ end -}}
|
||||
{{ range $key := .Order -}}
|
||||
{{ $commits := index $.Commits $key -}}
|
||||
{{ if $commits -}}
|
||||
### {{ $key }}
|
||||
{{ range $index,$commit := $commits -}}
|
||||
* {{ if $commit.Scope }}**{{$.Backtick}}{{$commit.Scope}}{{$.Backtick}}** {{end}}{{$commit.ParsedMessage}}{{if $.HasURL}} ([{{ printf "%.7s" $commit.Commit.Hash}}]({{ replace $.URL "{{hash}}" $commit.Commit.Hash}})){{end}}
|
||||
{{ end -}}
|
||||
{{ end -}}
|
||||
{{ end -}}
|
||||
{{ end -}}
|
||||
|
||||
# My custom release template v{{$.Version}} ({{.Now.Format "2006-01-02"}})
|
||||
{{ .Commits -}}
|
||||
{{ template "commitList" .CommitsContent -}}
|
||||
|
||||
{{ if .HasDocker}}
|
||||
## Docker image
|
||||
|
||||
|
||||
@@ -2,17 +2,26 @@
|
||||
package analyzer
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/Nightapes/go-semantic-release/internal/shared"
|
||||
"github.com/Nightapes/go-semantic-release/pkg/config"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
const breakingChangeKeywords = "BREAKING CHANGE"
|
||||
const defaultBreakingChangePrefix = breakingChangeKeywords + ":"
|
||||
const footerTokenRegex = "^(?P<token>[^\\s][\\w\\- ]+[^\\s])<SEP>.*"
|
||||
var defaultTokenSeparators = [2]string{ ": ", " #"}
|
||||
|
||||
// Analyzer struct
|
||||
type Analyzer struct {
|
||||
analyzeCommits analyzeCommits
|
||||
Config config.ChangelogConfig
|
||||
ChangelogConfig config.ChangelogConfig
|
||||
AnalyzerConfig config.AnalyzerConfig
|
||||
}
|
||||
|
||||
// Rule for commits
|
||||
@@ -24,14 +33,15 @@ type Rule struct {
|
||||
}
|
||||
|
||||
type analyzeCommits interface {
|
||||
analyze(commit shared.Commit, tag Rule) (*shared.AnalyzedCommit, bool)
|
||||
analyze(commit shared.Commit, tag Rule) *shared.AnalyzedCommit
|
||||
getRules() []Rule
|
||||
}
|
||||
|
||||
// New Analyzer struct for given commit format
|
||||
func New(format string, config config.ChangelogConfig) (*Analyzer, error) {
|
||||
func New(format string, analyzerConfig config.AnalyzerConfig, chglogConfig config.ChangelogConfig) (*Analyzer, error) {
|
||||
analyzer := &Analyzer{
|
||||
Config: config,
|
||||
AnalyzerConfig: analyzerConfig,
|
||||
ChangelogConfig: chglogConfig,
|
||||
}
|
||||
|
||||
switch format {
|
||||
@@ -39,7 +49,7 @@ func New(format string, config config.ChangelogConfig) (*Analyzer, error) {
|
||||
analyzer.analyzeCommits = newAngular()
|
||||
log.Debugf("Commit format set to %s", ANGULAR)
|
||||
case CONVENTIONAL:
|
||||
analyzer.analyzeCommits = newConventional()
|
||||
analyzer.analyzeCommits = newConventional(analyzerConfig)
|
||||
log.Debugf("Commit format set to %s", CONVENTIONAL)
|
||||
default:
|
||||
return nil, fmt.Errorf("invalid commit format: %s", format)
|
||||
@@ -62,14 +72,14 @@ func (a *Analyzer) Analyze(commits []shared.Commit) map[shared.Release][]shared.
|
||||
|
||||
for _, commit := range commits {
|
||||
for _, rule := range a.analyzeCommits.getRules() {
|
||||
analyzedCommit, hasBreakingChange := a.analyzeCommits.analyze(commit, rule)
|
||||
analyzedCommit := a.analyzeCommits.analyze(commit, rule)
|
||||
if analyzedCommit == nil {
|
||||
continue
|
||||
}
|
||||
if a.Config.PrintAll || rule.Changelog {
|
||||
if a.ChangelogConfig.PrintAll || rule.Changelog {
|
||||
analyzedCommit.Print = true
|
||||
}
|
||||
if hasBreakingChange {
|
||||
if analyzedCommit.IsBreaking {
|
||||
analyzedCommits["major"] = append(analyzedCommits["major"], *analyzedCommit)
|
||||
break
|
||||
}
|
||||
@@ -80,3 +90,114 @@ func (a *Analyzer) Analyze(commits []shared.Commit) map[shared.Release][]shared.
|
||||
log.Debugf("Analyzed commits: major=%d minor=%d patch=%d none=%d", len(analyzedCommits["major"]), len(analyzedCommits["minor"]), len(analyzedCommits["patch"]), len(analyzedCommits["none"]))
|
||||
return analyzedCommits
|
||||
}
|
||||
|
||||
//
|
||||
// getRegexMatchedMap will match a regex with named groups and map the matching
|
||||
// results to corresponding group names
|
||||
//
|
||||
func getRegexMatchedMap(regEx, url string) (paramsMap map[string]string) {
|
||||
var compRegEx = regexp.MustCompile(regEx)
|
||||
match := compRegEx.FindStringSubmatch(url)
|
||||
|
||||
paramsMap = make(map[string]string)
|
||||
for i, name := range compRegEx.SubexpNames() {
|
||||
if i > 0 && i <= len(match) {
|
||||
paramsMap[name] = match[i]
|
||||
}
|
||||
}
|
||||
return paramsMap
|
||||
}
|
||||
|
||||
//
|
||||
// getMessageBlocksFromTexts converts strings to an array of MessageBlock
|
||||
//
|
||||
func getMessageBlocksFromTexts(txtArray, separators []string) []shared.MessageBlock {
|
||||
blocks := make([]shared.MessageBlock, len(txtArray))
|
||||
for i, line := range txtArray{
|
||||
blocks[i] = parseMessageBlock(line, separators)
|
||||
}
|
||||
return blocks
|
||||
}
|
||||
|
||||
//
|
||||
// parseMessageBlock parses a text in to MessageBlock
|
||||
//
|
||||
func parseMessageBlock(msg string, separators []string) shared.MessageBlock {
|
||||
msgBlock := shared.MessageBlock{
|
||||
Label: "",
|
||||
Content: msg,
|
||||
}
|
||||
if token, sep := findFooterToken(msg, separators); len(token) > 0{
|
||||
msgBlock.Label = token
|
||||
content := strings.Replace(msg, token + sep, "", 1)
|
||||
msgBlock.Content = strings.TrimSpace(content)
|
||||
}
|
||||
return msgBlock
|
||||
}
|
||||
|
||||
//
|
||||
// findFooterToken checks if given text has a token with one of the separators and returns a token
|
||||
//
|
||||
func findFooterToken(text string, separators []string) (token string, sep string) {
|
||||
for _, sep := range separators {
|
||||
regex := strings.Replace(footerTokenRegex, "<SEP>", sep, 1)
|
||||
matches := getRegexMatchedMap(regex, text)
|
||||
if token, ok := matches["token"]; ok {
|
||||
return token, sep
|
||||
}
|
||||
}
|
||||
return "", ""
|
||||
}
|
||||
|
||||
//
|
||||
// getDefaultMessageBlockMap parses a text block and splits in to different sections.
|
||||
// default logic to distinguish different parts is:
|
||||
// - Body starts right after the header (without beginning with a token)
|
||||
// - Body ends when a footer is discovered or text ends
|
||||
// - A footer is detected when it starts with a token ending with a separator
|
||||
// - A footer ends when another footer is found or text ends
|
||||
//
|
||||
func getDefaultMessageBlockMap(txtBlock string, tokenSep []string) map[string][]shared.MessageBlock{
|
||||
msgBlockMap := make(map[string][]shared.MessageBlock)
|
||||
footers := make([]string, 0)
|
||||
body, footerBlock, line := "", "", ""
|
||||
footerFound := false
|
||||
// Look through each line
|
||||
scanner := bufio.NewScanner(strings.NewReader(txtBlock))
|
||||
for scanner.Scan() {
|
||||
line = scanner.Text()
|
||||
if token, _ := findFooterToken(line, tokenSep); len(token) > 0 {
|
||||
// if footer was already found from before
|
||||
if len(footerBlock) > 0{
|
||||
footers = append(footers, strings.TrimSpace(footerBlock))
|
||||
}
|
||||
footerFound = true
|
||||
footerBlock = ""
|
||||
}
|
||||
|
||||
//'\n' is removed when reading from scanner
|
||||
if !footerFound {
|
||||
body += line + "\n"
|
||||
}else{
|
||||
footerBlock += line + "\n"
|
||||
}
|
||||
}
|
||||
if len(footerBlock) > 0 {
|
||||
footers = append(footers, strings.TrimSpace(footerBlock))
|
||||
}
|
||||
|
||||
body = strings.TrimSpace(body)
|
||||
if len(body) > 0{
|
||||
msgBlockMap["body"] = []shared.MessageBlock {{
|
||||
Label: "",
|
||||
Content: body,
|
||||
} }
|
||||
}
|
||||
|
||||
footerBlocks := getMessageBlocksFromTexts(footers, tokenSep)
|
||||
if len(footerBlocks) > 0 {
|
||||
msgBlockMap["footer"] = footerBlocks
|
||||
}
|
||||
|
||||
return msgBlockMap
|
||||
}
|
||||
@@ -10,7 +10,7 @@ import (
|
||||
|
||||
func TestAnalyzer(t *testing.T) {
|
||||
|
||||
_, err := analyzer.New("unknown", config.ChangelogConfig{})
|
||||
_, err := analyzer.New("unknown", config.AnalyzerConfig{}, config.ChangelogConfig{})
|
||||
assert.Error(t, err)
|
||||
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
package analyzer
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"github.com/Nightapes/go-semantic-release/pkg/config"
|
||||
"strings"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
@@ -14,14 +14,16 @@ type angular struct {
|
||||
rules []Rule
|
||||
regex string
|
||||
log *log.Entry
|
||||
config config.AnalyzerConfig
|
||||
}
|
||||
|
||||
// ANGULAR identifier
|
||||
const ANGULAR = "angular"
|
||||
var angularFooterTokenSep = defaultTokenSeparators
|
||||
|
||||
func newAngular() *angular {
|
||||
return &angular{
|
||||
regex: `^(TAG)(?:\((.*)\))?: (?s)(.*)`,
|
||||
regex: `^(?P<type>\w*)(?:\((?P<scope>.*)\))?: (?P<subject>.*)`,
|
||||
log: log.WithField("analyzer", ANGULAR),
|
||||
rules: []Rule{
|
||||
{
|
||||
@@ -86,37 +88,52 @@ func (a *angular) getRules() []Rule {
|
||||
return a.rules
|
||||
}
|
||||
|
||||
func (a *angular) analyze(commit shared.Commit, rule Rule) (*shared.AnalyzedCommit, bool) {
|
||||
re := regexp.MustCompile(strings.Replace(a.regex, "TAG", rule.Tag, -1))
|
||||
matches := re.FindStringSubmatch(commit.Message)
|
||||
if matches == nil {
|
||||
a.log.Tracef("%s does not match %s, skip", commit.Message, rule.Tag)
|
||||
return nil, false
|
||||
func (a *angular) analyze(commit shared.Commit, rule Rule) *shared.AnalyzedCommit {
|
||||
tokenSep := append(a.config.TokenSeparators, angularFooterTokenSep[:]...)
|
||||
|
||||
firstSplit := strings.SplitN(commit.Message, "\n", 2)
|
||||
header := firstSplit[0]
|
||||
body := ""
|
||||
if len(firstSplit) > 1 {
|
||||
body = firstSplit[1]
|
||||
}
|
||||
matches := getRegexMatchedMap(a.regex, header)
|
||||
|
||||
if len(matches) == 0 || matches["type"] != rule.Tag{
|
||||
a.log.Tracef("%s does not match %s, skip", commit.Message, rule.Tag)
|
||||
return nil
|
||||
}
|
||||
|
||||
msgBlockMap := getDefaultMessageBlockMap(body, tokenSep)
|
||||
|
||||
analyzed := &shared.AnalyzedCommit{
|
||||
Commit: commit,
|
||||
Tag: rule.Tag,
|
||||
TagString: rule.TagString,
|
||||
Scope: shared.Scope(matches[2]),
|
||||
Scope: shared.Scope(matches["scope"]),
|
||||
Subject: strings.TrimSpace(matches["subject"]),
|
||||
MessageBlocks: msgBlockMap,
|
||||
}
|
||||
|
||||
message := strings.Join(matches[3:], "")
|
||||
if !strings.Contains(message, "BREAKING CHANGE:") {
|
||||
analyzed.ParsedMessage = strings.Trim(message, " ")
|
||||
isBreaking := strings.Contains(commit.Message, defaultBreakingChangePrefix)
|
||||
analyzed.IsBreaking = isBreaking
|
||||
|
||||
oldFormatMessage := strings.TrimSpace(matches["subject"] + "\n" + body)
|
||||
|
||||
if !isBreaking {
|
||||
analyzed.ParsedMessage = strings.Trim(oldFormatMessage, " ")
|
||||
a.log.Tracef("%s: found %s", commit.Message, rule.Tag)
|
||||
return analyzed, false
|
||||
return analyzed
|
||||
}
|
||||
|
||||
a.log.Tracef(" %s, BREAKING CHANGE found", commit.Message)
|
||||
breakingChange := strings.SplitN(message, "BREAKING CHANGE:", 2)
|
||||
breakingChange := strings.SplitN(oldFormatMessage, defaultBreakingChangePrefix, 2)
|
||||
|
||||
if len(breakingChange) > 1 {
|
||||
analyzed.ParsedMessage = strings.TrimSpace(breakingChange[0])
|
||||
analyzed.ParsedBreakingChangeMessage = strings.TrimSpace(breakingChange[1])
|
||||
return analyzed, true
|
||||
}
|
||||
|
||||
} else {
|
||||
analyzed.ParsedBreakingChangeMessage = breakingChange[0]
|
||||
return analyzed, true
|
||||
}
|
||||
return analyzed
|
||||
}
|
||||
|
||||
@@ -31,6 +31,8 @@ func TestAngular(t *testing.T) {
|
||||
Tag: "feat",
|
||||
TagString: "Features",
|
||||
Print: true,
|
||||
Subject: "my first commit",
|
||||
MessageBlocks: map[string][]shared.MessageBlock{},
|
||||
},
|
||||
},
|
||||
"major": {},
|
||||
@@ -60,6 +62,8 @@ func TestAngular(t *testing.T) {
|
||||
Tag: "feat",
|
||||
TagString: "Features",
|
||||
Print: true,
|
||||
Subject: "my first commit",
|
||||
MessageBlocks: map[string][]shared.MessageBlock{},
|
||||
},
|
||||
},
|
||||
"major": {
|
||||
@@ -75,6 +79,9 @@ func TestAngular(t *testing.T) {
|
||||
TagString: "Features",
|
||||
Print: true,
|
||||
ParsedBreakingChangeMessage: "change api to v2",
|
||||
IsBreaking: true,
|
||||
Subject: "my first break BREAKING CHANGE: change api to v2",
|
||||
MessageBlocks: map[string][]shared.MessageBlock{},
|
||||
},
|
||||
},
|
||||
"patch": {},
|
||||
@@ -120,6 +127,8 @@ func TestAngular(t *testing.T) {
|
||||
Tag: "feat",
|
||||
TagString: "Features",
|
||||
Print: true,
|
||||
Subject: "my first commit",
|
||||
MessageBlocks: map[string][]shared.MessageBlock{},
|
||||
},
|
||||
},
|
||||
"major": {
|
||||
@@ -135,6 +144,16 @@ func TestAngular(t *testing.T) {
|
||||
TagString: "Features",
|
||||
Print: true,
|
||||
ParsedBreakingChangeMessage: "change api to v2",
|
||||
IsBreaking: true,
|
||||
Subject: "my first break",
|
||||
MessageBlocks: map[string][]shared.MessageBlock{
|
||||
"footer": {
|
||||
shared.MessageBlock{
|
||||
Label: "BREAKING CHANGE",
|
||||
Content: "change api to v2",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"patch": {},
|
||||
@@ -177,6 +196,8 @@ func TestAngular(t *testing.T) {
|
||||
Tag: "feat",
|
||||
TagString: "Features",
|
||||
Print: true,
|
||||
Subject: "my first commit",
|
||||
MessageBlocks: map[string][]shared.MessageBlock{},
|
||||
},
|
||||
},
|
||||
"none": {
|
||||
@@ -192,6 +213,8 @@ func TestAngular(t *testing.T) {
|
||||
TagString: "Changes to CI/CD",
|
||||
Print: false,
|
||||
ParsedBreakingChangeMessage: "",
|
||||
Subject: "my first build",
|
||||
MessageBlocks: map[string][]shared.MessageBlock{},
|
||||
},
|
||||
},
|
||||
"patch": {},
|
||||
@@ -212,7 +235,7 @@ func TestAngular(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
angular, err := analyzer.New("angular", config.ChangelogConfig{})
|
||||
angular, err := analyzer.New("angular", config.AnalyzerConfig{}, config.ChangelogConfig{})
|
||||
assert.NoError(t, err)
|
||||
|
||||
for _, test := range testConfigs {
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
package analyzer
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"github.com/Nightapes/go-semantic-release/pkg/config"
|
||||
"strings"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
@@ -14,14 +14,17 @@ type conventional struct {
|
||||
rules []Rule
|
||||
regex string
|
||||
log *log.Entry
|
||||
config config.AnalyzerConfig
|
||||
}
|
||||
|
||||
// CONVENTIONAL identifier
|
||||
const CONVENTIONAL = "conventional"
|
||||
var conventionalFooterTokenSep = defaultTokenSeparators
|
||||
|
||||
func newConventional() *conventional {
|
||||
func newConventional(config config.AnalyzerConfig) *conventional {
|
||||
return &conventional{
|
||||
regex: `^(TAG)(?:\((.*)\))?(\!)?: (?s)(.*)`,
|
||||
config: config,
|
||||
regex: `^(?P<type>\w*)(?:\((?P<scope>.*)\))?(?P<breaking>\!)?: (?P<subject>.*)`,
|
||||
log: log.WithField("analyzer", CONVENTIONAL),
|
||||
rules: []Rule{
|
||||
{
|
||||
@@ -86,37 +89,55 @@ func (a *conventional) getRules() []Rule {
|
||||
return a.rules
|
||||
}
|
||||
|
||||
func (a *conventional) analyze(commit shared.Commit, rule Rule) (*shared.AnalyzedCommit, bool) {
|
||||
re := regexp.MustCompile(strings.Replace(a.regex, "TAG", rule.Tag, -1))
|
||||
matches := re.FindStringSubmatch(commit.Message)
|
||||
if matches == nil {
|
||||
a.log.Tracef("%s does not match %s, skip", commit.Message, rule.Tag)
|
||||
return nil, false
|
||||
func (a *conventional) analyze(commit shared.Commit, rule Rule) *shared.AnalyzedCommit {
|
||||
tokenSep := append(a.config.TokenSeparators, conventionalFooterTokenSep[:]...)
|
||||
|
||||
firstSplit := strings.SplitN(commit.Message, "\n", 2)
|
||||
header := firstSplit[0]
|
||||
body := ""
|
||||
if len(firstSplit) > 1 {
|
||||
body = firstSplit[1]
|
||||
}
|
||||
|
||||
matches := getRegexMatchedMap(a.regex, header)
|
||||
|
||||
if len(matches) == 0 || matches["type"] != rule.Tag{
|
||||
a.log.Tracef("%s does not match %s, skip", commit.Message, rule.Tag)
|
||||
return nil
|
||||
}
|
||||
|
||||
msgBlockMap := getDefaultMessageBlockMap(body, tokenSep)
|
||||
|
||||
analyzed := &shared.AnalyzedCommit{
|
||||
Commit: commit,
|
||||
Tag: rule.Tag,
|
||||
TagString: rule.TagString,
|
||||
Scope: shared.Scope(matches[2]),
|
||||
Scope: shared.Scope(matches["scope"]),
|
||||
Subject: strings.TrimSpace(matches["subject"]),
|
||||
MessageBlocks: msgBlockMap,
|
||||
}
|
||||
|
||||
message := strings.Join(matches[4:], "")
|
||||
if matches[3] == "" && !strings.Contains(message, "BREAKING CHANGE:") {
|
||||
analyzed.ParsedMessage = strings.Trim(message, " ")
|
||||
isBreaking := matches["breaking"] == "!" || strings.Contains(commit.Message, defaultBreakingChangePrefix)
|
||||
analyzed.IsBreaking = isBreaking
|
||||
|
||||
oldFormatMessage := strings.TrimSpace(matches["subject"] + "\n" + body)
|
||||
|
||||
if !isBreaking {
|
||||
analyzed.ParsedMessage = strings.Trim(oldFormatMessage, " ")
|
||||
a.log.Tracef("%s: found %s", commit.Message, rule.Tag)
|
||||
return analyzed, false
|
||||
return analyzed
|
||||
}
|
||||
|
||||
a.log.Infof(" %s, BREAKING CHANGE found", commit.Message)
|
||||
breakingChange := strings.SplitN(message, "BREAKING CHANGE:", 2)
|
||||
breakingChange := strings.SplitN(oldFormatMessage, defaultBreakingChangePrefix, 2)
|
||||
|
||||
if len(breakingChange) > 1 {
|
||||
analyzed.ParsedMessage = strings.TrimSpace(breakingChange[0])
|
||||
analyzed.ParsedBreakingChangeMessage = strings.TrimSpace(breakingChange[1])
|
||||
return analyzed, true
|
||||
} else {
|
||||
analyzed.ParsedBreakingChangeMessage = breakingChange[0]
|
||||
}
|
||||
|
||||
analyzed.ParsedBreakingChangeMessage = breakingChange[0]
|
||||
return analyzed, true
|
||||
return analyzed
|
||||
}
|
||||
|
||||
|
||||
@@ -30,6 +30,8 @@ func TestConventional(t *testing.T) {
|
||||
ParsedMessage: "my first commit",
|
||||
Tag: "feat",
|
||||
TagString: "Features",
|
||||
Subject: "my first commit",
|
||||
MessageBlocks: map[string][]shared.MessageBlock{},
|
||||
Print: true,
|
||||
},
|
||||
{
|
||||
@@ -42,6 +44,8 @@ func TestConventional(t *testing.T) {
|
||||
ParsedMessage: "no scope",
|
||||
Tag: "feat",
|
||||
TagString: "Features",
|
||||
Subject: "no scope",
|
||||
MessageBlocks: map[string][]shared.MessageBlock{},
|
||||
Print: true,
|
||||
},
|
||||
},
|
||||
@@ -77,6 +81,8 @@ func TestConventional(t *testing.T) {
|
||||
Tag: "feat",
|
||||
TagString: "Features",
|
||||
Print: true,
|
||||
Subject: "my first commit",
|
||||
MessageBlocks: map[string][]shared.MessageBlock{},
|
||||
},
|
||||
},
|
||||
"major": {
|
||||
@@ -92,6 +98,9 @@ func TestConventional(t *testing.T) {
|
||||
TagString: "Features",
|
||||
Print: true,
|
||||
ParsedBreakingChangeMessage: "my first break",
|
||||
IsBreaking: true,
|
||||
Subject: "my first break",
|
||||
MessageBlocks: map[string][]shared.MessageBlock{},
|
||||
},
|
||||
},
|
||||
"patch": {},
|
||||
@@ -125,6 +134,8 @@ func TestConventional(t *testing.T) {
|
||||
Tag: "feat",
|
||||
TagString: "Features",
|
||||
Print: true,
|
||||
Subject: "my first commit",
|
||||
MessageBlocks: map[string][]shared.MessageBlock{},
|
||||
},
|
||||
},
|
||||
"major": {
|
||||
@@ -140,6 +151,15 @@ func TestConventional(t *testing.T) {
|
||||
TagString: "Features",
|
||||
Print: true,
|
||||
ParsedBreakingChangeMessage: "change api to v2",
|
||||
IsBreaking: true,
|
||||
Subject: "my first break",
|
||||
MessageBlocks: map[string][]shared.MessageBlock{
|
||||
"footer" : { shared.MessageBlock{
|
||||
Label: "BREAKING CHANGE",
|
||||
Content: "change api to v2",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Commit: shared.Commit{
|
||||
@@ -153,6 +173,15 @@ func TestConventional(t *testing.T) {
|
||||
TagString: "Features",
|
||||
Print: true,
|
||||
ParsedBreakingChangeMessage: "hey from the change",
|
||||
IsBreaking: true,
|
||||
Subject: "my first break",
|
||||
MessageBlocks: map[string][]shared.MessageBlock{
|
||||
"footer" : {shared.MessageBlock{
|
||||
Label: "BREAKING CHANGE",
|
||||
Content: "hey from the change",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"patch": {},
|
||||
@@ -212,6 +241,8 @@ func TestConventional(t *testing.T) {
|
||||
Tag: "feat",
|
||||
TagString: "Features",
|
||||
Print: true,
|
||||
Subject: "my first commit",
|
||||
MessageBlocks: map[string][]shared.MessageBlock{},
|
||||
},
|
||||
},
|
||||
"none": {
|
||||
@@ -227,6 +258,8 @@ func TestConventional(t *testing.T) {
|
||||
TagString: "Changes to CI/CD",
|
||||
Print: false,
|
||||
ParsedBreakingChangeMessage: "",
|
||||
Subject: "my first build",
|
||||
MessageBlocks: map[string][]shared.MessageBlock{},
|
||||
},
|
||||
},
|
||||
"patch": {},
|
||||
@@ -262,6 +295,8 @@ func TestConventional(t *testing.T) {
|
||||
TagString: "Changes to CI/CD",
|
||||
Print: false,
|
||||
ParsedBreakingChangeMessage: "",
|
||||
Subject: "my first build",
|
||||
MessageBlocks: map[string][]shared.MessageBlock{},
|
||||
},
|
||||
},
|
||||
"patch": {{
|
||||
@@ -275,6 +310,8 @@ func TestConventional(t *testing.T) {
|
||||
Tag: "fix",
|
||||
TagString: "Bug fixes",
|
||||
Print: true,
|
||||
Subject: "my first commit",
|
||||
MessageBlocks: map[string][]shared.MessageBlock{},
|
||||
}},
|
||||
"major": {},
|
||||
},
|
||||
@@ -293,7 +330,7 @@ func TestConventional(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
conventional, err := analyzer.New("conventional", config.ChangelogConfig{})
|
||||
conventional, err := analyzer.New("conventional", config.AnalyzerConfig{}, config.ChangelogConfig{})
|
||||
assert.NoError(t, err)
|
||||
|
||||
for _, test := range testConfigs {
|
||||
@@ -304,3 +341,156 @@ func TestConventional(t *testing.T) {
|
||||
assert.Equalf(t, test.wantAnalyzedCommits["none"], analyzedCommits["none"], "Testcase %s should have none commits", test.testCase)
|
||||
}
|
||||
}
|
||||
|
||||
func TestConventional_BodyAndFooters(t *testing.T) {
|
||||
t.Parallel()
|
||||
testConfigs := []struct {
|
||||
testCase string
|
||||
commits []shared.Commit
|
||||
expectedAnalyzedCommits map[shared.Release][]shared.AnalyzedCommit
|
||||
}{
|
||||
{
|
||||
testCase: "Only body, no footer",
|
||||
commits: []shared.Commit{
|
||||
{
|
||||
Message: "fix: squash bug for logging\n\nNow the logs will not print lines twice. The following changed:\n\n-Buffer -Stdout",
|
||||
Author: "me",
|
||||
Hash: "12345667",
|
||||
},
|
||||
},
|
||||
expectedAnalyzedCommits: map[shared.Release][]shared.AnalyzedCommit{
|
||||
"patch": {
|
||||
{
|
||||
Commit: shared.Commit{
|
||||
Message: "fix: squash bug for logging\n\nNow the logs will not print lines twice. The following changed:\n\n-Buffer -Stdout",
|
||||
Author: "me",
|
||||
Hash: "12345667",
|
||||
},
|
||||
Scope: "",
|
||||
ParsedMessage: "squash bug for logging\n\nNow the logs will not print lines twice. The following changed:\n\n-Buffer -Stdout",
|
||||
Tag: "fix",
|
||||
TagString: "Bug fixes",
|
||||
Print: true,
|
||||
Subject: "squash bug for logging",
|
||||
MessageBlocks: map[string][]shared.MessageBlock{
|
||||
"body": {
|
||||
shared.MessageBlock{
|
||||
Label: "",
|
||||
Content: "Now the logs will not print lines twice. The following changed:\n\n-Buffer -Stdout",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"major": {},
|
||||
"minor": {},
|
||||
"none": {},
|
||||
},
|
||||
},
|
||||
{
|
||||
testCase: "Only footers, no body",
|
||||
commits: []shared.Commit{
|
||||
{
|
||||
Message: "fix: squash bug for logging\n\nNote: now the logs will not print lines twice.\n\nIssue: #123\nSeverity: medium",
|
||||
Author: "me",
|
||||
Hash: "12345667",
|
||||
},
|
||||
},
|
||||
expectedAnalyzedCommits: map[shared.Release][]shared.AnalyzedCommit{
|
||||
"patch": {
|
||||
{
|
||||
Commit: shared.Commit{
|
||||
Message: "fix: squash bug for logging\n\nNote: now the logs will not print lines twice.\n\nIssue: #123\nSeverity: medium",
|
||||
Author: "me",
|
||||
Hash: "12345667",
|
||||
},
|
||||
Scope: "",
|
||||
ParsedMessage: "squash bug for logging\n\nNote: now the logs will not print lines twice.\n\nIssue: #123\nSeverity: medium",
|
||||
Tag: "fix",
|
||||
TagString: "Bug fixes",
|
||||
Print: true,
|
||||
Subject: "squash bug for logging",
|
||||
MessageBlocks: map[string][]shared.MessageBlock{
|
||||
"footer": {
|
||||
shared.MessageBlock{
|
||||
Label: "Note",
|
||||
Content: "now the logs will not print lines twice.",
|
||||
},
|
||||
shared.MessageBlock{
|
||||
Label: "Issue",
|
||||
Content: "#123",
|
||||
},
|
||||
shared.MessageBlock{
|
||||
Label: "Severity",
|
||||
Content: "medium",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"major": {},
|
||||
"minor": {},
|
||||
"none": {},
|
||||
},
|
||||
},
|
||||
{
|
||||
testCase: "Body and footers",
|
||||
commits: []shared.Commit{
|
||||
{
|
||||
Message: "fix: squash bug for logging\n\nNow the logs will not print lines twice. The following changed:\n\n-Buffer -Stdout\n\nIssue: #123\nSeverity: medium",
|
||||
Author: "me",
|
||||
Hash: "12345667",
|
||||
},
|
||||
},
|
||||
expectedAnalyzedCommits: map[shared.Release][]shared.AnalyzedCommit{
|
||||
"patch": {
|
||||
{
|
||||
Commit: shared.Commit{
|
||||
Message: "fix: squash bug for logging\n\nNow the logs will not print lines twice. The following changed:\n\n-Buffer -Stdout\n\nIssue: #123\nSeverity: medium",
|
||||
Author: "me",
|
||||
Hash: "12345667",
|
||||
},
|
||||
Scope: "",
|
||||
ParsedMessage: "squash bug for logging\n\nNow the logs will not print lines twice. The following changed:\n\n-Buffer -Stdout\n\nIssue: #123\nSeverity: medium",
|
||||
Tag: "fix",
|
||||
TagString: "Bug fixes",
|
||||
Print: true,
|
||||
Subject: "squash bug for logging",
|
||||
MessageBlocks: map[string][]shared.MessageBlock{
|
||||
"body": {
|
||||
shared.MessageBlock{
|
||||
Label: "",
|
||||
Content: "Now the logs will not print lines twice. The following changed:\n\n-Buffer -Stdout",
|
||||
},
|
||||
},
|
||||
"footer": {
|
||||
shared.MessageBlock{
|
||||
Label: "Issue",
|
||||
Content: "#123",
|
||||
},
|
||||
shared.MessageBlock{
|
||||
Label: "Severity",
|
||||
Content: "medium",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"major": {},
|
||||
"minor": {},
|
||||
"none": {},
|
||||
},
|
||||
},
|
||||
|
||||
}
|
||||
|
||||
conventional, err := analyzer.New("conventional", config.AnalyzerConfig{}, config.ChangelogConfig{})
|
||||
assert.NoError(t, err)
|
||||
for _, test := range testConfigs {
|
||||
analyzedCommits := conventional.Analyze(test.commits)
|
||||
assert.Equalf(t, test.expectedAnalyzedCommits["major"], analyzedCommits["major"], "Testcase %s should have major commits", test.testCase)
|
||||
assert.Equalf(t, test.expectedAnalyzedCommits["minor"], analyzedCommits["minor"], "Testcase %s should have minor commits", test.testCase)
|
||||
assert.Equalf(t, test.expectedAnalyzedCommits["patch"], analyzedCommits["patch"], "Testcase %s should have patch commits", test.testCase)
|
||||
assert.Equalf(t, test.expectedAnalyzedCommits["none"], analyzedCommits["none"], "Testcase %s should have none commits", test.testCase)
|
||||
}
|
||||
}
|
||||
|
||||
2
internal/cache/cache_test.go
vendored
2
internal/cache/cache_test.go
vendored
@@ -69,6 +69,8 @@ func TestWriteAndReadCache(t *testing.T) {
|
||||
Tag: "feat",
|
||||
TagString: "Features",
|
||||
Print: true,
|
||||
Subject: "add gitlab as release option",
|
||||
MessageBlocks: map[string][]shared.MessageBlock{},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package changelog
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"io/ioutil"
|
||||
"strings"
|
||||
@@ -31,9 +32,11 @@ introduced by commit:
|
||||
{{ end -}}
|
||||
{{ end -}}
|
||||
{{ end -}}`
|
||||
const defaultCommitListSubTemplate string = `{{ define "commitList" }}` + defaultCommitList + "{{ end }}"
|
||||
const defaultChangelogTitle string = `v{{.Version}} ({{.Now.Format "2006-01-02"}})`
|
||||
const defaultChangelog string = `# v{{$.Version}} ({{.Now.Format "2006-01-02"}})
|
||||
{{ .Commits -}}
|
||||
{{ template "commitList" .CommitsContent -}}
|
||||
|
||||
{{ if .HasDocker}}
|
||||
## Docker image
|
||||
|
||||
@@ -52,6 +55,7 @@ or
|
||||
|
||||
type changelogContent struct {
|
||||
Commits string
|
||||
CommitsContent commitsContent
|
||||
Version string
|
||||
Now time.Time
|
||||
Backtick string
|
||||
@@ -64,8 +68,6 @@ type commitsContent struct {
|
||||
Commits map[string][]shared.AnalyzedCommit
|
||||
BreakingChanges []shared.AnalyzedCommit
|
||||
Order []string
|
||||
Version string
|
||||
Now time.Time
|
||||
Backtick string
|
||||
HasURL bool
|
||||
URL string
|
||||
@@ -89,11 +91,11 @@ func New(config *config.ReleaseConfig, rules []analyzer.Rule, releaseTime time.T
|
||||
}
|
||||
}
|
||||
|
||||
// GenerateChanglog from given commits
|
||||
func (c *Changelog) GenerateChanglog(templateConfig shared.ChangelogTemplateConfig, analyzedCommits map[shared.Release][]shared.AnalyzedCommit) (*shared.GeneratedChangelog, error) {
|
||||
// GenerateChangelog from given commits
|
||||
func (c *Changelog) GenerateChangelog(templateConfig shared.ChangelogTemplateConfig, analyzedCommits map[shared.Release][]shared.AnalyzedCommit) (*shared.GeneratedChangelog, error) {
|
||||
|
||||
commitsPerScope := map[string][]shared.AnalyzedCommit{}
|
||||
commitsBreakingChange := []shared.AnalyzedCommit{}
|
||||
var commitsBreakingChange []shared.AnalyzedCommit
|
||||
order := make([]string, 0)
|
||||
|
||||
for _, rule := range c.rules {
|
||||
@@ -106,7 +108,7 @@ func (c *Changelog) GenerateChanglog(templateConfig shared.ChangelogTemplateConf
|
||||
for _, commits := range analyzedCommits {
|
||||
for _, commit := range commits {
|
||||
if commit.Print {
|
||||
if commit.ParsedBreakingChangeMessage != "" {
|
||||
if commit.IsBreaking {
|
||||
commitsBreakingChange = append(commitsBreakingChange, commit)
|
||||
continue
|
||||
}
|
||||
@@ -119,9 +121,7 @@ func (c *Changelog) GenerateChanglog(templateConfig shared.ChangelogTemplateConf
|
||||
}
|
||||
|
||||
commitsContent := commitsContent{
|
||||
Version: templateConfig.Version,
|
||||
Commits: commitsPerScope,
|
||||
Now: c.releaseTime,
|
||||
BreakingChanges: commitsBreakingChange,
|
||||
Backtick: "`",
|
||||
Order: order,
|
||||
@@ -130,6 +130,7 @@ func (c *Changelog) GenerateChanglog(templateConfig shared.ChangelogTemplateConf
|
||||
}
|
||||
|
||||
changelogContent := changelogContent{
|
||||
CommitsContent: commitsContent,
|
||||
Version: templateConfig.Version,
|
||||
Now: c.releaseTime,
|
||||
Backtick: "`",
|
||||
@@ -137,13 +138,14 @@ func (c *Changelog) GenerateChanglog(templateConfig shared.ChangelogTemplateConf
|
||||
HasDockerLatest: c.config.Changelog.Docker.Latest,
|
||||
DockerRepository: c.config.Changelog.Docker.Repository,
|
||||
}
|
||||
template := defaultChangelog
|
||||
|
||||
chglogTemplate := defaultCommitListSubTemplate + defaultChangelog
|
||||
if c.config.Changelog.TemplatePath != "" {
|
||||
content, err := ioutil.ReadFile(c.config.Changelog.TemplatePath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
template = string(content)
|
||||
chglogTemplate = string(content)
|
||||
}
|
||||
|
||||
templateTitle := defaultChangelogTitle
|
||||
@@ -152,30 +154,41 @@ func (c *Changelog) GenerateChanglog(templateConfig shared.ChangelogTemplateConf
|
||||
}
|
||||
|
||||
log.Debugf("Render title")
|
||||
renderedTitle, err := generateTemplate(templateTitle, changelogContent)
|
||||
renderedTitle, err := generateTemplate(templateTitle, changelogContent, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
log.Debugf("Render commits")
|
||||
renderedCommitList, err := generateTemplate(defaultCommitList, commitsContent)
|
||||
renderedCommitList, err := generateTemplate(defaultCommitList, commitsContent, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
log.Tracef("Commits %s", renderedCommitList)
|
||||
|
||||
changelogContent.Commits = renderedCommitList
|
||||
|
||||
extraFuncMap := template.FuncMap {
|
||||
"commitUrl": func() string {return templateConfig.CommitURL},
|
||||
}
|
||||
log.Debugf("Render changelog")
|
||||
renderedContent, err := generateTemplate(template, changelogContent)
|
||||
renderedContent, err := generateTemplate(chglogTemplate, changelogContent, extraFuncMap)
|
||||
|
||||
return &shared.GeneratedChangelog{Title: renderedTitle, Content: renderedContent}, err
|
||||
}
|
||||
|
||||
func generateTemplate(text string, values interface{}) (string, error) {
|
||||
func generateTemplate(text string, values interface{}, extraFuncMap template.FuncMap) (string, error) {
|
||||
|
||||
funcMap := template.FuncMap{
|
||||
"replace": replace,
|
||||
"lower": lower,
|
||||
"upper": upper,
|
||||
"capitalize": capitalize,
|
||||
"addPrefixToLines": addPrefixToLines,
|
||||
}
|
||||
|
||||
for k, v := range extraFuncMap {
|
||||
funcMap[k] = v
|
||||
}
|
||||
|
||||
var tpl bytes.Buffer
|
||||
@@ -193,3 +206,30 @@ func generateTemplate(text string, values interface{}) (string, error) {
|
||||
func replace(input, from, to string) string {
|
||||
return strings.Replace(input, from, to, -1)
|
||||
}
|
||||
|
||||
func lower(input string) string {
|
||||
return strings.ToLower(input)
|
||||
}
|
||||
|
||||
func upper(input string) string {
|
||||
return strings.ToUpper(input)
|
||||
}
|
||||
|
||||
func capitalize(input string) string {
|
||||
if len(input) > 0 {
|
||||
return strings.ToUpper(string(input[0])) + input[1:]
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// Adds a prefix to each line of the given text block
|
||||
// this can be helpful in rendering correct indentation or bullets for multi-line texts
|
||||
func addPrefixToLines(input, prefix string) string {
|
||||
output := ""
|
||||
scanner := bufio.NewScanner(strings.NewReader(input))
|
||||
for scanner.Scan() {
|
||||
output += prefix + scanner.Text() + "\n"
|
||||
}
|
||||
output = strings.TrimRight(output, "\n")
|
||||
return output
|
||||
}
|
||||
|
||||
@@ -41,6 +41,8 @@ func TestChangelog(t *testing.T) {
|
||||
Tag: "feat",
|
||||
TagString: "Features",
|
||||
Print: true,
|
||||
Subject: "my first commit",
|
||||
MessageBlocks: map[string][]shared.MessageBlock{},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -64,6 +66,8 @@ func TestChangelog(t *testing.T) {
|
||||
Tag: "feat",
|
||||
TagString: "Features",
|
||||
Print: true,
|
||||
Subject: "my first commit",
|
||||
MessageBlocks: map[string][]shared.MessageBlock{},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -88,6 +92,8 @@ func TestChangelog(t *testing.T) {
|
||||
Tag: "feat",
|
||||
TagString: "Features",
|
||||
Print: true,
|
||||
Subject: "my first commit",
|
||||
MessageBlocks: map[string][]shared.MessageBlock{},
|
||||
},
|
||||
{
|
||||
Commit: shared.Commit{
|
||||
@@ -101,6 +107,15 @@ func TestChangelog(t *testing.T) {
|
||||
TagString: "Features",
|
||||
Print: true,
|
||||
ParsedBreakingChangeMessage: "change api to v2",
|
||||
IsBreaking: true,
|
||||
Subject: "my first break",
|
||||
MessageBlocks: map[string][]shared.MessageBlock{
|
||||
"body" : { shared.MessageBlock{
|
||||
Label: "BREAKING CHANGE",
|
||||
Content: "change api to v2",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -126,6 +141,15 @@ func TestChangelog(t *testing.T) {
|
||||
TagString: "Features",
|
||||
Print: true,
|
||||
ParsedBreakingChangeMessage: "hey from the change",
|
||||
IsBreaking: true,
|
||||
Subject: "my first break",
|
||||
MessageBlocks: map[string][]shared.MessageBlock{
|
||||
"body" : { shared.MessageBlock{
|
||||
Label: "BREAKING CHANGE",
|
||||
Content: "hey from the change",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Commit: shared.Commit{
|
||||
@@ -138,6 +162,8 @@ func TestChangelog(t *testing.T) {
|
||||
Tag: "feat",
|
||||
TagString: "Features",
|
||||
Print: true,
|
||||
Subject: "my first commit",
|
||||
MessageBlocks: map[string][]shared.MessageBlock{},
|
||||
},
|
||||
{
|
||||
Commit: shared.Commit{
|
||||
@@ -150,10 +176,12 @@ func TestChangelog(t *testing.T) {
|
||||
Tag: "feat",
|
||||
TagString: "Features",
|
||||
Print: true,
|
||||
Subject: "my second commit",
|
||||
MessageBlocks: map[string][]shared.MessageBlock{},
|
||||
},
|
||||
{
|
||||
Commit: shared.Commit{
|
||||
Message: "feat: my new commit \n\nmy first break: BREAKING CHANGE: change api to v2",
|
||||
Message: "feat: my new commit \n\nBREAKING CHANGE: change api to v2",
|
||||
Author: "me",
|
||||
Hash: "12345668",
|
||||
},
|
||||
@@ -163,6 +191,14 @@ func TestChangelog(t *testing.T) {
|
||||
TagString: "Features",
|
||||
Print: true,
|
||||
ParsedBreakingChangeMessage: "change api to v2",
|
||||
IsBreaking: true,
|
||||
Subject: "my new commit",
|
||||
MessageBlocks: map[string][]shared.MessageBlock{
|
||||
"body": { shared.MessageBlock{
|
||||
Label: "BREAKING CHANGE",
|
||||
Content: "change api to v2",
|
||||
}},
|
||||
},
|
||||
},
|
||||
{
|
||||
Commit: shared.Commit{
|
||||
@@ -176,6 +212,9 @@ func TestChangelog(t *testing.T) {
|
||||
TagString: "Features",
|
||||
Print: true,
|
||||
ParsedBreakingChangeMessage: "my next commit",
|
||||
IsBreaking: true,
|
||||
Subject: "my next commit",
|
||||
MessageBlocks: map[string][]shared.MessageBlock{},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -210,7 +249,7 @@ func TestChangelog(t *testing.T) {
|
||||
|
||||
for _, config := range testConfigs {
|
||||
t.Run(config.testCase, func(t *testing.T) {
|
||||
generatedChangelog, err := cl.GenerateChanglog(templateConfig, config.analyzedCommits)
|
||||
generatedChangelog, err := cl.GenerateChangelog(templateConfig, config.analyzedCommits)
|
||||
assert.Equalf(t, config.hasError, err != nil, "Testcase %s should have error: %t -> %s", config.testCase, config.hasError, err)
|
||||
assert.Equalf(t, config.result, generatedChangelog, "Testcase %s should have generated changelog", config.testCase)
|
||||
})
|
||||
|
||||
@@ -37,13 +37,22 @@ type ChangelogTemplateConfig struct {
|
||||
type AnalyzedCommit struct {
|
||||
Commit Commit `yaml:"commit"`
|
||||
ParsedMessage string `yaml:"parsedMessage"`
|
||||
Scope Scope `yaml:"scope"`
|
||||
ParsedBreakingChangeMessage string `yaml:"parsedBreakingChangeMessage"`
|
||||
Tag string `yaml:"tag"`
|
||||
TagString string `yaml:"tagString"`
|
||||
Scope Scope `yaml:"scope"`
|
||||
Subject string `yaml:"subject"`
|
||||
MessageBlocks map[string][]MessageBlock `yaml:"messageBlocks"`
|
||||
IsBreaking bool `yaml:"isBreaking"`
|
||||
Print bool `yaml:"print"`
|
||||
}
|
||||
|
||||
// MessageBlock represents a block in the body section of a commit message
|
||||
type MessageBlock struct {
|
||||
Label string `yaml:"label"`
|
||||
Content string `yaml:"content"`
|
||||
}
|
||||
|
||||
//Scope of the commit, like feat, fix,..
|
||||
type Scope string
|
||||
|
||||
|
||||
@@ -13,6 +13,11 @@ const (
|
||||
DefaultTagPrefix = "v"
|
||||
)
|
||||
|
||||
// AnalyzerConfig struct
|
||||
type AnalyzerConfig struct {
|
||||
TokenSeparators []string `yaml:"tokenSeparators"`
|
||||
}
|
||||
|
||||
// ChangelogConfig struct
|
||||
type ChangelogConfig struct {
|
||||
PrintAll bool `yaml:"printAll,omitempty"`
|
||||
@@ -83,6 +88,7 @@ type Checksum struct {
|
||||
type ReleaseConfig struct {
|
||||
CommitFormat string `yaml:"commitFormat"`
|
||||
Branch map[string]string `yaml:"branch"`
|
||||
Analyzer AnalyzerConfig `yaml:"analyzer"`
|
||||
Changelog ChangelogConfig `yaml:"changelog,omitempty"`
|
||||
Release string `yaml:"release,omitempty"`
|
||||
GitHubProvider GitHubProvider `yaml:"github,omitempty"`
|
||||
|
||||
@@ -100,6 +100,7 @@ github:
|
||||
Compress: false}},
|
||||
ReleaseTitle: "go-semantic-release release",
|
||||
IsPreRelease: false,
|
||||
Analyzer: config.AnalyzerConfig{TokenSeparators: []string{}},
|
||||
}, result)
|
||||
|
||||
}
|
||||
|
||||
@@ -39,7 +39,7 @@ func New(c *config.ReleaseConfig, repository string, checkConfig bool) (*Semanti
|
||||
return nil, err
|
||||
}
|
||||
|
||||
analyzer, err := analyzer.New(c.CommitFormat, c.Changelog)
|
||||
analyzer, err := analyzer.New(c.CommitFormat, c.Analyzer, c.Changelog)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -180,7 +180,7 @@ func (s *SemanticRelease) SetVersion(provider *ci.ProviderConfig, version string
|
||||
// GetChangelog from last version till now
|
||||
func (s *SemanticRelease) GetChangelog(releaseVersion *shared.ReleaseVersion) (*shared.GeneratedChangelog, error) {
|
||||
c := changelog.New(s.config, s.analyzer.GetRules(), time.Now())
|
||||
return c.GenerateChanglog(shared.ChangelogTemplateConfig{
|
||||
return c.GenerateChangelog(shared.ChangelogTemplateConfig{
|
||||
Version: releaseVersion.Next.Version.String(),
|
||||
Hash: releaseVersion.Last.Commit,
|
||||
CommitURL: s.releaser.GetCommitURL(),
|
||||
|
||||
Reference in New Issue
Block a user