mirror of
https://github.com/thegeeklab/git-sv.git
synced 2024-11-24 11:10:39 +00:00
193 lines
5.1 KiB
Go
193 lines
5.1 KiB
Go
package sv
|
|
|
|
import (
|
|
"time"
|
|
|
|
"github.com/Masterminds/semver/v3"
|
|
)
|
|
|
|
// ReleaseNotesConfig release notes preferences.
|
|
type ReleaseNotesConfig struct {
|
|
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"
|
|
)
|
|
|
|
// ReleaseNoteProcessor release note processor interface.
|
|
type ReleaseNoteProcessor interface {
|
|
Create(version *semver.Version, tag string, date time.Time, commits []CommitLog) ReleaseNote
|
|
}
|
|
|
|
// BaseReleaseNoteProcessor release note based on commit log.
|
|
type BaseReleaseNoteProcessor struct {
|
|
cfg ReleaseNotesConfig
|
|
}
|
|
|
|
// NewReleaseNoteProcessor ReleaseNoteProcessor constructor.
|
|
func NewReleaseNoteProcessor(cfg ReleaseNotesConfig) *BaseReleaseNoteProcessor {
|
|
return &BaseReleaseNoteProcessor{cfg: cfg}
|
|
}
|
|
|
|
// Create create a release note based on commits.
|
|
func (p BaseReleaseNoteProcessor) Create(
|
|
version *semver.Version,
|
|
tag string,
|
|
date time.Time,
|
|
commits []CommitLog,
|
|
) ReleaseNote {
|
|
mapping := commitSectionMapping(p.cfg.Sections)
|
|
|
|
sections := make(map[string]ReleaseNoteCommitsSection)
|
|
authors := make(map[string]struct{})
|
|
|
|
var breakingChanges []string
|
|
|
|
for _, commit := range commits {
|
|
authors[commit.AuthorName] = struct{}{}
|
|
|
|
if sectionCfg, exists := mapping[commit.Message.Type]; exists {
|
|
section, sexists := sections[sectionCfg.Name]
|
|
if !sexists {
|
|
section = ReleaseNoteCommitsSection{Name: sectionCfg.Name, Types: sectionCfg.CommitTypes}
|
|
}
|
|
|
|
section.Items = append(section.Items, commit)
|
|
sections[sectionCfg.Name] = section
|
|
}
|
|
|
|
if commit.Message.BreakingMessage() != "" {
|
|
// TODO: if no message found, should use description instead?
|
|
breakingChanges = append(breakingChanges, commit.Message.BreakingMessage())
|
|
}
|
|
}
|
|
|
|
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: p.toReleaseNoteSections(sections, breakingChangeSection),
|
|
AuthorsNames: authors,
|
|
}
|
|
}
|
|
|
|
func (p BaseReleaseNoteProcessor) 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 []ReleaseNoteSection
|
|
AuthorsNames map[string]struct{}
|
|
}
|
|
|
|
// ReleaseNoteSection section in release notes.
|
|
type ReleaseNoteSection interface {
|
|
SectionType() string
|
|
SectionName() string
|
|
}
|
|
|
|
// ReleaseNoteBreakingChangeSection breaking change section.
|
|
type ReleaseNoteBreakingChangeSection struct {
|
|
Name string
|
|
Messages []string
|
|
}
|
|
|
|
// 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 []CommitLog
|
|
}
|
|
|
|
// 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
|
|
}
|