From 7051e7fa89b66cff85f811c9e8a86355c8891743 Mon Sep 17 00:00:00 2001 From: Hau Nguyen Date: Sun, 3 Jul 2022 23:19:14 +0700 Subject: [PATCH] feat: auto commit after generating changelog --- internal/cli/action.go | 2 + internal/cli/action_generate.go | 66 ++++++++++++++++++--------------- internal/cli/app.go | 65 +++++++++++++++++--------------- internal/git/repository.go | 32 +++++++++++++--- internal/git/repository_test.go | 2 +- 5 files changed, 101 insertions(+), 66 deletions(-) diff --git a/internal/cli/action.go b/internal/cli/action.go index e6bc1ee..fe8e896 100644 --- a/internal/cli/action.go +++ b/internal/cli/action.go @@ -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) } diff --git a/internal/cli/action_generate.go b/internal/cli/action_generate.go index 5e3436e..3784ac4 100644 --- a/internal/cli/action_generate.go +++ b/internal/cli/action_generate.go @@ -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 diff --git a/internal/cli/app.go b/internal/cli/app.go index 4cdf1f1..4830078 100644 --- a/internal/cli/app.go +++ b/internal/cli/app.go @@ -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, }, diff --git a/internal/git/repository.go b/internal/git/repository.go index 5c7ffe2..698f648 100644 --- a/internal/git/repository.go +++ b/internal/git/repository.go @@ -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 { diff --git a/internal/git/repository_test.go b/internal/git/repository_test.go index baaf0db..591c98b 100644 --- a/internal/git/repository_test.go +++ b/internal/git/repository_test.go @@ -24,7 +24,7 @@ func (s *RepositorySuite) SetupTest() { s.NoError(err) s.repo = &repo{ - r: s.r, + gitRepo: s.r, } }