You've already forked go-semantic-release
feat(changelog): add first draft for changelog generation
This commit is contained in:
@@ -1,11 +1,11 @@
|
||||
commitFormat: angular
|
||||
branch:
|
||||
master: release
|
||||
sd: rc
|
||||
sds: beta
|
||||
travis: alpha
|
||||
rc: rc
|
||||
beta: beta
|
||||
alpha: alpha
|
||||
changelog:
|
||||
print: all/compact
|
||||
printAll: false
|
||||
template: ''
|
||||
templatePath: ''
|
||||
release: 'github'
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/Nightapes/go-semantic-release/pkg/config"
|
||||
@@ -24,6 +25,11 @@ var (
|
||||
setRepository = setCommand.Flag("repository", "Path to repository").String()
|
||||
setConfigPath = setCommand.Flag("config", "Path to config file").Default(".release.yml").String()
|
||||
setVersion = setCommand.Arg("version", "semver version").Required().String()
|
||||
|
||||
getChangelog = app.Command("changelog", "Print changelog.")
|
||||
getChangelogRepository = getChangelog.Flag("repository", "Path to repository").String()
|
||||
getChangelogConfigPath = getChangelog.Flag("config", "Path to config file").Default(".release.yml").String()
|
||||
getChangelogFile = getChangelog.Flag("file", "save changelog to file").Default("CHANGELOG.md").String()
|
||||
)
|
||||
|
||||
func main() {
|
||||
@@ -32,10 +38,11 @@ func main() {
|
||||
case nextCommand.FullCommand():
|
||||
setLoglevel(*loglevel)
|
||||
s := semanticrelease.New(readConfig(nextConfigPath))
|
||||
err := s.GetNextVersion(*nextRepository, *nextForce)
|
||||
version, err := s.GetNextVersion(*nextRepository, *nextForce)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
fmt.Println(version)
|
||||
|
||||
case setCommand.FullCommand():
|
||||
setLoglevel(*loglevel)
|
||||
@@ -45,6 +52,13 @@ func main() {
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
case getChangelog.FullCommand():
|
||||
setLoglevel(*loglevel)
|
||||
s := semanticrelease.New(readConfig(getChangelogConfigPath))
|
||||
err := s.GetChangelog(*getChangelogRepository, *getChangelogFile)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -3,24 +3,27 @@ package analyzer
|
||||
|
||||
import (
|
||||
"github.com/Nightapes/go-semantic-release/internal/gitutil"
|
||||
"github.com/Nightapes/go-semantic-release/pkg/config"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
//Analyzer struct
|
||||
type Analyzer struct {
|
||||
CommitFormat string
|
||||
Config config.ChangelogConfig
|
||||
}
|
||||
|
||||
//Rules for commits
|
||||
type Rules struct {
|
||||
//Rule for commits
|
||||
type Rule struct {
|
||||
Tag string
|
||||
TagString string
|
||||
Release string
|
||||
Changelog bool
|
||||
}
|
||||
|
||||
type analyzeCommit interface {
|
||||
analyze(commit gitutil.Commit, tag string) (AnalyzedCommit, bool, error)
|
||||
getRules() []Rules
|
||||
analyze(commit gitutil.Commit, tag Rule) (AnalyzedCommit, bool, error)
|
||||
getRules() []Rule
|
||||
}
|
||||
|
||||
//AnalyzedCommit struct
|
||||
@@ -30,12 +33,15 @@ type AnalyzedCommit struct {
|
||||
Scope string
|
||||
ParsedBreakingChangeMessage string
|
||||
Tag string
|
||||
TagString string
|
||||
Print bool
|
||||
}
|
||||
|
||||
//New Analyzer struct for given commit format
|
||||
func New(format string) *Analyzer {
|
||||
func New(format string, config config.ChangelogConfig) *Analyzer {
|
||||
return &Analyzer{
|
||||
CommitFormat: format,
|
||||
Config: config,
|
||||
}
|
||||
|
||||
}
|
||||
@@ -58,8 +64,13 @@ func (a *Analyzer) Analyze(commits []gitutil.Commit) map[string][]AnalyzedCommit
|
||||
|
||||
for _, commit := range commits {
|
||||
for _, rule := range commitAnalayzer.getRules() {
|
||||
analyzedCommit, hasBreakingChange, err := commitAnalayzer.analyze(commit, rule.Tag)
|
||||
analyzedCommit, hasBreakingChange, err := commitAnalayzer.analyze(commit, rule)
|
||||
if err == nil {
|
||||
if a.Config.PrintAll {
|
||||
analyzedCommit.Print = true
|
||||
} else {
|
||||
analyzedCommit.Print = rule.Changelog
|
||||
}
|
||||
if hasBreakingChange {
|
||||
analyzedCommits["major"] = append(analyzedCommits["major"], analyzedCommit)
|
||||
} else {
|
||||
|
||||
@@ -12,50 +12,59 @@ import (
|
||||
)
|
||||
|
||||
type angular struct {
|
||||
rules []Rules
|
||||
rules []Rule
|
||||
regex string
|
||||
}
|
||||
|
||||
func newAngular() *angular {
|
||||
return &angular{
|
||||
regex: `(TAG)(?:\((.*)\))?: (.*)`,
|
||||
rules: []Rules{
|
||||
rules: []Rule{
|
||||
{
|
||||
Tag: "feat",
|
||||
TagString: "Features",
|
||||
Release: "minor",
|
||||
Changelog: true,
|
||||
},
|
||||
{
|
||||
Tag: "fix",
|
||||
TagString: "Bug fixes",
|
||||
Release: "patch",
|
||||
Changelog: true,
|
||||
}, {
|
||||
Tag: "perf",
|
||||
TagString: "Performance improvments",
|
||||
Release: "patch",
|
||||
Changelog: true,
|
||||
}, {
|
||||
Tag: "docs",
|
||||
TagString: "Documentation changes",
|
||||
Release: "none",
|
||||
Changelog: false,
|
||||
},
|
||||
{
|
||||
Tag: "style",
|
||||
TagString: "Style",
|
||||
Release: "none",
|
||||
Changelog: false,
|
||||
}, {
|
||||
Tag: "refactor",
|
||||
TagString: "Code refactor",
|
||||
Release: "none",
|
||||
Changelog: false,
|
||||
}, {
|
||||
Tag: "test",
|
||||
TagString: "Testing",
|
||||
Release: "none",
|
||||
Changelog: false,
|
||||
}, {
|
||||
Tag: "chore",
|
||||
TagString: "Changes to the build process or auxiliary tools and libraries such as documentation generation",
|
||||
Release: "none",
|
||||
Changelog: false,
|
||||
}, {
|
||||
Tag: "build",
|
||||
TagString: "Changes to ci config",
|
||||
Release: "none",
|
||||
Changelog: false,
|
||||
},
|
||||
@@ -63,21 +72,23 @@ func newAngular() *angular {
|
||||
}
|
||||
}
|
||||
|
||||
func (a *angular) getRules() []Rules {
|
||||
func (a *angular) getRules() []Rule {
|
||||
return a.rules
|
||||
}
|
||||
|
||||
func (a *angular) analyze(commit gitutil.Commit, tag string) (AnalyzedCommit, bool, error) {
|
||||
func (a *angular) analyze(commit gitutil.Commit, rule Rule) (AnalyzedCommit, bool, error) {
|
||||
|
||||
analyzed := AnalyzedCommit{
|
||||
Commit: commit,
|
||||
Tag: tag,
|
||||
Commit: commit,
|
||||
Tag: rule.Tag,
|
||||
TagString: rule.TagString,
|
||||
}
|
||||
|
||||
re := regexp.MustCompile(strings.Replace(a.regex, "TAG", tag, -1))
|
||||
re := regexp.MustCompile(strings.Replace(a.regex, "TAG", rule.Tag, -1))
|
||||
matches := re.FindAllStringSubmatch(commit.Message, -1)
|
||||
if len(matches) >= 1 {
|
||||
if len(matches[0]) >= 3 {
|
||||
|
||||
analyzed.Scope = matches[0][2]
|
||||
|
||||
message := strings.Join(matches[0][3:], "")
|
||||
@@ -85,7 +96,7 @@ func (a *angular) analyze(commit gitutil.Commit, tag string) (AnalyzedCommit, bo
|
||||
|
||||
if len(splitted) == 1 {
|
||||
analyzed.ParsedMessage = splitted[0]
|
||||
log.Tracef("%s: found %s", commit.Message, tag)
|
||||
log.Tracef("%s: found %s", commit.Message, rule.Tag)
|
||||
return analyzed, false, nil
|
||||
}
|
||||
analyzed.ParsedMessage = splitted[0]
|
||||
@@ -95,7 +106,7 @@ func (a *angular) analyze(commit gitutil.Commit, tag string) (AnalyzedCommit, bo
|
||||
|
||||
}
|
||||
}
|
||||
log.Tracef("%s does not match %s, skip", commit.Message, tag)
|
||||
log.Tracef("%s does not match %s, skip", commit.Message, rule.Tag)
|
||||
return analyzed, false, fmt.Errorf("Not found")
|
||||
|
||||
}
|
||||
|
||||
82
internal/changelog/changelog.go
Normal file
82
internal/changelog/changelog.go
Normal file
@@ -0,0 +1,82 @@
|
||||
package changelog
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"text/template"
|
||||
"time"
|
||||
|
||||
"github.com/Nightapes/go-semantic-release/internal/analyzer"
|
||||
"github.com/Nightapes/go-semantic-release/pkg/config"
|
||||
)
|
||||
|
||||
const defaultChangelogTitle string = `v{{.Version}} ({{.Now.Format "2006-01-02"}})`
|
||||
const defaultChangelog string = `{{ $version := .Version -}}
|
||||
{{ $backtick := .Backtick -}}
|
||||
# v{{.Version}} ({{.Now.Format "2006-01-02"}})
|
||||
{{ range $key, $commits := .Commits }}
|
||||
### {{ $key }}
|
||||
|
||||
{{range $index,$commit := $commits}}* **{{$backtick}}{{$commit.Scope}}:{{$backtick}}** {{$commit.ParsedMessage}}
|
||||
{{ end -}}
|
||||
{{ end -}}
|
||||
`
|
||||
|
||||
type changelogContent struct {
|
||||
Commits map[string][]analyzer.AnalyzedCommit
|
||||
Version string
|
||||
Now time.Time
|
||||
Backtick string
|
||||
}
|
||||
|
||||
//CommitFormat struct
|
||||
type Changelog struct {
|
||||
config *config.ReleaseConfig
|
||||
}
|
||||
|
||||
//New Changelog struct for generating changelog from commits
|
||||
func New(config *config.ReleaseConfig) *Changelog {
|
||||
return &Changelog{
|
||||
config: config,
|
||||
}
|
||||
}
|
||||
|
||||
// GenerateChanglog from given commits
|
||||
func (c *Changelog) GenerateChanglog(version string, analyzedCommits map[string][]analyzer.AnalyzedCommit) (string, string, error) {
|
||||
|
||||
commitsPerScope := map[string][]analyzer.AnalyzedCommit{}
|
||||
for _, commits := range analyzedCommits {
|
||||
for _, commit := range commits {
|
||||
if commit.Print {
|
||||
if _, ok := commitsPerScope[commit.TagString]; !ok {
|
||||
commitsPerScope[commit.TagString] = make([]analyzer.AnalyzedCommit, 0)
|
||||
}
|
||||
commitsPerScope[commit.TagString] = append(commitsPerScope[commit.TagString], commit)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
changelogContent := changelogContent{
|
||||
Version: version,
|
||||
Commits: commitsPerScope,
|
||||
Now: time.Now(),
|
||||
Backtick: "`",
|
||||
}
|
||||
|
||||
title, err := generateTemplate(defaultChangelogTitle, changelogContent)
|
||||
content, err := generateTemplate(defaultChangelog, changelogContent)
|
||||
|
||||
return title, content, err
|
||||
}
|
||||
|
||||
func generateTemplate(text string, values changelogContent) (string, error) {
|
||||
var tpl bytes.Buffer
|
||||
tmpl, err := template.New("template").Parse(text)
|
||||
if err != nil {
|
||||
return "", nil
|
||||
}
|
||||
err = tmpl.Execute(&tpl, values)
|
||||
if err != nil {
|
||||
return "", nil
|
||||
}
|
||||
return tpl.String(), nil
|
||||
}
|
||||
@@ -10,7 +10,7 @@ import (
|
||||
|
||||
// ChangelogConfig struct
|
||||
type ChangelogConfig struct {
|
||||
Print string `yaml:"print,omitempty"`
|
||||
PrintAll bool `yaml:"printAll,omitempty"`
|
||||
Template string `yaml:"template,omitempty"`
|
||||
TemplatePath string `yaml:"templatePath,omitempty"`
|
||||
}
|
||||
|
||||
@@ -2,13 +2,14 @@
|
||||
package semanticrelease
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/Masterminds/semver"
|
||||
"github.com/Nightapes/go-semantic-release/internal/analyzer"
|
||||
"github.com/Nightapes/go-semantic-release/internal/cache"
|
||||
"github.com/Nightapes/go-semantic-release/internal/changelog"
|
||||
"github.com/Nightapes/go-semantic-release/internal/gitutil"
|
||||
"github.com/Nightapes/go-semantic-release/pkg/config"
|
||||
log "github.com/sirupsen/logrus"
|
||||
@@ -27,15 +28,15 @@ func New(c *config.ReleaseConfig) *SemanticRelease {
|
||||
}
|
||||
|
||||
// GetNextVersion from .version or calculate new from commits
|
||||
func (s *SemanticRelease) GetNextVersion(repro string, force bool) error {
|
||||
func (s *SemanticRelease) GetNextVersion(repro string, force bool) (string, error) {
|
||||
util, err := gitutil.New(repro)
|
||||
if err != nil {
|
||||
return err
|
||||
return "", err
|
||||
}
|
||||
|
||||
hash, err := util.GetHash()
|
||||
if err != nil {
|
||||
return err
|
||||
return "", err
|
||||
}
|
||||
|
||||
log.Debugf("Ignore .version file if exits, %t", force)
|
||||
@@ -44,15 +45,14 @@ func (s *SemanticRelease) GetNextVersion(repro string, force bool) error {
|
||||
|
||||
if err == nil && content.Commit == hash {
|
||||
log.Infof("Found cache, will return cached version %s", content.NextVersion)
|
||||
fmt.Printf(content.NextVersion)
|
||||
return nil
|
||||
return content.NextVersion, err
|
||||
}
|
||||
log.Debugf("Mismatch git and version file %s - %s", content.Commit, hash)
|
||||
}
|
||||
|
||||
lastVersion, lastVersionHash, err := util.GetLastVersion()
|
||||
if err != nil {
|
||||
return err
|
||||
return "", err
|
||||
}
|
||||
var newVersion semver.Version
|
||||
|
||||
@@ -65,17 +65,17 @@ func (s *SemanticRelease) GetNextVersion(repro string, force bool) error {
|
||||
|
||||
commits, err := util.GetCommits(lastVersionHash)
|
||||
if err != nil {
|
||||
return err
|
||||
return "", err
|
||||
}
|
||||
|
||||
log.Debugf("Found %d commits till last release", len(commits))
|
||||
|
||||
a := analyzer.New("angular")
|
||||
a := analyzer.New(s.config.CommitFormat, s.config.Changelog)
|
||||
result := a.Analyze(commits)
|
||||
|
||||
currentBranch, err := util.GetBranch()
|
||||
if err != nil {
|
||||
return err
|
||||
return "", err
|
||||
}
|
||||
|
||||
for branch, releaseType := range s.config.Branch {
|
||||
@@ -99,11 +99,12 @@ func (s *SemanticRelease) GetNextVersion(repro string, force bool) error {
|
||||
log.Infof("New version %s -> %s", lastVersion.String(), newVersion.String())
|
||||
err = saveToCache(util, lastVersion, &newVersion)
|
||||
if err != nil {
|
||||
return err
|
||||
return "", err
|
||||
}
|
||||
fmt.Printf("%s", newVersion.String())
|
||||
c := changelog.New(s.config)
|
||||
c.GenerateChanglog(newVersion.String(), result)
|
||||
|
||||
return err
|
||||
return newVersion.String(), err
|
||||
}
|
||||
|
||||
//SetVersion for git repository
|
||||
@@ -175,3 +176,38 @@ func incPrerelease(preReleaseType string, version semver.Version) semver.Version
|
||||
|
||||
return version
|
||||
}
|
||||
|
||||
// GetChangelog from last version till now
|
||||
func (s *SemanticRelease) GetChangelog(repro, file string) error {
|
||||
nextVersion, err := s.GetNextVersion(repro, false)
|
||||
if err != nil {
|
||||
log.Debugf("Could not get next version")
|
||||
return err
|
||||
}
|
||||
|
||||
util, err := gitutil.New(repro)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, lastVersionHash, err := util.GetLastVersion()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
commits, err := util.GetCommits(lastVersionHash)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Debugf("Found %d commits till last release", len(commits))
|
||||
|
||||
a := analyzer.New(s.config.CommitFormat, s.config.Changelog)
|
||||
result := a.Analyze(commits)
|
||||
|
||||
c := changelog.New(s.config)
|
||||
_, content, err := c.GenerateChanglog(nextVersion, result)
|
||||
|
||||
return ioutil.WriteFile(file, []byte(content), 0644)
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user