feat: auto commit after generating changelog
parent
ecd31677ce
commit
7051e7fa89
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
},
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -24,7 +24,7 @@ func (s *RepositorySuite) SetupTest() {
|
|||
s.NoError(err)
|
||||
|
||||
s.repo = &repo{
|
||||
r: s.r,
|
||||
gitRepo: s.r,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue