From e70283a0c5ee0163607731c00414a98b10a00056 Mon Sep 17 00:00:00 2001 From: Beatriz Vieira Date: Sun, 14 Feb 2021 19:42:22 -0300 Subject: [PATCH] feat: config sv4git using yaml BREAKING CHANGE: stop using var envs to config sv4git --- cmd/git-sv/config.go | 80 ++++++++++++++++++++++++++++++++++++++--- cmd/git-sv/handlers.go | 26 +++++++++++++- cmd/git-sv/main.go | 81 ++++++++++++++++++++++++++---------------- go.mod | 2 ++ go.sum | 6 ++++ sv/config.go | 40 ++++++++++----------- sv/message.go | 2 +- sv/message_test.go | 1 - sv/semver.go | 2 +- sv/semver_test.go | 18 +++++----- 10 files changed, 190 insertions(+), 68 deletions(-) diff --git a/cmd/git-sv/config.go b/cmd/git-sv/config.go index 4e4ae97..a006802 100644 --- a/cmd/git-sv/config.go +++ b/cmd/git-sv/config.go @@ -1,13 +1,22 @@ package main import ( + "errors" + "fmt" + "io/ioutil" "log" + "os/exec" + "strings" + "sv4git/sv" "github.com/kelseyhightower/envconfig" + "gopkg.in/yaml.v3" ) -// Config env vars for cli configuration -type Config struct { +// EnvConfig env vars for cli configuration +type EnvConfig struct { + Home string `envconfig:"SV4GIT_HOME" default:""` + MajorVersionTypes []string `envconfig:"MAJOR_VERSION_TYPES" default:""` MinorVersionTypes []string `envconfig:"MINOR_VERSION_TYPES" default:"feat"` PatchVersionTypes []string `envconfig:"PATCH_VERSION_TYPES" default:"build,ci,chore,docs,fix,perf,refactor,style,test"` @@ -24,11 +33,74 @@ type Config struct { BranchIssueSuffixRegex string `envconfig:"BRANCH_ISSUE_SUFFIX_REGEX" default:"(-.*)?"` } -func loadConfig() Config { - var c Config +func loadEnvConfig() EnvConfig { + var c EnvConfig err := envconfig.Process("SV4GIT", &c) if err != nil { log.Fatal(err.Error()) } return c } + +// Config cli yaml config +type Config struct { + Version string `yaml:"version"` + Versioning sv.VersioningConfig `yaml:"versioning"` + Tag sv.TagConfig `yaml:"tag"` + ReleaseNotes sv.ReleaseNotesConfig `yaml:"release-notes"` + Branches sv.BranchesConfig `yaml:"branches"` + CommitMessage sv.CommitMessageConfig `yaml:"commit-message"` +} + +func getRepoPath() (string, error) { + cmd := exec.Command("git", "rev-parse", "--show-toplevel") + out, err := cmd.CombinedOutput() + if err != nil { + return "", errors.New(string(out)) + } + return strings.TrimSpace(string(out)), nil +} + +func loadConfig(filepath string) (Config, error) { + content, rerr := ioutil.ReadFile(filepath) + if rerr != nil { + return Config{}, rerr + } + + var cfg Config + cerr := yaml.Unmarshal(content, &cfg) + if cerr != nil { + return Config{}, fmt.Errorf("could not parse config from path: %s, error: %v", filepath, cerr) + } + + return cfg, nil +} + +func defaultConfig() Config { + return Config{ + Version: "1.0", + Versioning: sv.VersioningConfig{ + UpdateMajor: []string{}, + UpdateMinor: []string{"feat"}, + UpdatePatch: []string{"build", "ci", "chore", "docs", "fix", "perf", "refactor", "style", "test"}, + IgnoreUnknown: false, + }, + Tag: sv.TagConfig{Pattern: "%d.%d.%d"}, + ReleaseNotes: sv.ReleaseNotesConfig{Headers: map[string]string{"fix": "Bug Fixes", "feat": "Features", "breaking-change": "Breaking Changes"}}, + Branches: sv.BranchesConfig{ + PrefixRegex: "([a-z]+\\/)?", + SuffixRegex: "(-.*)?", + DisableIssue: false, + Skip: []string{"master", "main", "developer"}, + }, + CommitMessage: sv.CommitMessageConfig{ + Types: []string{"build", "ci", "chore", "docs", "feat", "fix", "perf", "refactor", "revert", "style", "test"}, + Scope: sv.CommitMessageScopeConfig{}, + Footer: map[string]sv.CommitMessageFooterConfig{ + "issue": {Key: "jira", KeySynonyms: []string{"Jira", "JIRA"}}, + "breaking-change": {Key: "BREAKING CHANGE", KeySynonyms: []string{"BREAKING CHANGES"}}, + }, + Issue: sv.CommitMessageIssueConfig{Regex: "[A-Z]+-[0-9]+"}, + }, + } +} diff --git a/cmd/git-sv/handlers.go b/cmd/git-sv/handlers.go index 8bd9595..7ac3a24 100644 --- a/cmd/git-sv/handlers.go +++ b/cmd/git-sv/handlers.go @@ -12,8 +12,32 @@ import ( "github.com/Masterminds/semver/v3" "github.com/urfave/cli/v2" + "gopkg.in/yaml.v3" ) +func configDefaultHandler() func(c *cli.Context) error { + cfg := defaultConfig() + return func(c *cli.Context) error { + content, err := yaml.Marshal(&cfg) + if err != nil { + return err + } + fmt.Println(string(content)) + return nil + } +} + +func configShowHandler(cfg Config) func(c *cli.Context) error { + return func(c *cli.Context) error { + content, err := yaml.Marshal(&cfg) + if err != nil { + return err + } + fmt.Println(string(content)) + return nil + } +} + func currentVersionHandler(git sv.Git) func(c *cli.Context) error { return func(c *cli.Context) error { describe := git.Describe() @@ -238,7 +262,7 @@ 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 commitHandler(cfg EnvConfig, git sv.Git, messageProcessor sv.MessageProcessor) func(c *cli.Context) error { return func(c *cli.Context) error { ctype, err := promptType() diff --git a/cmd/git-sv/main.go b/cmd/git-sv/main.go index 6f05d80..ffdb0d9 100644 --- a/cmd/git-sv/main.go +++ b/cmd/git-sv/main.go @@ -3,49 +3,51 @@ package main import ( "log" "os" + "path/filepath" "sv4git/sv" + "github.com/imdario/mergo" "github.com/urfave/cli/v2" ) // Version for git-sv var Version = "" +const ( + configFilename = "config.yml" + repoConfigFilename = ".sv4git.yml" +) + func main() { log.SetFlags(0) - cfg := loadConfig() + envCfg := loadEnvConfig() - // TODO: config using yaml - commitMessageCfg := sv.CommitMessageConfig{ - Types: cfg.CommitMessageTypes, - Scope: sv.CommitMessageScopeConfig{}, - Footer: map[string]sv.CommitMessageFooterConfig{ - "issue": {Key: cfg.IssueIDPrefixes[0], KeySynonyms: cfg.IssueIDPrefixes[1:]}, - "breaking-change": {Key: cfg.BreakingChangePrefixes[0], KeySynonyms: cfg.BreakingChangePrefixes[1:]}, - }, - Issue: sv.CommitMessageIssueConfig{Regex: cfg.IssueRegex}, - } - branchesConfig := sv.BranchesConfig{ - Skip: cfg.ValidateMessageSkipBranches, - ExpectIssue: true, - PrefixRegex: cfg.BranchIssuePrefixRegex, - SuffixRegex: cfg.BranchIssueSuffixRegex, - } - versioningConfig := sv.VersioningConfig{ - UpdateMajor: cfg.MajorVersionTypes, - UpdateMinor: cfg.MinorVersionTypes, - UpdatePatch: cfg.PatchVersionTypes, - UnknownTypeAsPatch: cfg.IncludeUnknownTypeAsPatch, - } - tagConfig := sv.TagConfig{Pattern: cfg.TagPattern} - releaseNotesConfig := sv.ReleaseNotesConfig{Headers: cfg.ReleaseNotesTags} - //// + cfg := defaultConfig() - messageProcessor := sv.NewMessageProcessor(commitMessageCfg, branchesConfig) - git := sv.NewGit(messageProcessor, tagConfig) - semverProcessor := sv.NewSemVerCommitsProcessor(versioningConfig) - releasenotesProcessor := sv.NewReleaseNoteProcessor(releaseNotesConfig) + if envCfg.Home != "" { + if homeCfg, err := loadConfig(filepath.Join(envCfg.Home, configFilename)); err == nil { + if merr := mergo.Merge(&cfg, homeCfg, mergo.WithOverride); merr != nil { + log.Fatal(merr) + } + } + } + + repoPath, rerr := getRepoPath() + if rerr != nil { + log.Fatal(rerr) + } + + if repoCfg, err := loadConfig(filepath.Join(repoPath, repoConfigFilename)); err == nil { + if merr := mergo.Merge(&cfg, repoCfg, mergo.WithOverride); merr != nil { + log.Fatal(merr) + } + } + + messageProcessor := sv.NewMessageProcessor(cfg.CommitMessage, cfg.Branches) + git := sv.NewGit(messageProcessor, cfg.Tag) + semverProcessor := sv.NewSemVerCommitsProcessor(cfg.Versioning) + releasenotesProcessor := sv.NewReleaseNoteProcessor(cfg.ReleaseNotes) outputFormatter := sv.NewOutputFormatter() app := cli.NewApp() @@ -53,6 +55,23 @@ func main() { app.Version = Version app.Usage = "semantic version for git" app.Commands = []*cli.Command{ + { + Name: "config", + Aliases: []string{"cfg"}, + Usage: "cli configuration", + Subcommands: []*cli.Command{ + { + Name: "default", + Usage: "show default config", + Action: configDefaultHandler(), + }, + { + Name: "show", + Usage: "show current config", + Action: configShowHandler(cfg), + }, + }, + }, { Name: "current-version", Aliases: []string{"cv"}, @@ -117,7 +136,7 @@ func main() { Name: "commit", Aliases: []string{"cmt"}, Usage: "execute git commit with convetional commit message helper", - Action: commitHandler(cfg, git, messageProcessor), + Action: commitHandler(envCfg, git, messageProcessor), }, { Name: "validate-commit-message", diff --git a/go.mod b/go.mod index d39381e..3f98cf5 100644 --- a/go.mod +++ b/go.mod @@ -5,6 +5,7 @@ go 1.15 require ( github.com/Masterminds/semver/v3 v3.1.1 github.com/cpuguy83/go-md2man/v2 v2.0.0 // indirect + github.com/imdario/mergo v0.3.11 github.com/kelseyhightower/envconfig v1.4.0 github.com/kr/text v0.2.0 // indirect github.com/lunixbochs/vtclean v1.0.0 // indirect @@ -14,4 +15,5 @@ require ( github.com/urfave/cli/v2 v2.3.0 golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c // indirect gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect + gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b ) diff --git a/go.sum b/go.sum index b040cff..d0a91e1 100644 --- a/go.sum +++ b/go.sum @@ -12,6 +12,8 @@ github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:ma github.com/cpuguy83/go-md2man/v2 v2.0.0 h1:EoUDS0afbrsXAZ9YQ9jdu/mZ2sXgT1/2yyNng4PGlyM= github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/imdario/mergo v0.3.11 h1:3tnifQM4i+fbajXKBHXWEH+KvNHqojZ778UH75j3bGA= +github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= github.com/juju/ansiterm v0.0.0-20180109212912-720a0952cc2a h1:FaWFmfWdAUKbSCtOU2QjDaorUexogfaMgbipgYATUMU= github.com/juju/ansiterm v0.0.0-20180109212912-720a0952cc2a/go.mod h1:UJSiEoRfvx3hP73CvoARgeLjaIOjybY9vj8PUPPFGeU= github.com/kelseyhightower/envconfig v1.4.0 h1:Im6hONhd3pLkfDFsbRgu68RDNkGF1r3dvMUtDTo2cv8= @@ -61,3 +63,7 @@ gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/sv/config.go b/sv/config.go index 74e8c79..1e9e866 100644 --- a/sv/config.go +++ b/sv/config.go @@ -4,10 +4,10 @@ package sv // CommitMessageConfig config a commit message. type CommitMessageConfig struct { - Types []string - Scope CommitMessageScopeConfig - Footer map[string]CommitMessageFooterConfig - Issue CommitMessageIssueConfig + Types []string `yaml:"types"` + Scope CommitMessageScopeConfig `yaml:"scope"` + Footer map[string]CommitMessageFooterConfig `yaml:"footer"` + Issue CommitMessageIssueConfig `yaml:"issue"` } // IssueConfig config for issue. @@ -28,52 +28,52 @@ func (c CommitMessageConfig) BreakingChangeConfig() CommitMessageFooterConfig { // CommitMessageScopeConfig config scope preferences. type CommitMessageScopeConfig struct { - Mandatory bool - Values []string + Mandatory bool `yaml:"mandatory"` + Values []string `yaml:"values"` } // CommitMessageFooterConfig config footer metadata. type CommitMessageFooterConfig struct { - Key string - KeySynonyms []string - UseHash bool + Key string `yaml:"key"` + KeySynonyms []string `yaml:"key-synonyms"` + UseHash bool `yaml:"use-hash"` } // CommitMessageIssueConfig issue preferences. type CommitMessageIssueConfig struct { - Regex string + Regex string `yaml:"regex"` } // ==== Branches ==== // BranchesConfig branches preferences. type BranchesConfig struct { - PrefixRegex string - SuffixRegex string - ExpectIssue bool - Skip []string + PrefixRegex string `yaml:"prefix"` + SuffixRegex string `yaml:"sufix"` + DisableIssue bool `yaml:"disable-issue"` + Skip []string `yaml:"skip"` } // ==== Versioning ==== // VersioningConfig versioning preferences. type VersioningConfig struct { - UpdateMajor []string - UpdateMinor []string - UpdatePatch []string - UnknownTypeAsPatch bool + UpdateMajor []string `yaml:"update-major"` + UpdateMinor []string `yaml:"update-minor"` + UpdatePatch []string `yaml:"update-patch"` + IgnoreUnknown bool `yaml:"ignore-unknown"` } // ==== Tag ==== // TagConfig tag preferences. type TagConfig struct { - Pattern string + Pattern string `yaml:"pattern"` } // ==== Release Notes ==== // ReleaseNotesConfig release notes preferences. type ReleaseNotesConfig struct { - Headers map[string]string + Headers map[string]string `yaml:"headers"` } diff --git a/sv/message.go b/sv/message.go index 0d74508..505ac7c 100644 --- a/sv/message.go +++ b/sv/message.go @@ -88,7 +88,7 @@ func (p MessageProcessorImpl) Validate(message string) error { // Enhance add metadata on commit message. func (p MessageProcessorImpl) Enhance(branch string, message string) (string, error) { - if !p.branchesCfg.ExpectIssue || p.messageCfg.IssueConfig().Key == "" || hasIssueID(message, p.messageCfg.IssueConfig().Key) { + if p.branchesCfg.DisableIssue || p.messageCfg.IssueConfig().Key == "" || hasIssueID(message, p.messageCfg.IssueConfig().Key) { return "", nil //enhance disabled } diff --git a/sv/message_test.go b/sv/message_test.go index 6491f67..7de0712 100644 --- a/sv/message_test.go +++ b/sv/message_test.go @@ -17,7 +17,6 @@ var ccfg = CommitMessageConfig{ } var bcfg = BranchesConfig{ - ExpectIssue: true, PrefixRegex: "([a-z]+\\/)?", SuffixRegex: "(-.*)?", Skip: []string{"develop", "master"}, diff --git a/sv/semver.go b/sv/semver.go index 08bf072..a58365d 100644 --- a/sv/semver.go +++ b/sv/semver.go @@ -40,7 +40,7 @@ type SemVerCommitsProcessorImpl struct { // NewSemVerCommitsProcessor SemanticVersionCommitsProcessorImpl constructor func NewSemVerCommitsProcessor(cfg VersioningConfig) *SemVerCommitsProcessorImpl { return &SemVerCommitsProcessorImpl{ - IncludeUnknownTypeAsPatch: cfg.UnknownTypeAsPatch, + IncludeUnknownTypeAsPatch: !cfg.IgnoreUnknown, MajorVersionTypes: toMap(cfg.UpdateMajor), MinorVersionTypes: toMap(cfg.UpdateMinor), PatchVersionTypes: toMap(cfg.UpdatePatch), diff --git a/sv/semver_test.go b/sv/semver_test.go index 5d3f1d6..3117e16 100644 --- a/sv/semver_test.go +++ b/sv/semver_test.go @@ -9,15 +9,15 @@ import ( func TestSemVerCommitsProcessorImpl_NextVersion(t *testing.T) { tests := []struct { - name string - unknownAsPatch bool - version semver.Version - commits []GitCommitLog - want semver.Version + name string + ignoreUnknown bool + version semver.Version + commits []GitCommitLog + want semver.Version }{ - {"no update", false, version("0.0.0"), []GitCommitLog{}, version("0.0.0")}, - {"no update on unknown type", false, version("0.0.0"), []GitCommitLog{commitlog("a", map[string]string{})}, version("0.0.0")}, - {"update patch on unknown type", true, version("0.0.0"), []GitCommitLog{commitlog("a", map[string]string{})}, version("0.0.1")}, + {"no update", true, version("0.0.0"), []GitCommitLog{}, version("0.0.0")}, + {"no update on unknown type", true, version("0.0.0"), []GitCommitLog{commitlog("a", map[string]string{})}, version("0.0.0")}, + {"update patch on unknown type", false, version("0.0.0"), []GitCommitLog{commitlog("a", map[string]string{})}, version("0.0.1")}, {"patch update", false, version("0.0.0"), []GitCommitLog{commitlog("patch", map[string]string{})}, version("0.0.1")}, {"minor update", false, version("0.0.0"), []GitCommitLog{commitlog("patch", map[string]string{}), commitlog("minor", map[string]string{})}, version("0.1.0")}, {"major update", false, version("0.0.0"), []GitCommitLog{commitlog("patch", map[string]string{}), commitlog("major", map[string]string{})}, version("1.0.0")}, @@ -25,7 +25,7 @@ func TestSemVerCommitsProcessorImpl_NextVersion(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - p := NewSemVerCommitsProcessor(VersioningConfig{UpdateMajor: []string{"major"}, UpdateMinor: []string{"minor"}, UpdatePatch: []string{"patch"}, UnknownTypeAsPatch: tt.unknownAsPatch}) + p := NewSemVerCommitsProcessor(VersioningConfig{UpdateMajor: []string{"major"}, UpdateMinor: []string{"minor"}, UpdatePatch: []string{"patch"}, IgnoreUnknown: tt.ignoreUnknown}) if got := p.NextVersion(tt.version, tt.commits); !reflect.DeepEqual(got, tt.want) { t.Errorf("SemVerCommitsProcessorImpl.NextVersion() = %v, want %v", got, tt.want) }