mirror of
https://github.com/thegeeklab/git-sv.git
synced 2024-11-22 08:20:39 +00:00
Merge pull request #12 from bvieira/#11
Feature: skip detached branches
This commit is contained in:
commit
4b15622117
@ -97,6 +97,7 @@ branches: # git branches config
|
|||||||
- master
|
- master
|
||||||
- main
|
- main
|
||||||
- developer
|
- developer
|
||||||
|
skip-detached: false # set true if a detached branch should be ignored on commit message validation
|
||||||
|
|
||||||
commit-message:
|
commit-message:
|
||||||
types: # supported commit types
|
types: # supported commit types
|
||||||
@ -203,7 +204,7 @@ COMMIT_MSG_FILE=$1
|
|||||||
COMMIT_SOURCE=$2
|
COMMIT_SOURCE=$2
|
||||||
SHA1=$3
|
SHA1=$3
|
||||||
|
|
||||||
git sv vcm --path "$(pwd)" --file $COMMIT_MSG_FILE --source $COMMIT_SOURCE
|
git sv vcm --path "$(pwd)" --file "$COMMIT_MSG_FILE" --source "$COMMIT_SOURCE"
|
||||||
```
|
```
|
||||||
|
|
||||||
tip: you can configure a directory as your global git templates using the command below, check [git config docs](https://git-scm.com/docs/git-config#Documentation/git-config.txt-inittemplateDir) for more information!
|
tip: you can configure a directory as your global git templates using the command below, check [git config docs](https://git-scm.com/docs/git-config#Documentation/git-config.txt-inittemplateDir) for more information!
|
||||||
|
@ -62,6 +62,7 @@ func loadConfig(filepath string) (Config, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func defaultConfig() Config {
|
func defaultConfig() Config {
|
||||||
|
skipDetached := false
|
||||||
return Config{
|
return Config{
|
||||||
Version: "1.0",
|
Version: "1.0",
|
||||||
Versioning: sv.VersioningConfig{
|
Versioning: sv.VersioningConfig{
|
||||||
@ -77,6 +78,7 @@ func defaultConfig() Config {
|
|||||||
SuffixRegex: "(-.*)?",
|
SuffixRegex: "(-.*)?",
|
||||||
DisableIssue: false,
|
DisableIssue: false,
|
||||||
Skip: []string{"master", "main", "developer"},
|
Skip: []string{"master", "main", "developer"},
|
||||||
|
SkipDetached: &skipDetached,
|
||||||
},
|
},
|
||||||
CommitMessage: sv.CommitMessageConfig{
|
CommitMessage: sv.CommitMessageConfig{
|
||||||
Types: []string{"build", "ci", "chore", "docs", "feat", "fix", "perf", "refactor", "revert", "style", "test"},
|
Types: []string{"build", "ci", "chore", "docs", "feat", "fix", "perf", "refactor", "revert", "style", "test"},
|
||||||
|
@ -371,8 +371,15 @@ func changelogHandler(git sv.Git, semverProcessor sv.SemVerCommitsProcessor, rnP
|
|||||||
func validateCommitMessageHandler(git sv.Git, messageProcessor sv.MessageProcessor) func(c *cli.Context) error {
|
func validateCommitMessageHandler(git sv.Git, messageProcessor sv.MessageProcessor) func(c *cli.Context) error {
|
||||||
return func(c *cli.Context) error {
|
return func(c *cli.Context) error {
|
||||||
branch := git.Branch()
|
branch := git.Branch()
|
||||||
if messageProcessor.SkipBranch(branch) {
|
detached, derr := git.IsDetached()
|
||||||
warn("commit message validation skipped, branch in ignore list...")
|
|
||||||
|
if messageProcessor.SkipBranch(branch, derr == nil && detached) {
|
||||||
|
warn("commit message validation skipped, branch in ignore list or detached...")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if source := c.String("source"); source == "merge" {
|
||||||
|
warn("commit message validation skipped, ignoring source: %s...", source)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,6 +4,7 @@ import (
|
|||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"reflect"
|
||||||
"sv4git/sv"
|
"sv4git/sv"
|
||||||
|
|
||||||
"github.com/imdario/mergo"
|
"github.com/imdario/mergo"
|
||||||
@ -27,7 +28,7 @@ func main() {
|
|||||||
|
|
||||||
if envCfg.Home != "" {
|
if envCfg.Home != "" {
|
||||||
if homeCfg, err := loadConfig(filepath.Join(envCfg.Home, configFilename)); err == nil {
|
if homeCfg, err := loadConfig(filepath.Join(envCfg.Home, configFilename)); err == nil {
|
||||||
if merr := mergo.Merge(&cfg, homeCfg, mergo.WithOverride); merr != nil {
|
if merr := mergo.Merge(&cfg, homeCfg, mergo.WithOverride, mergo.WithTransformers(&nullTransformer{})); merr != nil {
|
||||||
log.Fatal(merr)
|
log.Fatal(merr)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -39,7 +40,7 @@ func main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if repoCfg, err := loadConfig(filepath.Join(repoPath, repoConfigFilename)); err == nil {
|
if repoCfg, err := loadConfig(filepath.Join(repoPath, repoConfigFilename)); err == nil {
|
||||||
if merr := mergo.Merge(&cfg, repoCfg, mergo.WithOverride); merr != nil {
|
if merr := mergo.Merge(&cfg, repoCfg, mergo.WithOverride, mergo.WithTransformers(&nullTransformer{})); merr != nil {
|
||||||
log.Fatal(merr)
|
log.Fatal(merr)
|
||||||
}
|
}
|
||||||
if len(repoCfg.ReleaseNotes.Headers) > 0 { // mergo is merging maps, headers will be overwritten
|
if len(repoCfg.ReleaseNotes.Headers) > 0 { // mergo is merging maps, headers will be overwritten
|
||||||
@ -144,7 +145,7 @@ func main() {
|
|||||||
{
|
{
|
||||||
Name: "validate-commit-message",
|
Name: "validate-commit-message",
|
||||||
Aliases: []string{"vcm"},
|
Aliases: []string{"vcm"},
|
||||||
Usage: "use as prepare-commit-message hook to validate message",
|
Usage: "use as prepare-commit-message hook to validate and enhance commit message",
|
||||||
Action: validateCommitMessageHandler(git, messageProcessor),
|
Action: validateCommitMessageHandler(git, messageProcessor),
|
||||||
Flags: []cli.Flag{
|
Flags: []cli.Flag{
|
||||||
&cli.StringFlag{Name: "path", Required: true, Usage: "git working directory"},
|
&cli.StringFlag{Name: "path", Required: true, Usage: "git working directory"},
|
||||||
@ -159,3 +160,18 @@ func main() {
|
|||||||
log.Fatal(apperr)
|
log.Fatal(apperr)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type nullTransformer struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *nullTransformer) Transformer(typ reflect.Type) func(dst, src reflect.Value) error {
|
||||||
|
if typ.Kind() == reflect.Ptr {
|
||||||
|
return func(dst, src reflect.Value) error {
|
||||||
|
if dst.CanSet() && !src.IsNil() {
|
||||||
|
dst.Set(src)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
@ -43,6 +43,7 @@ type BranchesConfig struct {
|
|||||||
SuffixRegex string `yaml:"suffix"`
|
SuffixRegex string `yaml:"suffix"`
|
||||||
DisableIssue bool `yaml:"disable-issue"`
|
DisableIssue bool `yaml:"disable-issue"`
|
||||||
Skip []string `yaml:"skip"`
|
Skip []string `yaml:"skip"`
|
||||||
|
SkipDetached *bool `yaml:"skip-detached"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// ==== Versioning ====
|
// ==== Versioning ====
|
||||||
|
15
sv/git.go
15
sv/git.go
@ -3,6 +3,7 @@ package sv
|
|||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
@ -25,6 +26,7 @@ type Git interface {
|
|||||||
Tag(version semver.Version) error
|
Tag(version semver.Version) error
|
||||||
Tags() ([]GitTag, error)
|
Tags() ([]GitTag, error)
|
||||||
Branch() string
|
Branch() string
|
||||||
|
IsDetached() (bool, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GitCommitLog description of a single commit log
|
// GitCommitLog description of a single commit log
|
||||||
@ -154,6 +156,19 @@ func (GitImpl) Branch() string {
|
|||||||
return strings.TrimSpace(strings.Trim(string(out), "\n"))
|
return strings.TrimSpace(strings.Trim(string(out), "\n"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IsDetached check if is detached.
|
||||||
|
func (GitImpl) IsDetached() (bool, error) {
|
||||||
|
cmd := exec.Command("git", "symbolic-ref", "-q", "HEAD")
|
||||||
|
out, err := cmd.CombinedOutput()
|
||||||
|
if output := string(out); err != nil { //-q: do not issue an error message if the <name> is not a symbolic ref, but a detached HEAD; instead exit with non-zero status silently.
|
||||||
|
if output == "" {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
return false, errors.New(output)
|
||||||
|
}
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
func parseTagsOutput(input string) ([]GitTag, error) {
|
func parseTagsOutput(input string) ([]GitTag, error) {
|
||||||
scanner := bufio.NewScanner(strings.NewReader(input))
|
scanner := bufio.NewScanner(strings.NewReader(input))
|
||||||
var result []GitTag
|
var result []GitTag
|
||||||
|
@ -2,7 +2,6 @@ package sv
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
@ -48,7 +47,7 @@ func (m CommitMessage) BreakingMessage() string {
|
|||||||
|
|
||||||
// MessageProcessor interface.
|
// MessageProcessor interface.
|
||||||
type MessageProcessor interface {
|
type MessageProcessor interface {
|
||||||
SkipBranch(branch string) bool
|
SkipBranch(branch string, detached bool) bool
|
||||||
Validate(message string) error
|
Validate(message 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)
|
||||||
@ -71,8 +70,8 @@ type MessageProcessorImpl struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// SkipBranch check if branch should be ignored.
|
// SkipBranch check if branch should be ignored.
|
||||||
func (p MessageProcessorImpl) SkipBranch(branch string) bool {
|
func (p MessageProcessorImpl) SkipBranch(branch string, detached bool) bool {
|
||||||
return contains(branch, p.branchesCfg.Skip)
|
return contains(branch, p.branchesCfg.Skip) || (p.branchesCfg.SkipDetached != nil && *p.branchesCfg.SkipDetached && detached)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate commit message.
|
// Validate commit message.
|
||||||
@ -81,7 +80,7 @@ func (p MessageProcessorImpl) Validate(message string) error {
|
|||||||
msg := p.Parse(subject, body)
|
msg := p.Parse(subject, body)
|
||||||
|
|
||||||
if !regexp.MustCompile("^[a-z+]+(\\(.+\\))?!?: .+$").MatchString(subject) {
|
if !regexp.MustCompile("^[a-z+]+(\\(.+\\))?!?: .+$").MatchString(subject) {
|
||||||
return errors.New("message should be valid according with conventional commits")
|
return fmt.Errorf("subject [%s] should be valid according with conventional commits", subject)
|
||||||
}
|
}
|
||||||
|
|
||||||
if msg.Type == "" || !contains(msg.Type, p.messageCfg.Types) {
|
if msg.Type == "" || !contains(msg.Type, p.messageCfg.Types) {
|
||||||
|
@ -25,10 +25,13 @@ var ccfgWithScope = CommitMessageConfig{
|
|||||||
Issue: CommitMessageIssueConfig{Regex: "[A-Z]+-[0-9]+"},
|
Issue: CommitMessageIssueConfig{Regex: "[A-Z]+-[0-9]+"},
|
||||||
}
|
}
|
||||||
|
|
||||||
var bcfg = BranchesConfig{
|
func newBranchCfg(skipDetached bool) BranchesConfig {
|
||||||
|
return BranchesConfig{
|
||||||
PrefixRegex: "([a-z]+\\/)?",
|
PrefixRegex: "([a-z]+\\/)?",
|
||||||
SuffixRegex: "(-.*)?",
|
SuffixRegex: "(-.*)?",
|
||||||
Skip: []string{"develop", "master"},
|
Skip: []string{"develop", "master"},
|
||||||
|
SkipDetached: &skipDetached,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// messages samples start
|
// messages samples start
|
||||||
@ -67,6 +70,30 @@ BREAKING CHANGE: refactor to use JavaScript features not available in Node 6.`
|
|||||||
|
|
||||||
// multiline samples end
|
// multiline samples end
|
||||||
|
|
||||||
|
func TestMessageProcessorImpl_SkipBranch(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
bcfg BranchesConfig
|
||||||
|
branch string
|
||||||
|
detached bool
|
||||||
|
want bool
|
||||||
|
}{
|
||||||
|
{"normal branch", newBranchCfg(false), "JIRA-123", false, false},
|
||||||
|
{"dont ignore detached branch", newBranchCfg(false), "JIRA-123", true, false},
|
||||||
|
{"ignore branch on skip list", newBranchCfg(false), "master", false, true},
|
||||||
|
{"ignore detached branch", newBranchCfg(true), "JIRA-123", true, true},
|
||||||
|
{"null skip detached", BranchesConfig{Skip: []string{}}, "JIRA-123", true, false},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
p := NewMessageProcessor(ccfg, tt.bcfg)
|
||||||
|
if got := p.SkipBranch(tt.branch, tt.detached); got != tt.want {
|
||||||
|
t.Errorf("MessageProcessorImpl.SkipBranch() = %v, want %v", got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestMessageProcessorImpl_Validate(t *testing.T) {
|
func TestMessageProcessorImpl_Validate(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
@ -94,7 +121,7 @@ func TestMessageProcessorImpl_Validate(t *testing.T) {
|
|||||||
}
|
}
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
p := NewMessageProcessor(tt.cfg, bcfg)
|
p := NewMessageProcessor(tt.cfg, newBranchCfg(false))
|
||||||
if err := p.Validate(tt.message); (err != nil) != tt.wantErr {
|
if err := p.Validate(tt.message); (err != nil) != tt.wantErr {
|
||||||
t.Errorf("MessageProcessorImpl.Validate() error = %v, wantErr %v", err, tt.wantErr)
|
t.Errorf("MessageProcessorImpl.Validate() error = %v, wantErr %v", err, tt.wantErr)
|
||||||
}
|
}
|
||||||
@ -103,7 +130,7 @@ func TestMessageProcessorImpl_Validate(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestMessageProcessorImpl_Enhance(t *testing.T) {
|
func TestMessageProcessorImpl_Enhance(t *testing.T) {
|
||||||
p := NewMessageProcessor(ccfg, bcfg)
|
p := NewMessageProcessor(ccfg, newBranchCfg(false))
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
@ -136,7 +163,7 @@ func TestMessageProcessorImpl_Enhance(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestMessageProcessorImpl_IssueID(t *testing.T) {
|
func TestMessageProcessorImpl_IssueID(t *testing.T) {
|
||||||
p := NewMessageProcessor(ccfg, bcfg)
|
p := NewMessageProcessor(ccfg, newBranchCfg(false))
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
@ -248,7 +275,7 @@ Jira: JIRA-999
|
|||||||
Refs #123`
|
Refs #123`
|
||||||
|
|
||||||
func TestMessageProcessorImpl_Parse(t *testing.T) {
|
func TestMessageProcessorImpl_Parse(t *testing.T) {
|
||||||
p := NewMessageProcessor(ccfg, bcfg)
|
p := NewMessageProcessor(ccfg, newBranchCfg(false))
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
@ -275,7 +302,7 @@ func TestMessageProcessorImpl_Parse(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestMessageProcessorImpl_Format(t *testing.T) {
|
func TestMessageProcessorImpl_Format(t *testing.T) {
|
||||||
p := NewMessageProcessor(ccfg, bcfg)
|
p := NewMessageProcessor(ccfg, newBranchCfg(false))
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
|
Loading…
Reference in New Issue
Block a user