feat: auto commit after generating changelog

main
sudo pacman -Syu 2022-07-03 23:19:14 +07:00 committed by sudo pacman -Syu
parent ecd31677ce
commit 7051e7fa89
5 changed files with 101 additions and 66 deletions

View File

@ -30,6 +30,7 @@ type action struct {
verbose bool
dryRun bool
interactive bool
autoCommit bool
}
}
@ -54,6 +55,7 @@ func (a *action) getFlags(c *cli.Context) {
a.flags.filetype = c.String(flagFiletype)
a.flags.dryRun = c.Bool(flagDryRun)
a.flags.interactive = c.Bool(flagInteractive)
a.flags.autoCommit = c.Bool(flagAutoCommit)
a.log("flags %+v", a.flags)
}

View File

@ -20,6 +20,8 @@ import (
"golang.org/x/mod/semver"
)
const autoCommitMessageTemplate = "chore(changelog): generate version %s"
var (
ErrUnknownFiletype = errors.New("unknown filetype")
ErrInvalidVersion = errors.New("invalid version")
@ -37,34 +39,45 @@ func (a *action) RunGenerate(c *cli.Context) error {
fmt.Printf("Input version (%s):\n", usageFlagVersion)
a.flags.version = ioe.ReadInput()
fmt.Printf("Input from (%s):\n", usageFrom)
fmt.Printf("Input from (%s):\n", usageFlagFrom)
a.flags.from = ioe.ReadInputEmpty()
fmt.Printf("Input to (%s):\n", usageTo)
fmt.Printf("Input to (%s):\n", usageFlagTo)
a.flags.to = ioe.ReadInputEmpty()
}
commits, err := a.getCommits()
repo, err := git.NewRepository(a.flags.repository)
if err != nil {
return err
}
commits, err := repo.Log(a.flags.from, a.flags.to)
if err != nil {
return err
}
conventionalCommits := a.getConventionalCommits(commits)
if err := a.generateChangelog(conventionalCommits); err != nil {
finalOutput := a.getFinalOutput()
version, err := a.getVersion()
if err != nil {
return err
}
return nil
}
func (a *action) getCommits() ([]git.Commit, error) {
r, err := git.NewRepository(a.flags.repository)
if err != nil {
return nil, err
if err := a.generateChangelog(conventionalCommits, finalOutput, version); err != nil {
return err
}
return r.Log(a.flags.from, a.flags.to)
if a.flags.autoCommit {
commitMsg := fmt.Sprintf(autoCommitMessageTemplate, version)
if err := repo.Commit(commitMsg, a.flags.filename, finalOutput); err != nil {
return err
}
}
return nil
}
func (a *action) getConventionalCommits(commits []git.Commit) []convention.Commit {
@ -83,24 +96,6 @@ func (a *action) getConventionalCommits(commits []git.Commit) []convention.Commi
return conventionalCommits
}
func (a *action) generateChangelog(commits []convention.Commit) error {
finalOutput := a.getFinalOutput()
version, err := a.getVersion()
if err != nil {
return err
}
switch a.flags.filetype {
case markdownFiletype:
return a.generateMarkdownChangelog(finalOutput, version, commits)
case rstFiletype:
return a.generateRSTChangelog(finalOutput, version, commits)
default:
return fmt.Errorf("unknown filetype %s: %w", a.flags.filetype, ErrUnknownFiletype)
}
}
func (a *action) getFinalOutput() string {
nameWithExt := a.flags.filename + "." + a.flags.filetype
finalOutput := filepath.Join(a.flags.output, nameWithExt)
@ -128,6 +123,17 @@ func (a *action) getVersion() (string, error) {
return a.flags.version, nil
}
func (a *action) generateChangelog(commits []convention.Commit, finalOutput, version string) error {
switch a.flags.filetype {
case markdownFiletype:
return a.generateMarkdownChangelog(finalOutput, version, commits)
case rstFiletype:
return a.generateRSTChangelog(finalOutput, version, commits)
default:
return fmt.Errorf("unknown filetype %s: %w", a.flags.filetype, ErrUnknownFiletype)
}
}
func (a *action) generateMarkdownChangelog(output, version string, commits []convention.Commit) error {
// If changelog file already exist, parse markdown from exist file
var oldNodes []markdown.Node

View File

@ -22,27 +22,29 @@ const (
flagFiletype = "filetype"
flagDryRun = "dry-run"
flagInteractive = "interactive"
flagAutoCommit = "auto-commit"
commandGenerate = "generate"
usageGenerate = "generate changelog"
usageVerbose = "show what is going on"
usageFlagVersion = "`VERSION` to generate, follow Semantic Versioning"
usageFrom = "from `COMMIT`, which is kinda new commit, default is latest commit"
usageTo = "to `COMMIT`, which is kinda old commit, default is oldest commit"
usageScope = "scope to generate"
usageRepository = "`REPOSITORY` directory path"
usageOutput = "`OUTPUT` directory path"
usageFilename = "output `FILENAME`"
usageFiletype = "output `FILETYPE`"
usageDryRun = "demo run without actually changing anything"
usageInteractive = "interactive mode, default is true"
usageCommandGenerate = "generate changelog"
usageFlagVerbose = "show what is going on"
usageFlagVersion = "`VERSION` to generate, follow Semantic Versioning"
usageFlagFrom = "from `COMMIT`, which is kinda new commit, default is latest commit"
usageFlagTo = "to `COMMIT`, which is kinda old commit, default is oldest commit"
usageFlagScope = "scope to generate"
usageFlagRepository = "`REPOSITORY` directory path"
usageFlagOutput = "`OUTPUT` directory path"
usageFlagFilename = "output `FILENAME`"
usageFlagFiletype = "output `FILETYPE`"
usageFlagDryRun = "demo run without actually changing anything"
usageFlagInteractive = "interactive mode"
usageFlagAutoCommit = "enable auto commit after generating changelog"
)
var (
aliasGenerate = []string{"g", "gen"}
aliasVerbose = []string{"v"}
aliasInteractive = []string{"i"}
aliasCommandGenerate = []string{"g", "gen"}
aliasFlagVerbose = []string{"v"}
aliasFlagInteractive = []string{"i"}
)
type App struct {
@ -58,13 +60,13 @@ func NewApp() *App {
Commands: []*cli.Command{
{
Name: commandGenerate,
Aliases: aliasGenerate,
Usage: usageGenerate,
Aliases: aliasCommandGenerate,
Usage: usageCommandGenerate,
Flags: []cli.Flag{
&cli.BoolFlag{
Name: flagVerbose,
Aliases: aliasVerbose,
Usage: usageVerbose,
Aliases: aliasFlagVerbose,
Usage: usageFlagVerbose,
},
&cli.StringFlag{
Name: flagVersion,
@ -72,46 +74,51 @@ func NewApp() *App {
},
&cli.StringFlag{
Name: flagFrom,
Usage: usageFrom,
Usage: usageFlagFrom,
},
&cli.StringFlag{
Name: flagTo,
Usage: usageTo,
Usage: usageFlagTo,
},
&cli.StringSliceFlag{
Name: flagScope,
Usage: usageScope,
Usage: usageFlagScope,
},
&cli.StringFlag{
Name: flagRepository,
Usage: usageRepository,
Usage: usageFlagRepository,
Value: defaultRepository,
},
&cli.StringFlag{
Name: flagOutput,
Usage: usageOutput,
Usage: usageFlagOutput,
Value: defaultOutput,
},
&cli.StringFlag{
Name: flagFilename,
Usage: usageFilename,
Usage: usageFlagFilename,
Value: defaultFilename,
},
&cli.StringFlag{
Name: flagFiletype,
Usage: usageFiletype,
Usage: usageFlagFiletype,
Value: defaultFiletype,
},
&cli.BoolFlag{
Name: flagDryRun,
Usage: usageDryRun,
Usage: usageFlagDryRun,
},
&cli.BoolFlag{
Name: flagInteractive,
Usage: usageInteractive,
Aliases: aliasInteractive,
Usage: usageFlagInteractive,
Aliases: aliasFlagInteractive,
Value: true,
},
&cli.BoolFlag{
Name: flagAutoCommit,
Usage: usageFlagAutoCommit,
Value: true,
},
},
Action: a.RunGenerate,
},

View File

@ -18,23 +18,24 @@ const (
// Repository is an abstraction for git-repository
type Repository interface {
Log(fromRev, toRev string) ([]Commit, error)
Commit(commitMessage string, paths ...string) error
}
type repo struct {
r *git.Repository
gitRepo *git.Repository
}
type stopFn func(*object.Commit) error
// NewRepository return Repository from path
func NewRepository(path string) (Repository, error) {
r, err := git.PlainOpen(path)
gitRepo, err := git.PlainOpen(path)
if err != nil {
return nil, err
}
return &repo{
r: r,
gitRepo: gitRepo,
}, nil
}
@ -44,7 +45,7 @@ func (r *repo) Log(fromRev, toRev string) ([]Commit, error) {
fromRev = head
}
fromHash, err := r.r.ResolveRevision(plumbing.Revision(fromRev))
fromHash, err := r.gitRepo.ResolveRevision(plumbing.Revision(fromRev))
if err != nil {
return nil, fmt.Errorf("failed to resolve %s: %w", fromRev, err)
}
@ -53,7 +54,7 @@ func (r *repo) Log(fromRev, toRev string) ([]Commit, error) {
return r.logWithStopFn(fromHash, nil, nil)
}
toHash, err := r.r.ResolveRevision(plumbing.Revision(toRev))
toHash, err := r.gitRepo.ResolveRevision(plumbing.Revision(toRev))
if err != nil {
return nil, fmt.Errorf("failed to resolve %s: %w", toRev, err)
}
@ -61,8 +62,27 @@ func (r *repo) Log(fromRev, toRev string) ([]Commit, error) {
return r.logWithStopFn(fromHash, nil, stopAtHash(toHash))
}
func (r *repo) Commit(commitMessage string, paths ...string) error {
gitWorktree, err := r.gitRepo.Worktree()
if err != nil {
return fmt.Errorf("failed to get git worktree: %w", err)
}
for _, path := range paths {
if _, err := gitWorktree.Add(path); err != nil {
return fmt.Errorf("failed to git add %s: %w", path, err)
}
}
if _, err := gitWorktree.Commit(commitMessage, &git.CommitOptions{}); err != nil {
return fmt.Errorf("failed to git commit: %w", err)
}
return nil
}
func (r *repo) logWithStopFn(fromHash *plumbing.Hash, beginFn, endFn stopFn) ([]Commit, error) {
cIter, err := r.r.Log(&git.LogOptions{
cIter, err := r.gitRepo.Log(&git.LogOptions{
From: *fromHash,
})
if err != nil {

View File

@ -24,7 +24,7 @@ func (s *RepositorySuite) SetupTest() {
s.NoError(err)
s.repo = &repo{
r: s.r,
gitRepo: s.r,
}
}