Skip to content
Snippets Groups Projects
Commit 2fe4ae45 authored by Christoph Witzko's avatar Christoph Witzko
Browse files

feat: allow to customize the format commit template

parent 7821ef8e
Branches
No related tags found
No related merge requests found
...@@ -2,10 +2,14 @@ module github.com/go-semantic-release/changelog-generator-default ...@@ -2,10 +2,14 @@ module github.com/go-semantic-release/changelog-generator-default
go 1.19 go 1.19
require github.com/go-semantic-release/semantic-release/v2 v2.25.0 require (
github.com/go-semantic-release/semantic-release/v2 v2.25.0
github.com/stretchr/testify v1.8.1
)
require ( require (
github.com/Masterminds/semver/v3 v3.2.0 // indirect github.com/Masterminds/semver/v3 v3.2.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/fatih/color v1.13.0 // indirect github.com/fatih/color v1.13.0 // indirect
github.com/fsnotify/fsnotify v1.6.0 // indirect github.com/fsnotify/fsnotify v1.6.0 // indirect
github.com/golang/protobuf v1.5.2 // indirect github.com/golang/protobuf v1.5.2 // indirect
...@@ -22,6 +26,7 @@ require ( ...@@ -22,6 +26,7 @@ require (
github.com/oklog/run v1.1.0 // indirect github.com/oklog/run v1.1.0 // indirect
github.com/pelletier/go-toml v1.9.5 // indirect github.com/pelletier/go-toml v1.9.5 // indirect
github.com/pelletier/go-toml/v2 v2.0.6 // indirect github.com/pelletier/go-toml/v2 v2.0.6 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/spf13/afero v1.9.3 // indirect github.com/spf13/afero v1.9.3 // indirect
github.com/spf13/cast v1.5.0 // indirect github.com/spf13/cast v1.5.0 // indirect
github.com/spf13/cobra v1.6.1 // indirect github.com/spf13/cobra v1.6.1 // indirect
......
package generator package generator
import ( import (
"bytes"
"fmt" "fmt"
"strings" "strings"
"text/template"
"time" "time"
"github.com/go-semantic-release/semantic-release/v2/pkg/generator" "github.com/go-semantic-release/semantic-release/v2/pkg/generator"
...@@ -16,32 +18,49 @@ func trimSHA(sha string) string { ...@@ -16,32 +18,49 @@ func trimSHA(sha string) string {
return sha[:8] return sha[:8]
} }
func formatCommit(c *semrel.Commit) string { var templateFuncMap = template.FuncMap{
ret := "* " "trimSHA": trimSHA,
if c.Scope != "" {
ret += fmt.Sprintf("**%s:** ", c.Scope)
} }
ret += fmt.Sprintf("%s (%s)", c.Message, trimSHA(c.SHA))
return ret var defaultFormatCommitTemplateStr = `* {{with .Scope -}} **{{.}}:** {{end}} {{- .Message}} ({{trimSHA .SHA}})`
func formatCommit(tpl *template.Template, c *semrel.Commit) string {
ret := &bytes.Buffer{}
err := tpl.Execute(ret, c)
if err != nil {
panic(err)
}
return ret.String()
} }
var CGVERSION = "dev" var CGVERSION = "dev"
type DefaultChangelogGenerator struct { type DefaultChangelogGenerator struct {
emojis bool emojis bool
formatCommitTpl *template.Template
} }
func (g *DefaultChangelogGenerator) Init(m map[string]string) error { func (g *DefaultChangelogGenerator) Init(m map[string]string) error {
emojis := false emojis := false
emojiConfig := m["emojis"] emojiConfig := m["emojis"]
if emojiConfig == "true" { if emojiConfig == "true" {
emojis = true emojis = true
} }
g.emojis = emojis g.emojis = emojis
templateStr := defaultFormatCommitTemplateStr
if tplStr := m["format_commit_template"]; tplStr != "" {
templateStr = tplStr
}
parsedTemplate, err := template.New("commit-template").
Funcs(templateFuncMap).
Parse(templateStr)
if err != nil {
return fmt.Errorf("failed to parse commit template: %w", err)
}
g.formatCommitTpl = parsedTemplate
return nil return nil
} }
...@@ -61,14 +80,14 @@ func (g *DefaultChangelogGenerator) Generate(changelogConfig *generator.Changelo ...@@ -61,14 +80,14 @@ func (g *DefaultChangelogGenerator) Generate(changelogConfig *generator.Changelo
break break
} }
if commit.Change != nil && commit.Change.Major { if commit.Change != nil && commit.Change.Major {
bc := fmt.Sprintf("%s\n```\n%s\n```", formatCommit(commit), strings.Join(commit.Raw[1:], "\n")) bc := fmt.Sprintf("%s\n```\n%s\n```", formatCommit(g.formatCommitTpl, commit), strings.Join(commit.Raw[1:], "\n"))
clTypes.AppendContent("%%bc%%", bc) clTypes.AppendContent("%%bc%%", bc)
continue continue
} }
if commit.Type == "" { if commit.Type == "" {
continue continue
} }
clTypes.AppendContent(commit.Type, formatCommit(commit)) clTypes.AppendContent(commit.Type, formatCommit(g.formatCommitTpl, commit))
} }
for _, ct := range clTypes { for _, ct := range clTypes {
if ct.Content == "" { if ct.Content == "" {
......
package generator package generator
import ( import (
"strings"
"testing" "testing"
"text/template"
"github.com/go-semantic-release/semantic-release/v2/pkg/generator" "github.com/go-semantic-release/semantic-release/v2/pkg/generator"
"github.com/go-semantic-release/semantic-release/v2/pkg/semrel" "github.com/go-semantic-release/semantic-release/v2/pkg/semrel"
"github.com/stretchr/testify/require"
) )
func TestDefaultGenerator(t *testing.T) { var testCommits = []*semrel.Commit{
changelogConfig := &generator.ChangelogGeneratorConfig{}
changelogConfig.Commits = []*semrel.Commit{
{}, {},
{SHA: "123456789", Type: "feat", Scope: "app", Message: "commit message"}, {
{SHA: "abcd", Type: "fix", Scope: "", Message: "commit message"}, SHA: "123456789", Type: "feat", Scope: "app", Message: "commit message",
{SHA: "87654321", Type: "ci", Scope: "", Message: "commit message"}, Annotations: map[string]string{"author_login": "test"},
{SHA: "43218765", Type: "build", Scope: "", Message: "commit message"}, },
{SHA: "12345678", Type: "yolo", Scope: "swag", Message: "commit message"}, {
{SHA: "12345678", Type: "chore", Scope: "", Message: "commit message", Raw: []string{"", "BREAKING CHANGE: test"}, Change: &semrel.Change{Major: true}}, SHA: "deadbeef", Type: "fix", Scope: "", Message: "commit message",
{SHA: "12345679", Type: "chore!", Scope: "user", Message: "another commit message", Raw: []string{"another commit message", "changed ID int into UUID"}, Change: &semrel.Change{Major: true}}, Annotations: map[string]string{"author_login": "test"},
{SHA: "stop", Type: "chore", Scope: "", Message: "not included"}, },
{
SHA: "87654321", Type: "ci", Scope: "", Message: "commit message",
Annotations: map[string]string{"author_login": "test"},
},
{
SHA: "43218765", Type: "build", Scope: "", Message: "commit message",
Annotations: map[string]string{"author_login": "test"},
},
{
SHA: "12345678", Type: "yolo", Scope: "swag", Message: "commit message",
},
{
SHA: "12345678", Type: "chore", Scope: "", Message: "commit message",
Raw: []string{"", "BREAKING CHANGE: test"},
Change: &semrel.Change{Major: true},
Annotations: map[string]string{"author_login": "test"},
},
{
SHA: "12345679", Type: "chore!", Scope: "user", Message: "another commit message",
Raw: []string{"another commit message", "changed ID int into UUID"},
Change: &semrel.Change{Major: true},
},
{
SHA: "stop", Type: "chore", Scope: "", Message: "not included",
},
} }
changelogConfig.LatestRelease = &semrel.Release{SHA: "stop"}
changelogConfig.NewVersion = "2.0.0" var testChangelogConfig = &generator.ChangelogGeneratorConfig{
generator := &DefaultChangelogGenerator{} Commits: testCommits,
changelog := generator.Generate(changelogConfig) LatestRelease: &semrel.Release{SHA: "stop"},
if !strings.Contains(changelog, "* **app:** commit message (12345678)") || NewVersion: "2.0.0",
!strings.Contains(changelog, "* commit message (abcd)") ||
!strings.Contains(changelog, "#### yolo") ||
!strings.Contains(changelog, "#### Build") ||
!strings.Contains(changelog, "#### CI") ||
!strings.Contains(changelog, "```\nBREAKING CHANGE: test\n```") ||
strings.Contains(changelog, "not included") {
t.Fail()
} }
func TestDefaultGenerator(t *testing.T) {
clGen := &DefaultChangelogGenerator{}
require.NoError(t, clGen.Init(map[string]string{}))
changelog := clGen.Generate(testChangelogConfig)
require.Contains(t, changelog, "* **app:** commit message (12345678)")
require.Contains(t, changelog, "* commit message (deadbeef)")
require.Contains(t, changelog, "#### yolo")
require.Contains(t, changelog, "#### Build")
require.Contains(t, changelog, "#### CI")
require.Contains(t, changelog, "```\nBREAKING CHANGE: test\n```")
require.NotContains(t, changelog, "not included")
} }
func TestEmojiGenerator(t *testing.T) { func TestEmojiGenerator(t *testing.T) {
changelogConfig := &generator.ChangelogGeneratorConfig{} clGen := &DefaultChangelogGenerator{}
changelogConfig.Commits = []*semrel.Commit{ require.NoError(t, clGen.Init(map[string]string{"emojis": "true"}))
{}, changelog := clGen.Generate(testChangelogConfig)
{SHA: "123456789", Type: "feat", Scope: "app", Message: "commit message"},
{SHA: "abcd", Type: "fix", Scope: "", Message: "commit message"}, require.Contains(t, changelog, "* **app:** commit message (12345678)")
{SHA: "87654321", Type: "ci", Scope: "", Message: "commit message"}, require.Contains(t, changelog, "* commit message (deadbeef)")
{SHA: "43218765", Type: "build", Scope: "", Message: "commit message"}, require.Contains(t, changelog, "#### 🎁 Feature")
{SHA: "12345678", Type: "yolo", Scope: "swag", Message: "commit message"}, require.Contains(t, changelog, "#### 🐞 Bug Fixes")
{SHA: "12345678", Type: "chore", Scope: "", Message: "commit message", Raw: []string{"", "BREAKING CHANGE: test"}, Change: &semrel.Change{Major: true}}, require.Contains(t, changelog, "#### 🔁 CI")
{SHA: "12345679", Type: "chore!", Scope: "user", Message: "another commit message", Raw: []string{"another commit message", "changed ID int into UUID"}, Change: &semrel.Change{Major: true}}, require.Contains(t, changelog, "#### 📦 Build")
{SHA: "stop", Type: "chore", Scope: "", Message: "not included"}, require.Contains(t, changelog, "#### 📣 Breaking Changes")
require.Contains(t, changelog, "#### yolo")
require.Contains(t, changelog, "```\nBREAKING CHANGE: test\n```")
require.NotContains(t, changelog, "not included")
}
func TestFormatCommit(t *testing.T) {
testCases := []struct {
tpl string
commit *semrel.Commit
expectedOutput string
}{
{
tpl: defaultFormatCommitTemplateStr,
commit: &semrel.Commit{SHA: "123456789", Type: "feat", Scope: "", Message: "commit message"},
expectedOutput: "* commit message (12345678)",
},
{
tpl: defaultFormatCommitTemplateStr,
commit: &semrel.Commit{SHA: "123", Type: "feat", Scope: "app", Message: "commit message"},
expectedOutput: "* **app:** commit message (123)",
},
{
tpl: `* {{.SHA}} - {{.Message}} {{- with index .Annotations "author_login" }} [by @{{.}}] {{- end}}`,
commit: &semrel.Commit{SHA: "deadbeef", Type: "fix", Message: "custom template", Annotations: map[string]string{"author_login": "test"}},
expectedOutput: "* deadbeef - custom template [by @test]",
},
} }
changelogConfig.LatestRelease = &semrel.Release{SHA: "stop"} for _, tc := range testCases {
changelogConfig.NewVersion = "2.0.0" t.Run(tc.expectedOutput, func(t *testing.T) {
generator := &DefaultChangelogGenerator{emojis: true} tpl := template.Must(template.New("test").Funcs(templateFuncMap).Parse(tc.tpl))
changelog := generator.Generate(changelogConfig) output := formatCommit(tpl, tc.commit)
if !strings.Contains(changelog, "* **app:** commit message (12345678)") || require.Equal(t, tc.expectedOutput, output)
!strings.Contains(changelog, "* commit message (abcd)") || })
!strings.Contains(changelog, "#### 🎁 Feature") ||
!strings.Contains(changelog, "#### 🐞 Bug Fixes") ||
!strings.Contains(changelog, "#### 🔁 CI") ||
!strings.Contains(changelog, "#### 📦 Build") ||
!strings.Contains(changelog, "#### 📣 Breaking Changes") ||
!strings.Contains(changelog, "#### yolo") ||
!strings.Contains(changelog, "```\nBREAKING CHANGE: test\n```") ||
strings.Contains(changelog, "not included") {
t.Fail()
} }
} }
func TestFormatCommitWithCustomTemplate(t *testing.T) {
clGen := &DefaultChangelogGenerator{}
require.NoError(t, clGen.Init(map[string]string{
"format_commit_template": "* `{{ trimSHA .SHA}}` - {{.Message}} {{- with index .Annotations \"author_login\" }} [by @{{.}}] {{- end}}",
}))
changelog := clGen.Generate(testChangelogConfig)
require.Contains(t, changelog, "* `12345678` - commit message [by @test]")
require.NotContains(t, changelog, "* `deadbeef` - commit message (deadbeef) [by @test]")
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment