ref(analyzer): simplified analyze method for angular and conventional

This commit is contained in:
fwiedmann
2021-01-23 22:49:42 +01:00
parent 450383bdbf
commit 6cd43d7957
6 changed files with 181 additions and 149 deletions

View File

@@ -24,7 +24,7 @@ type Rule struct {
} }
type analyzeCommits interface { type analyzeCommits interface {
analyze(commit shared.Commit, tag Rule) (shared.AnalyzedCommit, bool, error) analyze(commit shared.Commit, tag Rule) (*shared.AnalyzedCommit, bool)
getRules() []Rule getRules() []Rule
} }
@@ -45,7 +45,6 @@ func New(format string, config config.ChangelogConfig) (*Analyzer, error) {
return nil, fmt.Errorf("invalid commit format: %s", format) return nil, fmt.Errorf("invalid commit format: %s", format)
} }
return analyzer, nil return analyzer, nil
} }
// GetRules from current mode // GetRules from current mode
@@ -53,9 +52,8 @@ func (a *Analyzer) GetRules() []Rule {
return a.analyzeCommits.getRules() return a.analyzeCommits.getRules()
} }
// Analyze commits and return commits splitted by major,minor,patch // Analyze commits and return commits split by major,minor,patch
func (a *Analyzer) Analyze(commits []shared.Commit) map[shared.Release][]shared.AnalyzedCommit { func (a *Analyzer) Analyze(commits []shared.Commit) map[shared.Release][]shared.AnalyzedCommit {
analyzedCommits := make(map[shared.Release][]shared.AnalyzedCommit) analyzedCommits := make(map[shared.Release][]shared.AnalyzedCommit)
analyzedCommits["major"] = make([]shared.AnalyzedCommit, 0) analyzedCommits["major"] = make([]shared.AnalyzedCommit, 0)
analyzedCommits["minor"] = make([]shared.AnalyzedCommit, 0) analyzedCommits["minor"] = make([]shared.AnalyzedCommit, 0)
@@ -64,25 +62,21 @@ func (a *Analyzer) Analyze(commits []shared.Commit) map[shared.Release][]shared.
for _, commit := range commits { for _, commit := range commits {
for _, rule := range a.analyzeCommits.getRules() { for _, rule := range a.analyzeCommits.getRules() {
analyzedCommit, hasBreakingChange, err := a.analyzeCommits.analyze(commit, rule) analyzedCommit, hasBreakingChange := a.analyzeCommits.analyze(commit, rule)
if err == nil { if analyzedCommit == nil {
if a.Config.PrintAll { continue
}
if a.Config.PrintAll || rule.Changelog {
analyzedCommit.Print = true analyzedCommit.Print = true
} else {
analyzedCommit.Print = rule.Changelog
} }
if hasBreakingChange { if hasBreakingChange {
analyzedCommits["major"] = append(analyzedCommits["major"], analyzedCommit) analyzedCommits["major"] = append(analyzedCommits["major"], *analyzedCommit)
} else {
analyzedCommits[rule.Release] = append(analyzedCommits[rule.Release], analyzedCommit)
}
break break
} }
analyzedCommits[rule.Release] = append(analyzedCommits[rule.Release], *analyzedCommit)
break
} }
} }
log.Debugf("Analyzed commits: major=%d minor=%d patch=%d none=%d", len(analyzedCommits["major"]), len(analyzedCommits["minor"]), len(analyzedCommits["patch"]), len(analyzedCommits["none"])) 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 return analyzedCommits
} }

View File

@@ -2,7 +2,6 @@
package analyzer package analyzer
import ( import (
"fmt"
"regexp" "regexp"
"strings" "strings"
@@ -36,12 +35,14 @@ func newAngular() *angular {
TagString: "Bug fixes", TagString: "Bug fixes",
Release: "patch", Release: "patch",
Changelog: true, Changelog: true,
}, { },
{
Tag: "perf", Tag: "perf",
TagString: "Performance improvments", TagString: "Performance improvements",
Release: "patch", Release: "patch",
Changelog: true, Changelog: true,
}, { },
{
Tag: "docs", Tag: "docs",
TagString: "Documentation changes", TagString: "Documentation changes",
Release: "none", Release: "none",
@@ -52,22 +53,26 @@ func newAngular() *angular {
TagString: "Style", TagString: "Style",
Release: "none", Release: "none",
Changelog: false, Changelog: false,
}, { },
{
Tag: "refactor", Tag: "refactor",
TagString: "Code refactor", TagString: "Code refactor",
Release: "none", Release: "none",
Changelog: false, Changelog: false,
}, { },
{
Tag: "test", Tag: "test",
TagString: "Testing", TagString: "Testing",
Release: "none", Release: "none",
Changelog: false, Changelog: false,
}, { },
{
Tag: "chore", Tag: "chore",
TagString: "Changes to the build process or auxiliary tools and libraries such as documentation generation", TagString: "Changes to the build process or auxiliary tools and libraries such as documentation generation",
Release: "none", Release: "none",
Changelog: false, Changelog: false,
}, { },
{
Tag: "build", Tag: "build",
TagString: "Changes to CI/CD", TagString: "Changes to CI/CD",
Release: "none", Release: "none",
@@ -81,38 +86,37 @@ func (a *angular) getRules() []Rule {
return a.rules return a.rules
} }
func (a *angular) analyze(commit shared.Commit, rule Rule) (shared.AnalyzedCommit, bool, error) { 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
}
analyzed := shared.AnalyzedCommit{ analyzed := &shared.AnalyzedCommit{
Commit: commit, Commit: commit,
Tag: rule.Tag, Tag: rule.Tag,
TagString: rule.TagString, TagString: rule.TagString,
Scope: shared.Scope(matches[2]),
} }
re := regexp.MustCompile(strings.Replace(a.regex, "TAG", rule.Tag, -1)) message := strings.Join(matches[3:], "")
matches := re.FindAllStringSubmatch(commit.Message, -1)
if len(matches) >= 1 {
if len(matches[0]) >= 3 {
analyzed.Scope = shared.Scope(matches[0][2])
message := strings.Join(matches[0][3:], "")
if !strings.Contains(message, "BREAKING CHANGE:") { if !strings.Contains(message, "BREAKING CHANGE:") {
analyzed.ParsedMessage = strings.Trim(message, " ") analyzed.ParsedMessage = strings.Trim(message, " ")
a.log.Tracef("%s: found %s", commit.Message, rule.Tag) a.log.Tracef("%s: found %s", commit.Message, rule.Tag)
return analyzed, false, nil return analyzed, false
} }
breakingChange := strings.SplitN(message, "BREAKING CHANGE:", 2)
analyzed.ParsedMessage = strings.TrimSpace(breakingChange[0])
analyzed.ParsedBreakingChangeMessage = strings.TrimSpace(breakingChange[1])
a.log.Tracef(" %s, BREAKING CHANGE found", commit.Message) a.log.Tracef(" %s, BREAKING CHANGE found", commit.Message)
return analyzed, true, nil breakingChange := strings.SplitN(message, "BREAKING CHANGE:", 2)
}
}
a.log.Tracef("%s does not match %s, skip", commit.Message, rule.Tag)
return analyzed, false, fmt.Errorf("not found")
if len(breakingChange) > 1 {
analyzed.ParsedMessage = strings.TrimSpace(breakingChange[0])
analyzed.ParsedBreakingChangeMessage = strings.TrimSpace(breakingChange[1])
return analyzed, true
}
analyzed.ParsedBreakingChangeMessage = breakingChange[0]
return analyzed, true
} }

View File

@@ -10,7 +10,7 @@ import (
) )
func TestAngular(t *testing.T) { func TestAngular(t *testing.T) {
t.Parallel()
testConfigs := []struct { testConfigs := []struct {
testCase string testCase string
commits []shared.Commit commits []shared.Commit
@@ -93,7 +93,8 @@ func TestAngular(t *testing.T) {
}, },
}, },
}, },
{testCase: "feat breaking change footer", {
testCase: "feat breaking change footer",
commits: []shared.Commit{ commits: []shared.Commit{
{ {
Message: "feat(internal/changelog): my first commit", Message: "feat(internal/changelog): my first commit",
@@ -221,5 +222,4 @@ func TestAngular(t *testing.T) {
assert.Equalf(t, test.analyzedCommits["patch"], analyzedCommits["patch"], "Testcase %s should have patch commits", test.testCase) assert.Equalf(t, test.analyzedCommits["patch"], analyzedCommits["patch"], "Testcase %s should have patch commits", test.testCase)
assert.Equalf(t, test.analyzedCommits["none"], analyzedCommits["none"], "Testcase %s should have none commits", test.testCase) assert.Equalf(t, test.analyzedCommits["none"], analyzedCommits["none"], "Testcase %s should have none commits", test.testCase)
} }
} }

View File

@@ -2,7 +2,6 @@
package analyzer package analyzer
import ( import (
"fmt"
"regexp" "regexp"
"strings" "strings"
@@ -36,12 +35,14 @@ func newConventional() *conventional {
TagString: "Bug fixes", TagString: "Bug fixes",
Release: "patch", Release: "patch",
Changelog: true, Changelog: true,
}, { },
{
Tag: "perf", Tag: "perf",
TagString: "Performance improvments", TagString: "Performance improvements",
Release: "patch", Release: "patch",
Changelog: true, Changelog: true,
}, { },
{
Tag: "docs", Tag: "docs",
TagString: "Documentation changes", TagString: "Documentation changes",
Release: "none", Release: "none",
@@ -52,22 +53,26 @@ func newConventional() *conventional {
TagString: "Style", TagString: "Style",
Release: "none", Release: "none",
Changelog: false, Changelog: false,
}, { },
{
Tag: "refactor", Tag: "refactor",
TagString: "Code refactor", TagString: "Code refactor",
Release: "none", Release: "none",
Changelog: false, Changelog: false,
}, { },
{
Tag: "test", Tag: "test",
TagString: "Testing", TagString: "Testing",
Release: "none", Release: "none",
Changelog: false, Changelog: false,
}, { },
{
Tag: "chore", Tag: "chore",
TagString: "Changes to the build process or auxiliary tools and libraries such as documentation generation", TagString: "Changes to the build process or auxiliary tools and libraries such as documentation generation",
Release: "none", Release: "none",
Changelog: false, Changelog: false,
}, { },
{
Tag: "build", Tag: "build",
TagString: "Changes to CI/CD", TagString: "Changes to CI/CD",
Release: "none", Release: "none",
@@ -81,50 +86,37 @@ func (a *conventional) getRules() []Rule {
return a.rules return a.rules
} }
func (a *conventional) analyze(commit shared.Commit, rule Rule) (shared.AnalyzedCommit, bool, error) { 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
}
analyzed := shared.AnalyzedCommit{ analyzed := &shared.AnalyzedCommit{
Commit: commit, Commit: commit,
Tag: rule.Tag, Tag: rule.Tag,
TagString: rule.TagString, TagString: rule.TagString,
Scope: shared.Scope(matches[2]),
} }
re := regexp.MustCompile(strings.Replace(a.regex, "TAG", rule.Tag, -1)) message := strings.Join(matches[4:], "")
matches := re.FindAllStringSubmatch(commit.Message, -1) if matches[3] == "" && !strings.Contains(message, "BREAKING CHANGE:") {
if len(matches) >= 1 {
if len(matches[0]) >= 4 {
analyzed.Scope = shared.Scope(matches[0][2])
message := strings.Join(matches[0][4:], "")
if matches[0][3] == "" && !strings.Contains(message, "BREAKING CHANGE:") {
analyzed.ParsedMessage = strings.Trim(message, " ") analyzed.ParsedMessage = strings.Trim(message, " ")
a.log.Tracef("%s: found %s", commit.Message, rule.Tag) a.log.Tracef("%s: found %s", commit.Message, rule.Tag)
return analyzed, false, nil return analyzed, false
}
if matches[0][3] == "" {
breakingChange := strings.SplitN(message, "BREAKING CHANGE:", 2)
analyzed.ParsedMessage = strings.TrimSpace(breakingChange[0])
analyzed.ParsedBreakingChangeMessage = strings.TrimSpace(breakingChange[1])
a.log.Tracef(" %s, BREAKING CHANGE found", commit.Message)
return analyzed, true, nil
} }
a.log.Infof(" %s, BREAKING CHANGE found", commit.Message)
breakingChange := strings.SplitN(message, "BREAKING CHANGE:", 2) breakingChange := strings.SplitN(message, "BREAKING CHANGE:", 2)
if len(breakingChange) > 1 { if len(breakingChange) > 1 {
analyzed.ParsedMessage = strings.TrimSpace(breakingChange[0]) analyzed.ParsedMessage = strings.TrimSpace(breakingChange[0])
analyzed.ParsedBreakingChangeMessage = strings.TrimSpace(breakingChange[1]) analyzed.ParsedBreakingChangeMessage = strings.TrimSpace(breakingChange[1])
} else { return analyzed, true
analyzed.ParsedBreakingChangeMessage = breakingChange[0]
} }
a.log.Infof(" %s, BREAKING CHANGE found", commit.Message)
return analyzed, true, nil
}
}
a.log.Tracef("%s does not match %s, skip", commit.Message, rule.Tag)
return analyzed, false, fmt.Errorf("not found")
analyzed.ParsedBreakingChangeMessage = breakingChange[0]
return analyzed, true
} }

View File

@@ -10,15 +10,15 @@ import (
) )
func TestConventional(t *testing.T) { func TestConventional(t *testing.T) {
t.Parallel()
testConfigs := []struct { testConfigs := []struct {
testCase string testCase string
commits []shared.Commit commits []shared.Commit
analyzedCommits map[shared.Release][]shared.AnalyzedCommit wantAnalyzedCommits map[shared.Release][]shared.AnalyzedCommit
}{ }{
{ {
testCase: "feat", testCase: "feat",
analyzedCommits: map[shared.Release][]shared.AnalyzedCommit{ wantAnalyzedCommits: map[shared.Release][]shared.AnalyzedCommit{
"minor": { "minor": {
{ {
Commit: shared.Commit{ Commit: shared.Commit{
@@ -64,7 +64,7 @@ func TestConventional(t *testing.T) {
}, },
{ {
testCase: "feat breaking change", testCase: "feat breaking change",
analyzedCommits: map[shared.Release][]shared.AnalyzedCommit{ wantAnalyzedCommits: map[shared.Release][]shared.AnalyzedCommit{
"minor": { "minor": {
{ {
Commit: shared.Commit{ Commit: shared.Commit{
@@ -112,24 +112,7 @@ func TestConventional(t *testing.T) {
}, },
{ {
testCase: "feat breaking change footer", testCase: "feat breaking change footer",
commits: []shared.Commit{ wantAnalyzedCommits: map[shared.Release][]shared.AnalyzedCommit{
{
Message: "feat: my first commit",
Author: "me",
Hash: "12345667",
},
{
Message: "feat: my first break \n\nBREAKING CHANGE: change api to v2\n",
Author: "me",
Hash: "12345668",
},
{
Message: "feat!: my first break \n\nBREAKING CHANGE: hey from the change",
Author: "me",
Hash: "12345669",
},
},
analyzedCommits: map[shared.Release][]shared.AnalyzedCommit{
"minor": { "minor": {
{ {
Commit: shared.Commit{ Commit: shared.Commit{
@@ -175,10 +158,27 @@ func TestConventional(t *testing.T) {
"patch": {}, "patch": {},
"none": {}, "none": {},
}, },
commits: []shared.Commit{
{
Message: "feat: my first commit",
Author: "me",
Hash: "12345667",
},
{
Message: "feat: my first break \n\nBREAKING CHANGE: change api to v2\n",
Author: "me",
Hash: "12345668",
},
{
Message: "feat!: my first break \n\nBREAKING CHANGE: hey from the change",
Author: "me",
Hash: "12345669",
},
},
}, },
{ {
testCase: "invalid", testCase: "invalid",
analyzedCommits: map[shared.Release][]shared.AnalyzedCommit{ wantAnalyzedCommits: map[shared.Release][]shared.AnalyzedCommit{
"minor": {}, "minor": {},
"major": {}, "major": {},
"patch": {}, "patch": {},
@@ -199,7 +199,7 @@ func TestConventional(t *testing.T) {
}, },
{ {
testCase: "feat and build", testCase: "feat and build",
analyzedCommits: map[shared.Release][]shared.AnalyzedCommit{ wantAnalyzedCommits: map[shared.Release][]shared.AnalyzedCommit{
"minor": { "minor": {
{ {
Commit: shared.Commit{ Commit: shared.Commit{
@@ -245,6 +245,52 @@ func TestConventional(t *testing.T) {
}, },
}, },
}, },
{
testCase: "fix and build",
wantAnalyzedCommits: map[shared.Release][]shared.AnalyzedCommit{
"minor": {},
"none": {
{
Commit: shared.Commit{
Message: "build: my first build",
Author: "me",
Hash: "12345668",
},
Scope: "",
ParsedMessage: "my first build",
Tag: "build",
TagString: "Changes to CI/CD",
Print: false,
ParsedBreakingChangeMessage: "",
},
},
"patch": {{
Commit: shared.Commit{
Message: "fix: my first commit",
Author: "me",
Hash: "12345667",
},
Scope: "",
ParsedMessage: "my first commit",
Tag: "fix",
TagString: "Bug fixes",
Print: true,
}},
"major": {},
},
commits: []shared.Commit{
{
Message: "fix: my first commit",
Author: "me",
Hash: "12345667",
},
{
Message: "build: my first build",
Author: "me",
Hash: "12345668",
},
},
},
} }
conventional, err := analyzer.New("conventional", config.ChangelogConfig{}) conventional, err := analyzer.New("conventional", config.ChangelogConfig{})
@@ -252,10 +298,9 @@ func TestConventional(t *testing.T) {
for _, test := range testConfigs { for _, test := range testConfigs {
analyzedCommits := conventional.Analyze(test.commits) analyzedCommits := conventional.Analyze(test.commits)
assert.Equalf(t, test.analyzedCommits["major"], analyzedCommits["major"], "Testcase %s should have major commits", test.testCase) assert.Equalf(t, test.wantAnalyzedCommits["major"], analyzedCommits["major"], "Testcase %s should have major commits", test.testCase)
assert.Equalf(t, test.analyzedCommits["minor"], analyzedCommits["minor"], "Testcase %s should have minor commits", test.testCase) assert.Equalf(t, test.wantAnalyzedCommits["minor"], analyzedCommits["minor"], "Testcase %s should have minor commits", test.testCase)
assert.Equalf(t, test.analyzedCommits["patch"], analyzedCommits["patch"], "Testcase %s should have patch commits", test.testCase) assert.Equalf(t, test.wantAnalyzedCommits["patch"], analyzedCommits["patch"], "Testcase %s should have patch commits", test.testCase)
assert.Equalf(t, test.analyzedCommits["none"], analyzedCommits["none"], "Testcase %s should have none commits", test.testCase) assert.Equalf(t, test.wantAnalyzedCommits["none"], analyzedCommits["none"], "Testcase %s should have none commits", test.testCase)
} }
} }

View File

@@ -23,7 +23,7 @@ import (
// SemanticRelease struct // SemanticRelease struct
type SemanticRelease struct { type SemanticRelease struct {
config *config.ReleaseConfig config *config.ReleaseConfig
gitutil *gitutil.GitUtil gitUtil *gitutil.GitUtil
analyzer *analyzer.Analyzer analyzer *analyzer.Analyzer
calculator *calculator.Calculator calculator *calculator.Calculator
releaser releaser.Releaser releaser releaser.Releaser
@@ -57,7 +57,7 @@ func New(c *config.ReleaseConfig, repository string, checkConfig bool) (*Semanti
return &SemanticRelease{ return &SemanticRelease{
config: c, config: c,
gitutil: util, gitUtil: util,
releaser: releaser, releaser: releaser,
analyzer: analyzer, analyzer: analyzer,
repository: repository, repository: repository,
@@ -69,7 +69,7 @@ func New(c *config.ReleaseConfig, repository string, checkConfig bool) (*Semanti
// GetCIProvider result with ci config // GetCIProvider result with ci config
func (s *SemanticRelease) GetCIProvider() (*ci.ProviderConfig, error) { func (s *SemanticRelease) GetCIProvider() (*ci.ProviderConfig, error) {
return ci.GetCIProvider(s.gitutil, s.checkConfig, ci.ReadAllEnvs()) return ci.GetCIProvider(s.gitUtil, s.checkConfig, ci.ReadAllEnvs())
} }
// GetNextVersion from .version or calculate new from commits // GetNextVersion from .version or calculate new from commits
@@ -86,7 +86,7 @@ func (s *SemanticRelease) GetNextVersion(provider *ci.ProviderConfig, force bool
} }
} }
lastVersion, lastVersionHash, err := s.gitutil.GetLastVersion() lastVersion, lastVersionHash, err := s.gitUtil.GetLastVersion()
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -99,7 +99,7 @@ func (s *SemanticRelease) GetNextVersion(provider *ci.ProviderConfig, force bool
firstRelease = true firstRelease = true
} }
commits, err := s.gitutil.GetCommits(lastVersionHash) commits, err := s.gitUtil.GetCommits(lastVersionHash)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -151,13 +151,12 @@ func (s *SemanticRelease) GetNextVersion(provider *ci.ProviderConfig, force bool
// SetVersion for git repository // SetVersion for git repository
func (s *SemanticRelease) SetVersion(provider *ci.ProviderConfig, version string) error { func (s *SemanticRelease) SetVersion(provider *ci.ProviderConfig, version string) error {
newVersion, err := semver.NewVersion(version) newVersion, err := semver.NewVersion(version)
if err != nil { if err != nil {
return err return err
} }
lastVersion, lastVersionHash, err := s.gitutil.GetLastVersion() lastVersion, lastVersionHash, err := s.gitUtil.GetLastVersion()
if err != nil { if err != nil {
return err return err
} }
@@ -187,17 +186,15 @@ func (s *SemanticRelease) GetChangelog(releaseVersion *shared.ReleaseVersion) (*
CommitURL: s.releaser.GetCommitURL(), CommitURL: s.releaser.GetCommitURL(),
CompareURL: s.releaser.GetCompareURL(releaseVersion.Last.Version.String(), releaseVersion.Next.Version.String()), CompareURL: s.releaser.GetCompareURL(releaseVersion.Last.Version.String(), releaseVersion.Next.Version.String()),
}, releaseVersion.Commits) }, releaseVersion.Commits)
} }
// WriteChangeLog wirtes changelog content to the given file // WriteChangeLog writes changelog content to the given file
func (s *SemanticRelease) WriteChangeLog(changelogContent, file string) error { func (s *SemanticRelease) WriteChangeLog(changelogContent, file string) error {
return ioutil.WriteFile(file, []byte(changelogContent), 0644) return ioutil.WriteFile(file, []byte(changelogContent), 644)
} }
// Release publish release to provider // Release publish release to provider
func (s *SemanticRelease) Release(provider *ci.ProviderConfig, force bool) error { func (s *SemanticRelease) Release(provider *ci.ProviderConfig, force bool) error {
if provider.IsPR { if provider.IsPR {
log.Infof("Will not perform a new release. This is a pull request") log.Infof("Will not perform a new release. This is a pull request")
return nil return nil