0
0
mirror of https://github.com/thegeeklab/git-sv.git synced 2024-11-24 11:10:39 +00:00

feat: add sections to release-notes config (headers is deprecated)

issue: #40
This commit is contained in:
Beatriz Vieira 2022-02-07 20:40:53 -03:00
parent e64d4ddcc1
commit e650f64783
4 changed files with 92 additions and 14 deletions

View File

@ -77,8 +77,14 @@ func defaultConfig() Config {
UpdatePatch: []string{"build", "ci", "chore", "docs", "fix", "perf", "refactor", "style", "test"}, UpdatePatch: []string{"build", "ci", "chore", "docs", "fix", "perf", "refactor", "style", "test"},
IgnoreUnknown: false, IgnoreUnknown: false,
}, },
Tag: sv.TagConfig{Pattern: "%d.%d.%d"}, Tag: sv.TagConfig{Pattern: "%d.%d.%d"},
ReleaseNotes: sv.ReleaseNotesConfig{Headers: map[string]string{"fix": "Bug Fixes", "feat": "Features", "breaking-change": "Breaking Changes"}}, ReleaseNotes: sv.ReleaseNotesConfig{
Sections: []sv.ReleaseNotesSectionConfig{
{Name: "Features", SectionType: "commits", CommitTypes: []string{"feat"}},
{Name: "Bug Fixes", SectionType: "commits", CommitTypes: []string{"fix"}},
{Name: "Breaking Changes", SectionType: "breaking-change"},
},
},
Branches: sv.BranchesConfig{ Branches: sv.BranchesConfig{
Prefix: "([a-z]+\\/)?", Prefix: "([a-z]+\\/)?",
Suffix: "(-.*)?", Suffix: "(-.*)?",
@ -129,3 +135,35 @@ func (t *mergeTransformer) Transformer(typ reflect.Type) func(dst, src reflect.V
} }
return nil return nil
} }
func migrateConfig(cfg Config) Config {
if cfg.ReleaseNotes.Headers == nil {
return cfg
}
warnf("config 'release-notes.headers' is deprecated, please use 'sections' instead!")
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.ReleaseNotesSectionTypeBreakingChange})
}
return sections
}

View File

@ -68,5 +68,29 @@ type TagConfig struct {
// ReleaseNotesConfig release notes preferences. // ReleaseNotesConfig release notes preferences.
type ReleaseNotesConfig struct { type ReleaseNotesConfig struct {
Headers map[string]string `yaml:"headers"` Headers map[string]string `yaml:"headers"`
Sections []ReleaseNotesSectionConfig `yaml:"sections"`
} }
func (cfg ReleaseNotesConfig) sectionConfig(sectionType string) *ReleaseNotesSectionConfig {
for _, sectionCfg := range cfg.Sections {
if sectionCfg.SectionType == sectionType {
return &sectionCfg
}
}
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"`
}
const (
// ReleaseNotesSectionTypeCommits ReleaseNotesSectionConfig.SectionType value.
ReleaseNotesSectionTypeCommits = "commits"
// ReleaseNotesSectionTypeBreakingChange ReleaseNotesSectionConfig.SectionType value.
ReleaseNotesSectionTypeBreakingChange = "breaking-change"
)

View File

@ -23,18 +23,20 @@ func NewReleaseNoteProcessor(cfg ReleaseNotesConfig) *ReleaseNoteProcessorImpl {
// Create create a release note based on commits. // Create create a release note based on commits.
func (p ReleaseNoteProcessorImpl) Create(version *semver.Version, tag string, date time.Time, commits []GitCommitLog) ReleaseNote { func (p ReleaseNoteProcessorImpl) Create(version *semver.Version, tag string, date time.Time, commits []GitCommitLog) ReleaseNote {
mapping := commitSectionMapping(p.cfg.Sections)
sections := make(map[string]ReleaseNoteSection) sections := make(map[string]ReleaseNoteSection)
authors := make(map[string]struct{}) authors := make(map[string]struct{})
var breakingChanges []string var breakingChanges []string
for _, commit := range commits { for _, commit := range commits {
authors[commit.AuthorName] = struct{}{} authors[commit.AuthorName] = struct{}{}
if name, exists := p.cfg.Headers[commit.Message.Type]; exists { if sectionCfg, exists := mapping[commit.Message.Type]; exists {
section, sexists := sections[commit.Message.Type] section, sexists := sections[sectionCfg.Name]
if !sexists { if !sexists {
section = ReleaseNoteSection{Name: name, Types: []string{commit.Message.Type}} //TODO: change to support more than one type per section section = ReleaseNoteSection{Name: sectionCfg.Name, Types: sectionCfg.CommitTypes}
} }
section.Items = append(section.Items, commit) section.Items = append(section.Items, commit)
sections[commit.Message.Type] = section sections[sectionCfg.Name] = section
} }
if commit.Message.BreakingMessage() != "" { if commit.Message.BreakingMessage() != "" {
// TODO: if no message found, should use description instead? // TODO: if no message found, should use description instead?
@ -43,12 +45,24 @@ func (p ReleaseNoteProcessorImpl) Create(version *semver.Version, tag string, da
} }
var breakingChangeSection BreakingChangeSection var breakingChangeSection BreakingChangeSection
if name, exists := p.cfg.Headers[breakingChangeMetadataKey]; exists && len(breakingChanges) > 0 { if bcCfg := p.cfg.sectionConfig(ReleaseNotesSectionTypeBreakingChange); bcCfg != nil && len(breakingChanges) > 0 {
breakingChangeSection = BreakingChangeSection{Name: name, Messages: breakingChanges} breakingChangeSection = BreakingChangeSection{Name: bcCfg.Name, Messages: breakingChanges}
} }
return ReleaseNote{Version: version, Tag: tag, Date: date.Truncate(time.Minute), Sections: sections, BreakingChanges: breakingChangeSection, AuthorsNames: authors} return ReleaseNote{Version: version, Tag: tag, Date: date.Truncate(time.Minute), Sections: sections, BreakingChanges: breakingChangeSection, AuthorsNames: authors}
} }
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. // ReleaseNote release note.
type ReleaseNote struct { type ReleaseNote struct {
Version *semver.Version Version *semver.Version

View File

@ -25,7 +25,7 @@ func TestReleaseNoteProcessorImpl_Create(t *testing.T) {
tag: "v1.0.0", tag: "v1.0.0",
date: date, date: date,
commits: []GitCommitLog{commitlog("t1", map[string]string{}, "a")}, 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": {}}), want: releaseNote(semver.MustParse("1.0.0"), "v1.0.0", date, map[string]ReleaseNoteSection{"Tag 1": newReleaseNoteSection("Tag 1", []string{"t1"}, []GitCommitLog{commitlog("t1", map[string]string{}, "a")})}, nil, map[string]struct{}{"a": {}}),
}, },
{ {
name: "unmapped tag", name: "unmapped tag",
@ -33,7 +33,7 @@ func TestReleaseNoteProcessorImpl_Create(t *testing.T) {
tag: "v1.0.0", tag: "v1.0.0",
date: date, date: date,
commits: []GitCommitLog{commitlog("t1", map[string]string{}, "a"), commitlog("unmapped", map[string]string{}, "a")}, 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": {}}), want: releaseNote(semver.MustParse("1.0.0"), "v1.0.0", date, map[string]ReleaseNoteSection{"Tag 1": newReleaseNoteSection("Tag 1", []string{"t1"}, []GitCommitLog{commitlog("t1", map[string]string{}, "a")})}, nil, map[string]struct{}{"a": {}}),
}, },
{ {
name: "breaking changes tag", name: "breaking changes tag",
@ -41,7 +41,7 @@ func TestReleaseNoteProcessorImpl_Create(t *testing.T) {
tag: "v1.0.0", tag: "v1.0.0",
date: date, date: date,
commits: []GitCommitLog{commitlog("t1", map[string]string{}, "a"), commitlog("unmapped", map[string]string{"breaking-change": "breaks"}, "a")}, 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": {}}), want: releaseNote(semver.MustParse("1.0.0"), "v1.0.0", date, map[string]ReleaseNoteSection{"Tag 1": newReleaseNoteSection("Tag 1", []string{"t1"}, []GitCommitLog{commitlog("t1", map[string]string{}, "a")})}, []string{"breaks"}, map[string]struct{}{"a": {}}),
}, },
{ {
name: "multiple authors", name: "multiple authors",
@ -49,12 +49,14 @@ func TestReleaseNoteProcessorImpl_Create(t *testing.T) {
tag: "v1.0.0", tag: "v1.0.0",
date: date, date: date,
commits: []GitCommitLog{commitlog("t1", map[string]string{}, "author3"), commitlog("t1", map[string]string{}, "author2"), commitlog("t1", map[string]string{}, "author1")}, 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": {}}), want: releaseNote(semver.MustParse("1.0.0"), "v1.0.0", date, map[string]ReleaseNoteSection{"Tag 1": 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 { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { 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-change"}}})
if got := p.Create(tt.version, tt.tag, tt.date, tt.commits); !reflect.DeepEqual(got, tt.want) { 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) t.Errorf("ReleaseNoteProcessorImpl.Create() = %v, want %v", got, tt.want)
} }