mirror of
https://github.com/thegeeklab/git-sv.git
synced 2024-11-21 22:10:39 +00:00
Merge pull request #43 from bvieira/#40
Feature: support templates for release-notes and changelog
This commit is contained in:
commit
cf43b2af4e
@ -1,4 +1,4 @@
|
||||
version: "1.0"
|
||||
version: "1.1"
|
||||
|
||||
versioning:
|
||||
update-major: []
|
||||
|
126
README.md
126
README.md
@ -1,6 +1,6 @@
|
||||
<p align="center">
|
||||
<h1 align="center">sv4git</h1>
|
||||
<p align="center">semantic version for git</p>
|
||||
<p align="center">A command line tool (CLI) to validate commit messages, bump version, create tags and generate changelogs!</p>
|
||||
<p align="center">
|
||||
<a href="https://github.com/bvieira/sv4git/releases/latest"><img alt="Release" src="https://img.shields.io/github/release/bvieira/sv4git.svg?style=for-the-badge"></a>
|
||||
<a href="https://pkg.go.dev/github.com/bvieira/sv4git/v2"><img alt="Go Reference" src="https://img.shields.io/badge/-Reference-blue?style=for-the-badge&logo=go&labelColor=gray"></a>
|
||||
@ -26,11 +26,17 @@
|
||||
If you want to install from source using `go install`, just run:
|
||||
|
||||
```bash
|
||||
# keep in mind that with this, it will compile from source and won't show the version on cli -h.
|
||||
go install github.com/bvieira/sv4git/v2/cmd/git-sv@latest
|
||||
|
||||
# if you want to add the version on the binary, run this command instead.
|
||||
SV4GIT_VERSION=$(go list -f '{{ .Version }}' -m github.com/bvieira/sv4git/v2@latest | sed 's/v//') && go install --ldflags "-X main.Version=$SV4GIT_VERSION" github.com/bvieira/sv4git/v2/cmd/git-sv@v$SV4GIT_VERSION
|
||||
```
|
||||
|
||||
### Config
|
||||
|
||||
#### YAML
|
||||
|
||||
There are 3 config levels when using sv4git: [default](#default), [user](#user), [repository](#repository). All of them are merged considering the follow priority: **repository > user > default**.
|
||||
|
||||
To see the current config, run:
|
||||
@ -39,9 +45,9 @@ To see the current config, run:
|
||||
git sv cfg show
|
||||
```
|
||||
|
||||
#### Configuration Types
|
||||
##### Configuration Types
|
||||
|
||||
##### Default
|
||||
###### Default
|
||||
|
||||
To check the default configuration, run:
|
||||
|
||||
@ -49,7 +55,7 @@ To check the default configuration, run:
|
||||
git sv cfg default
|
||||
```
|
||||
|
||||
##### User
|
||||
###### User
|
||||
|
||||
For user config, it is necessary to define the `SV4GIT_HOME` environment variable, eg.:
|
||||
|
||||
@ -64,14 +70,14 @@ And create a `config.yml` file inside it, eg.:
|
||||
└── config.yml
|
||||
```
|
||||
|
||||
##### Repository
|
||||
###### Repository
|
||||
|
||||
Create a `.sv4git.yml` file on the root of your repository, eg.: [.sv4git.yml](.sv4git.yml).
|
||||
|
||||
#### Configuration format
|
||||
##### Configuration format
|
||||
|
||||
```yml
|
||||
version: "1.0" #config version
|
||||
version: "1.1" #config version
|
||||
|
||||
versioning: # versioning bump
|
||||
update-major: [] # Commit types used to bump major.
|
||||
@ -85,14 +91,25 @@ tag:
|
||||
pattern: '%d.%d.%d' # Pattern used to create git tag.
|
||||
|
||||
release-notes:
|
||||
# Headers names for release notes markdown. To disable a section just remove the header line.
|
||||
# It's possible to add other commit types, the release note will be created respecting the following order:
|
||||
# feat, fix, refactor, perf, test, build, ci, chore, docs, style, breaking-change
|
||||
# Deprecated!!! please use 'sections' instead!
|
||||
# Headers names for release notes markdown. To disable a section just remove the header
|
||||
# line. It's possible to add other commit types, the release note will be created
|
||||
# respecting the following order: feat, fix, refactor, perf, test, build, ci, chore, docs, style, breaking-change.
|
||||
headers:
|
||||
breaking-change: Breaking Changes
|
||||
feat: Features
|
||||
fix: Bug Fixes
|
||||
|
||||
sections: # Array with each section of release note. Check template section for more information.
|
||||
- name: Features # Name used on section.
|
||||
section-type: commits # Type of the section, supported types: commits, breaking-changes.
|
||||
commit-types: [feat] # Commit types for commit section-type, one commit type cannot be in more than one section.
|
||||
- name: Bug Fixes
|
||||
section-type: commits
|
||||
commit-types: [fix]
|
||||
- name: Breaking Changes
|
||||
section-type: breaking-changes
|
||||
|
||||
branches: # Git branches config.
|
||||
prefix: ([a-z]+\/)? # Prefix used on branch name, it should be a regex group.
|
||||
suffix: (-.*)? # Suffix used on branch name, it should be a regex group.
|
||||
@ -116,6 +133,95 @@ commit-message:
|
||||
regex: '[A-Z]+-[0-9]+' # Regex for issue id.
|
||||
```
|
||||
|
||||
#### Templates
|
||||
|
||||
**sv4git** uses *go templates* to format the output for `release-notes` and `changelog`, to see how the default template is configured check [template directory](cmd/git-sv/resources/templates). On v2.7.0+, its possible to overwrite the default configuration by adding `.sv4git/templates` on your repository. The cli expects that at least 2 files exists on your directory: `changelog-md.tpl` and `releasenotes-md.tpl`.
|
||||
|
||||
```bash
|
||||
.sv4git
|
||||
└── templates
|
||||
├── changelog-md.tpl
|
||||
└── releasenotes-md.tpl
|
||||
```
|
||||
|
||||
Everything inside `.sv4git/templates` will be loaded, so it's possible to add more files to be used as needed.
|
||||
|
||||
##### Variables
|
||||
|
||||
To execute the template the `releasenotes-md.tpl` will receive a single **ReleaseNote** and `changelog-md.tpl` will receive a list of **ReleaseNote** as variables.
|
||||
|
||||
Each **ReleaseNoteSection** will be configured according with `release-notes.section` from config file. The order for each section will be maintained and the **SectionType** is defined according with `section-type` attribute as described on the table below.
|
||||
|
||||
| section-type | ReleaseNoteSection |
|
||||
| -- | -- |
|
||||
| commits | ReleaseNoteCommitsSection |
|
||||
| breaking-changes | ReleaseNoteBreakingChangeSection |
|
||||
|
||||
> :warning: currently only `commits` and `breaking-changes` are supported as `section-types`, using a different value for this field will make the section to be removed from the template variables.
|
||||
|
||||
Check below the variables available:
|
||||
|
||||
```go
|
||||
ReleaseNote
|
||||
Release string // 'v' followed by version if present, if not tag will be used instead.
|
||||
Tag string // Current tag, if available.
|
||||
Version *Version // Version from tag or next version according with semver.
|
||||
Date time.Time
|
||||
Sections []ReleaseNoteSection // ReleaseNoteCommitsSection or ReleaseNoteBreakingChangeSection
|
||||
AuthorNames []string // Author names recovered from commit message (user.name from git)
|
||||
|
||||
Version
|
||||
Major int
|
||||
Minor int
|
||||
Patch int
|
||||
Prerelease string
|
||||
Metadata string
|
||||
Original string
|
||||
|
||||
ReleaseNoteCommitsSection // SectionType == commits
|
||||
SectionType string
|
||||
SectionName string
|
||||
Types []string
|
||||
Items []GitCommitLog
|
||||
HasMultipleTypes bool
|
||||
|
||||
ReleaseNoteBreakingChangeSection // SectionType == breaking-changes
|
||||
SectionType string
|
||||
SectionName string
|
||||
Messages []string
|
||||
|
||||
GitCommitLog
|
||||
Date string
|
||||
Timestamp int
|
||||
AuthorName string
|
||||
Hash string
|
||||
Message CommitMessage
|
||||
|
||||
CommitMessage
|
||||
Type string
|
||||
Scope string
|
||||
Description string
|
||||
Body string
|
||||
IsBreakingChange bool
|
||||
Metadata map[string]string
|
||||
```
|
||||
|
||||
##### Functions
|
||||
|
||||
Beside the [go template functions](https://pkg.go.dev/text/template#hdr-Functions), the folowing functions are availiable to use in the templates. Check [formatter_functions.go](sv/formatter_functions.go) to see the functions implementation.
|
||||
|
||||
###### timefmt
|
||||
|
||||
**Usage:** timefmt time "2006-01-02"
|
||||
|
||||
Receive a time.Time and a layout string and returns a textual representation of the time according with the layout provided. Check <https://pkg.go.dev/time#Time.Format> for more information.
|
||||
|
||||
###### getsection
|
||||
|
||||
**Usage:** getsection sections "Features"
|
||||
|
||||
Receive a list of ReleaseNoteSection and a Section name and returns a section with the provided name. If no section is found, it will return `nil`.
|
||||
|
||||
### Running
|
||||
|
||||
Run `git-sv` to get the list of available parameters:
|
||||
|
@ -70,15 +70,21 @@ func readConfig(filepath string) (Config, error) {
|
||||
func defaultConfig() Config {
|
||||
skipDetached := false
|
||||
return Config{
|
||||
Version: "1.0",
|
||||
Version: "1.1",
|
||||
Versioning: sv.VersioningConfig{
|
||||
UpdateMajor: []string{},
|
||||
UpdateMinor: []string{"feat"},
|
||||
UpdatePatch: []string{"build", "ci", "chore", "docs", "fix", "perf", "refactor", "style", "test"},
|
||||
IgnoreUnknown: false,
|
||||
},
|
||||
Tag: sv.TagConfig{Pattern: "%d.%d.%d"},
|
||||
ReleaseNotes: sv.ReleaseNotesConfig{Headers: map[string]string{"fix": "Bug Fixes", "feat": "Features", "breaking-change": "Breaking Changes"}},
|
||||
Tag: sv.TagConfig{Pattern: "%d.%d.%d"},
|
||||
ReleaseNotes: sv.ReleaseNotesConfig{
|
||||
Sections: []sv.ReleaseNotesSectionConfig{
|
||||
{Name: "Features", SectionType: sv.ReleaseNotesSectionTypeCommits, CommitTypes: []string{"feat"}},
|
||||
{Name: "Bug Fixes", SectionType: sv.ReleaseNotesSectionTypeCommits, CommitTypes: []string{"fix"}},
|
||||
{Name: "Breaking Changes", SectionType: sv.ReleaseNotesSectionTypeBreakingChanges},
|
||||
},
|
||||
},
|
||||
Branches: sv.BranchesConfig{
|
||||
Prefix: "([a-z]+\\/)?",
|
||||
Suffix: "(-.*)?",
|
||||
@ -129,3 +135,35 @@ func (t *mergeTransformer) Transformer(typ reflect.Type) func(dst, src reflect.V
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func migrateConfig(cfg Config, filename string) Config {
|
||||
if cfg.ReleaseNotes.Headers == nil {
|
||||
return cfg
|
||||
}
|
||||
warnf("config 'release-notes.headers' on %s is deprecated, please use 'sections' instead!", filename)
|
||||
|
||||
return Config{
|
||||
Version: cfg.Version,
|
||||
Versioning: cfg.Versioning,
|
||||
Tag: cfg.Tag,
|
||||
ReleaseNotes: sv.ReleaseNotesConfig{
|
||||
Sections: migrateReleaseNotesConfig(cfg.ReleaseNotes.Headers),
|
||||
},
|
||||
Branches: cfg.Branches,
|
||||
CommitMessage: cfg.CommitMessage,
|
||||
}
|
||||
}
|
||||
|
||||
func migrateReleaseNotesConfig(headers map[string]string) []sv.ReleaseNotesSectionConfig {
|
||||
order := []string{"feat", "fix", "refactor", "perf", "test", "build", "ci", "chore", "docs", "style"}
|
||||
var sections []sv.ReleaseNotesSectionConfig
|
||||
for _, key := range order {
|
||||
if name, exists := headers[key]; exists {
|
||||
sections = append(sections, sv.ReleaseNotesSectionConfig{Name: name, SectionType: sv.ReleaseNotesSectionTypeCommits, CommitTypes: []string{key}})
|
||||
}
|
||||
}
|
||||
if name, exists := headers["breaking-change"]; exists {
|
||||
sections = append(sections, sv.ReleaseNotesSectionConfig{Name: name, SectionType: sv.ReleaseNotesSectionTypeBreakingChanges})
|
||||
}
|
||||
return sections
|
||||
}
|
||||
|
@ -1,7 +1,10 @@
|
||||
package main
|
||||
|
||||
import "fmt"
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
)
|
||||
|
||||
func warnf(format string, values ...interface{}) {
|
||||
fmt.Printf("WARN: "+format+"\n", values...)
|
||||
fmt.Fprintf(os.Stderr, "WARN: "+format+"\n", values...)
|
||||
}
|
||||
|
@ -1,6 +1,8 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"embed"
|
||||
"io/fs"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
@ -15,17 +17,36 @@ var Version = "source"
|
||||
const (
|
||||
configFilename = "config.yml"
|
||||
repoConfigFilename = ".sv4git.yml"
|
||||
configDir = ".sv4git"
|
||||
)
|
||||
|
||||
var (
|
||||
//go:embed resources/templates/*.tpl
|
||||
defaultTemplatesFS embed.FS
|
||||
)
|
||||
|
||||
func templateFS(filepath string) fs.FS {
|
||||
if _, err := os.Stat(filepath); err != nil {
|
||||
defaultTemplatesFS, _ := fs.Sub(defaultTemplatesFS, "resources/templates")
|
||||
return defaultTemplatesFS
|
||||
}
|
||||
return os.DirFS(filepath)
|
||||
}
|
||||
|
||||
func main() {
|
||||
log.SetFlags(0)
|
||||
|
||||
cfg := loadCfg()
|
||||
repoPath, rerr := getRepoPath()
|
||||
if rerr != nil {
|
||||
log.Fatal("failed to discovery repository top level, error: ", rerr)
|
||||
}
|
||||
|
||||
cfg := loadCfg(repoPath)
|
||||
messageProcessor := sv.NewMessageProcessor(cfg.CommitMessage, cfg.Branches)
|
||||
git := sv.NewGit(messageProcessor, cfg.Tag)
|
||||
semverProcessor := sv.NewSemVerCommitsProcessor(cfg.Versioning, cfg.CommitMessage)
|
||||
releasenotesProcessor := sv.NewReleaseNoteProcessor(cfg.ReleaseNotes)
|
||||
outputFormatter := sv.NewOutputFormatter()
|
||||
outputFormatter := sv.NewOutputFormatter(templateFS(filepath.Join(repoPath, configDir, "templates")))
|
||||
|
||||
app := cli.NewApp()
|
||||
app.Name = "sv"
|
||||
@ -145,26 +166,22 @@ func main() {
|
||||
}
|
||||
}
|
||||
|
||||
func loadCfg() Config {
|
||||
envCfg := loadEnvConfig()
|
||||
|
||||
func loadCfg(repoPath string) Config {
|
||||
cfg := defaultConfig()
|
||||
|
||||
envCfg := loadEnvConfig()
|
||||
if envCfg.Home != "" {
|
||||
if homeCfg, err := readConfig(filepath.Join(envCfg.Home, configFilename)); err == nil {
|
||||
if merr := merge(&cfg, homeCfg); merr != nil {
|
||||
homeCfgFilepath := filepath.Join(envCfg.Home, configFilename)
|
||||
if homeCfg, err := readConfig(homeCfgFilepath); err == nil {
|
||||
if merr := merge(&cfg, migrateConfig(homeCfg, homeCfgFilepath)); merr != nil {
|
||||
log.Fatal("failed to merge user config, error: ", merr)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
repoPath, rerr := getRepoPath()
|
||||
if rerr != nil {
|
||||
log.Fatal("failed to get repository path, error: ", rerr)
|
||||
}
|
||||
|
||||
if repoCfg, err := readConfig(filepath.Join(repoPath, repoConfigFilename)); err == nil {
|
||||
if merr := merge(&cfg, repoCfg); merr != nil {
|
||||
repoCfgFilepath := filepath.Join(repoPath, repoConfigFilename)
|
||||
if repoCfg, err := readConfig(repoCfgFilepath); err == nil {
|
||||
if merr := merge(&cfg, migrateConfig(repoCfg, repoCfgFilepath)); merr != nil {
|
||||
log.Fatal("failed to merge repo config, error: ", merr)
|
||||
}
|
||||
if len(repoCfg.ReleaseNotes.Headers) > 0 { // mergo is merging maps, headers will be overwritten
|
||||
|
6
cmd/git-sv/resources/templates/changelog-md.tpl
Normal file
6
cmd/git-sv/resources/templates/changelog-md.tpl
Normal file
@ -0,0 +1,6 @@
|
||||
# Changelog
|
||||
{{- range .}}
|
||||
|
||||
{{template "releasenotes-md.tpl" .}}
|
||||
---
|
||||
{{- end}}
|
8
cmd/git-sv/resources/templates/releasenotes-md.tpl
Normal file
8
cmd/git-sv/resources/templates/releasenotes-md.tpl
Normal file
@ -0,0 +1,8 @@
|
||||
## {{if .Release}}{{.Release}}{{end}}{{if and (not .Date.IsZero) .Release}} ({{end}}{{timefmt .Date "2006-01-02"}}{{if and (not .Date.IsZero) .Release}}){{end}}
|
||||
{{- range $section := .Sections }}
|
||||
{{- if (eq $section.SectionType "commits") }}
|
||||
{{- template "rn-md-section-commits.tpl" $section }}
|
||||
{{- else if (eq $section.SectionType "breaking-changes")}}
|
||||
{{- template "rn-md-section-breaking-changes.tpl" $section }}
|
||||
{{- end}}
|
||||
{{- end}}
|
@ -0,0 +1,7 @@
|
||||
{{- if ne .Name ""}}
|
||||
|
||||
### {{.Name}}
|
||||
{{range $k,$v := .Messages}}
|
||||
- {{$v}}
|
||||
{{- end}}
|
||||
{{- end}}
|
7
cmd/git-sv/resources/templates/rn-md-section-commits.tpl
Normal file
7
cmd/git-sv/resources/templates/rn-md-section-commits.tpl
Normal file
@ -0,0 +1,7 @@
|
||||
{{- if .}}{{- if ne .SectionName ""}}
|
||||
|
||||
### {{.SectionName}}
|
||||
{{range $k,$v := .Items}}
|
||||
- {{if $v.Message.Scope}}**{{$v.Message.Scope}}:** {{end}}{{$v.Message.Description}} ({{$v.Hash}}){{if $v.Message.Metadata.issue}} ({{$v.Message.Metadata.issue}}){{end}}
|
||||
{{- end}}
|
||||
{{- end}}{{- end}}
|
24
cmd/git-sv/resources_test.go
Normal file
24
cmd/git-sv/resources_test.go
Normal file
@ -0,0 +1,24 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func Test_checkTemplatesFiles(t *testing.T) {
|
||||
tests := []string{
|
||||
"resources/templates/changelog-md.tpl",
|
||||
"resources/templates/releasenotes-md.tpl",
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt, func(t *testing.T) {
|
||||
got, err := defaultTemplatesFS.ReadFile(tt)
|
||||
if err != nil {
|
||||
t.Errorf("missing template error = %v", err)
|
||||
return
|
||||
}
|
||||
if len(got) <= 0 {
|
||||
t.Errorf("empty template")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
26
sv/config.go
26
sv/config.go
@ -68,5 +68,29 @@ type TagConfig struct {
|
||||
|
||||
// ReleaseNotesConfig release notes preferences.
|
||||
type ReleaseNotesConfig struct {
|
||||
Headers map[string]string `yaml:"headers"`
|
||||
Headers map[string]string `yaml:"headers,omitempty"`
|
||||
Sections []ReleaseNotesSectionConfig `yaml:"sections"`
|
||||
}
|
||||
|
||||
func (cfg ReleaseNotesConfig) sectionConfig(sectionType string) *ReleaseNotesSectionConfig {
|
||||
for _, sectionCfg := range cfg.Sections {
|
||||
if sectionCfg.SectionType == sectionType {
|
||||
return §ionCfg
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ReleaseNotesSectionConfig preferences for a single section on release notes.
|
||||
type ReleaseNotesSectionConfig struct {
|
||||
Name string `yaml:"name"`
|
||||
SectionType string `yaml:"section-type"`
|
||||
CommitTypes []string `yaml:"commit-types,flow,omitempty"`
|
||||
}
|
||||
|
||||
const (
|
||||
// ReleaseNotesSectionTypeCommits ReleaseNotesSectionConfig.SectionType value.
|
||||
ReleaseNotesSectionTypeCommits = "commits"
|
||||
// ReleaseNotesSectionTypeBreakingChanges ReleaseNotesSectionConfig.SectionType value.
|
||||
ReleaseNotesSectionTypeBreakingChanges = "breaking-changes"
|
||||
)
|
||||
|
104
sv/formatter.go
104
sv/formatter.go
@ -2,53 +2,23 @@ package sv
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io/fs"
|
||||
"sort"
|
||||
"text/template"
|
||||
"time"
|
||||
|
||||
"github.com/Masterminds/semver/v3"
|
||||
)
|
||||
|
||||
type releaseNoteTemplateVariables struct {
|
||||
Release string
|
||||
Date string
|
||||
Sections map[string]ReleaseNoteSection
|
||||
Order []string
|
||||
BreakingChanges BreakingChangeSection
|
||||
Release string
|
||||
Tag string
|
||||
Version *semver.Version
|
||||
Date time.Time
|
||||
Sections []ReleaseNoteSection
|
||||
AuthorNames []string
|
||||
}
|
||||
|
||||
const (
|
||||
cglTemplate = `# Changelog
|
||||
{{- range .}}
|
||||
|
||||
{{template "rnTemplate" .}}
|
||||
---
|
||||
{{- end}}
|
||||
`
|
||||
|
||||
rnSectionItem = "- {{if .Message.Scope}}**{{.Message.Scope}}:** {{end}}{{.Message.Description}} ({{.Hash}}){{if .Message.Metadata.issue}} ({{.Message.Metadata.issue}}){{end}}"
|
||||
|
||||
rnSection = `{{- if .}}{{- if ne .Name ""}}
|
||||
|
||||
### {{.Name}}
|
||||
{{range $k,$v := .Items}}
|
||||
{{template "rnSectionItem" $v}}
|
||||
{{- end}}
|
||||
{{- end}}{{- end}}`
|
||||
|
||||
rnSectionBreakingChanges = `{{- if ne .Name ""}}
|
||||
|
||||
### {{.Name}}
|
||||
{{range $k,$v := .Messages}}
|
||||
- {{$v}}
|
||||
{{- end}}
|
||||
{{- end}}`
|
||||
|
||||
rnTemplate = `## {{if .Release}}{{.Release}}{{end}}{{if and .Date .Release}} ({{end}}{{.Date}}{{if and .Date .Release}}){{end}}
|
||||
{{- $sections := .Sections }}
|
||||
{{- range $key := .Order }}
|
||||
{{- template "rnSection" (index $sections $key) }}
|
||||
{{- end}}
|
||||
{{- template "rnSectionBreakingChanges" .BreakingChanges}}
|
||||
`
|
||||
)
|
||||
|
||||
// OutputFormatter output formatter interface.
|
||||
type OutputFormatter interface {
|
||||
FormatReleaseNote(releasenote ReleaseNote) (string, error)
|
||||
@ -57,24 +27,23 @@ type OutputFormatter interface {
|
||||
|
||||
// OutputFormatterImpl formater for release note and changelog.
|
||||
type OutputFormatterImpl struct {
|
||||
releasenoteTemplate *template.Template
|
||||
changelogTemplate *template.Template
|
||||
templates *template.Template
|
||||
}
|
||||
|
||||
// NewOutputFormatter TemplateProcessor constructor.
|
||||
func NewOutputFormatter() *OutputFormatterImpl {
|
||||
cgl := template.Must(template.New("cglTemplate").Parse(cglTemplate))
|
||||
rn := template.Must(cgl.New("rnTemplate").Parse(rnTemplate))
|
||||
template.Must(rn.New("rnSectionItem").Parse(rnSectionItem))
|
||||
template.Must(rn.New("rnSection").Parse(rnSection))
|
||||
template.Must(rn.New("rnSectionBreakingChanges").Parse(rnSectionBreakingChanges))
|
||||
return &OutputFormatterImpl{releasenoteTemplate: rn, changelogTemplate: cgl}
|
||||
func NewOutputFormatter(templatesFS fs.FS) *OutputFormatterImpl {
|
||||
templateFNs := map[string]interface{}{
|
||||
"timefmt": timeFormat,
|
||||
"getsection": getSection,
|
||||
}
|
||||
tpls := template.Must(template.New("templates").Funcs(templateFNs).ParseFS(templatesFS, "*"))
|
||||
return &OutputFormatterImpl{templates: tpls}
|
||||
}
|
||||
|
||||
// FormatReleaseNote format a release note.
|
||||
func (p OutputFormatterImpl) FormatReleaseNote(releasenote ReleaseNote) (string, error) {
|
||||
var b bytes.Buffer
|
||||
if err := p.releasenoteTemplate.Execute(&b, releaseNoteVariables(releasenote)); err != nil {
|
||||
if err := p.templates.ExecuteTemplate(&b, "releasenotes-md.tpl", releaseNoteVariables(releasenote)); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return b.String(), nil
|
||||
@ -88,29 +57,34 @@ func (p OutputFormatterImpl) FormatChangelog(releasenotes []ReleaseNote) (string
|
||||
}
|
||||
|
||||
var b bytes.Buffer
|
||||
if err := p.changelogTemplate.Execute(&b, templateVars); err != nil {
|
||||
if err := p.templates.ExecuteTemplate(&b, "changelog-md.tpl", templateVars); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return b.String(), nil
|
||||
}
|
||||
|
||||
func releaseNoteVariables(releasenote ReleaseNote) releaseNoteTemplateVariables {
|
||||
date := ""
|
||||
if !releasenote.Date.IsZero() {
|
||||
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,
|
||||
Date: date,
|
||||
Sections: releasenote.Sections,
|
||||
Order: []string{"feat", "fix", "refactor", "perf", "test", "build", "ci", "chore", "docs", "style"},
|
||||
BreakingChanges: releasenote.BreakingChanges,
|
||||
Release: release,
|
||||
Tag: releasenote.Tag,
|
||||
Version: releasenote.Version,
|
||||
Date: releasenote.Date,
|
||||
Sections: releasenote.Sections,
|
||||
AuthorNames: toSortedArray(releasenote.AuthorsNames),
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
19
sv/formatter_functions.go
Normal file
19
sv/formatter_functions.go
Normal file
@ -0,0 +1,19 @@
|
||||
package sv
|
||||
|
||||
import "time"
|
||||
|
||||
func timeFormat(t time.Time, format string) string {
|
||||
if t.IsZero() {
|
||||
return ""
|
||||
}
|
||||
return t.Format(format)
|
||||
}
|
||||
|
||||
func getSection(sections []ReleaseNoteSection, name string) ReleaseNoteSection {
|
||||
for _, section := range sections {
|
||||
if section.SectionName() == name {
|
||||
return section
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
45
sv/formatter_functions_test.go
Normal file
45
sv/formatter_functions_test.go
Normal file
@ -0,0 +1,45 @@
|
||||
package sv
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func Test_timeFormat(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
time time.Time
|
||||
format string
|
||||
want string
|
||||
}{
|
||||
{"valid time", time.Date(2022, 1, 1, 0, 0, 0, 0, time.UTC), "2006-01-02", "2022-01-01"},
|
||||
{"empty time", time.Time{}, "2006-01-02", ""},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := timeFormat(tt.time, tt.format); got != tt.want {
|
||||
t.Errorf("timeFormat() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_getSection(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
sections []ReleaseNoteSection
|
||||
sectionName string
|
||||
want ReleaseNoteSection
|
||||
}{
|
||||
{"existing section", []ReleaseNoteSection{ReleaseNoteCommitsSection{Name: "section 0"}, ReleaseNoteCommitsSection{Name: "section 1"}, ReleaseNoteCommitsSection{Name: "section 2"}}, "section 1", ReleaseNoteCommitsSection{Name: "section 1"}},
|
||||
{"nonexisting section", []ReleaseNoteSection{ReleaseNoteCommitsSection{Name: "section 0"}, ReleaseNoteCommitsSection{Name: "section 1"}, ReleaseNoteCommitsSection{Name: "section 2"}}, "section 10", nil},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := getSection(tt.sections, tt.sectionName); !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("getSection() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
@ -1,12 +1,16 @@
|
||||
package sv
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/Masterminds/semver/v3"
|
||||
)
|
||||
|
||||
var templatesFS = os.DirFS("../cmd/git-sv/resources/templates")
|
||||
|
||||
var dateChangelog = `## v1.0.0 (2020-05-01)
|
||||
`
|
||||
|
||||
@ -55,7 +59,7 @@ func TestOutputFormatterImpl_FormatReleaseNote(t *testing.T) {
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := NewOutputFormatter().FormatReleaseNote(tt.input)
|
||||
got, err := NewOutputFormatter(templatesFS).FormatReleaseNote(tt.input)
|
||||
if got != tt.want {
|
||||
t.Errorf("OutputFormatterImpl.FormatReleaseNote() = %v, want %v", got, tt.want)
|
||||
}
|
||||
@ -78,10 +82,57 @@ 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{})}),
|
||||
sections := []ReleaseNoteSection{
|
||||
newReleaseNoteCommitsSection("Features", []string{"feat"}, []GitCommitLog{commitlog("feat", map[string]string{}, "a")}),
|
||||
newReleaseNoteCommitsSection("Bug Fixes", []string{"fix"}, []GitCommitLog{commitlog("fix", map[string]string{}, "a")}),
|
||||
newReleaseNoteCommitsSection("Build", []string{"build"}, []GitCommitLog{commitlog("build", map[string]string{}, "a")}),
|
||||
ReleaseNoteBreakingChangeSection{"Breaking Changes", []string{"break change message"}},
|
||||
}
|
||||
return releaseNote(v, tag, date, sections, []string{"break change message"})
|
||||
return releaseNote(v, tag, date, sections, map[string]struct{}{"a": {}})
|
||||
}
|
||||
|
||||
func Test_checkTemplatesExecution(t *testing.T) {
|
||||
tpls := NewOutputFormatter(templatesFS).templates
|
||||
tests := []struct {
|
||||
template string
|
||||
variables interface{}
|
||||
}{
|
||||
{"changelog-md.tpl", changelogVariables("v1.0.0", "v1.0.1")},
|
||||
{"releasenotes-md.tpl", releaseNotesVariables("v1.0.0")},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.template, func(t *testing.T) {
|
||||
var b bytes.Buffer
|
||||
err := tpls.ExecuteTemplate(&b, tt.template, tt.variables)
|
||||
if err != nil {
|
||||
t.Errorf("invalid template err = %v", err)
|
||||
return
|
||||
}
|
||||
if len(b.Bytes()) <= 0 {
|
||||
t.Errorf("empty template")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func releaseNotesVariables(release string) releaseNoteTemplateVariables {
|
||||
return releaseNoteTemplateVariables{
|
||||
Release: release,
|
||||
Date: time.Date(2006, 1, 02, 0, 0, 0, 0, time.UTC),
|
||||
Sections: []ReleaseNoteSection{
|
||||
newReleaseNoteCommitsSection("Features", []string{"feat"}, []GitCommitLog{commitlog("feat", map[string]string{}, "a")}),
|
||||
newReleaseNoteCommitsSection("Bug Fixes", []string{"fix"}, []GitCommitLog{commitlog("fix", map[string]string{}, "a")}),
|
||||
newReleaseNoteCommitsSection("Build", []string{"build"}, []GitCommitLog{commitlog("build", map[string]string{}, "a")}),
|
||||
ReleaseNoteBreakingChangeSection{"Breaking Changes", []string{"break change message"}},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func changelogVariables(releases ...string) []releaseNoteTemplateVariables {
|
||||
var variables []releaseNoteTemplateVariables
|
||||
for _, r := range releases {
|
||||
variables = append(variables, releaseNotesVariables(r))
|
||||
}
|
||||
return variables
|
||||
|
||||
}
|
||||
|
20
sv/git.go
20
sv/git.go
@ -7,6 +7,7 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@ -31,9 +32,11 @@ type Git interface {
|
||||
|
||||
// GitCommitLog description of a single commit log.
|
||||
type GitCommitLog struct {
|
||||
Date string `json:"date,omitempty"`
|
||||
Hash string `json:"hash,omitempty"`
|
||||
Message CommitMessage `json:"message,omitempty"`
|
||||
Date string `json:"date,omitempty"`
|
||||
Timestamp int `json:"timestamp,omitempty"`
|
||||
AuthorName string `json:"authorName,omitempty"`
|
||||
Hash string `json:"hash,omitempty"`
|
||||
Message CommitMessage `json:"message,omitempty"`
|
||||
}
|
||||
|
||||
// GitTag git tag info.
|
||||
@ -90,7 +93,7 @@ func (GitImpl) LastTag() string {
|
||||
|
||||
// Log return git log.
|
||||
func (g GitImpl) Log(lr LogRange) ([]GitCommitLog, error) {
|
||||
format := "--pretty=format:\"%ad" + logSeparator + "%h" + logSeparator + "%s" + logSeparator + "%b" + endLine + "\""
|
||||
format := "--pretty=format:\"%ad" + logSeparator + "%at" + logSeparator + "%cN" + logSeparator + "%h" + logSeparator + "%s" + logSeparator + "%b" + endLine + "\""
|
||||
params := []string{"log", "--date=short", format}
|
||||
|
||||
if lr.start != "" || lr.end != "" {
|
||||
@ -200,10 +203,13 @@ func parseLogOutput(messageProcessor MessageProcessor, log string) []GitCommitLo
|
||||
func parseCommitLog(messageProcessor MessageProcessor, commit string) GitCommitLog {
|
||||
content := strings.Split(strings.Trim(commit, "\""), logSeparator)
|
||||
|
||||
timestamp, _ := strconv.Atoi(content[1])
|
||||
return GitCommitLog{
|
||||
Date: content[0],
|
||||
Hash: content[1],
|
||||
Message: messageProcessor.Parse(content[2], content[3]),
|
||||
Date: content[0],
|
||||
Timestamp: timestamp,
|
||||
AuthorName: content[2],
|
||||
Hash: content[3],
|
||||
Message: messageProcessor.Parse(content[4], content[5]),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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,26 +23,24 @@ 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 {
|
||||
var bchanges BreakingChangeSection
|
||||
if len(breakingChanges) > 0 {
|
||||
bchanges = BreakingChangeSection{Name: "Breaking Changes", Messages: breakingChanges}
|
||||
}
|
||||
func releaseNote(version *semver.Version, tag string, date time.Time, sections []ReleaseNoteSection, authorsNames map[string]struct{}) ReleaseNote {
|
||||
return ReleaseNote{
|
||||
Version: version,
|
||||
Tag: tag,
|
||||
Date: date.Truncate(time.Minute),
|
||||
Sections: sections,
|
||||
BreakingChanges: bchanges,
|
||||
Version: version,
|
||||
Tag: tag,
|
||||
Date: date.Truncate(time.Minute),
|
||||
Sections: sections,
|
||||
AuthorsNames: authorsNames,
|
||||
}
|
||||
}
|
||||
|
||||
func newReleaseNoteSection(name string, items []GitCommitLog) ReleaseNoteSection {
|
||||
return ReleaseNoteSection{
|
||||
func newReleaseNoteCommitsSection(name string, types []string, items []GitCommitLog) ReleaseNoteCommitsSection {
|
||||
return ReleaseNoteCommitsSection{
|
||||
Name: name,
|
||||
Types: types,
|
||||
Items: items,
|
||||
}
|
||||
}
|
||||
|
@ -23,16 +23,20 @@ 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)
|
||||
mapping := commitSectionMapping(p.cfg.Sections)
|
||||
|
||||
sections := make(map[string]ReleaseNoteCommitsSection)
|
||||
authors := make(map[string]struct{})
|
||||
var breakingChanges []string
|
||||
for _, commit := range commits {
|
||||
if name, exists := p.cfg.Headers[commit.Message.Type]; exists {
|
||||
section, sexists := sections[commit.Message.Type]
|
||||
authors[commit.AuthorName] = struct{}{}
|
||||
if sectionCfg, exists := mapping[commit.Message.Type]; exists {
|
||||
section, sexists := sections[sectionCfg.Name]
|
||||
if !sexists {
|
||||
section = ReleaseNoteSection{Name: name}
|
||||
section = ReleaseNoteCommitsSection{Name: sectionCfg.Name, Types: sectionCfg.CommitTypes}
|
||||
}
|
||||
section.Items = append(section.Items, commit)
|
||||
sections[commit.Message.Type] = section
|
||||
sections[sectionCfg.Name] = section
|
||||
}
|
||||
if commit.Message.BreakingMessage() != "" {
|
||||
// TODO: if no message found, should use description instead?
|
||||
@ -40,30 +44,96 @@ func (p ReleaseNoteProcessorImpl) Create(version *semver.Version, tag string, da
|
||||
}
|
||||
}
|
||||
|
||||
var breakingChangeSection BreakingChangeSection
|
||||
if name, exists := p.cfg.Headers[breakingChangeMetadataKey]; exists && len(breakingChanges) > 0 {
|
||||
breakingChangeSection = BreakingChangeSection{Name: name, Messages: breakingChanges}
|
||||
var breakingChangeSection ReleaseNoteBreakingChangeSection
|
||||
if bcCfg := p.cfg.sectionConfig(ReleaseNotesSectionTypeBreakingChanges); bcCfg != nil && len(breakingChanges) > 0 {
|
||||
breakingChangeSection = ReleaseNoteBreakingChangeSection{Name: bcCfg.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: p.toReleaseNoteSections(sections, breakingChangeSection), AuthorsNames: authors}
|
||||
}
|
||||
|
||||
func (p ReleaseNoteProcessorImpl) toReleaseNoteSections(commitSections map[string]ReleaseNoteCommitsSection, breakingChange ReleaseNoteBreakingChangeSection) []ReleaseNoteSection {
|
||||
hasBreaking := 0
|
||||
if breakingChange.Name != "" {
|
||||
hasBreaking = 1
|
||||
}
|
||||
|
||||
sections := make([]ReleaseNoteSection, len(commitSections)+hasBreaking)
|
||||
i := 0
|
||||
for _, cfg := range p.cfg.Sections {
|
||||
if cfg.SectionType == ReleaseNotesSectionTypeBreakingChanges && hasBreaking > 0 {
|
||||
sections[i] = breakingChange
|
||||
i++
|
||||
}
|
||||
if s, exists := commitSections[cfg.Name]; cfg.SectionType == ReleaseNotesSectionTypeCommits && exists {
|
||||
sections[i] = s
|
||||
i++
|
||||
}
|
||||
}
|
||||
|
||||
return sections
|
||||
}
|
||||
|
||||
func commitSectionMapping(sections []ReleaseNotesSectionConfig) map[string]ReleaseNotesSectionConfig {
|
||||
mapping := make(map[string]ReleaseNotesSectionConfig)
|
||||
for _, section := range sections {
|
||||
if section.SectionType == ReleaseNotesSectionTypeCommits {
|
||||
for _, commitType := range section.CommitTypes {
|
||||
mapping[commitType] = section
|
||||
}
|
||||
}
|
||||
}
|
||||
return mapping
|
||||
}
|
||||
|
||||
// ReleaseNote release note.
|
||||
type ReleaseNote struct {
|
||||
Version *semver.Version
|
||||
Tag string
|
||||
Date time.Time
|
||||
Sections map[string]ReleaseNoteSection
|
||||
BreakingChanges BreakingChangeSection
|
||||
Version *semver.Version
|
||||
Tag string
|
||||
Date time.Time
|
||||
Sections []ReleaseNoteSection
|
||||
AuthorsNames map[string]struct{}
|
||||
}
|
||||
|
||||
// BreakingChangeSection breaking change section.
|
||||
type BreakingChangeSection struct {
|
||||
// ReleaseNoteSection section in release notes.
|
||||
type ReleaseNoteSection interface {
|
||||
SectionType() string
|
||||
SectionName() string
|
||||
}
|
||||
|
||||
// ReleaseNoteBreakingChangeSection breaking change section.
|
||||
type ReleaseNoteBreakingChangeSection struct {
|
||||
Name string
|
||||
Messages []string
|
||||
}
|
||||
|
||||
// ReleaseNoteSection release note section.
|
||||
type ReleaseNoteSection struct {
|
||||
// SectionType section type.
|
||||
func (ReleaseNoteBreakingChangeSection) SectionType() string {
|
||||
return ReleaseNotesSectionTypeBreakingChanges
|
||||
}
|
||||
|
||||
// SectionName section name.
|
||||
func (s ReleaseNoteBreakingChangeSection) SectionName() string {
|
||||
return s.Name
|
||||
}
|
||||
|
||||
// ReleaseNoteCommitsSection release note section.
|
||||
type ReleaseNoteCommitsSection struct {
|
||||
Name string
|
||||
Types []string
|
||||
Items []GitCommitLog
|
||||
}
|
||||
|
||||
// SectionType section type.
|
||||
func (ReleaseNoteCommitsSection) SectionType() string {
|
||||
return ReleaseNotesSectionTypeCommits
|
||||
}
|
||||
|
||||
// SectionName section name.
|
||||
func (s ReleaseNoteCommitsSection) SectionName() string {
|
||||
return s.Name
|
||||
}
|
||||
|
||||
// HasMultipleTypes return true if has more than one commit type.
|
||||
func (s ReleaseNoteCommitsSection) HasMultipleTypes() bool {
|
||||
return len(s.Types) > 1
|
||||
}
|
||||
|
@ -24,29 +24,37 @@ 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, []ReleaseNoteSection{newReleaseNoteCommitsSection("Tag 1", []string{"t1"}, []GitCommitLog{commitlog("t1", map[string]string{}, "a")})}, 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, []ReleaseNoteSection{newReleaseNoteCommitsSection("Tag 1", []string{"t1"}, []GitCommitLog{commitlog("t1", map[string]string{}, "a")})}, 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, []ReleaseNoteSection{newReleaseNoteCommitsSection("Tag 1", []string{"t1"}, []GitCommitLog{commitlog("t1", map[string]string{}, "a")}), ReleaseNoteBreakingChangeSection{Name: "Breaking Changes", Messages: []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, []ReleaseNoteSection{newReleaseNoteCommitsSection("Tag 1", []string{"t1"}, []GitCommitLog{commitlog("t1", map[string]string{}, "author3"), commitlog("t1", map[string]string{}, "author2"), commitlog("t1", map[string]string{}, "author1")})}, map[string]struct{}{"author1": {}, "author2": {}, "author3": {}}),
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
p := NewReleaseNoteProcessor(ReleaseNotesConfig{Headers: map[string]string{"t1": "Tag 1", "t2": "Tag 2", "breaking-change": "Breaking Changes"}})
|
||||
p := NewReleaseNoteProcessor(ReleaseNotesConfig{Sections: []ReleaseNotesSectionConfig{{Name: "Tag 1", SectionType: "commits", CommitTypes: []string{"t1"}}, {Name: "Tag 2", SectionType: "commits", CommitTypes: []string{"t2"}}, {Name: "Breaking Changes", SectionType: "breaking-changes"}}})
|
||||
if got := p.Create(tt.version, tt.tag, tt.date, tt.commits); !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("ReleaseNoteProcessorImpl.Create() = %v, want %v", got, tt.want)
|
||||
}
|
||||
|
@ -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) {
|
||||
|
Loading…
Reference in New Issue
Block a user