From b98c62c46fa34b633f1d46fd888ed8fb2a702c3b Mon Sep 17 00:00:00 2001 From: Nguyen Tran Hau Date: Mon, 22 Mar 2021 12:44:54 +0700 Subject: [PATCH] feat: support filter commit scope (#5) * feat: add scope flag * refactor: use scopes as map to quickly check * feat: add scope to generate commits * feat(convention): add scope to commit * refactor: remove obsolete commit GetType * refactor(convention): combie UpdateType and NewCommit * feat(changelog): skip commit outside scopes * feat(convention): remove () in scope * feat(changelog): support filter scope Co-authored-by: Tran Hau --- main.go | 18 +++++++- pkg/changelog/markdown.go | 16 +++++-- pkg/changelog/markdown_test.go | 45 ++++++++++++++++++- .../ignore_commits_outside_scope.golden | 19 ++++++++ pkg/convention/commit.go | 40 +++++++++-------- .../Commit_message_with_no_body.golden | 3 +- ...o_draw_attention_to_breaking_change.golden | 3 +- .../Commit_message_with_scope.golden | 3 +- .../testdata/TestNewCommit/Misc.golden | 3 +- .../testdata/TestNewCommit/Mixedcase.golden | 3 +- .../testdata/TestNewCommit/Uppercase.golden | 3 +- 11 files changed, 126 insertions(+), 30 deletions(-) create mode 100644 pkg/changelog/testdata/TestMarkdownGeneratorGenerate/ignore_commits_outside_scope.golden diff --git a/main.go b/main.go index 0e303f5..4a0a0ae 100644 --- a/main.go +++ b/main.go @@ -30,6 +30,7 @@ const ( fromFlag = "from" toFlag = "to" versionFlag = "version" + scopeFlag = "scope" repositoryFlag = "repository" outputFlag = "output" filenameFlag = "filename" @@ -60,6 +61,10 @@ func main() { Name: versionFlag, Usage: "`VERSION` to generate, follow Semantic Versioning", }, + &cli.StringSliceFlag{ + Name: scopeFlag, + Usage: "scope to generate", + }, &cli.StringFlag{ Name: repositoryFlag, Usage: "`REPOSITORY` directory path", @@ -102,6 +107,7 @@ type action struct { from string to string version string + scopes map[string]struct{} repository string output string filename string @@ -136,10 +142,20 @@ func (a *action) getFlags(c *cli.Context) { a.flags.from = c.String(fromFlag) a.flags.to = c.String(toFlag) a.flags.version = c.String(versionFlag) + + a.flags.scopes = make(map[string]struct{}) + for _, scope := range c.StringSlice(scopeFlag) { + a.flags.scopes[scope] = struct{}{} + } + a.flags.repository = a.getFlagValue(c, repositoryFlag, defaultRepository) a.flags.output = a.getFlagValue(c, outputFlag, defaultOutput) a.flags.filename = a.getFlagValue(c, filenameFlag, defaultFilename) a.flags.filetype = a.getFlagValue(c, filetypeFlag, defaultFiletype) + + if a.flags.debug { + a.logDebug("flags %+v", a.flags) + } } func (a *action) getFlagValue(c *cli.Context, flag, fallback string) string { @@ -225,7 +241,7 @@ func (a *action) generateMarkdownChangelog(output, version string, commits []con } markdownGenerator := changelog.NewMarkdownGenerator(oldData, version, time.Now()) - newData := markdownGenerator.Generate(commits) + newData := markdownGenerator.Generate(commits, a.flags.scopes) if err := os.WriteFile(output, []byte(newData), 0o644); err != nil { return fmt.Errorf("failed to write file %s: %w", output, err) diff --git a/pkg/changelog/markdown.go b/pkg/changelog/markdown.go index d81f111..1aa51c3 100644 --- a/pkg/changelog/markdown.go +++ b/pkg/changelog/markdown.go @@ -33,8 +33,8 @@ func NewMarkdownGenerator(oldData, version string, t time.Time) *MarkdownGenerat } } -func (g *MarkdownGenerator) Generate(commits []convention.Commit) string { - newBases := g.getNewNodes(commits) +func (g *MarkdownGenerator) Generate(commits []convention.Commit, scopes map[string]struct{}) string { + newBases := g.getNewNodes(commits, scopes) if len(newBases) == 0 { return "" } @@ -57,7 +57,7 @@ func (g *MarkdownGenerator) Generate(commits []convention.Commit) string { return markdown.Generate(nodes) } -func (g *MarkdownGenerator) getNewNodes(commits []convention.Commit) []markdown.Node { +func (g *MarkdownGenerator) getNewNodes(commits []convention.Commit, scopes map[string]struct{}) []markdown.Node { if len(commits) == 0 { return nil } @@ -70,7 +70,15 @@ func (g *MarkdownGenerator) getNewNodes(commits []convention.Commit) []markdown. commitBases[othersType] = make([]markdown.Node, 0, defaultNodesLen) for _, commit := range commits { - t := getType(commit.GetType()) + // If scopes is empty or commit scope is empty, pass all commits + if len(scopes) != 0 && commit.Scope != "" { + // Skip commit outside scopes + if _, ok := scopes[commit.Scope]; !ok { + continue + } + } + + t := getType(commit.Type) switch t { case addedType: commitBases[addedType] = append(commitBases[addedType], markdown.NewListItem(commit.String())) diff --git a/pkg/changelog/markdown_test.go b/pkg/changelog/markdown_test.go index e5c70bc..1382d15 100644 --- a/pkg/changelog/markdown_test.go +++ b/pkg/changelog/markdown_test.go @@ -15,6 +15,7 @@ func TestMarkdownGeneratorGenerate(t *testing.T) { version string t time.Time commits []convention.Commit + scopes map[string]struct{} }{ { name: "empty old data", @@ -74,13 +75,55 @@ func TestMarkdownGeneratorGenerate(t *testing.T) { }, }, }, + { + name: "ignore commits outside scope", + version: "v1.0.0", + t: time.Date(2020, 3, 22, 0, 0, 0, 0, time.Local), + commits: []convention.Commit{ + { + RawHeader: "feat: new feature", + Type: convention.FeatType, + }, + { + RawHeader: "feat(a): support new client", + Type: convention.FeatType, + Scope: "a", + }, + { + RawHeader: "fix: new fix", + Type: convention.FixType, + }, + { + RawHeader: "fix(b): wrong color", + Type: convention.FixType, + Scope: "b", + }, + { + RawHeader: "chore(a): new build", + Type: convention.ChoreType, + Scope: "a", + }, + { + RawHeader: "chore(b): new build", + Type: convention.ChoreType, + Scope: "b", + }, + { + RawHeader: "unleash the dragon", + Type: convention.MiscType, + }, + }, + scopes: map[string]struct{}{ + "a": struct{}{}, + }, + }, } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { g := goldie.New(t) markdownGenerator := NewMarkdownGenerator(tc.oldData, tc.version, tc.t) - got := markdownGenerator.Generate(tc.commits) + got := markdownGenerator.Generate(tc.commits, tc.scopes) g.Assert(t, t.Name(), []byte(got)) }) } diff --git a/pkg/changelog/testdata/TestMarkdownGeneratorGenerate/ignore_commits_outside_scope.golden b/pkg/changelog/testdata/TestMarkdownGeneratorGenerate/ignore_commits_outside_scope.golden new file mode 100644 index 0000000..d9f267f --- /dev/null +++ b/pkg/changelog/testdata/TestMarkdownGeneratorGenerate/ignore_commits_outside_scope.golden @@ -0,0 +1,19 @@ +# CHANGELOG + +## v1.0.0 (2020-3-22) + +### Added + +- feat: new feature + +- feat(a): support new client + +### Fixed + +- fix: new fix + +### Others + +- chore(a): new build + +- unleash the dragon diff --git a/pkg/convention/commit.go b/pkg/convention/commit.go index 8bad1fd..9a00140 100644 --- a/pkg/convention/commit.go +++ b/pkg/convention/commit.go @@ -13,41 +13,45 @@ import ( // [optional body] // [optional footer(s)] -var headerRegex = regexp.MustCompile(`(?P[a-zA-Z]+)(?P\([a-zA-Z]+\))?(?P!)?:\s(?P.+)`) +var ( + headerRegex = regexp.MustCompile(`(?P[a-zA-Z]+)(?P\([a-zA-Z]+\))?(?P!)?:\s(?P.+)`) + + ErrEmptyCommit = errors.New("empty commit") +) type Commit struct { + // Commit as is RawHeader string - Type string + + Type string + Scope string } func NewCommit(c git.Commit) (result Commit, err error) { + // Preprocess message := strings.TrimSpace(c.Message) messages := strings.Split(message, "\n") if len(messages) == 0 { - err = errors.New("empty commit") + err = ErrEmptyCommit return } + // Use regex to detect result.RawHeader = strings.TrimSpace(messages[0]) - result.UpdateType() + if !headerRegex.MatchString(result.RawHeader) { + result.Type = MiscType + return + } + + headerSubmatches := headerRegex.FindStringSubmatch(result.RawHeader) + result.Type = strings.ToLower(headerSubmatches[1]) + result.Scope = strings.ToLower(headerSubmatches[2]) + result.Scope = strings.TrimLeft(result.Scope, "(") + result.Scope = strings.TrimRight(result.Scope, ")") return } -func (c *Commit) GetType() string { - return c.Type -} - func (c *Commit) String() string { return c.RawHeader } - -func (c *Commit) UpdateType() { - if !headerRegex.MatchString(c.RawHeader) { - c.Type = MiscType - return - } - - headerSubmatches := headerRegex.FindStringSubmatch(c.RawHeader) - c.Type = strings.ToLower(headerSubmatches[1]) -} diff --git a/pkg/convention/testdata/TestNewCommit/Commit_message_with_no_body.golden b/pkg/convention/testdata/TestNewCommit/Commit_message_with_no_body.golden index 31397b5..7486b3a 100644 --- a/pkg/convention/testdata/TestNewCommit/Commit_message_with_no_body.golden +++ b/pkg/convention/testdata/TestNewCommit/Commit_message_with_no_body.golden @@ -1,4 +1,5 @@ { "RawHeader": "docs: correct spelling of CHANGELOG", - "Type": "docs" + "Type": "docs", + "Scope": "" } \ No newline at end of file diff --git a/pkg/convention/testdata/TestNewCommit/Commit_message_with_not_character_to_draw_attention_to_breaking_change.golden b/pkg/convention/testdata/TestNewCommit/Commit_message_with_not_character_to_draw_attention_to_breaking_change.golden index 1c95e9b..3039035 100644 --- a/pkg/convention/testdata/TestNewCommit/Commit_message_with_not_character_to_draw_attention_to_breaking_change.golden +++ b/pkg/convention/testdata/TestNewCommit/Commit_message_with_not_character_to_draw_attention_to_breaking_change.golden @@ -1,4 +1,5 @@ { "RawHeader": "refactor!: drop support for Node 6", - "Type": "refactor" + "Type": "refactor", + "Scope": "" } \ No newline at end of file diff --git a/pkg/convention/testdata/TestNewCommit/Commit_message_with_scope.golden b/pkg/convention/testdata/TestNewCommit/Commit_message_with_scope.golden index 0921d71..8b35ada 100644 --- a/pkg/convention/testdata/TestNewCommit/Commit_message_with_scope.golden +++ b/pkg/convention/testdata/TestNewCommit/Commit_message_with_scope.golden @@ -1,4 +1,5 @@ { "RawHeader": "feat(lang): add polish language", - "Type": "feat" + "Type": "feat", + "Scope": "lang" } \ No newline at end of file diff --git a/pkg/convention/testdata/TestNewCommit/Misc.golden b/pkg/convention/testdata/TestNewCommit/Misc.golden index 44e5f50..c005429 100644 --- a/pkg/convention/testdata/TestNewCommit/Misc.golden +++ b/pkg/convention/testdata/TestNewCommit/Misc.golden @@ -1,4 +1,5 @@ { "RawHeader": "random git message", - "Type": "misc" + "Type": "misc", + "Scope": "" } \ No newline at end of file diff --git a/pkg/convention/testdata/TestNewCommit/Mixedcase.golden b/pkg/convention/testdata/TestNewCommit/Mixedcase.golden index 3f34a7d..dbaabcc 100644 --- a/pkg/convention/testdata/TestNewCommit/Mixedcase.golden +++ b/pkg/convention/testdata/TestNewCommit/Mixedcase.golden @@ -1,4 +1,5 @@ { "RawHeader": "Docs: correct spelling of CHANGELOG", - "Type": "docs" + "Type": "docs", + "Scope": "" } \ No newline at end of file diff --git a/pkg/convention/testdata/TestNewCommit/Uppercase.golden b/pkg/convention/testdata/TestNewCommit/Uppercase.golden index d302542..aa7e76e 100644 --- a/pkg/convention/testdata/TestNewCommit/Uppercase.golden +++ b/pkg/convention/testdata/TestNewCommit/Uppercase.golden @@ -1,4 +1,5 @@ { "RawHeader": "REFACTOR!: drop support for Node 6", - "Type": "refactor" + "Type": "refactor", + "Scope": "" } \ No newline at end of file