diff --git a/README.md b/README.md index 8cf33a0..a70235a 100644 --- a/README.md +++ b/README.md @@ -55,14 +55,15 @@ git-sv rn -h ##### Available commands -| Variable | description | -| --------- | ---------- | -| current-version, cv | get last released version from git | -| next-version, nv | generate the next version based on git commit messages | -| commit-log, cl | list all commit logs since last version as jsons | -| release-notes, rn | generate release notes | -| tag, tg | generate tag with version based on git commit messages | -| help, h | Shows a list of commands or help for one command | +| Variable | description | has options | +| --------- | ---------- | ---------- | +| current-version, cv | get last released version from git | :x: | +| next-version, nv | generate the next version based on git commit messages | :x: | +| commit-log, cl | list all commit logs since last version as jsons | :heavy_check_mark: | +| release-notes, rn | generate release notes | :heavy_check_mark: | +| changelog, cgl | generate changelog | :heavy_check_mark: | +| tag, tg | generate tag with version based on git commit messages | :x: | +| help, h | Shows a list of commands or help for one command | :x: | ## Development diff --git a/cmd/git-sv/handlers.go b/cmd/git-sv/handlers.go index ebde62b..6e2473c 100644 --- a/cmd/git-sv/handlers.go +++ b/cmd/git-sv/handlers.go @@ -3,6 +3,7 @@ package main import ( "encoding/json" "fmt" + "sort" "sv4git/sv" "time" @@ -184,3 +185,46 @@ func tagHandler(git sv.Git, semverProcessor sv.SemVerCommitsProcessor) func(c *c return nil } } + +func changelogHandler(git sv.Git, semverProcessor sv.SemVerCommitsProcessor, rnProcessor sv.ReleaseNoteProcessor, formatter sv.OutputFormatter) func(c *cli.Context) error { + return func(c *cli.Context) error { + + tags, err := git.Tags() + if err != nil { + return err + } + sort.Slice(tags, func(i, j int) bool { + return tags[i].Date.After(tags[j].Date) + }) + + var releaseNotes []sv.ReleaseNote + + size := c.Int("size") + all := c.Bool("all") + for i, tag := range tags { + if !all && i >= size { + break + } + + previousTag := "" + if i+1 < len(tags) { + previousTag = tags[i+1].Name + } + + commits, err := git.Log(previousTag, tag.Name) + if err != nil { + return fmt.Errorf("error getting git log from tag: %s, message: %v", tag.Name, err) + } + + currentVer, err := sv.ToVersion(tag.Name) + if err != nil { + return fmt.Errorf("error parsing version: %s from describe, message: %v", tag.Name, err) + } + releaseNotes = append(releaseNotes, rnProcessor.Create(currentVer, tag.Date, commits)) + } + + fmt.Println(formatter.FormatChangelog(releaseNotes)) + + return nil + } +} diff --git a/cmd/git-sv/main.go b/cmd/git-sv/main.go index c4f1913..ebb1449 100644 --- a/cmd/git-sv/main.go +++ b/cmd/git-sv/main.go @@ -50,6 +50,16 @@ func main() { Action: releaseNotesHandler(git, semverProcessor, releasenotesProcessor, outputFormatter), Flags: []cli.Flag{&cli.StringFlag{Name: "t", Aliases: []string{"tag"}, Usage: "get release note from tag"}}, }, + { + Name: "changelog", + Aliases: []string{"cgl"}, + Usage: "generate changelog", + Action: changelogHandler(git, semverProcessor, releasenotesProcessor, outputFormatter), + Flags: []cli.Flag{ + &cli.IntFlag{Name: "size", Value: 10, Aliases: []string{"n"}, Usage: "get changelog from last 'n' tags"}, + &cli.BoolFlag{Name: "all", Usage: "ignore size parameter, get changelog for every tag"}, + }, + }, { Name: "tag", Aliases: []string{"tg"}, diff --git a/sv/formatter.go b/sv/formatter.go index ce33f16..74860db 100644 --- a/sv/formatter.go +++ b/sv/formatter.go @@ -13,56 +13,86 @@ type releaseNoteTemplateVariables struct { BreakingChanges []string } -const rnSectionItem = "- {{if .Scope}}**{{.Scope}}:** {{end}}{{.Subject}} ({{.Hash}}){{if .Metadata.issueid}} ({{.Metadata.issueid}}){{end}}" -const rnSection = `{{- if .}} +const ( + cglTemplate = `# Changelog +{{- range .}} + +{{template "rnTemplate" .}} +--- +{{- end}} +` + + rnSectionItem = "- {{if .Scope}}**{{.Scope}}:** {{end}}{{.Subject}} ({{.Hash}}){{if .Metadata.issueid}} ({{.Metadata.issueid}}){{end}}" + + rnSection = `{{- if .}} ### {{.Name}} {{range $k,$v := .Items}} {{template "rnSectionItem" $v}} {{- end}} {{- end}}` -const rnSectionBreakingChanges = `{{- if .}} + + rnSectionBreakingChanges = `{{- if .}} ### Breaking Changes {{range $k,$v := .}} - {{$v}} {{- end}} {{- end}}` -const rnTemplate = `## v{{.Version}} ({{.Date}}) + + rnTemplate = `## v{{.Version}} ({{.Date}}) {{- template "rnSection" .Sections.feat}} {{- template "rnSection" .Sections.fix}} {{- template "rnSectionBreakingChanges" .BreakingChanges}} ` +) // OutputFormatter output formatter interface. type OutputFormatter interface { FormatReleaseNote(releasenote ReleaseNote) string + FormatChangelog(releasenotes []ReleaseNote) string } // OutputFormatterImpl formater for release note and changelog. type OutputFormatterImpl struct { releasenoteTemplate *template.Template + changelogTemplate *template.Template } // NewOutputFormatter TemplateProcessor constructor. func NewOutputFormatter() *OutputFormatterImpl { - t := template.Must(template.New("releasenotes").Parse(rnTemplate)) - template.Must(t.New("rnSectionItem").Parse(rnSectionItem)) - template.Must(t.New("rnSection").Parse(rnSection)) - template.Must(t.New("rnSectionBreakingChanges").Parse(rnSectionBreakingChanges)) - return &OutputFormatterImpl{releasenoteTemplate: t} + 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} } // FormatReleaseNote format a release note. func (p OutputFormatterImpl) FormatReleaseNote(releasenote ReleaseNote) string { - templateVars := releaseNoteTemplateVariables{ + var b bytes.Buffer + p.releasenoteTemplate.Execute(&b, releaseNoteVariables(releasenote)) + return b.String() +} + +// FormatChangelog format a changelog +func (p OutputFormatterImpl) FormatChangelog(releasenotes []ReleaseNote) string { + var templateVars []releaseNoteTemplateVariables + for _, v := range releasenotes { + templateVars = append(templateVars, releaseNoteVariables(v)) + } + + var b bytes.Buffer + p.changelogTemplate.Execute(&b, templateVars) + return b.String() +} + +func releaseNoteVariables(releasenote ReleaseNote) releaseNoteTemplateVariables { + return releaseNoteTemplateVariables{ Version: fmt.Sprintf("%d.%d.%d", releasenote.Version.Major(), releasenote.Version.Minor(), releasenote.Version.Patch()), Date: releasenote.Date.Format("2006-01-02"), Sections: releasenote.Sections, BreakingChanges: releasenote.BreakingChanges, } - - var b bytes.Buffer - p.releasenoteTemplate.Execute(&b, templateVars) - return b.String() }