Skip to content

Commit 2fe4ae4

Browse files
feat: allow to customize the format commit template
1 parent 7821ef8 commit 2fe4ae4

File tree

3 files changed

+146
-65
lines changed

3 files changed

+146
-65
lines changed

go.mod

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,14 @@ module github.com/go-semantic-release/changelog-generator-default
22

33
go 1.19
44

5-
require github.com/go-semantic-release/semantic-release/v2 v2.25.0
5+
require (
6+
github.com/go-semantic-release/semantic-release/v2 v2.25.0
7+
github.com/stretchr/testify v1.8.1
8+
)
69

710
require (
811
github.com/Masterminds/semver/v3 v3.2.0 // indirect
12+
github.com/davecgh/go-spew v1.1.1 // indirect
913
github.com/fatih/color v1.13.0 // indirect
1014
github.com/fsnotify/fsnotify v1.6.0 // indirect
1115
github.com/golang/protobuf v1.5.2 // indirect
@@ -22,6 +26,7 @@ require (
2226
github.com/oklog/run v1.1.0 // indirect
2327
github.com/pelletier/go-toml v1.9.5 // indirect
2428
github.com/pelletier/go-toml/v2 v2.0.6 // indirect
29+
github.com/pmezard/go-difflib v1.0.0 // indirect
2530
github.com/spf13/afero v1.9.3 // indirect
2631
github.com/spf13/cast v1.5.0 // indirect
2732
github.com/spf13/cobra v1.6.1 // indirect

pkg/generator/changelog_generator.go

Lines changed: 31 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
package generator
22

33
import (
4+
"bytes"
45
"fmt"
56
"strings"
7+
"text/template"
68
"time"
79

810
"github.com/go-semantic-release/semantic-release/v2/pkg/generator"
@@ -16,32 +18,49 @@ func trimSHA(sha string) string {
1618
return sha[:8]
1719
}
1820

19-
func formatCommit(c *semrel.Commit) string {
20-
ret := "* "
21-
if c.Scope != "" {
22-
ret += fmt.Sprintf("**%s:** ", c.Scope)
21+
var templateFuncMap = template.FuncMap{
22+
"trimSHA": trimSHA,
23+
}
24+
25+
var defaultFormatCommitTemplateStr = `* {{with .Scope -}} **{{.}}:** {{end}} {{- .Message}} ({{trimSHA .SHA}})`
26+
27+
func formatCommit(tpl *template.Template, c *semrel.Commit) string {
28+
ret := &bytes.Buffer{}
29+
err := tpl.Execute(ret, c)
30+
if err != nil {
31+
panic(err)
2332
}
24-
ret += fmt.Sprintf("%s (%s)", c.Message, trimSHA(c.SHA))
25-
return ret
33+
return ret.String()
2634
}
2735

2836
var CGVERSION = "dev"
2937

3038
type DefaultChangelogGenerator struct {
31-
emojis bool
39+
emojis bool
40+
formatCommitTpl *template.Template
3241
}
3342

3443
func (g *DefaultChangelogGenerator) Init(m map[string]string) error {
3544
emojis := false
36-
3745
emojiConfig := m["emojis"]
38-
3946
if emojiConfig == "true" {
4047
emojis = true
4148
}
42-
4349
g.emojis = emojis
4450

51+
templateStr := defaultFormatCommitTemplateStr
52+
if tplStr := m["format_commit_template"]; tplStr != "" {
53+
templateStr = tplStr
54+
}
55+
56+
parsedTemplate, err := template.New("commit-template").
57+
Funcs(templateFuncMap).
58+
Parse(templateStr)
59+
if err != nil {
60+
return fmt.Errorf("failed to parse commit template: %w", err)
61+
}
62+
g.formatCommitTpl = parsedTemplate
63+
4564
return nil
4665
}
4766

@@ -61,14 +80,14 @@ func (g *DefaultChangelogGenerator) Generate(changelogConfig *generator.Changelo
6180
break
6281
}
6382
if commit.Change != nil && commit.Change.Major {
64-
bc := fmt.Sprintf("%s\n```\n%s\n```", formatCommit(commit), strings.Join(commit.Raw[1:], "\n"))
83+
bc := fmt.Sprintf("%s\n```\n%s\n```", formatCommit(g.formatCommitTpl, commit), strings.Join(commit.Raw[1:], "\n"))
6584
clTypes.AppendContent("%%bc%%", bc)
6685
continue
6786
}
6887
if commit.Type == "" {
6988
continue
7089
}
71-
clTypes.AppendContent(commit.Type, formatCommit(commit))
90+
clTypes.AppendContent(commit.Type, formatCommit(g.formatCommitTpl, commit))
7291
}
7392
for _, ct := range clTypes {
7493
if ct.Content == "" {
Lines changed: 109 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1,68 +1,125 @@
11
package generator
22

33
import (
4-
"strings"
54
"testing"
5+
"text/template"
66

77
"github.com/go-semantic-release/semantic-release/v2/pkg/generator"
88
"github.com/go-semantic-release/semantic-release/v2/pkg/semrel"
9+
"github.com/stretchr/testify/require"
910
)
1011

12+
var testCommits = []*semrel.Commit{
13+
{},
14+
{
15+
SHA: "123456789", Type: "feat", Scope: "app", Message: "commit message",
16+
Annotations: map[string]string{"author_login": "test"},
17+
},
18+
{
19+
SHA: "deadbeef", Type: "fix", Scope: "", Message: "commit message",
20+
Annotations: map[string]string{"author_login": "test"},
21+
},
22+
{
23+
SHA: "87654321", Type: "ci", Scope: "", Message: "commit message",
24+
Annotations: map[string]string{"author_login": "test"},
25+
},
26+
{
27+
SHA: "43218765", Type: "build", Scope: "", Message: "commit message",
28+
Annotations: map[string]string{"author_login": "test"},
29+
},
30+
{
31+
SHA: "12345678", Type: "yolo", Scope: "swag", Message: "commit message",
32+
},
33+
{
34+
SHA: "12345678", Type: "chore", Scope: "", Message: "commit message",
35+
Raw: []string{"", "BREAKING CHANGE: test"},
36+
Change: &semrel.Change{Major: true},
37+
Annotations: map[string]string{"author_login": "test"},
38+
},
39+
{
40+
SHA: "12345679", Type: "chore!", Scope: "user", Message: "another commit message",
41+
Raw: []string{"another commit message", "changed ID int into UUID"},
42+
Change: &semrel.Change{Major: true},
43+
},
44+
{
45+
SHA: "stop", Type: "chore", Scope: "", Message: "not included",
46+
},
47+
}
48+
49+
var testChangelogConfig = &generator.ChangelogGeneratorConfig{
50+
Commits: testCommits,
51+
LatestRelease: &semrel.Release{SHA: "stop"},
52+
NewVersion: "2.0.0",
53+
}
54+
1155
func TestDefaultGenerator(t *testing.T) {
12-
changelogConfig := &generator.ChangelogGeneratorConfig{}
13-
changelogConfig.Commits = []*semrel.Commit{
14-
{},
15-
{SHA: "123456789", Type: "feat", Scope: "app", Message: "commit message"},
16-
{SHA: "abcd", Type: "fix", Scope: "", Message: "commit message"},
17-
{SHA: "87654321", Type: "ci", Scope: "", Message: "commit message"},
18-
{SHA: "43218765", Type: "build", Scope: "", Message: "commit message"},
19-
{SHA: "12345678", Type: "yolo", Scope: "swag", Message: "commit message"},
20-
{SHA: "12345678", Type: "chore", Scope: "", Message: "commit message", Raw: []string{"", "BREAKING CHANGE: test"}, Change: &semrel.Change{Major: true}},
21-
{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}},
22-
{SHA: "stop", Type: "chore", Scope: "", Message: "not included"},
23-
}
24-
changelogConfig.LatestRelease = &semrel.Release{SHA: "stop"}
25-
changelogConfig.NewVersion = "2.0.0"
26-
generator := &DefaultChangelogGenerator{}
27-
changelog := generator.Generate(changelogConfig)
28-
if !strings.Contains(changelog, "* **app:** commit message (12345678)") ||
29-
!strings.Contains(changelog, "* commit message (abcd)") ||
30-
!strings.Contains(changelog, "#### yolo") ||
31-
!strings.Contains(changelog, "#### Build") ||
32-
!strings.Contains(changelog, "#### CI") ||
33-
!strings.Contains(changelog, "```\nBREAKING CHANGE: test\n```") ||
34-
strings.Contains(changelog, "not included") {
35-
t.Fail()
36-
}
56+
clGen := &DefaultChangelogGenerator{}
57+
require.NoError(t, clGen.Init(map[string]string{}))
58+
changelog := clGen.Generate(testChangelogConfig)
59+
60+
require.Contains(t, changelog, "* **app:** commit message (12345678)")
61+
require.Contains(t, changelog, "* commit message (deadbeef)")
62+
require.Contains(t, changelog, "#### yolo")
63+
require.Contains(t, changelog, "#### Build")
64+
require.Contains(t, changelog, "#### CI")
65+
require.Contains(t, changelog, "```\nBREAKING CHANGE: test\n```")
66+
require.NotContains(t, changelog, "not included")
3767
}
3868

3969
func TestEmojiGenerator(t *testing.T) {
40-
changelogConfig := &generator.ChangelogGeneratorConfig{}
41-
changelogConfig.Commits = []*semrel.Commit{
42-
{},
43-
{SHA: "123456789", Type: "feat", Scope: "app", Message: "commit message"},
44-
{SHA: "abcd", Type: "fix", Scope: "", Message: "commit message"},
45-
{SHA: "87654321", Type: "ci", Scope: "", Message: "commit message"},
46-
{SHA: "43218765", Type: "build", Scope: "", Message: "commit message"},
47-
{SHA: "12345678", Type: "yolo", Scope: "swag", Message: "commit message"},
48-
{SHA: "12345678", Type: "chore", Scope: "", Message: "commit message", Raw: []string{"", "BREAKING CHANGE: test"}, Change: &semrel.Change{Major: true}},
49-
{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}},
50-
{SHA: "stop", Type: "chore", Scope: "", Message: "not included"},
70+
clGen := &DefaultChangelogGenerator{}
71+
require.NoError(t, clGen.Init(map[string]string{"emojis": "true"}))
72+
changelog := clGen.Generate(testChangelogConfig)
73+
74+
require.Contains(t, changelog, "* **app:** commit message (12345678)")
75+
require.Contains(t, changelog, "* commit message (deadbeef)")
76+
require.Contains(t, changelog, "#### 🎁 Feature")
77+
require.Contains(t, changelog, "#### 🐞 Bug Fixes")
78+
require.Contains(t, changelog, "#### 🔁 CI")
79+
require.Contains(t, changelog, "#### 📦 Build")
80+
require.Contains(t, changelog, "#### 📣 Breaking Changes")
81+
require.Contains(t, changelog, "#### yolo")
82+
require.Contains(t, changelog, "```\nBREAKING CHANGE: test\n```")
83+
require.NotContains(t, changelog, "not included")
84+
}
85+
86+
func TestFormatCommit(t *testing.T) {
87+
testCases := []struct {
88+
tpl string
89+
commit *semrel.Commit
90+
expectedOutput string
91+
}{
92+
{
93+
tpl: defaultFormatCommitTemplateStr,
94+
commit: &semrel.Commit{SHA: "123456789", Type: "feat", Scope: "", Message: "commit message"},
95+
expectedOutput: "* commit message (12345678)",
96+
},
97+
{
98+
tpl: defaultFormatCommitTemplateStr,
99+
commit: &semrel.Commit{SHA: "123", Type: "feat", Scope: "app", Message: "commit message"},
100+
expectedOutput: "* **app:** commit message (123)",
101+
},
102+
{
103+
tpl: `* {{.SHA}} - {{.Message}} {{- with index .Annotations "author_login" }} [by @{{.}}] {{- end}}`,
104+
commit: &semrel.Commit{SHA: "deadbeef", Type: "fix", Message: "custom template", Annotations: map[string]string{"author_login": "test"}},
105+
expectedOutput: "* deadbeef - custom template [by @test]",
106+
},
51107
}
52-
changelogConfig.LatestRelease = &semrel.Release{SHA: "stop"}
53-
changelogConfig.NewVersion = "2.0.0"
54-
generator := &DefaultChangelogGenerator{emojis: true}
55-
changelog := generator.Generate(changelogConfig)
56-
if !strings.Contains(changelog, "* **app:** commit message (12345678)") ||
57-
!strings.Contains(changelog, "* commit message (abcd)") ||
58-
!strings.Contains(changelog, "#### 🎁 Feature") ||
59-
!strings.Contains(changelog, "#### 🐞 Bug Fixes") ||
60-
!strings.Contains(changelog, "#### 🔁 CI") ||
61-
!strings.Contains(changelog, "#### 📦 Build") ||
62-
!strings.Contains(changelog, "#### 📣 Breaking Changes") ||
63-
!strings.Contains(changelog, "#### yolo") ||
64-
!strings.Contains(changelog, "```\nBREAKING CHANGE: test\n```") ||
65-
strings.Contains(changelog, "not included") {
66-
t.Fail()
108+
for _, tc := range testCases {
109+
t.Run(tc.expectedOutput, func(t *testing.T) {
110+
tpl := template.Must(template.New("test").Funcs(templateFuncMap).Parse(tc.tpl))
111+
output := formatCommit(tpl, tc.commit)
112+
require.Equal(t, tc.expectedOutput, output)
113+
})
67114
}
68115
}
116+
117+
func TestFormatCommitWithCustomTemplate(t *testing.T) {
118+
clGen := &DefaultChangelogGenerator{}
119+
require.NoError(t, clGen.Init(map[string]string{
120+
"format_commit_template": "* `{{ trimSHA .SHA}}` - {{.Message}} {{- with index .Annotations \"author_login\" }} [by @{{.}}] {{- end}}",
121+
}))
122+
changelog := clGen.Generate(testChangelogConfig)
123+
require.Contains(t, changelog, "* `12345678` - commit message [by @test]")
124+
require.NotContains(t, changelog, "* `deadbeef` - commit message (deadbeef) [by @test]")
125+
}

0 commit comments

Comments
 (0)