0
0
mirror of https://github.com/thegeeklab/git-sv.git synced 2024-11-24 11:10:39 +00:00

feat: add flags to commit command

This commit is contained in:
Beatriz Vieira 2021-07-31 00:47:58 -03:00
parent 1023742e7d
commit ff34b484a5
6 changed files with 227 additions and 59 deletions

View File

@ -142,7 +142,7 @@ git-sv rn -h
##### Available commands ##### Available commands
| Variable | description | has options or subcommands | | Variable | description | has options or subcommands |
| ---------------------------- | ------------------------------------------------------------- | :------------------------: | | ---------------------------- | -------------------------------------------------------------- | :------------------------: |
| config, cfg | Show config information. | :heavy_check_mark: | | config, cfg | Show config information. | :heavy_check_mark: |
| current-version, cv | Get last released version from git. | :x: | | current-version, cv | Get last released version from git. | :x: |
| next-version, nv | Generate the next version based on git commit messages. | :x: | | next-version, nv | Generate the next version based on git commit messages. | :x: |
@ -151,7 +151,7 @@ git-sv rn -h
| release-notes, rn | Generate release notes. | :heavy_check_mark: | | release-notes, rn | Generate release notes. | :heavy_check_mark: |
| changelog, cgl | Generate changelog. | :heavy_check_mark: | | changelog, cgl | Generate changelog. | :heavy_check_mark: |
| tag, tg | Generate tag with version based on git commit messages. | :x: | | tag, tg | Generate tag with version based on git commit messages. | :x: |
| commit, cmt | Execute git commit with convetional commit message helper. | :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: | | 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: | | help, h | Shows a list of commands or help for one command. | :x: |
@ -210,7 +210,7 @@ make
#### Make configs #### Make configs
| Variable | description | | Variable | description |
| ---------- | ---------------------- | | ---------- | ----------------------- |
| BUILDOS | Build OS. | | BUILDOS | Build OS. |
| BUILDARCH | Build arch. | | BUILDARCH | Build arch. |
| ECHOFLAGS | Flags used on echo. | | ECHOFLAGS | Flags used on echo. |
@ -218,7 +218,7 @@ make
| BUILDFLAGS | Flags used on build. | | BUILDFLAGS | Flags used on build. |
| Parameters | description | | Parameters | description |
| ---------- | ----------------------------------- | | ---------- | ------------------------------------ |
| args | Parameters that will be used on run. | | args | Parameters that will be used on run. |
```bash ```bash

View File

@ -266,27 +266,37 @@ func tagHandler(git sv.Git, semverProcessor sv.SemVerCommitsProcessor) func(c *c
} }
} }
func commitHandler(cfg Config, git sv.Git, messageProcessor sv.MessageProcessor) func(c *cli.Context) error { func getCommitType(cfg Config, p sv.MessageProcessor, input string) (string, error) {
return func(c *cli.Context) error { if input == "" {
ctype, err := promptType(cfg.CommitMessage.Types) t, err := promptType(cfg.CommitMessage.Types)
if err != nil { return t.Type, err
return err }
return input, p.ValidateType(input)
} }
scope, err := promptScope(cfg.CommitMessage.Scope.Values) func getCommitScope(cfg Config, p sv.MessageProcessor, input string, noScope bool) (string, error) {
if err != nil { if input == "" && !noScope {
return err return promptScope(cfg.CommitMessage.Scope.Values)
}
return input, p.ValidateScope(input)
} }
subject, err := promptSubject() func getCommitDescription(cfg Config, p sv.MessageProcessor, input string) (string, error) {
if err != nil { if input == "" {
return err return promptSubject()
}
return input, p.ValidateDescription(input)
}
func getCommitBody(noBody bool) (string, error) {
if noBody {
return "", nil
} }
var fullBody strings.Builder var fullBody strings.Builder
for body, err := promptBody(); body != "" || err != nil; body, err = promptBody() { for body, err := promptBody(); body != "" || err != nil; body, err = promptBody() {
if err != nil { if err != nil {
return err return "", err
} }
if fullBody.Len() > 0 { if fullBody.Len() > 0 {
fullBody.WriteString("\n") fullBody.WriteString("\n")
@ -295,33 +305,88 @@ func commitHandler(cfg Config, git sv.Git, messageProcessor sv.MessageProcessor)
fullBody.WriteString(body) fullBody.WriteString(body)
} }
} }
return fullBody.String(), nil
}
branchIssue, err := messageProcessor.IssueID(git.Branch()) 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 {
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 { if err != nil {
return err return err
} }
var issue string scope, err := getCommitScope(cfg, messageProcessor, inputScope, noScope)
if cfg.CommitMessage.IssueFooterConfig().Key != "" && cfg.CommitMessage.Issue.Regex != "" {
issue, err = promptIssueID("issue id", cfg.CommitMessage.Issue.Regex, branchIssue)
if err != nil { if err != nil {
return err return err
} }
}
hasBreakingChanges, err := promptConfirm("has breaking changes?") subject, err := getCommitDescription(cfg, messageProcessor, inputDescription)
if err != nil { if err != nil {
return err return err
} }
breakingChanges := ""
if hasBreakingChanges {
breakingChanges, err = promptBreakingChanges()
if err != nil {
return err
}
}
header, body, footer := messageProcessor.Format(sv.NewCommitMessage(ctype.Type, scope, subject, fullBody.String(), issue, breakingChanges)) fullBody, err := getCommitBody(noBody)
if err != nil {
return err
}
issue, err := getCommitIssue(cfg, messageProcessor, git.Branch(), noIssue)
if err != nil {
return err
}
breakingChange, err := getCommitBreakingChange(noBreaking, inputBreakingChange)
if err != nil {
return err
}
header, body, footer := messageProcessor.Format(sv.NewCommitMessage(ctype, scope, subject, fullBody, issue, breakingChange))
err = git.Commit(header, body, footer) err = git.Commit(header, body, footer)
if err != nil { if err != nil {

View File

@ -141,6 +141,16 @@ func main() {
Aliases: []string{"cmt"}, Aliases: []string{"cmt"},
Usage: "execute git commit with convetional commit message helper", Usage: "execute git commit with convetional commit message helper",
Action: commitHandler(cfg, git, messageProcessor), 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", Name: "validate-commit-message",

View File

@ -80,7 +80,7 @@ func promptIssueID(issueLabel, issueRegex, defaultValue string) (string, error)
} }
func promptBreakingChanges() (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) { func promptSelect(label string, items interface{}, template *promptui.SelectTemplates) (int, error) {

View File

@ -49,6 +49,9 @@ func (m CommitMessage) BreakingMessage() string {
type MessageProcessor interface { type MessageProcessor interface {
SkipBranch(branch string, detached bool) bool SkipBranch(branch string, detached bool) bool
Validate(message string) error Validate(message string) error
ValidateType(ctype string) error
ValidateScope(scope string) error
ValidateDescription(description string) error
Enhance(branch string, message string) (string, error) Enhance(branch string, message string) (string, error)
IssueID(branch string) (string, error) IssueID(branch string) (string, error)
Format(msg CommitMessage) (string, string, string) 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) 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 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) {
return fmt.Errorf("message scope should one of [%v]", strings.Join(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 return nil
} }

View File

@ -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) { func TestMessageProcessorImpl_Enhance(t *testing.T) {
tests := []struct { tests := []struct {
name string name string