From 94fff067a18d73d3ecdd50df1cb1354f07813ab1 Mon Sep 17 00:00:00 2001 From: Beatriz Vieira Date: Sun, 6 Feb 2022 22:06:46 -0300 Subject: [PATCH] refactor: remove order from templates variables and add authors issue: #40 --- .../resources/templates/releasenotes-md.tpl | 5 +- sv/formatter.go | 60 ++++++++++++++++--- sv/formatter_test.go | 17 +++--- sv/helpers_test.go | 9 ++- sv/releasenotes.go | 8 ++- sv/releasenotes_test.go | 20 +++++-- sv/semver_test.go | 16 ++--- 7 files changed, 97 insertions(+), 38 deletions(-) diff --git a/cmd/git-sv/resources/templates/releasenotes-md.tpl b/cmd/git-sv/resources/templates/releasenotes-md.tpl index 440cb6b..f6ba595 100644 --- a/cmd/git-sv/resources/templates/releasenotes-md.tpl +++ b/cmd/git-sv/resources/templates/releasenotes-md.tpl @@ -1,6 +1,5 @@ ## {{if .Release}}{{.Release}}{{end}}{{if and .Date .Release}} ({{end}}{{.Date}}{{if and .Date .Release}}){{end}} -{{- $sections := .Sections }} -{{- range $key := .Order }} -{{- template "rn-md-section.tpl" (index $sections $key) }} +{{- range $section := .Sections }} +{{- template "rn-md-section.tpl" $section }} {{- end}} {{- template "rn-md-section-breaking-changes.tpl" .BreakingChanges}} diff --git a/sv/formatter.go b/sv/formatter.go index f47d345..80fa2d1 100644 --- a/sv/formatter.go +++ b/sv/formatter.go @@ -3,15 +3,20 @@ package sv import ( "bytes" "io/fs" + "sort" + "strings" "text/template" + + "github.com/Masterminds/semver/v3" ) type releaseNoteTemplateVariables struct { Release string + Version *semver.Version Date string - Sections map[string]ReleaseNoteSection - Order []string + Sections []ReleaseNoteSection BreakingChanges BreakingChangeSection + AuthorNames []string } // OutputFormatter output formatter interface. @@ -60,17 +65,58 @@ func releaseNoteVariables(releasenote ReleaseNote) releaseNoteTemplateVariables date = releasenote.Date.Format("2006-01-02") } - release := "" + release := releasenote.Tag if releasenote.Version != nil { release = "v" + releasenote.Version.String() - } else if releasenote.Tag != "" { - release = releasenote.Tag } return releaseNoteTemplateVariables{ Release: release, + Version: releasenote.Version, Date: date, - Sections: releasenote.Sections, - Order: []string{"feat", "fix", "refactor", "perf", "test", "build", "ci", "chore", "docs", "style"}, + Sections: toTemplateSections(releasenote.Sections), BreakingChanges: releasenote.BreakingChanges, + AuthorNames: toSortedArray(releasenote.AuthorsNames), } } + +func toTemplateSections(sections map[string]ReleaseNoteSection) []ReleaseNoteSection { + result := make([]ReleaseNoteSection, len(sections)) + i := 0 + for _, section := range sections { + result[i] = section + i++ + } + + order := map[string]int{"feat": 0, "fix": 1, "refactor": 2, "perf": 3, "test": 4, "build": 5, "ci": 6, "chore": 7, "docs": 8, "style": 9} + sort.SliceStable(result, func(i, j int) bool { + priority1, disambiguity1 := priority(result[i].Types, order) + priority2, disambiguity2 := priority(result[j].Types, order) + if priority1 == priority2 { + return disambiguity1 < disambiguity2 + } + return priority1 < priority2 + }) + return result +} + +func priority(types []string, order map[string]int) (int, string) { + sort.Strings(types) + p := -1 + for _, t := range types { + if p == -1 || order[t] < p { + p = order[t] + } + } + return p, strings.Join(types, "-") +} + +func toSortedArray(input map[string]struct{}) []string { + result := make([]string, len(input)) + i := 0 + for k := range input { + result[i] = k + i++ + } + sort.Strings(result) + return result +} diff --git a/sv/formatter_test.go b/sv/formatter_test.go index e186548..05acb30 100644 --- a/sv/formatter_test.go +++ b/sv/formatter_test.go @@ -84,11 +84,11 @@ func emptyReleaseNote(tag string, date time.Time) ReleaseNote { func fullReleaseNote(tag string, date time.Time) ReleaseNote { v, _ := semver.NewVersion(tag) sections := map[string]ReleaseNoteSection{ - "build": newReleaseNoteSection("Build", []GitCommitLog{commitlog("build", map[string]string{})}), - "feat": newReleaseNoteSection("Features", []GitCommitLog{commitlog("feat", map[string]string{})}), - "fix": newReleaseNoteSection("Bug Fixes", []GitCommitLog{commitlog("fix", map[string]string{})}), + "build": newReleaseNoteSection("Build", []string{"build"}, []GitCommitLog{commitlog("build", map[string]string{}, "a")}), + "feat": newReleaseNoteSection("Features", []string{"feat"}, []GitCommitLog{commitlog("feat", map[string]string{}, "a")}), + "fix": newReleaseNoteSection("Bug Fixes", []string{"fix"}, []GitCommitLog{commitlog("fix", map[string]string{}, "a")}), } - return releaseNote(v, tag, date, sections, []string{"break change message"}) + return releaseNote(v, tag, date, sections, []string{"break change message"}, map[string]struct{}{"a": {}}) } func Test_checkTemplatesExecution(t *testing.T) { @@ -119,12 +119,11 @@ func releaseNotesVariables(release string) releaseNoteTemplateVariables { return releaseNoteTemplateVariables{ Release: release, Date: "2006-01-02", - Sections: map[string]ReleaseNoteSection{ - "build": newReleaseNoteSection("Build", []GitCommitLog{commitlog("build", map[string]string{})}), - "feat": newReleaseNoteSection("Features", []GitCommitLog{commitlog("feat", map[string]string{})}), - "fix": newReleaseNoteSection("Bug Fixes", []GitCommitLog{commitlog("fix", map[string]string{})}), + Sections: []ReleaseNoteSection{ + newReleaseNoteSection("Features", []string{"feat"}, []GitCommitLog{commitlog("feat", map[string]string{}, "a")}), + newReleaseNoteSection("Bug Fixes", []string{"fix"}, []GitCommitLog{commitlog("fix", map[string]string{}, "a")}), + newReleaseNoteSection("Build", []string{"build"}, []GitCommitLog{commitlog("build", map[string]string{}, "a")}), }, - Order: []string{"feat", "fix", "refactor", "perf", "test", "build", "ci", "chore", "docs", "style"}, BreakingChanges: BreakingChangeSection{"Breaking Changes", []string{"break change message"}}, } } diff --git a/sv/helpers_test.go b/sv/helpers_test.go index 90975dd..5b17f4d 100644 --- a/sv/helpers_test.go +++ b/sv/helpers_test.go @@ -11,7 +11,7 @@ func version(v string) *semver.Version { return r } -func commitlog(ctype string, metadata map[string]string) GitCommitLog { +func commitlog(ctype string, metadata map[string]string, author string) GitCommitLog { breaking := false if _, found := metadata[breakingChangeMetadataKey]; found { breaking = true @@ -23,10 +23,11 @@ func commitlog(ctype string, metadata map[string]string) GitCommitLog { IsBreakingChange: breaking, Metadata: metadata, }, + AuthorName: author, } } -func releaseNote(version *semver.Version, tag string, date time.Time, sections map[string]ReleaseNoteSection, breakingChanges []string) ReleaseNote { +func releaseNote(version *semver.Version, tag string, date time.Time, sections map[string]ReleaseNoteSection, breakingChanges []string, authorsNames map[string]struct{}) ReleaseNote { var bchanges BreakingChangeSection if len(breakingChanges) > 0 { bchanges = BreakingChangeSection{Name: "Breaking Changes", Messages: breakingChanges} @@ -37,12 +38,14 @@ func releaseNote(version *semver.Version, tag string, date time.Time, sections m Date: date.Truncate(time.Minute), Sections: sections, BreakingChanges: bchanges, + AuthorsNames: authorsNames, } } -func newReleaseNoteSection(name string, items []GitCommitLog) ReleaseNoteSection { +func newReleaseNoteSection(name string, types []string, items []GitCommitLog) ReleaseNoteSection { return ReleaseNoteSection{ Name: name, + Types: types, Items: items, } } diff --git a/sv/releasenotes.go b/sv/releasenotes.go index 3c44ba0..0462df0 100644 --- a/sv/releasenotes.go +++ b/sv/releasenotes.go @@ -24,12 +24,14 @@ func NewReleaseNoteProcessor(cfg ReleaseNotesConfig) *ReleaseNoteProcessorImpl { // Create create a release note based on commits. func (p ReleaseNoteProcessorImpl) Create(version *semver.Version, tag string, date time.Time, commits []GitCommitLog) ReleaseNote { sections := make(map[string]ReleaseNoteSection) + authors := make(map[string]struct{}) var breakingChanges []string for _, commit := range commits { + authors[commit.AuthorName] = struct{}{} if name, exists := p.cfg.Headers[commit.Message.Type]; exists { section, sexists := sections[commit.Message.Type] if !sexists { - section = ReleaseNoteSection{Name: name} + section = ReleaseNoteSection{Name: name, Types: []string{commit.Message.Type}} //TODO: change to support more than one type per section } section.Items = append(section.Items, commit) sections[commit.Message.Type] = section @@ -44,7 +46,7 @@ func (p ReleaseNoteProcessorImpl) Create(version *semver.Version, tag string, da if name, exists := p.cfg.Headers[breakingChangeMetadataKey]; exists && len(breakingChanges) > 0 { breakingChangeSection = BreakingChangeSection{Name: name, Messages: breakingChanges} } - return ReleaseNote{Version: version, Tag: tag, Date: date.Truncate(time.Minute), Sections: sections, BreakingChanges: breakingChangeSection} + return ReleaseNote{Version: version, Tag: tag, Date: date.Truncate(time.Minute), Sections: sections, BreakingChanges: breakingChangeSection, AuthorsNames: authors} } // ReleaseNote release note. @@ -54,6 +56,7 @@ type ReleaseNote struct { Date time.Time Sections map[string]ReleaseNoteSection BreakingChanges BreakingChangeSection + AuthorsNames map[string]struct{} } // BreakingChangeSection breaking change section. @@ -65,5 +68,6 @@ type BreakingChangeSection struct { // ReleaseNoteSection release note section. type ReleaseNoteSection struct { Name string + Types []string Items []GitCommitLog } diff --git a/sv/releasenotes_test.go b/sv/releasenotes_test.go index 7af1578..8cfcac8 100644 --- a/sv/releasenotes_test.go +++ b/sv/releasenotes_test.go @@ -24,24 +24,32 @@ func TestReleaseNoteProcessorImpl_Create(t *testing.T) { version: semver.MustParse("1.0.0"), tag: "v1.0.0", date: date, - commits: []GitCommitLog{commitlog("t1", map[string]string{})}, - want: releaseNote(semver.MustParse("1.0.0"), "v1.0.0", date, map[string]ReleaseNoteSection{"t1": newReleaseNoteSection("Tag 1", []GitCommitLog{commitlog("t1", map[string]string{})})}, nil), + commits: []GitCommitLog{commitlog("t1", map[string]string{}, "a")}, + want: releaseNote(semver.MustParse("1.0.0"), "v1.0.0", date, map[string]ReleaseNoteSection{"t1": newReleaseNoteSection("Tag 1", []string{"t1"}, []GitCommitLog{commitlog("t1", map[string]string{}, "a")})}, nil, map[string]struct{}{"a": {}}), }, { name: "unmapped tag", version: semver.MustParse("1.0.0"), tag: "v1.0.0", date: date, - commits: []GitCommitLog{commitlog("t1", map[string]string{}), commitlog("unmapped", map[string]string{})}, - want: releaseNote(semver.MustParse("1.0.0"), "v1.0.0", date, map[string]ReleaseNoteSection{"t1": newReleaseNoteSection("Tag 1", []GitCommitLog{commitlog("t1", map[string]string{})})}, nil), + commits: []GitCommitLog{commitlog("t1", map[string]string{}, "a"), commitlog("unmapped", map[string]string{}, "a")}, + want: releaseNote(semver.MustParse("1.0.0"), "v1.0.0", date, map[string]ReleaseNoteSection{"t1": newReleaseNoteSection("Tag 1", []string{"t1"}, []GitCommitLog{commitlog("t1", map[string]string{}, "a")})}, nil, map[string]struct{}{"a": {}}), }, { name: "breaking changes tag", version: semver.MustParse("1.0.0"), tag: "v1.0.0", date: date, - commits: []GitCommitLog{commitlog("t1", map[string]string{}), commitlog("unmapped", map[string]string{"breaking-change": "breaks"})}, - want: releaseNote(semver.MustParse("1.0.0"), "v1.0.0", date, map[string]ReleaseNoteSection{"t1": newReleaseNoteSection("Tag 1", []GitCommitLog{commitlog("t1", map[string]string{})})}, []string{"breaks"}), + commits: []GitCommitLog{commitlog("t1", map[string]string{}, "a"), commitlog("unmapped", map[string]string{"breaking-change": "breaks"}, "a")}, + want: releaseNote(semver.MustParse("1.0.0"), "v1.0.0", date, map[string]ReleaseNoteSection{"t1": newReleaseNoteSection("Tag 1", []string{"t1"}, []GitCommitLog{commitlog("t1", map[string]string{}, "a")})}, []string{"breaks"}, map[string]struct{}{"a": {}}), + }, + { + name: "multiple authors", + version: semver.MustParse("1.0.0"), + tag: "v1.0.0", + date: date, + commits: []GitCommitLog{commitlog("t1", map[string]string{}, "author3"), commitlog("t1", map[string]string{}, "author2"), commitlog("t1", map[string]string{}, "author1")}, + want: releaseNote(semver.MustParse("1.0.0"), "v1.0.0", date, map[string]ReleaseNoteSection{"t1": newReleaseNoteSection("Tag 1", []string{"t1"}, []GitCommitLog{commitlog("t1", map[string]string{}, "author3"), commitlog("t1", map[string]string{}, "author2"), commitlog("t1", map[string]string{}, "author1")})}, nil, map[string]struct{}{"author1": {}, "author2": {}, "author3": {}}), }, } for _, tt := range tests { diff --git a/sv/semver_test.go b/sv/semver_test.go index 0f2a5f4..1940a39 100644 --- a/sv/semver_test.go +++ b/sv/semver_test.go @@ -18,14 +18,14 @@ func TestSemVerCommitsProcessorImpl_NextVersion(t *testing.T) { }{ {"no update", true, version("0.0.0"), []GitCommitLog{}, version("0.0.0"), false}, {"no update without version", true, nil, []GitCommitLog{}, nil, false}, - {"no update on unknown type", true, version("0.0.0"), []GitCommitLog{commitlog("a", map[string]string{})}, version("0.0.0"), false}, - {"no update on unmapped known type", false, version("0.0.0"), []GitCommitLog{commitlog("none", map[string]string{})}, version("0.0.0"), false}, - {"update patch on unknown type", false, version("0.0.0"), []GitCommitLog{commitlog("a", map[string]string{})}, version("0.0.1"), true}, - {"patch update", false, version("0.0.0"), []GitCommitLog{commitlog("patch", map[string]string{})}, version("0.0.1"), true}, - {"patch update without version", false, nil, []GitCommitLog{commitlog("patch", map[string]string{})}, nil, true}, - {"minor update", false, version("0.0.0"), []GitCommitLog{commitlog("patch", map[string]string{}), commitlog("minor", map[string]string{})}, version("0.1.0"), true}, - {"major update", false, version("0.0.0"), []GitCommitLog{commitlog("patch", map[string]string{}), commitlog("major", map[string]string{})}, version("1.0.0"), true}, - {"breaking change update", false, version("0.0.0"), []GitCommitLog{commitlog("patch", map[string]string{}), commitlog("patch", map[string]string{"breaking-change": "break"})}, version("1.0.0"), true}, + {"no update on unknown type", true, version("0.0.0"), []GitCommitLog{commitlog("a", map[string]string{}, "a")}, version("0.0.0"), false}, + {"no update on unmapped known type", false, version("0.0.0"), []GitCommitLog{commitlog("none", map[string]string{}, "a")}, version("0.0.0"), false}, + {"update patch on unknown type", false, version("0.0.0"), []GitCommitLog{commitlog("a", map[string]string{}, "a")}, version("0.0.1"), true}, + {"patch update", false, version("0.0.0"), []GitCommitLog{commitlog("patch", map[string]string{}, "a")}, version("0.0.1"), true}, + {"patch update without version", false, nil, []GitCommitLog{commitlog("patch", map[string]string{}, "a")}, nil, true}, + {"minor update", false, version("0.0.0"), []GitCommitLog{commitlog("patch", map[string]string{}, "a"), commitlog("minor", map[string]string{}, "a")}, version("0.1.0"), true}, + {"major update", false, version("0.0.0"), []GitCommitLog{commitlog("patch", map[string]string{}, "a"), commitlog("major", map[string]string{}, "a")}, version("1.0.0"), true}, + {"breaking change update", false, version("0.0.0"), []GitCommitLog{commitlog("patch", map[string]string{}, "a"), commitlog("patch", map[string]string{"breaking-change": "break"}, "a")}, version("1.0.0"), true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) {