From ff34b484a5e02c43381599aead7bd0f1592cbed1 Mon Sep 17 00:00:00 2001 From: Beatriz Vieira Date: Sat, 31 Jul 2021 00:47:58 -0300 Subject: [PATCH] feat: add flags to commit command --- README.md | 46 +++++++-------- cmd/git-sv/handlers.go | 131 ++++++++++++++++++++++++++++++----------- cmd/git-sv/main.go | 10 ++++ cmd/git-sv/prompt.go | 2 +- sv/message.go | 32 +++++++++- sv/message_test.go | 65 ++++++++++++++++++++ 6 files changed, 227 insertions(+), 59 deletions(-) diff --git a/README.md b/README.md index f4597ad..eb1ba6f 100644 --- a/README.md +++ b/README.md @@ -141,19 +141,19 @@ git-sv rn -h ##### Available commands -| Variable | description | has options or subcommands | -| ---------------------------- | ------------------------------------------------------------- | :------------------------: | -| config, cfg | Show config information. | :heavy_check_mark: | -| current-version, cv | Get last released version from git. | :x: | -| next-version, nv | Generate the next version based on git commit messages. | :x: | -| commit-log, cl | List all commit logs according to range as jsons. | :heavy_check_mark: | -| commit-notes, cn | Generate a commit notes according to range. | :heavy_check_mark: | -| release-notes, rn | Generate release notes. | :heavy_check_mark: | -| changelog, cgl | Generate changelog. | :heavy_check_mark: | -| tag, tg | Generate tag with version based on git commit messages. | :x: | -| commit, cmt | Execute git commit with convetional commit message helper. | :x: | -| validate-commit-message, vcm | Use as prepare-commit-message hook to validate commit message.| :heavy_check_mark: | -| help, h | Shows a list of commands or help for one command. | :x: | +| Variable | description | has options or subcommands | +| ---------------------------- | -------------------------------------------------------------- | :------------------------: | +| config, cfg | Show config information. | :heavy_check_mark: | +| current-version, cv | Get last released version from git. | :x: | +| next-version, nv | Generate the next version based on git commit messages. | :x: | +| commit-log, cl | List all commit logs according to range as jsons. | :heavy_check_mark: | +| commit-notes, cn | Generate a commit notes according to range. | :heavy_check_mark: | +| release-notes, rn | Generate release notes. | :heavy_check_mark: | +| changelog, cgl | Generate changelog. | :heavy_check_mark: | +| tag, tg | Generate tag with version based on git commit messages. | :x: | +| commit, cmt | Execute git commit with convetional commit message helper. | :heavy_check_mark: | +| validate-commit-message, vcm | Use as prepare-commit-message hook to validate commit message. | :heavy_check_mark: | +| help, h | Shows a list of commands or help for one command. | :x: | ##### Use range @@ -209,17 +209,17 @@ make #### Make configs -| Variable | description | -| ---------- | ---------------------- | -| BUILDOS | Build OS. | -| BUILDARCH | Build arch. | -| ECHOFLAGS | Flags used on echo. | -| BUILDENVS | Var envs used on build.| -| BUILDFLAGS | Flags used on build. | +| Variable | description | +| ---------- | ----------------------- | +| BUILDOS | Build OS. | +| BUILDARCH | Build arch. | +| ECHOFLAGS | Flags used on echo. | +| BUILDENVS | Var envs used on build. | +| BUILDFLAGS | Flags used on build. | -| Parameters | description | -| ---------- | ----------------------------------- | -| args | Parameters that will be used on run.| +| Parameters | description | +| ---------- | ------------------------------------ | +| args | Parameters that will be used on run. | ```bash #variables diff --git a/cmd/git-sv/handlers.go b/cmd/git-sv/handlers.go index cc3da8e..64a8564 100644 --- a/cmd/git-sv/handlers.go +++ b/cmd/git-sv/handlers.go @@ -266,62 +266,127 @@ func tagHandler(git sv.Git, semverProcessor sv.SemVerCommitsProcessor) func(c *c } } +func getCommitType(cfg Config, p sv.MessageProcessor, input string) (string, error) { + if input == "" { + t, err := promptType(cfg.CommitMessage.Types) + return t.Type, err + } + return input, p.ValidateType(input) +} + +func getCommitScope(cfg Config, p sv.MessageProcessor, input string, noScope bool) (string, error) { + if input == "" && !noScope { + return promptScope(cfg.CommitMessage.Scope.Values) + } + return input, p.ValidateScope(input) +} + +func getCommitDescription(cfg Config, p sv.MessageProcessor, input string) (string, error) { + if input == "" { + return promptSubject() + } + return input, p.ValidateDescription(input) +} + +func getCommitBody(noBody bool) (string, error) { + if noBody { + return "", nil + } + + var fullBody strings.Builder + for body, err := promptBody(); body != "" || err != nil; body, err = promptBody() { + if err != nil { + return "", err + } + if fullBody.Len() > 0 { + fullBody.WriteString("\n") + } + if body != "" { + fullBody.WriteString(body) + } + } + return fullBody.String(), nil +} + +func getCommitIssue(cfg Config, p sv.MessageProcessor, branch string, noIssue bool) (string, error) { + branchIssue, err := p.IssueID(branch) + if err != nil { + return "", err + } + + if cfg.CommitMessage.IssueFooterConfig().Key == "" || cfg.CommitMessage.Issue.Regex == "" { + return "", nil + } + + if noIssue { + return branchIssue, nil + } + + return promptIssueID("issue id", cfg.CommitMessage.Issue.Regex, branchIssue) +} + +func getCommitBreakingChange(noBreaking bool, input string) (string, error) { + if noBreaking { + return "", nil + } + + if strings.TrimSpace(input) != "" { + return input, nil + } + + hasBreakingChanges, err := promptConfirm("has breaking change?") + if err != nil { + return "", err + } + if !hasBreakingChanges { + return "", nil + } + + return promptBreakingChanges() +} + func commitHandler(cfg Config, git sv.Git, messageProcessor sv.MessageProcessor) func(c *cli.Context) error { return func(c *cli.Context) error { - ctype, err := promptType(cfg.CommitMessage.Types) + noBreaking := c.Bool("no-breaking") + noBody := c.Bool("no-body") + noIssue := c.Bool("no-issue") + noScope := c.Bool("no-scope") + inputType := c.String("type") + inputScope := c.String("scope") + inputDescription := c.String("description") + inputBreakingChange := c.String("breaking-change") + + ctype, err := getCommitType(cfg, messageProcessor, inputType) if err != nil { return err } - scope, err := promptScope(cfg.CommitMessage.Scope.Values) + scope, err := getCommitScope(cfg, messageProcessor, inputScope, noScope) if err != nil { return err } - subject, err := promptSubject() + subject, err := getCommitDescription(cfg, messageProcessor, inputDescription) if err != nil { return err } - var fullBody strings.Builder - for body, err := promptBody(); body != "" || err != nil; body, err = promptBody() { - if err != nil { - return err - } - if fullBody.Len() > 0 { - fullBody.WriteString("\n") - } - if body != "" { - fullBody.WriteString(body) - } - } - - branchIssue, err := messageProcessor.IssueID(git.Branch()) + fullBody, err := getCommitBody(noBody) if err != nil { return err } - var issue string - if cfg.CommitMessage.IssueFooterConfig().Key != "" && cfg.CommitMessage.Issue.Regex != "" { - issue, err = promptIssueID("issue id", cfg.CommitMessage.Issue.Regex, branchIssue) - if err != nil { - return err - } - } - - hasBreakingChanges, err := promptConfirm("has breaking changes?") + issue, err := getCommitIssue(cfg, messageProcessor, git.Branch(), noIssue) if err != nil { return err } - breakingChanges := "" - if hasBreakingChanges { - breakingChanges, err = promptBreakingChanges() - if err != nil { - return err - } + + breakingChange, err := getCommitBreakingChange(noBreaking, inputBreakingChange) + if err != nil { + return err } - header, body, footer := messageProcessor.Format(sv.NewCommitMessage(ctype.Type, scope, subject, fullBody.String(), issue, breakingChanges)) + header, body, footer := messageProcessor.Format(sv.NewCommitMessage(ctype, scope, subject, fullBody, issue, breakingChange)) err = git.Commit(header, body, footer) if err != nil { diff --git a/cmd/git-sv/main.go b/cmd/git-sv/main.go index 21edcee..c00d14e 100644 --- a/cmd/git-sv/main.go +++ b/cmd/git-sv/main.go @@ -141,6 +141,16 @@ func main() { Aliases: []string{"cmt"}, Usage: "execute git commit with convetional commit message helper", Action: commitHandler(cfg, git, messageProcessor), + Flags: []cli.Flag{ + &cli.BoolFlag{Name: "no-scope", Aliases: []string{"nsc"}, Usage: "do not prompt for commit scope"}, + &cli.BoolFlag{Name: "no-body", Aliases: []string{"nbd"}, Usage: "do not prompt for commit body"}, + &cli.BoolFlag{Name: "no-issue", Aliases: []string{"nis"}, Usage: "do not prompt for commit issue, will try to recover from branch if enabled"}, + &cli.BoolFlag{Name: "no-breaking", Aliases: []string{"nbc"}, Usage: "do not prompt for breaking changes"}, + &cli.StringFlag{Name: "type", Aliases: []string{"t"}, Usage: "define commit type"}, + &cli.StringFlag{Name: "scope", Aliases: []string{"s"}, Usage: "define commit scope"}, + &cli.StringFlag{Name: "description", Aliases: []string{"d"}, Usage: "define commit description"}, + &cli.StringFlag{Name: "breaking-change", Aliases: []string{"bc"}, Usage: "define commit breaking change message"}, + }, }, { Name: "validate-commit-message", diff --git a/cmd/git-sv/prompt.go b/cmd/git-sv/prompt.go index b43166e..d967436 100644 --- a/cmd/git-sv/prompt.go +++ b/cmd/git-sv/prompt.go @@ -80,7 +80,7 @@ func promptIssueID(issueLabel, issueRegex, defaultValue string) (string, error) } func promptBreakingChanges() (string, error) { - return promptText("Breaking changes description", "[a-z].+", "") + return promptText("Breaking change description", "[a-z].+", "") } func promptSelect(label string, items interface{}, template *promptui.SelectTemplates) (int, error) { diff --git a/sv/message.go b/sv/message.go index 99f99bd..cc5e354 100644 --- a/sv/message.go +++ b/sv/message.go @@ -49,6 +49,9 @@ func (m CommitMessage) BreakingMessage() string { type MessageProcessor interface { SkipBranch(branch string, detached bool) bool Validate(message string) error + ValidateType(ctype string) error + ValidateScope(scope string) error + ValidateDescription(description string) error Enhance(branch string, message string) (string, error) IssueID(branch string) (string, error) Format(msg CommitMessage) (string, string, string) @@ -83,14 +86,39 @@ func (p MessageProcessorImpl) Validate(message string) error { return fmt.Errorf("subject [%s] should be valid according with conventional commits", subject) } - if msg.Type == "" || !contains(msg.Type, p.messageCfg.Types) { + if err := p.ValidateType(msg.Type); err != nil { + return err + } + + if err := p.ValidateScope(msg.Scope); err != nil { + return err + } + + if err := p.ValidateDescription(msg.Description); err != nil { + return err + } + + return nil +} + +func (p MessageProcessorImpl) ValidateType(ctype string) error { + if ctype == "" || !contains(ctype, p.messageCfg.Types) { return fmt.Errorf("message type should be one of [%v]", strings.Join(p.messageCfg.Types, ", ")) } + return nil +} - if len(p.messageCfg.Scope.Values) > 0 && !contains(msg.Scope, p.messageCfg.Scope.Values) { +func (p MessageProcessorImpl) ValidateScope(scope string) error { + if len(p.messageCfg.Scope.Values) > 0 && !contains(scope, p.messageCfg.Scope.Values) { return fmt.Errorf("message scope should one of [%v]", strings.Join(p.messageCfg.Scope.Values, ", ")) } + return nil +} +func (p MessageProcessorImpl) ValidateDescription(description string) error { + if !regexp.MustCompile("^[a-z]+.*$").MatchString(description) { + return fmt.Errorf("description [%s] should begins with lowercase letter", description) + } return nil } diff --git a/sv/message_test.go b/sv/message_test.go index 6e0f3c1..4d3c43f 100644 --- a/sv/message_test.go +++ b/sv/message_test.go @@ -157,6 +157,71 @@ func TestMessageProcessorImpl_Validate(t *testing.T) { } } +func TestMessageProcessorImpl_ValidateType(t *testing.T) { + tests := []struct { + name string + cfg CommitMessageConfig + ctype string + wantErr bool + }{ + {"valid type", ccfg, "feat", false}, + {"invalid type", ccfg, "aaa", true}, + {"empty type", ccfg, "", true}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + p := NewMessageProcessor(tt.cfg, newBranchCfg(false)) + if err := p.ValidateType(tt.ctype); (err != nil) != tt.wantErr { + t.Errorf("MessageProcessorImpl.ValidateType() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +func TestMessageProcessorImpl_ValidateScope(t *testing.T) { + tests := []struct { + name string + cfg CommitMessageConfig + scope string + wantErr bool + }{ + {"any scope", ccfg, "aaa", false}, + {"valid scope with scope list", ccfgWithScope, "scope", false}, + {"invalid scope with scope list", ccfgWithScope, "aaa", true}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + p := NewMessageProcessor(tt.cfg, newBranchCfg(false)) + if err := p.ValidateScope(tt.scope); (err != nil) != tt.wantErr { + t.Errorf("MessageProcessorImpl.ValidateScope() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +func TestMessageProcessorImpl_ValidateDescription(t *testing.T) { + tests := []struct { + name string + cfg CommitMessageConfig + description string + wantErr bool + }{ + {"empty description", ccfg, "", true}, + {"sigle letter description", ccfg, "a", false}, + {"number description", ccfg, "1", true}, + {"valid description", ccfg, "add some feature", false}, + {"invalid capital letter description", ccfg, "Add some feature", true}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + p := NewMessageProcessor(tt.cfg, newBranchCfg(false)) + if err := p.ValidateDescription(tt.description); (err != nil) != tt.wantErr { + t.Errorf("MessageProcessorImpl.ValidateDescription() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + func TestMessageProcessorImpl_Enhance(t *testing.T) { tests := []struct { name string