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

feat: add option to write changelogs to file (#6)

This commit is contained in:
Robert Kaussow 2023-10-16 00:29:02 +02:00 committed by GitHub
parent a222bd3430
commit 14ee7e71c2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 172 additions and 86 deletions

View File

@ -57,6 +57,7 @@ func NewLogRange(t LogRangeType, start, end string) LogRange {
// Impl git command implementation. // Impl git command implementation.
type GitSV struct { type GitSV struct {
Settings *Settings
Config *Config Config *Config
MessageProcessor sv.MessageProcessor MessageProcessor sv.MessageProcessor
@ -68,6 +69,7 @@ type GitSV struct {
// New constructor. // New constructor.
func New() GitSV { func New() GitSV {
g := GitSV{ g := GitSV{
Settings: &Settings{},
Config: NewConfig(configDir, configFilename), Config: NewConfig(configDir, configFilename),
} }

View File

@ -2,6 +2,7 @@ package commands
import ( import (
"fmt" "fmt"
"os"
"sort" "sort"
"github.com/thegeeklab/git-sv/v2/app" "github.com/thegeeklab/git-sv/v2/app"
@ -9,7 +10,7 @@ import (
"github.com/urfave/cli/v2" "github.com/urfave/cli/v2"
) )
func ChangelogFlags() []cli.Flag { func ChangelogFlags(settings *app.ChangelogSettings) []cli.Flag {
return []cli.Flag{ return []cli.Flag{
&cli.IntFlag{ &cli.IntFlag{
Name: "size", Name: "size",
@ -20,21 +21,29 @@ func ChangelogFlags() []cli.Flag {
&cli.BoolFlag{ &cli.BoolFlag{
Name: "all", Name: "all",
Usage: "ignore size parameter, get changelog for every tag", Usage: "ignore size parameter, get changelog for every tag",
Destination: &settings.All,
}, },
&cli.BoolFlag{ &cli.BoolFlag{
Name: "add-next-version", Name: "add-next",
Usage: "add next version on change log (commits since last tag, but only if there is a new version to release)", Usage: "add next version on change log (commits since last tag, only if there is a new release)",
Destination: &settings.AddNext,
}, },
&cli.BoolFlag{ &cli.BoolFlag{
Name: "semantic-version-only", Name: "strict",
Usage: "only show tags 'SemVer-ish'", Usage: "only show tags 'SemVer-ish'",
Destination: &settings.Strict,
},
&cli.StringFlag{
Name: "o",
Aliases: []string{"output"},
Usage: "output file name. Omit to use standard output.",
Destination: &settings.Out,
}, },
} }
} }
func ChangelogHandler( //nolint:gocognit
g app.GitSV, func ChangelogHandler(g app.GitSV) cli.ActionFunc {
) cli.ActionFunc {
return func(c *cli.Context) error { return func(c *cli.Context) error {
tags, err := g.Tags() tags, err := g.Tags()
if err != nil { if err != nil {
@ -47,12 +56,7 @@ func ChangelogHandler(
var releaseNotes []sv.ReleaseNote var releaseNotes []sv.ReleaseNote
size := c.Int("size") if g.Settings.ChangelogSettings.AddNext {
all := c.Bool("all")
addNextVersion := c.Bool("add-next-version")
semanticVersionOnly := c.Bool("semantic-version-only")
if addNextVersion {
rnVersion, updated, date, commits, uerr := getNextVersionInfo(g, g.CommitProcessor) rnVersion, updated, date, commits, uerr := getNextVersionInfo(g, g.CommitProcessor)
if uerr != nil { if uerr != nil {
return uerr return uerr
@ -64,7 +68,7 @@ func ChangelogHandler(
} }
for i, tag := range tags { for i, tag := range tags {
if !all && i >= size { if !g.Settings.ChangelogSettings.All && i >= g.Settings.ChangelogSettings.Size {
break break
} }
@ -73,13 +77,13 @@ func ChangelogHandler(
previousTag = tags[i+1].Name previousTag = tags[i+1].Name
} }
if semanticVersionOnly && !sv.IsValidVersion(tag.Name) { if g.Settings.ChangelogSettings.Strict && !sv.IsValidVersion(tag.Name) {
continue continue
} }
commits, err := g.Log(app.NewLogRange(app.TagRange, previousTag, tag.Name)) commits, err := g.Log(app.NewLogRange(app.TagRange, previousTag, tag.Name))
if err != nil { if err != nil {
return fmt.Errorf("error getting git log from tag: %s, message: %w", tag.Name, err) return fmt.Errorf("error getting git log from tag: %s: %w", tag.Name, err)
} }
currentVer, _ := sv.ToVersion(tag.Name) currentVer, _ := sv.ToVersion(tag.Name)
@ -88,10 +92,24 @@ func ChangelogHandler(
output, err := g.OutputFormatter.FormatChangelog(releaseNotes) output, err := g.OutputFormatter.FormatChangelog(releaseNotes)
if err != nil { if err != nil {
return fmt.Errorf("could not format changelog, message: %w", err) return fmt.Errorf("could not format changelog: %w", err)
} }
fmt.Println(output) if g.Settings.ChangelogSettings.Out == "" {
os.Stdout.WriteString(fmt.Sprintf("%s\n", output))
return nil
}
w, err := os.Create(g.Settings.ChangelogSettings.Out)
if err != nil {
return fmt.Errorf("could not write changelog: %w", err)
}
defer w.Close()
if _, err := w.Write(output); err != nil {
return err
}
return nil return nil
} }

View File

@ -100,7 +100,7 @@ func CommitHandler(g app.GitSV) cli.ActionFunc {
err = g.Commit(header, body, footer) err = g.Commit(header, body, footer)
if err != nil { if err != nil {
return fmt.Errorf("error executing git commit, message: %w", err) return fmt.Errorf("error executing git commit: %w", err)
} }
return nil return nil

View File

@ -69,7 +69,7 @@ func CommitLogHandler(g app.GitSV) cli.ActionFunc {
} }
if err != nil { if err != nil {
return fmt.Errorf("error getting git log, message: %w", err) return fmt.Errorf("error getting git log: %w", err)
} }
for _, commit := range commits { for _, commit := range commits {

View File

@ -2,28 +2,38 @@ package commands
import ( import (
"fmt" "fmt"
"os"
"time" "time"
"github.com/thegeeklab/git-sv/v2/app" "github.com/thegeeklab/git-sv/v2/app"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v2"
) )
func CommitNotesFlags() []cli.Flag { func CommitNotesFlags(settings *app.CommitNotesSettings) []cli.Flag {
return []cli.Flag{ return []cli.Flag{
&cli.StringFlag{ &cli.StringFlag{
Name: "r", Aliases: []string{"range"}, Name: "r", Aliases: []string{"range"},
Usage: "type of range of commits, use: tag, date or hash", Usage: "type of range of commits, use: tag, date or hash",
Required: true, Required: true,
Destination: &settings.Range,
}, },
&cli.StringFlag{ &cli.StringFlag{
Name: "s", Name: "s",
Aliases: []string{"start"}, Aliases: []string{"start"},
Usage: "start range of git log revision range, if date, the value is used on since flag instead", Usage: "start range of git log revision range, if date, the value is used on since flag instead",
Destination: &settings.Start,
}, },
&cli.StringFlag{ &cli.StringFlag{
Name: "e", Name: "e",
Aliases: []string{"end"}, Aliases: []string{"end"},
Usage: "end range of git log revision range, if date, the value is used on until flag instead", Usage: "end range of git log revision range, if date, the value is used on until flag instead",
Destination: &settings.End,
},
&cli.StringFlag{
Name: "o",
Aliases: []string{"output"},
Usage: "output file name. Omit to use standard output.",
Destination: &settings.Out,
}, },
} }
} }
@ -32,16 +42,16 @@ func CommitNotesHandler(g app.GitSV) cli.ActionFunc {
return func(c *cli.Context) error { return func(c *cli.Context) error {
var date time.Time var date time.Time
rangeFlag := c.String("r") rangeFlag := g.Settings.CommitNotesSettings.Range
lr, err := logRange(g, rangeFlag, c.String("s"), c.String("e")) lr, err := logRange(g, rangeFlag, g.Settings.CommitNotesSettings.Start, g.Settings.CommitNotesSettings.End)
if err != nil { if err != nil {
return err return err
} }
commits, err := g.Log(lr) commits, err := g.Log(lr)
if err != nil { if err != nil {
return fmt.Errorf("error getting git log from range: %s, message: %w", rangeFlag, err) return fmt.Errorf("error getting git log from range: %s: %w", rangeFlag, err)
} }
if len(commits) > 0 { if len(commits) > 0 {
@ -50,10 +60,24 @@ func CommitNotesHandler(g app.GitSV) cli.ActionFunc {
output, err := g.OutputFormatter.FormatReleaseNote(g.ReleasenotesProcessor.Create(nil, "", date, commits)) output, err := g.OutputFormatter.FormatReleaseNote(g.ReleasenotesProcessor.Create(nil, "", date, commits))
if err != nil { if err != nil {
return fmt.Errorf("could not format release notes, message: %w", err) return fmt.Errorf("could not format commit notes: %w", err)
} }
fmt.Println(output) if g.Settings.CommitNotesSettings.Out == "" {
os.Stdout.WriteString(fmt.Sprintf("%s\n", output))
return nil
}
w, err := os.Create(g.Settings.CommitNotesSettings.Out)
if err != nil {
return fmt.Errorf("could not write commit notes: %w", err)
}
defer w.Close()
if _, err := w.Write(output); err != nil {
return err
}
return nil return nil
} }

View File

@ -14,7 +14,7 @@ func CurrentVersionHandler(gsv app.GitSV) cli.ActionFunc {
currentVer, err := sv.ToVersion(lastTag) currentVer, err := sv.ToVersion(lastTag)
if err != nil { if err != nil {
return fmt.Errorf("error parsing version: %s from git tag, message: %w", lastTag, err) return fmt.Errorf("error parsing version: %s from git tag: %w", lastTag, err)
} }
fmt.Printf("%d.%d.%d\n", currentVer.Major(), currentVer.Minor(), currentVer.Patch()) fmt.Printf("%d.%d.%d\n", currentVer.Major(), currentVer.Minor(), currentVer.Patch())

View File

@ -14,12 +14,12 @@ func NextVersionHandler(g app.GitSV) cli.ActionFunc {
currentVer, err := sv.ToVersion(lastTag) currentVer, err := sv.ToVersion(lastTag)
if err != nil { if err != nil {
return fmt.Errorf("error parsing version: %s from git tag, message: %w", lastTag, err) return fmt.Errorf("error parsing version: %s from git tag: %w", lastTag, err)
} }
commits, err := g.Log(app.NewLogRange(app.TagRange, lastTag, "")) commits, err := g.Log(app.NewLogRange(app.TagRange, lastTag, ""))
if err != nil { if err != nil {
return fmt.Errorf("error getting git log, message: %w", err) return fmt.Errorf("error getting git log: %w", err)
} }
nextVer, _ := g.CommitProcessor.NextVersion(currentVer, commits) nextVer, _ := g.CommitProcessor.NextVersion(currentVer, commits)

View File

@ -2,6 +2,7 @@ package commands
import ( import (
"fmt" "fmt"
"os"
"time" "time"
"github.com/Masterminds/semver/v3" "github.com/Masterminds/semver/v3"
@ -10,12 +11,19 @@ import (
"github.com/urfave/cli/v2" "github.com/urfave/cli/v2"
) )
func ReleaseNotesFlags() []cli.Flag { func ReleaseNotesFlags(settings *app.ReleaseNotesSettings) []cli.Flag {
return []cli.Flag{ return []cli.Flag{
&cli.StringFlag{ &cli.StringFlag{
Name: "t", Name: "t",
Aliases: []string{"tag"}, Aliases: []string{"tag"},
Usage: "get release note from tag", Usage: "get release note from tag",
Destination: &settings.Tag,
},
&cli.StringFlag{
Name: "o",
Aliases: []string{"output"},
Usage: "output file name. Omit to use standard output.",
Destination: &settings.Out,
}, },
} }
} }
@ -30,7 +38,7 @@ func ReleaseNotesHandler(g app.GitSV) cli.ActionFunc {
err error err error
) )
if tag = c.String("t"); tag != "" { if tag = g.Settings.ReleaseNotesSettings.Tag; tag != "" {
rnVersion, date, commits, err = getTagVersionInfo(g, tag) rnVersion, date, commits, err = getTagVersionInfo(g, tag)
} else { } else {
// TODO: should generate release notes if version was not updated? // TODO: should generate release notes if version was not updated?
@ -48,7 +56,21 @@ func ReleaseNotesHandler(g app.GitSV) cli.ActionFunc {
return fmt.Errorf("could not format release notes: %w", err) return fmt.Errorf("could not format release notes: %w", err)
} }
fmt.Println(output) if g.Settings.ReleaseNotesSettings.Out == "" {
os.Stdout.WriteString(fmt.Sprintf("%s\n", output))
return nil
}
w, err := os.Create(g.Settings.ReleaseNotesSettings.Out)
if err != nil {
return fmt.Errorf("could not write release notes: %w", err)
}
defer w.Close()
if _, err := w.Write(output); err != nil {
return err
}
return nil return nil
} }

View File

@ -14,12 +14,12 @@ func TagHandler(g app.GitSV) cli.ActionFunc {
currentVer, err := sv.ToVersion(lastTag) currentVer, err := sv.ToVersion(lastTag)
if err != nil { if err != nil {
return fmt.Errorf("error parsing version: %s from git tag, message: %w", lastTag, err) return fmt.Errorf("error parsing version: %s from git tag: %w", lastTag, err)
} }
commits, err := g.Log(app.NewLogRange(app.TagRange, lastTag, "")) commits, err := g.Log(app.NewLogRange(app.TagRange, lastTag, ""))
if err != nil { if err != nil {
return fmt.Errorf("error getting git log, message: %w", err) return fmt.Errorf("error getting git log: %w", err)
} }
nextVer, _ := g.CommitProcessor.NextVersion(currentVer, commits) nextVer, _ := g.CommitProcessor.NextVersion(currentVer, commits)
@ -28,7 +28,7 @@ func TagHandler(g app.GitSV) cli.ActionFunc {
fmt.Println(tagname) fmt.Println(tagname)
if err != nil { if err != nil {
return fmt.Errorf("error generating tag version: %s, message: %w", nextVer.String(), err) return fmt.Errorf("error generating tag version: %s: %w", nextVer.String(), err)
} }
return nil return nil

View File

@ -83,12 +83,12 @@ func getTagVersionInfo(gsv app.GitSV, tag string) (*semver.Version, time.Time, [
previousTag, currentTag, err := getTags(gsv, tag) previousTag, currentTag, err := getTags(gsv, tag)
if err != nil { if err != nil {
return nil, time.Time{}, nil, fmt.Errorf("error listing tags, message: %w", err) return nil, time.Time{}, nil, fmt.Errorf("error listing tags: %w", err)
} }
commits, err := gsv.Log(app.NewLogRange(app.TagRange, previousTag, tag)) commits, err := gsv.Log(app.NewLogRange(app.TagRange, previousTag, tag))
if err != nil { if err != nil {
return nil, time.Time{}, nil, fmt.Errorf("error getting git log from tag: %s, message: %w", tag, err) return nil, time.Time{}, nil, fmt.Errorf("error getting git log from tag: %s: %w", tag, err)
} }
return tagVersion, currentTag.Date, commits, nil return tagVersion, currentTag.Date, commits, nil
@ -101,7 +101,7 @@ func getNextVersionInfo(
commits, err := gsv.Log(app.NewLogRange(app.TagRange, lastTag, "")) commits, err := gsv.Log(app.NewLogRange(app.TagRange, lastTag, ""))
if err != nil { if err != nil {
return nil, false, time.Time{}, nil, fmt.Errorf("error getting git log, message: %w", err) return nil, false, time.Time{}, nil, fmt.Errorf("error getting git log: %w", err)
} }
currentVer, _ := sv.ToVersion(lastTag) currentVer, _ := sv.ToVersion(lastTag)

View File

@ -12,6 +12,34 @@ import (
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
) )
type Settings struct {
LogLevel string
ChangelogSettings ChangelogSettings
ReleaseNotesSettings ReleaseNotesSettings
CommitNotesSettings CommitNotesSettings
}
type ChangelogSettings struct {
Size int
All bool
AddNext bool
Strict bool
Out string
}
type ReleaseNotesSettings struct {
Tag string
Out string
}
type CommitNotesSettings struct {
Range string
Start string
End string
Out string
}
// Config cli yaml config. // Config cli yaml config.
type Config struct { type Config struct {
Version string `yaml:"version"` Version string `yaml:"version"`
@ -23,6 +51,12 @@ type Config struct {
CommitMessage sv.CommitMessageConfig `yaml:"commit-message"` CommitMessage sv.CommitMessageConfig `yaml:"commit-message"`
} }
// TagConfig tag preferences.
type TagConfig struct {
Pattern *string `yaml:"pattern"`
Filter *string `yaml:"filter"`
}
func NewConfig(configDir, configFilename string) *Config { func NewConfig(configDir, configFilename string) *Config {
workDir, _ := os.Getwd() workDir, _ := os.Getwd()
cfg := GetDefault() cfg := GetDefault()
@ -184,19 +218,3 @@ func migrateReleaseNotes(headers map[string]string) []sv.ReleaseNotesSectionConf
return sections return sections
} }
// ==== Message ====
// CommitMessageConfig config a commit message.
// ==== Branches ====
// ==== Versioning ====
// ==== Tag ====
// TagConfig tag preferences.
type TagConfig struct {
Pattern *string `yaml:"pattern"`
Filter *string `yaml:"filter"`
}

View File

@ -20,6 +20,7 @@ var (
func main() { func main() {
gsv := app.New() gsv := app.New()
cli.VersionPrinter = func(c *cli.Context) { cli.VersionPrinter = func(c *cli.Context) {
fmt.Printf("%s version=%s date=%s\n", c.App.Name, c.App.Version, BuildDate) fmt.Printf("%s version=%s date=%s\n", c.App.Name, c.App.Version, BuildDate)
} }
@ -33,12 +34,13 @@ func main() {
Name: "log-level", Name: "log-level",
Usage: "log level", Usage: "log level",
Value: "info", Value: "info",
Destination: &gsv.Settings.LogLevel,
}, },
}, },
Before: func(ctx *cli.Context) error { Before: func(ctx *cli.Context) error {
log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr}) log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr})
lvl, err := zerolog.ParseLevel(ctx.String("log-level")) lvl, err := zerolog.ParseLevel(gsv.Settings.LogLevel)
if err != nil { if err != nil {
return err return err
} }
@ -95,21 +97,21 @@ When flag range is "date", if "end" is YYYY-MM-DD the range will be inclusive.`,
for more info. When flag range is "tag" and start is empty, last tag created will be used instead. for more info. When flag range is "tag" and start is empty, last tag created will be used instead.
When flag range is "date", if "end" is YYYY-MM-DD the range will be inclusive.`, When flag range is "date", if "end" is YYYY-MM-DD the range will be inclusive.`,
Action: commands.CommitNotesHandler(gsv), Action: commands.CommitNotesHandler(gsv),
Flags: commands.CommitNotesFlags(), Flags: commands.CommitNotesFlags(&gsv.Settings.CommitNotesSettings),
}, },
{ {
Name: "release-notes", Name: "release-notes",
Aliases: []string{"rn"}, Aliases: []string{"rn"},
Usage: "generate release notes", Usage: "generate release notes",
Action: commands.ReleaseNotesHandler(gsv), Action: commands.ReleaseNotesHandler(gsv),
Flags: commands.ReleaseNotesFlags(), Flags: commands.ReleaseNotesFlags(&gsv.Settings.ReleaseNotesSettings),
}, },
{ {
Name: "changelog", Name: "changelog",
Aliases: []string{"cgl"}, Aliases: []string{"cgl"},
Usage: "generate changelog", Usage: "generate changelog",
Action: commands.ChangelogHandler(gsv), Action: commands.ChangelogHandler(gsv),
Flags: commands.ChangelogFlags(), Flags: commands.ChangelogFlags(&gsv.Settings.ChangelogSettings),
}, },
{ {
Name: "tag", Name: "tag",

View File

@ -21,8 +21,8 @@ type releaseNoteTemplateVariables struct {
// OutputFormatter output formatter interface. // OutputFormatter output formatter interface.
type OutputFormatter interface { type OutputFormatter interface {
FormatReleaseNote(releasenote sv.ReleaseNote) (string, error) FormatReleaseNote(releasenote sv.ReleaseNote) ([]byte, error)
FormatChangelog(releasenotes []sv.ReleaseNote) (string, error) FormatChangelog(releasenotes []sv.ReleaseNote) ([]byte, error)
} }
// BaseOutputFormatter formater for release note and changelog. // BaseOutputFormatter formater for release note and changelog.
@ -36,17 +36,17 @@ func NewOutputFormatter(tpls *template.Template) *BaseOutputFormatter {
} }
// FormatReleaseNote format a release note. // FormatReleaseNote format a release note.
func (p BaseOutputFormatter) FormatReleaseNote(releasenote sv.ReleaseNote) (string, error) { func (p BaseOutputFormatter) FormatReleaseNote(releasenote sv.ReleaseNote) ([]byte, error) {
var b bytes.Buffer var b bytes.Buffer
if err := p.templates.ExecuteTemplate(&b, "releasenotes-md.tpl", releaseNoteVariables(releasenote)); err != nil { if err := p.templates.ExecuteTemplate(&b, "releasenotes-md.tpl", releaseNoteVariables(releasenote)); err != nil {
return "", err return b.Bytes(), err
} }
return b.String(), nil return b.Bytes(), nil
} }
// FormatChangelog format a changelog. // FormatChangelog format a changelog.
func (p BaseOutputFormatter) FormatChangelog(releasenotes []sv.ReleaseNote) (string, error) { func (p BaseOutputFormatter) FormatChangelog(releasenotes []sv.ReleaseNote) ([]byte, error) {
templateVars := make([]releaseNoteTemplateVariables, len(releasenotes)) templateVars := make([]releaseNoteTemplateVariables, len(releasenotes))
for i, v := range releasenotes { for i, v := range releasenotes {
templateVars[i] = releaseNoteVariables(v) templateVars[i] = releaseNoteVariables(v)
@ -54,10 +54,10 @@ func (p BaseOutputFormatter) FormatChangelog(releasenotes []sv.ReleaseNote) (str
var b bytes.Buffer var b bytes.Buffer
if err := p.templates.ExecuteTemplate(&b, "changelog-md.tpl", templateVars); err != nil { if err := p.templates.ExecuteTemplate(&b, "changelog-md.tpl", templateVars); err != nil {
return "", err return b.Bytes(), err
} }
return b.String(), nil return b.Bytes(), nil
} }
func releaseNoteVariables(releasenote sv.ReleaseNote) releaseNoteTemplateVariables { func releaseNoteVariables(releasenote sv.ReleaseNote) releaseNoteTemplateVariables {

View File

@ -56,7 +56,7 @@ func TestBaseOutputFormatter_FormatReleaseNote(t *testing.T) {
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
got, err := NewOutputFormatter(tmpls).FormatReleaseNote(tt.input) got, err := NewOutputFormatter(tmpls).FormatReleaseNote(tt.input)
if got != tt.want { if string(got) != tt.want {
t.Errorf("BaseOutputFormatter.FormatReleaseNote() = %v, want %v", got, tt.want) t.Errorf("BaseOutputFormatter.FormatReleaseNote() = %v, want %v", got, tt.want)
} }