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 <ngtranhau@gmail.com>main
parent
f2a995e15f
commit
b98c62c46f
18
main.go
18
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)
|
||||
|
|
|
@ -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()))
|
||||
|
|
|
@ -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))
|
||||
})
|
||||
}
|
||||
|
|
19
pkg/changelog/testdata/TestMarkdownGeneratorGenerate/ignore_commits_outside_scope.golden
vendored
Normal file
19
pkg/changelog/testdata/TestMarkdownGeneratorGenerate/ignore_commits_outside_scope.golden
vendored
Normal file
|
@ -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
|
|
@ -13,41 +13,45 @@ import (
|
|||
// [optional body]
|
||||
// [optional footer(s)]
|
||||
|
||||
var headerRegex = regexp.MustCompile(`(?P<type>[a-zA-Z]+)(?P<scope>\([a-zA-Z]+\))?(?P<attention>!)?:\s(?P<description>.+)`)
|
||||
var (
|
||||
headerRegex = regexp.MustCompile(`(?P<type>[a-zA-Z]+)(?P<scope>\([a-zA-Z]+\))?(?P<attention>!)?:\s(?P<description>.+)`)
|
||||
|
||||
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])
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
{
|
||||
"RawHeader": "docs: correct spelling of CHANGELOG",
|
||||
"Type": "docs"
|
||||
"Type": "docs",
|
||||
"Scope": ""
|
||||
}
|
|
@ -1,4 +1,5 @@
|
|||
{
|
||||
"RawHeader": "refactor!: drop support for Node 6",
|
||||
"Type": "refactor"
|
||||
"Type": "refactor",
|
||||
"Scope": ""
|
||||
}
|
|
@ -1,4 +1,5 @@
|
|||
{
|
||||
"RawHeader": "feat(lang): add polish language",
|
||||
"Type": "feat"
|
||||
"Type": "feat",
|
||||
"Scope": "lang"
|
||||
}
|
|
@ -1,4 +1,5 @@
|
|||
{
|
||||
"RawHeader": "random git message",
|
||||
"Type": "misc"
|
||||
"Type": "misc",
|
||||
"Scope": ""
|
||||
}
|
|
@ -1,4 +1,5 @@
|
|||
{
|
||||
"RawHeader": "Docs: correct spelling of CHANGELOG",
|
||||
"Type": "docs"
|
||||
"Type": "docs",
|
||||
"Scope": ""
|
||||
}
|
|
@ -1,4 +1,5 @@
|
|||
{
|
||||
"RawHeader": "REFACTOR!: drop support for Node 6",
|
||||
"Type": "refactor"
|
||||
"Type": "refactor",
|
||||
"Scope": ""
|
||||
}
|
Loading…
Reference in New Issue