feat: auto commit after generating changelog
parent
ecd31677ce
commit
7051e7fa89
|
@ -30,6 +30,7 @@ type action struct {
|
||||||
verbose bool
|
verbose bool
|
||||||
dryRun bool
|
dryRun bool
|
||||||
interactive bool
|
interactive bool
|
||||||
|
autoCommit bool
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -54,6 +55,7 @@ func (a *action) getFlags(c *cli.Context) {
|
||||||
a.flags.filetype = c.String(flagFiletype)
|
a.flags.filetype = c.String(flagFiletype)
|
||||||
a.flags.dryRun = c.Bool(flagDryRun)
|
a.flags.dryRun = c.Bool(flagDryRun)
|
||||||
a.flags.interactive = c.Bool(flagInteractive)
|
a.flags.interactive = c.Bool(flagInteractive)
|
||||||
|
a.flags.autoCommit = c.Bool(flagAutoCommit)
|
||||||
|
|
||||||
a.log("flags %+v", a.flags)
|
a.log("flags %+v", a.flags)
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,6 +20,8 @@ import (
|
||||||
"golang.org/x/mod/semver"
|
"golang.org/x/mod/semver"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const autoCommitMessageTemplate = "chore(changelog): generate version %s"
|
||||||
|
|
||||||
var (
|
var (
|
||||||
ErrUnknownFiletype = errors.New("unknown filetype")
|
ErrUnknownFiletype = errors.New("unknown filetype")
|
||||||
ErrInvalidVersion = errors.New("invalid version")
|
ErrInvalidVersion = errors.New("invalid version")
|
||||||
|
@ -37,34 +39,45 @@ func (a *action) RunGenerate(c *cli.Context) error {
|
||||||
fmt.Printf("Input version (%s):\n", usageFlagVersion)
|
fmt.Printf("Input version (%s):\n", usageFlagVersion)
|
||||||
a.flags.version = ioe.ReadInput()
|
a.flags.version = ioe.ReadInput()
|
||||||
|
|
||||||
fmt.Printf("Input from (%s):\n", usageFrom)
|
fmt.Printf("Input from (%s):\n", usageFlagFrom)
|
||||||
a.flags.from = ioe.ReadInputEmpty()
|
a.flags.from = ioe.ReadInputEmpty()
|
||||||
|
|
||||||
fmt.Printf("Input to (%s):\n", usageTo)
|
fmt.Printf("Input to (%s):\n", usageFlagTo)
|
||||||
a.flags.to = ioe.ReadInputEmpty()
|
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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
conventionalCommits := a.getConventionalCommits(commits)
|
conventionalCommits := a.getConventionalCommits(commits)
|
||||||
|
|
||||||
if err := a.generateChangelog(conventionalCommits); err != nil {
|
finalOutput := a.getFinalOutput()
|
||||||
|
|
||||||
|
version, err := a.getVersion()
|
||||||
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
if err := a.generateChangelog(conventionalCommits, finalOutput, version); err != nil {
|
||||||
}
|
return err
|
||||||
|
|
||||||
func (a *action) getCommits() ([]git.Commit, error) {
|
|
||||||
r, err := git.NewRepository(a.flags.repository)
|
|
||||||
if err != nil {
|
|
||||||
return nil, 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 {
|
func (a *action) getConventionalCommits(commits []git.Commit) []convention.Commit {
|
||||||
|
@ -83,24 +96,6 @@ func (a *action) getConventionalCommits(commits []git.Commit) []convention.Commi
|
||||||
return conventionalCommits
|
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 {
|
func (a *action) getFinalOutput() string {
|
||||||
nameWithExt := a.flags.filename + "." + a.flags.filetype
|
nameWithExt := a.flags.filename + "." + a.flags.filetype
|
||||||
finalOutput := filepath.Join(a.flags.output, nameWithExt)
|
finalOutput := filepath.Join(a.flags.output, nameWithExt)
|
||||||
|
@ -128,6 +123,17 @@ func (a *action) getVersion() (string, error) {
|
||||||
return a.flags.version, nil
|
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 {
|
func (a *action) generateMarkdownChangelog(output, version string, commits []convention.Commit) error {
|
||||||
// If changelog file already exist, parse markdown from exist file
|
// If changelog file already exist, parse markdown from exist file
|
||||||
var oldNodes []markdown.Node
|
var oldNodes []markdown.Node
|
||||||
|
|
|
@ -22,27 +22,29 @@ const (
|
||||||
flagFiletype = "filetype"
|
flagFiletype = "filetype"
|
||||||
flagDryRun = "dry-run"
|
flagDryRun = "dry-run"
|
||||||
flagInteractive = "interactive"
|
flagInteractive = "interactive"
|
||||||
|
flagAutoCommit = "auto-commit"
|
||||||
|
|
||||||
commandGenerate = "generate"
|
commandGenerate = "generate"
|
||||||
|
|
||||||
usageGenerate = "generate changelog"
|
usageCommandGenerate = "generate changelog"
|
||||||
usageVerbose = "show what is going on"
|
usageFlagVerbose = "show what is going on"
|
||||||
usageFlagVersion = "`VERSION` to generate, follow Semantic Versioning"
|
usageFlagVersion = "`VERSION` to generate, follow Semantic Versioning"
|
||||||
usageFrom = "from `COMMIT`, which is kinda new commit, default is latest commit"
|
usageFlagFrom = "from `COMMIT`, which is kinda new commit, default is latest commit"
|
||||||
usageTo = "to `COMMIT`, which is kinda old commit, default is oldest commit"
|
usageFlagTo = "to `COMMIT`, which is kinda old commit, default is oldest commit"
|
||||||
usageScope = "scope to generate"
|
usageFlagScope = "scope to generate"
|
||||||
usageRepository = "`REPOSITORY` directory path"
|
usageFlagRepository = "`REPOSITORY` directory path"
|
||||||
usageOutput = "`OUTPUT` directory path"
|
usageFlagOutput = "`OUTPUT` directory path"
|
||||||
usageFilename = "output `FILENAME`"
|
usageFlagFilename = "output `FILENAME`"
|
||||||
usageFiletype = "output `FILETYPE`"
|
usageFlagFiletype = "output `FILETYPE`"
|
||||||
usageDryRun = "demo run without actually changing anything"
|
usageFlagDryRun = "demo run without actually changing anything"
|
||||||
usageInteractive = "interactive mode, default is true"
|
usageFlagInteractive = "interactive mode"
|
||||||
|
usageFlagAutoCommit = "enable auto commit after generating changelog"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
aliasGenerate = []string{"g", "gen"}
|
aliasCommandGenerate = []string{"g", "gen"}
|
||||||
aliasVerbose = []string{"v"}
|
aliasFlagVerbose = []string{"v"}
|
||||||
aliasInteractive = []string{"i"}
|
aliasFlagInteractive = []string{"i"}
|
||||||
)
|
)
|
||||||
|
|
||||||
type App struct {
|
type App struct {
|
||||||
|
@ -58,13 +60,13 @@ func NewApp() *App {
|
||||||
Commands: []*cli.Command{
|
Commands: []*cli.Command{
|
||||||
{
|
{
|
||||||
Name: commandGenerate,
|
Name: commandGenerate,
|
||||||
Aliases: aliasGenerate,
|
Aliases: aliasCommandGenerate,
|
||||||
Usage: usageGenerate,
|
Usage: usageCommandGenerate,
|
||||||
Flags: []cli.Flag{
|
Flags: []cli.Flag{
|
||||||
&cli.BoolFlag{
|
&cli.BoolFlag{
|
||||||
Name: flagVerbose,
|
Name: flagVerbose,
|
||||||
Aliases: aliasVerbose,
|
Aliases: aliasFlagVerbose,
|
||||||
Usage: usageVerbose,
|
Usage: usageFlagVerbose,
|
||||||
},
|
},
|
||||||
&cli.StringFlag{
|
&cli.StringFlag{
|
||||||
Name: flagVersion,
|
Name: flagVersion,
|
||||||
|
@ -72,46 +74,51 @@ func NewApp() *App {
|
||||||
},
|
},
|
||||||
&cli.StringFlag{
|
&cli.StringFlag{
|
||||||
Name: flagFrom,
|
Name: flagFrom,
|
||||||
Usage: usageFrom,
|
Usage: usageFlagFrom,
|
||||||
},
|
},
|
||||||
&cli.StringFlag{
|
&cli.StringFlag{
|
||||||
Name: flagTo,
|
Name: flagTo,
|
||||||
Usage: usageTo,
|
Usage: usageFlagTo,
|
||||||
},
|
},
|
||||||
&cli.StringSliceFlag{
|
&cli.StringSliceFlag{
|
||||||
Name: flagScope,
|
Name: flagScope,
|
||||||
Usage: usageScope,
|
Usage: usageFlagScope,
|
||||||
},
|
},
|
||||||
&cli.StringFlag{
|
&cli.StringFlag{
|
||||||
Name: flagRepository,
|
Name: flagRepository,
|
||||||
Usage: usageRepository,
|
Usage: usageFlagRepository,
|
||||||
Value: defaultRepository,
|
Value: defaultRepository,
|
||||||
},
|
},
|
||||||
&cli.StringFlag{
|
&cli.StringFlag{
|
||||||
Name: flagOutput,
|
Name: flagOutput,
|
||||||
Usage: usageOutput,
|
Usage: usageFlagOutput,
|
||||||
Value: defaultOutput,
|
Value: defaultOutput,
|
||||||
},
|
},
|
||||||
&cli.StringFlag{
|
&cli.StringFlag{
|
||||||
Name: flagFilename,
|
Name: flagFilename,
|
||||||
Usage: usageFilename,
|
Usage: usageFlagFilename,
|
||||||
Value: defaultFilename,
|
Value: defaultFilename,
|
||||||
},
|
},
|
||||||
&cli.StringFlag{
|
&cli.StringFlag{
|
||||||
Name: flagFiletype,
|
Name: flagFiletype,
|
||||||
Usage: usageFiletype,
|
Usage: usageFlagFiletype,
|
||||||
Value: defaultFiletype,
|
Value: defaultFiletype,
|
||||||
},
|
},
|
||||||
&cli.BoolFlag{
|
&cli.BoolFlag{
|
||||||
Name: flagDryRun,
|
Name: flagDryRun,
|
||||||
Usage: usageDryRun,
|
Usage: usageFlagDryRun,
|
||||||
},
|
},
|
||||||
&cli.BoolFlag{
|
&cli.BoolFlag{
|
||||||
Name: flagInteractive,
|
Name: flagInteractive,
|
||||||
Usage: usageInteractive,
|
Usage: usageFlagInteractive,
|
||||||
Aliases: aliasInteractive,
|
Aliases: aliasFlagInteractive,
|
||||||
Value: true,
|
Value: true,
|
||||||
},
|
},
|
||||||
|
&cli.BoolFlag{
|
||||||
|
Name: flagAutoCommit,
|
||||||
|
Usage: usageFlagAutoCommit,
|
||||||
|
Value: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
Action: a.RunGenerate,
|
Action: a.RunGenerate,
|
||||||
},
|
},
|
||||||
|
|
|
@ -18,23 +18,24 @@ const (
|
||||||
// Repository is an abstraction for git-repository
|
// Repository is an abstraction for git-repository
|
||||||
type Repository interface {
|
type Repository interface {
|
||||||
Log(fromRev, toRev string) ([]Commit, error)
|
Log(fromRev, toRev string) ([]Commit, error)
|
||||||
|
Commit(commitMessage string, paths ...string) error
|
||||||
}
|
}
|
||||||
|
|
||||||
type repo struct {
|
type repo struct {
|
||||||
r *git.Repository
|
gitRepo *git.Repository
|
||||||
}
|
}
|
||||||
|
|
||||||
type stopFn func(*object.Commit) error
|
type stopFn func(*object.Commit) error
|
||||||
|
|
||||||
// NewRepository return Repository from path
|
// NewRepository return Repository from path
|
||||||
func NewRepository(path string) (Repository, error) {
|
func NewRepository(path string) (Repository, error) {
|
||||||
r, err := git.PlainOpen(path)
|
gitRepo, err := git.PlainOpen(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return &repo{
|
return &repo{
|
||||||
r: r,
|
gitRepo: gitRepo,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -44,7 +45,7 @@ func (r *repo) Log(fromRev, toRev string) ([]Commit, error) {
|
||||||
fromRev = head
|
fromRev = head
|
||||||
}
|
}
|
||||||
|
|
||||||
fromHash, err := r.r.ResolveRevision(plumbing.Revision(fromRev))
|
fromHash, err := r.gitRepo.ResolveRevision(plumbing.Revision(fromRev))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to resolve %s: %w", fromRev, err)
|
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)
|
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 {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to resolve %s: %w", toRev, err)
|
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))
|
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) {
|
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,
|
From: *fromHash,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -24,7 +24,7 @@ func (s *RepositorySuite) SetupTest() {
|
||||||
s.NoError(err)
|
s.NoError(err)
|
||||||
|
|
||||||
s.repo = &repo{
|
s.repo = &repo{
|
||||||
r: s.r,
|
gitRepo: s.r,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue