mirror of
https://github.com/git-chglog/git-chglog.git
synced 2026-01-22 18:06:11 +00:00
feat: add Jira integration (#52)
This commit is contained in:
parent
8713d96856
commit
a1c84d7a0d
22 changed files with 742 additions and 331 deletions
2
Makefile
2
Makefile
|
|
@ -7,7 +7,7 @@ clean:
|
|||
|
||||
.PHONY: build
|
||||
build:
|
||||
go build -i -o git-chglog ./cmd/git-chglog
|
||||
go build -o git-chglog ./cmd/git-chglog
|
||||
|
||||
.PHONY: test
|
||||
test:
|
||||
|
|
|
|||
83
README.md
83
README.md
|
|
@ -44,6 +44,7 @@
|
|||
- [`options.notes`](#optionsnotes)
|
||||
- [Templates](#templates)
|
||||
- [Supported Styles](#supported-styles)
|
||||
- [Jira Integration](#jira-integration)
|
||||
- [FAQ](#faq)
|
||||
- [TODO](#todo)
|
||||
- [Thanks](#thanks)
|
||||
|
|
@ -486,6 +487,88 @@ See the godoc [RenderData][doc-render-data] documentation for available variable
|
|||
|
||||
> :memo: Even with styles that are not yet supported, it is possible to make ordinary CHANGELOG.
|
||||
|
||||
|
||||
## Jira Integration
|
||||
|
||||
|
||||
Jira is a popular project management tool. When a project uses Jira to track feature development and bug fixes,
|
||||
it may also want to generate change log based information stored in Jira. With embedding a Jira story id in git
|
||||
commit header, the git-chglog tool may automatically fetch data of the story from Jira, those data then can be
|
||||
used to render the template.
|
||||
|
||||
Take the following steps to add Jira integration:
|
||||
|
||||
#### 1. Change the header parse pattern to recognize Jira issue id in the configure file.
|
||||
|
||||
__Where Jira issue is identical Jira story.__
|
||||
|
||||
The following is a sample pattern:
|
||||
|
||||
```yaml
|
||||
header:
|
||||
pattern: "^(?:(\\w*)|(?:\\[(.*)\\])?)\\:\\s(.*)$"
|
||||
pattern_maps:
|
||||
- Type
|
||||
- JiraIssueId
|
||||
- Subject
|
||||
```
|
||||
|
||||
This sample pattern can match both forms of commit headers:
|
||||
|
||||
* `feat: new feature of something`
|
||||
* `[JIRA-ID]: something`
|
||||
|
||||
#### 2. Add Jira configuration to the configure file.
|
||||
|
||||
The following is a sample:
|
||||
|
||||
```yaml
|
||||
jira:
|
||||
info:
|
||||
username: u
|
||||
token: p
|
||||
url: https://jira.com
|
||||
issue:
|
||||
type_maps:
|
||||
Task: fix
|
||||
Story: feat
|
||||
description_pattern: "<changelog>(.*)</changelog>"
|
||||
```
|
||||
|
||||
Here you need to define Jira URL, access username and token (password). If you don't want to
|
||||
write your Jira access credential in configure file, you may define them with environment variables:
|
||||
`JIRA_URL`, `JIRA_USERNAME` and `JIRA_TOKEN`.
|
||||
|
||||
You also needs to define a issue type map. In above sample, Jira issue type `Task` will be
|
||||
mapped to `fix` and `Story` will be mapped to `feat`.
|
||||
|
||||
As a Jira story's description could be very long, you might not want to include the entire
|
||||
description into change log. In that case, you may define `description_pattern` like above,
|
||||
so that only content embraced with `<changelog> ... </changelog>` will be included.
|
||||
|
||||
#### 3. Update the template to show Jira data.
|
||||
|
||||
In the template, if a commit contains a Jira issue id, then you may show Jira data. For example:
|
||||
|
||||
```markdown
|
||||
{{ range .CommitGroups -}}
|
||||
### {{ .Title }}
|
||||
{{ range .Commits -}}
|
||||
- {{ if .Scope }}**{{ .Scope }}:** {{ end }}{{ .Subject }}
|
||||
{{ if .JiraIssue }} {{ .JiraIssue.Description }}
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
{{ end -}}
|
||||
```
|
||||
|
||||
Within a `Commit`, the following Jira data can be used in template:
|
||||
|
||||
* `.JiraIssue.Summary` - Summary of the Jira story
|
||||
* `.JiraIssue.Description` - Description of the Jira story
|
||||
* `.JiraIssue.Type` - Original type of the Jira story, and `.Type` will be mapped type.
|
||||
* `.JiraIssue.Labels` - A list of strings, each is a Jira label.
|
||||
|
||||
|
||||
## FAQ
|
||||
|
||||
<details>
|
||||
|
|
|
|||
49
chglog.go
49
chglog.go
|
|
@ -16,25 +16,30 @@ import (
|
|||
|
||||
// Options is an option used to process commits
|
||||
type Options struct {
|
||||
Processor Processor
|
||||
NextTag string // Treat unreleased commits as specified tags (EXPERIMENTAL)
|
||||
TagFilterPattern string // Filter tag by regexp
|
||||
NoCaseSensitive bool // Filter commits in a case insensitive way
|
||||
CommitFilters map[string][]string // Filter by using `Commit` properties and values. Filtering is not done by specifying an empty value
|
||||
CommitSortBy string // Property name to use for sorting `Commit` (e.g. `Scope`)
|
||||
CommitGroupBy string // Property name of `Commit` to be grouped into `CommitGroup` (e.g. `Type`)
|
||||
CommitGroupSortBy string // Property name to use for sorting `CommitGroup` (e.g. `Title`)
|
||||
CommitGroupTitleOrder []string // Predefined sorted list of titles to use for sorting `CommitGroup`. Only if `CommitGroupSortBy` is `Custom`
|
||||
CommitGroupTitleMaps map[string]string // Map for `CommitGroup` title conversion
|
||||
HeaderPattern string // A regular expression to use for parsing the commit header
|
||||
HeaderPatternMaps []string // A rule for mapping the result of `HeaderPattern` to the property of `Commit`
|
||||
IssuePrefix []string // Prefix used for issues (e.g. `#`, `gh-`)
|
||||
RefActions []string // Word list of `Ref.Action`
|
||||
MergePattern string // A regular expression to use for parsing the merge commit
|
||||
MergePatternMaps []string // Similar to `HeaderPatternMaps`
|
||||
RevertPattern string // A regular expression to use for parsing the revert commit
|
||||
RevertPatternMaps []string // Similar to `HeaderPatternMaps`
|
||||
NoteKeywords []string // Keyword list to find `Note`. A semicolon is a separator, like `<keyword>:` (e.g. `BREAKING CHANGE`)
|
||||
Processor Processor
|
||||
NextTag string // Treat unreleased commits as specified tags (EXPERIMENTAL)
|
||||
TagFilterPattern string // Filter tag by regexp
|
||||
NoCaseSensitive bool // Filter commits in a case insensitive way
|
||||
CommitFilters map[string][]string // Filter by using `Commit` properties and values. Filtering is not done by specifying an empty value
|
||||
CommitSortBy string // Property name to use for sorting `Commit` (e.g. `Scope`)
|
||||
CommitGroupBy string // Property name of `Commit` to be grouped into `CommitGroup` (e.g. `Type`)
|
||||
CommitGroupSortBy string // Property name to use for sorting `CommitGroup` (e.g. `Title`)
|
||||
CommitGroupTitleOrder []string // Predefined sorted list of titles to use for sorting `CommitGroup`. Only if `CommitGroupSortBy` is `Custom`
|
||||
CommitGroupTitleMaps map[string]string // Map for `CommitGroup` title conversion
|
||||
HeaderPattern string // A regular expression to use for parsing the commit header
|
||||
HeaderPatternMaps []string // A rule for mapping the result of `HeaderPattern` to the property of `Commit`
|
||||
IssuePrefix []string // Prefix used for issues (e.g. `#`, `gh-`)
|
||||
RefActions []string // Word list of `Ref.Action`
|
||||
MergePattern string // A regular expression to use for parsing the merge commit
|
||||
MergePatternMaps []string // Similar to `HeaderPatternMaps`
|
||||
RevertPattern string // A regular expression to use for parsing the revert commit
|
||||
RevertPatternMaps []string // Similar to `HeaderPatternMaps`
|
||||
NoteKeywords []string // Keyword list to find `Note`. A semicolon is a separator, like `<keyword>:` (e.g. `BREAKING CHANGE`)
|
||||
JiraUsername string
|
||||
JiraToken string
|
||||
JiraUrl string
|
||||
JiraTypeMaps map[string]string
|
||||
JiraIssueDescriptionPattern string
|
||||
}
|
||||
|
||||
// Info is metadata related to CHANGELOG
|
||||
|
|
@ -97,11 +102,13 @@ type Generator struct {
|
|||
}
|
||||
|
||||
// NewGenerator receives `Config` and create an new `Generator`
|
||||
func NewGenerator(config *Config) *Generator {
|
||||
func NewGenerator(logger *Logger, config *Config) *Generator {
|
||||
client := gitcmd.New(&gitcmd.Config{
|
||||
Bin: config.Bin,
|
||||
})
|
||||
|
||||
jiraClient := NewJiraClient(config)
|
||||
|
||||
if config.Options.Processor != nil {
|
||||
config.Options.Processor.Bootstrap(config)
|
||||
}
|
||||
|
|
@ -113,7 +120,7 @@ func NewGenerator(config *Config) *Generator {
|
|||
config: config,
|
||||
tagReader: newTagReader(client, config.Options.TagFilterPattern),
|
||||
tagSelector: newTagSelector(),
|
||||
commitParser: newCommitParser(client, config),
|
||||
commitParser: newCommitParser(logger, client, jiraClient, config),
|
||||
commitExtractor: newCommitExtractor(config.Options),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
312
chglog_test.go
312
chglog_test.go
|
|
@ -76,15 +76,16 @@ func TestGeneratorNotFoundTags(t *testing.T) {
|
|||
commit("2018-01-01 00:00:00", "feat(*): New feature", "")
|
||||
})
|
||||
|
||||
gen := NewGenerator(&Config{
|
||||
Bin: "git",
|
||||
WorkingDir: filepath.Join(testRepoRoot, testName),
|
||||
Template: filepath.Join(cwd, "testdata", testName+".md"),
|
||||
Info: &Info{
|
||||
RepositoryURL: "https://github.com/git-chglog/git-chglog",
|
||||
},
|
||||
Options: &Options{},
|
||||
})
|
||||
gen := NewGenerator(NewLogger(os.Stdout, os.Stderr, false, true),
|
||||
&Config{
|
||||
Bin: "git",
|
||||
WorkingDir: filepath.Join(testRepoRoot, testName),
|
||||
Template: filepath.Join(cwd, "testdata", testName+".md"),
|
||||
Info: &Info{
|
||||
RepositoryURL: "https://github.com/git-chglog/git-chglog",
|
||||
},
|
||||
Options: &Options{},
|
||||
})
|
||||
|
||||
buf := &bytes.Buffer{}
|
||||
err := gen.Generate(buf, "")
|
||||
|
|
@ -102,15 +103,16 @@ func TestGeneratorNotFoundCommits(t *testing.T) {
|
|||
tag("1.0.0")
|
||||
})
|
||||
|
||||
gen := NewGenerator(&Config{
|
||||
Bin: "git",
|
||||
WorkingDir: filepath.Join(testRepoRoot, testName),
|
||||
Template: filepath.Join(cwd, "testdata", testName+".md"),
|
||||
Info: &Info{
|
||||
RepositoryURL: "https://github.com/git-chglog/git-chglog",
|
||||
},
|
||||
Options: &Options{},
|
||||
})
|
||||
gen := NewGenerator(NewLogger(os.Stdout, os.Stderr, false, true),
|
||||
&Config{
|
||||
Bin: "git",
|
||||
WorkingDir: filepath.Join(testRepoRoot, testName),
|
||||
Template: filepath.Join(cwd, "testdata", testName+".md"),
|
||||
Info: &Info{
|
||||
RepositoryURL: "https://github.com/git-chglog/git-chglog",
|
||||
},
|
||||
Options: &Options{},
|
||||
})
|
||||
|
||||
buf := &bytes.Buffer{}
|
||||
err := gen.Generate(buf, "foo")
|
||||
|
|
@ -127,44 +129,45 @@ func TestGeneratorNotFoundCommitsOne(t *testing.T) {
|
|||
tag("1.0.0")
|
||||
})
|
||||
|
||||
gen := NewGenerator(&Config{
|
||||
Bin: "git",
|
||||
WorkingDir: filepath.Join(testRepoRoot, testName),
|
||||
Template: filepath.Join(cwd, "testdata", testName+".md"),
|
||||
Info: &Info{
|
||||
RepositoryURL: "https://github.com/git-chglog/git-chglog",
|
||||
},
|
||||
Options: &Options{
|
||||
CommitFilters: map[string][]string{},
|
||||
CommitSortBy: "Scope",
|
||||
CommitGroupBy: "Type",
|
||||
CommitGroupSortBy: "Title",
|
||||
CommitGroupTitleMaps: map[string]string{},
|
||||
HeaderPattern: "^(\\w*)(?:\\(([\\w\\$\\.\\-\\*\\s]*)\\))?\\:\\s(.*)$",
|
||||
HeaderPatternMaps: []string{
|
||||
"Type",
|
||||
"Scope",
|
||||
"Subject",
|
||||
gen := NewGenerator(NewLogger(os.Stdout, os.Stderr, false, true),
|
||||
&Config{
|
||||
Bin: "git",
|
||||
WorkingDir: filepath.Join(testRepoRoot, testName),
|
||||
Template: filepath.Join(cwd, "testdata", testName+".md"),
|
||||
Info: &Info{
|
||||
RepositoryURL: "https://github.com/git-chglog/git-chglog",
|
||||
},
|
||||
IssuePrefix: []string{
|
||||
"#",
|
||||
"gh-",
|
||||
Options: &Options{
|
||||
CommitFilters: map[string][]string{},
|
||||
CommitSortBy: "Scope",
|
||||
CommitGroupBy: "Type",
|
||||
CommitGroupSortBy: "Title",
|
||||
CommitGroupTitleMaps: map[string]string{},
|
||||
HeaderPattern: "^(\\w*)(?:\\(([\\w\\$\\.\\-\\*\\s]*)\\))?\\:\\s(.*)$",
|
||||
HeaderPatternMaps: []string{
|
||||
"Type",
|
||||
"Scope",
|
||||
"Subject",
|
||||
},
|
||||
IssuePrefix: []string{
|
||||
"#",
|
||||
"gh-",
|
||||
},
|
||||
RefActions: []string{},
|
||||
MergePattern: "^Merge pull request #(\\d+) from (.*)$",
|
||||
MergePatternMaps: []string{
|
||||
"Ref",
|
||||
"Source",
|
||||
},
|
||||
RevertPattern: "^Revert \"([\\s\\S]*)\"$",
|
||||
RevertPatternMaps: []string{
|
||||
"Header",
|
||||
},
|
||||
NoteKeywords: []string{
|
||||
"BREAKING CHANGE",
|
||||
},
|
||||
},
|
||||
RefActions: []string{},
|
||||
MergePattern: "^Merge pull request #(\\d+) from (.*)$",
|
||||
MergePatternMaps: []string{
|
||||
"Ref",
|
||||
"Source",
|
||||
},
|
||||
RevertPattern: "^Revert \"([\\s\\S]*)\"$",
|
||||
RevertPatternMaps: []string{
|
||||
"Header",
|
||||
},
|
||||
NoteKeywords: []string{
|
||||
"BREAKING CHANGE",
|
||||
},
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
buf := &bytes.Buffer{}
|
||||
err := gen.Generate(buf, "foo")
|
||||
|
|
@ -202,53 +205,54 @@ change message.`)
|
|||
commit("2018-01-04 00:01:00", "fix(core): Fix commit\n\nThis is body message.", "")
|
||||
})
|
||||
|
||||
gen := NewGenerator(&Config{
|
||||
Bin: "git",
|
||||
WorkingDir: filepath.Join(testRepoRoot, testName),
|
||||
Template: filepath.Join(cwd, "testdata", testName+".md"),
|
||||
Info: &Info{
|
||||
Title: "CHANGELOG Example",
|
||||
RepositoryURL: "https://github.com/git-chglog/git-chglog",
|
||||
},
|
||||
Options: &Options{
|
||||
CommitFilters: map[string][]string{
|
||||
"Type": []string{
|
||||
"feat",
|
||||
"fix",
|
||||
gen := NewGenerator(NewLogger(os.Stdout, os.Stderr, false, true),
|
||||
&Config{
|
||||
Bin: "git",
|
||||
WorkingDir: filepath.Join(testRepoRoot, testName),
|
||||
Template: filepath.Join(cwd, "testdata", testName+".md"),
|
||||
Info: &Info{
|
||||
Title: "CHANGELOG Example",
|
||||
RepositoryURL: "https://github.com/git-chglog/git-chglog",
|
||||
},
|
||||
Options: &Options{
|
||||
CommitFilters: map[string][]string{
|
||||
"Type": []string{
|
||||
"feat",
|
||||
"fix",
|
||||
},
|
||||
},
|
||||
CommitSortBy: "Scope",
|
||||
CommitGroupBy: "Type",
|
||||
CommitGroupSortBy: "Title",
|
||||
CommitGroupTitleMaps: map[string]string{
|
||||
"feat": "Features",
|
||||
"fix": "Bug Fixes",
|
||||
},
|
||||
HeaderPattern: "^(\\w*)(?:\\(([\\w\\$\\.\\-\\*\\s]*)\\))?\\:\\s(.*)$",
|
||||
HeaderPatternMaps: []string{
|
||||
"Type",
|
||||
"Scope",
|
||||
"Subject",
|
||||
},
|
||||
IssuePrefix: []string{
|
||||
"#",
|
||||
"gh-",
|
||||
},
|
||||
RefActions: []string{},
|
||||
MergePattern: "^Merge pull request #(\\d+) from (.*)$",
|
||||
MergePatternMaps: []string{
|
||||
"Ref",
|
||||
"Source",
|
||||
},
|
||||
RevertPattern: "^Revert \"([\\s\\S]*)\"$",
|
||||
RevertPatternMaps: []string{
|
||||
"Header",
|
||||
},
|
||||
NoteKeywords: []string{
|
||||
"BREAKING CHANGE",
|
||||
},
|
||||
},
|
||||
CommitSortBy: "Scope",
|
||||
CommitGroupBy: "Type",
|
||||
CommitGroupSortBy: "Title",
|
||||
CommitGroupTitleMaps: map[string]string{
|
||||
"feat": "Features",
|
||||
"fix": "Bug Fixes",
|
||||
},
|
||||
HeaderPattern: "^(\\w*)(?:\\(([\\w\\$\\.\\-\\*\\s]*)\\))?\\:\\s(.*)$",
|
||||
HeaderPatternMaps: []string{
|
||||
"Type",
|
||||
"Scope",
|
||||
"Subject",
|
||||
},
|
||||
IssuePrefix: []string{
|
||||
"#",
|
||||
"gh-",
|
||||
},
|
||||
RefActions: []string{},
|
||||
MergePattern: "^Merge pull request #(\\d+) from (.*)$",
|
||||
MergePatternMaps: []string{
|
||||
"Ref",
|
||||
"Source",
|
||||
},
|
||||
RevertPattern: "^Revert \"([\\s\\S]*)\"$",
|
||||
RevertPatternMaps: []string{
|
||||
"Header",
|
||||
},
|
||||
NoteKeywords: []string{
|
||||
"BREAKING CHANGE",
|
||||
},
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
buf := &bytes.Buffer{}
|
||||
err := gen.Generate(buf, "")
|
||||
|
|
@ -315,35 +319,36 @@ func TestGeneratorWithNextTag(t *testing.T) {
|
|||
commit("2018-03-01 00:00:00", "feat(core): version 3.0.0", "")
|
||||
})
|
||||
|
||||
gen := NewGenerator(&Config{
|
||||
Bin: "git",
|
||||
WorkingDir: filepath.Join(testRepoRoot, testName),
|
||||
Template: filepath.Join(cwd, "testdata", testName+".md"),
|
||||
Info: &Info{
|
||||
Title: "CHANGELOG Example",
|
||||
RepositoryURL: "https://github.com/git-chglog/git-chglog",
|
||||
},
|
||||
Options: &Options{
|
||||
NextTag: "3.0.0",
|
||||
CommitFilters: map[string][]string{
|
||||
"Type": []string{
|
||||
"feat",
|
||||
gen := NewGenerator(NewLogger(os.Stdout, os.Stderr, false, true),
|
||||
&Config{
|
||||
Bin: "git",
|
||||
WorkingDir: filepath.Join(testRepoRoot, testName),
|
||||
Template: filepath.Join(cwd, "testdata", testName+".md"),
|
||||
Info: &Info{
|
||||
Title: "CHANGELOG Example",
|
||||
RepositoryURL: "https://github.com/git-chglog/git-chglog",
|
||||
},
|
||||
Options: &Options{
|
||||
NextTag: "3.0.0",
|
||||
CommitFilters: map[string][]string{
|
||||
"Type": []string{
|
||||
"feat",
|
||||
},
|
||||
},
|
||||
CommitSortBy: "Scope",
|
||||
CommitGroupBy: "Type",
|
||||
CommitGroupSortBy: "Title",
|
||||
CommitGroupTitleMaps: map[string]string{
|
||||
"feat": "Features",
|
||||
},
|
||||
HeaderPattern: "^(\\w*)(?:\\(([\\w\\$\\.\\-\\*\\s]*)\\))?\\:\\s(.*)$",
|
||||
HeaderPatternMaps: []string{
|
||||
"Type",
|
||||
"Scope",
|
||||
"Subject",
|
||||
},
|
||||
},
|
||||
CommitSortBy: "Scope",
|
||||
CommitGroupBy: "Type",
|
||||
CommitGroupSortBy: "Title",
|
||||
CommitGroupTitleMaps: map[string]string{
|
||||
"feat": "Features",
|
||||
},
|
||||
HeaderPattern: "^(\\w*)(?:\\(([\\w\\$\\.\\-\\*\\s]*)\\))?\\:\\s(.*)$",
|
||||
HeaderPatternMaps: []string{
|
||||
"Type",
|
||||
"Scope",
|
||||
"Subject",
|
||||
},
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
buf := &bytes.Buffer{}
|
||||
err := gen.Generate(buf, "")
|
||||
|
|
@ -407,35 +412,36 @@ func TestGeneratorWithTagFiler(t *testing.T) {
|
|||
tag("v1.0.0")
|
||||
})
|
||||
|
||||
gen := NewGenerator(&Config{
|
||||
Bin: "git",
|
||||
WorkingDir: filepath.Join(testRepoRoot, testName),
|
||||
Template: filepath.Join(cwd, "testdata", testName+".md"),
|
||||
Info: &Info{
|
||||
Title: "CHANGELOG Example",
|
||||
RepositoryURL: "https://github.com/git-chglog/git-chglog",
|
||||
},
|
||||
Options: &Options{
|
||||
TagFilterPattern: "^v",
|
||||
CommitFilters: map[string][]string{
|
||||
"Type": []string{
|
||||
"feat",
|
||||
gen := NewGenerator(NewLogger(os.Stdout, os.Stderr, false, true),
|
||||
&Config{
|
||||
Bin: "git",
|
||||
WorkingDir: filepath.Join(testRepoRoot, testName),
|
||||
Template: filepath.Join(cwd, "testdata", testName+".md"),
|
||||
Info: &Info{
|
||||
Title: "CHANGELOG Example",
|
||||
RepositoryURL: "https://github.com/git-chglog/git-chglog",
|
||||
},
|
||||
Options: &Options{
|
||||
TagFilterPattern: "^v",
|
||||
CommitFilters: map[string][]string{
|
||||
"Type": []string{
|
||||
"feat",
|
||||
},
|
||||
},
|
||||
CommitSortBy: "Scope",
|
||||
CommitGroupBy: "Type",
|
||||
CommitGroupSortBy: "Title",
|
||||
CommitGroupTitleMaps: map[string]string{
|
||||
"feat": "Features",
|
||||
},
|
||||
HeaderPattern: "^(\\w*)(?:\\(([\\w\\$\\.\\-\\*\\s]*)\\))?\\:\\s(.*)$",
|
||||
HeaderPatternMaps: []string{
|
||||
"Type",
|
||||
"Scope",
|
||||
"Subject",
|
||||
},
|
||||
},
|
||||
CommitSortBy: "Scope",
|
||||
CommitGroupBy: "Type",
|
||||
CommitGroupSortBy: "Title",
|
||||
CommitGroupTitleMaps: map[string]string{
|
||||
"feat": "Features",
|
||||
},
|
||||
HeaderPattern: "^(\\w*)(?:\\(([\\w\\$\\.\\-\\*\\s]*)\\))?\\:\\s(.*)$",
|
||||
HeaderPatternMaps: []string{
|
||||
"Type",
|
||||
"Scope",
|
||||
"Subject",
|
||||
},
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
buf := &bytes.Buffer{}
|
||||
err := gen.Generate(buf, "")
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ import (
|
|||
type CLI struct {
|
||||
ctx *CLIContext
|
||||
fs FileSystem
|
||||
logger *Logger
|
||||
logger *chglog.Logger
|
||||
configLoader ConfigLoader
|
||||
generator Generator
|
||||
processorFactory *ProcessorFactory
|
||||
|
|
@ -34,7 +34,7 @@ func NewCLI(
|
|||
return &CLI{
|
||||
ctx: ctx,
|
||||
fs: fs,
|
||||
logger: NewLogger(ctx.Stdout, ctx.Stderr, silent, ctx.NoEmoji),
|
||||
logger: chglog.NewLogger(ctx.Stdout, ctx.Stderr, silent, ctx.NoEmoji),
|
||||
configLoader: configLoader,
|
||||
generator: generator,
|
||||
processorFactory: NewProcessorFactory(),
|
||||
|
|
@ -69,7 +69,7 @@ func (c *CLI) Run() int {
|
|||
return ExitCodeError
|
||||
}
|
||||
|
||||
err = c.generator.Generate(w, c.ctx.Query, changelogConfig)
|
||||
err = c.generator.Generate(c.logger, w, c.ctx.Query, changelogConfig)
|
||||
if err != nil {
|
||||
c.logger.Error(err.Error())
|
||||
return ExitCodeError
|
||||
|
|
|
|||
|
|
@ -49,6 +49,24 @@ type NoteOptions struct {
|
|||
Keywords []string `yaml:"keywords"`
|
||||
}
|
||||
|
||||
type JiraClientInfoOptions struct {
|
||||
Username string `yaml:"username"`
|
||||
Token string `yaml:"token"`
|
||||
URL string `yaml:"url"`
|
||||
}
|
||||
|
||||
// JiraIssueOptions ...
|
||||
type JiraIssueOptions struct {
|
||||
TypeMaps map[string]string `yaml:"type_maps"`
|
||||
DescriptionPattern string `yaml:"description_pattern"`
|
||||
}
|
||||
|
||||
// JiraOptions ...
|
||||
type JiraOptions struct {
|
||||
ClintInfo JiraClientInfoOptions `yaml:"info"`
|
||||
Issue JiraIssueOptions `yaml:"issue"`
|
||||
}
|
||||
|
||||
// Options ...
|
||||
type Options struct {
|
||||
TagFilterPattern string `yaml:"tag_filter_pattern"`
|
||||
|
|
@ -60,6 +78,7 @@ type Options struct {
|
|||
Merges PatternOptions `yaml:"merges"`
|
||||
Reverts PatternOptions `yaml:"reverts"`
|
||||
Notes NoteOptions `yaml:"notes"`
|
||||
Jira JiraOptions `yaml:"jira"`
|
||||
}
|
||||
|
||||
// Config ...
|
||||
|
|
@ -245,6 +264,13 @@ func (config *Config) normalizeStyleOfBitbucket() {
|
|||
config.Options = opts
|
||||
}
|
||||
|
||||
func orValue(str1 string, str2 string) string {
|
||||
if str1 != "" {
|
||||
return str1
|
||||
}
|
||||
return str2
|
||||
}
|
||||
|
||||
// Convert ...
|
||||
func (config *Config) Convert(ctx *CLIContext) *chglog.Config {
|
||||
info := config.Info
|
||||
|
|
@ -257,30 +283,34 @@ func (config *Config) Convert(ctx *CLIContext) *chglog.Config {
|
|||
return &chglog.Config{
|
||||
Bin: config.Bin,
|
||||
WorkingDir: ctx.WorkingDir,
|
||||
Template: config.Template,
|
||||
Template: orValue(ctx.Template, config.Template),
|
||||
Info: &chglog.Info{
|
||||
Title: info.Title,
|
||||
RepositoryURL: info.RepositoryURL,
|
||||
RepositoryURL: orValue(ctx.RepositoryUrl, info.RepositoryURL),
|
||||
},
|
||||
Options: &chglog.Options{
|
||||
NextTag: ctx.NextTag,
|
||||
TagFilterPattern: ctx.TagFilterPattern,
|
||||
NoCaseSensitive: ctx.NoCaseSensitive,
|
||||
CommitFilters: opts.Commits.Filters,
|
||||
CommitSortBy: opts.Commits.SortBy,
|
||||
CommitGroupBy: opts.CommitGroups.GroupBy,
|
||||
CommitGroupSortBy: opts.CommitGroups.SortBy,
|
||||
CommitGroupTitleMaps: opts.CommitGroups.TitleMaps,
|
||||
CommitGroupTitleOrder: opts.CommitGroups.TitleOrder,
|
||||
HeaderPattern: opts.Header.Pattern,
|
||||
HeaderPatternMaps: opts.Header.PatternMaps,
|
||||
IssuePrefix: opts.Issues.Prefix,
|
||||
RefActions: opts.Refs.Actions,
|
||||
MergePattern: opts.Merges.Pattern,
|
||||
MergePatternMaps: opts.Merges.PatternMaps,
|
||||
RevertPattern: opts.Reverts.Pattern,
|
||||
RevertPatternMaps: opts.Reverts.PatternMaps,
|
||||
NoteKeywords: opts.Notes.Keywords,
|
||||
NextTag: ctx.NextTag,
|
||||
TagFilterPattern: ctx.TagFilterPattern,
|
||||
NoCaseSensitive: ctx.NoCaseSensitive,
|
||||
CommitFilters: opts.Commits.Filters,
|
||||
CommitSortBy: opts.Commits.SortBy,
|
||||
CommitGroupBy: opts.CommitGroups.GroupBy,
|
||||
CommitGroupSortBy: opts.CommitGroups.SortBy,
|
||||
CommitGroupTitleMaps: opts.CommitGroups.TitleMaps,
|
||||
HeaderPattern: opts.Header.Pattern,
|
||||
HeaderPatternMaps: opts.Header.PatternMaps,
|
||||
IssuePrefix: opts.Issues.Prefix,
|
||||
RefActions: opts.Refs.Actions,
|
||||
MergePattern: opts.Merges.Pattern,
|
||||
MergePatternMaps: opts.Merges.PatternMaps,
|
||||
RevertPattern: opts.Reverts.Pattern,
|
||||
RevertPatternMaps: opts.Reverts.PatternMaps,
|
||||
NoteKeywords: opts.Notes.Keywords,
|
||||
JiraUsername: orValue(ctx.JiraUsername, opts.Jira.ClintInfo.Username),
|
||||
JiraToken: orValue(ctx.JiraToken, opts.Jira.ClintInfo.Token),
|
||||
JiraUrl: orValue(ctx.JiraUrl, opts.Jira.ClintInfo.URL),
|
||||
JiraTypeMaps: opts.Jira.Issue.TypeMaps,
|
||||
JiraIssueDescriptionPattern: opts.Jira.Issue.DescriptionPattern,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,6 +10,8 @@ type CLIContext struct {
|
|||
Stdout io.Writer
|
||||
Stderr io.Writer
|
||||
ConfigPath string
|
||||
Template string
|
||||
RepositoryUrl string
|
||||
OutputPath string
|
||||
Silent bool
|
||||
NoColor bool
|
||||
|
|
@ -18,6 +20,9 @@ type CLIContext struct {
|
|||
Query string
|
||||
NextTag string
|
||||
TagFilterPattern string
|
||||
JiraUsername string
|
||||
JiraToken string
|
||||
JiraUrl string
|
||||
}
|
||||
|
||||
// InitContext ...
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import (
|
|||
|
||||
// Generator ...
|
||||
type Generator interface {
|
||||
Generate(io.Writer, string, *chglog.Config) error
|
||||
Generate(*chglog.Logger, io.Writer, string, *chglog.Config) error
|
||||
}
|
||||
|
||||
type generatorImpl struct{}
|
||||
|
|
@ -19,6 +19,6 @@ func NewGenerator() Generator {
|
|||
}
|
||||
|
||||
// Generate ...
|
||||
func (*generatorImpl) Generate(w io.Writer, query string, config *chglog.Config) error {
|
||||
return chglog.NewGenerator(config).Generate(w, query)
|
||||
func (*generatorImpl) Generate(logger *chglog.Logger, w io.Writer, query string, config *chglog.Config) error {
|
||||
return chglog.NewGenerator(logger, config).Generate(w, query)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,6 +10,6 @@ type mockGeneratorImpl struct {
|
|||
ReturnGenerate func(io.Writer, string, *chglog.Config) error
|
||||
}
|
||||
|
||||
func (m *mockGeneratorImpl) Generate(w io.Writer, query string, config *chglog.Config) error {
|
||||
func (m *mockGeneratorImpl) Generate(logger *chglog.Logger, w io.Writer, query string, config *chglog.Config) error {
|
||||
return m.ReturnGenerate(w, query, config)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package main
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/git-chglog/git-chglog"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/fatih/color"
|
||||
|
|
@ -13,7 +14,7 @@ type Initializer struct {
|
|||
ctx *InitContext
|
||||
client gitcmd.Client
|
||||
fs FileSystem
|
||||
logger *Logger
|
||||
logger *chglog.Logger
|
||||
questioner Questioner
|
||||
configBuilder ConfigBuilder
|
||||
templateBuilderFactory TemplateBuilderFactory
|
||||
|
|
@ -29,7 +30,7 @@ func NewInitializer(
|
|||
return &Initializer{
|
||||
ctx: ctx,
|
||||
fs: fs,
|
||||
logger: NewLogger(ctx.Stdout, ctx.Stderr, false, false),
|
||||
logger: chglog.NewLogger(ctx.Stdout, ctx.Stderr, false, false),
|
||||
questioner: questioner,
|
||||
configBuilder: configBuilder,
|
||||
templateBuilderFactory: tplBuilderFactory,
|
||||
|
|
|
|||
|
|
@ -83,6 +83,18 @@ func CreateApp(actionFunc cli.ActionFunc) *cli.App {
|
|||
Value: ".chglog/config.yml",
|
||||
},
|
||||
|
||||
// template
|
||||
&cli.StringFlag{
|
||||
Name: "template, t",
|
||||
Usage: "specifies a template file to pick up. If not specified, use the one in config",
|
||||
},
|
||||
|
||||
// repository url
|
||||
&cli.StringFlag{
|
||||
Name: "repository-url",
|
||||
Usage: "specifies git repo URL. If not specified, use 'repository_url' in config",
|
||||
},
|
||||
|
||||
// output
|
||||
&cli.StringFlag{
|
||||
Name: "output, o",
|
||||
|
|
@ -102,15 +114,15 @@ func CreateApp(actionFunc cli.ActionFunc) *cli.App {
|
|||
|
||||
// no-color
|
||||
&cli.BoolFlag{
|
||||
Name: "no-color",
|
||||
Usage: "disable color output",
|
||||
Name: "no-color",
|
||||
Usage: "disable color output",
|
||||
EnvVars: []string{"NO_COLOR"},
|
||||
},
|
||||
|
||||
// no-emoji
|
||||
&cli.BoolFlag{
|
||||
Name: "no-emoji",
|
||||
Usage: "disable emoji output",
|
||||
Name: "no-emoji",
|
||||
Usage: "disable emoji output",
|
||||
EnvVars: []string{"NO_EMOJI"},
|
||||
},
|
||||
|
||||
|
|
@ -122,10 +134,31 @@ func CreateApp(actionFunc cli.ActionFunc) *cli.App {
|
|||
|
||||
// tag-filter-pattern
|
||||
&cli.StringFlag{
|
||||
Name: "tag-filter-pattern, p",
|
||||
Name: "tag-filter-pattern",
|
||||
Usage: "Regular expression of tag filter. Is specified, only matched tags will be picked",
|
||||
},
|
||||
|
||||
// jira-url
|
||||
&cli.StringFlag{
|
||||
Name: "jira-url",
|
||||
Usage: "Jira URL",
|
||||
EnvVars: []string{"JIRA_URL"},
|
||||
},
|
||||
|
||||
// jira-username
|
||||
&cli.StringFlag{
|
||||
Name: "jira-username",
|
||||
Usage: "Jira username",
|
||||
EnvVars: []string{"JIRA_USERNAME"},
|
||||
},
|
||||
|
||||
// jira-token
|
||||
&cli.StringFlag{
|
||||
Name: "jira-token",
|
||||
Usage: "Jira token",
|
||||
EnvVars: []string{"JIRA_TOKEN"},
|
||||
},
|
||||
|
||||
// help & version
|
||||
cli.HelpFlag,
|
||||
cli.VersionFlag,
|
||||
|
|
@ -180,6 +213,9 @@ func AppAction(c *cli.Context) error {
|
|||
Query: c.Args().First(),
|
||||
NextTag: c.String("next-tag"),
|
||||
TagFilterPattern: c.String("tag-filter-pattern"),
|
||||
JiraUsername: c.String("jira-username"),
|
||||
JiraToken: c.String("jira-token"),
|
||||
JiraUrl: c.String("jira-url"),
|
||||
},
|
||||
fs,
|
||||
NewConfigLoader(),
|
||||
|
|
|
|||
|
|
@ -1,12 +1,13 @@
|
|||
package chglog
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
gitcmd "github.com/tsuyoshiwada/go-gitcmd"
|
||||
"github.com/tsuyoshiwada/go-gitcmd"
|
||||
)
|
||||
|
||||
var (
|
||||
|
|
@ -47,18 +48,21 @@ func joinAndQuoteMeta(list []string, sep string) string {
|
|||
}
|
||||
|
||||
type commitParser struct {
|
||||
client gitcmd.Client
|
||||
config *Config
|
||||
reHeader *regexp.Regexp
|
||||
reMerge *regexp.Regexp
|
||||
reRevert *regexp.Regexp
|
||||
reRef *regexp.Regexp
|
||||
reIssue *regexp.Regexp
|
||||
reNotes *regexp.Regexp
|
||||
reMention *regexp.Regexp
|
||||
logger *Logger
|
||||
client gitcmd.Client
|
||||
jiraClient JiraClient
|
||||
config *Config
|
||||
reHeader *regexp.Regexp
|
||||
reMerge *regexp.Regexp
|
||||
reRevert *regexp.Regexp
|
||||
reRef *regexp.Regexp
|
||||
reIssue *regexp.Regexp
|
||||
reNotes *regexp.Regexp
|
||||
reMention *regexp.Regexp
|
||||
reJiraIssueDescription *regexp.Regexp
|
||||
}
|
||||
|
||||
func newCommitParser(client gitcmd.Client, config *Config) *commitParser {
|
||||
func newCommitParser(logger *Logger, client gitcmd.Client, jiraClient JiraClient, config *Config) *commitParser {
|
||||
opts := config.Options
|
||||
|
||||
joinedRefActions := joinAndQuoteMeta(opts.RefActions, "|")
|
||||
|
|
@ -66,15 +70,18 @@ func newCommitParser(client gitcmd.Client, config *Config) *commitParser {
|
|||
joinedNoteKeywords := joinAndQuoteMeta(opts.NoteKeywords, "|")
|
||||
|
||||
return &commitParser{
|
||||
client: client,
|
||||
config: config,
|
||||
reHeader: regexp.MustCompile(opts.HeaderPattern),
|
||||
reMerge: regexp.MustCompile(opts.MergePattern),
|
||||
reRevert: regexp.MustCompile(opts.RevertPattern),
|
||||
reRef: regexp.MustCompile("(?i)(" + joinedRefActions + ")\\s?([\\w/\\.\\-]+)?(?:" + joinedIssuePrefix + ")(\\d+)"),
|
||||
reIssue: regexp.MustCompile("(?:" + joinedIssuePrefix + ")(\\d+)"),
|
||||
reNotes: regexp.MustCompile("^(?i)\\s*(" + joinedNoteKeywords + ")[:\\s]+(.*)"),
|
||||
reMention: regexp.MustCompile("@([\\w-]+)"),
|
||||
logger: logger,
|
||||
client: client,
|
||||
jiraClient: jiraClient,
|
||||
config: config,
|
||||
reHeader: regexp.MustCompile(opts.HeaderPattern),
|
||||
reMerge: regexp.MustCompile(opts.MergePattern),
|
||||
reRevert: regexp.MustCompile(opts.RevertPattern),
|
||||
reRef: regexp.MustCompile("(?i)(" + joinedRefActions + ")\\s?([\\w/\\.\\-]+)?(?:" + joinedIssuePrefix + ")(\\d+)"),
|
||||
reIssue: regexp.MustCompile("(?:" + joinedIssuePrefix + ")(\\d+)"),
|
||||
reNotes: regexp.MustCompile("^(?i)\\s*(" + joinedNoteKeywords + ")[:\\s]+(.*)"),
|
||||
reMention: regexp.MustCompile("@([\\w-]+)"),
|
||||
reJiraIssueDescription: regexp.MustCompile(opts.JiraIssueDescriptionPattern),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -206,6 +213,11 @@ func (p *commitParser) processHeader(commit *Commit, input string) {
|
|||
// refs & mentions
|
||||
commit.Refs = p.parseRefs(input)
|
||||
commit.Mentions = p.parseMentions(input)
|
||||
|
||||
// Jira
|
||||
if commit.JiraIssueId != "" {
|
||||
p.processJiraIssue(commit, commit.JiraIssueId)
|
||||
}
|
||||
}
|
||||
|
||||
func (p *commitParser) processBody(commit *Commit, input string) {
|
||||
|
|
@ -344,6 +356,28 @@ func (p *commitParser) uniqMentions(mentions []string) []string {
|
|||
return arr
|
||||
}
|
||||
|
||||
func (p *commitParser) processJiraIssue(commit *Commit, issueId string) {
|
||||
issue, err := p.jiraClient.GetJiraIssue(commit.JiraIssueId)
|
||||
if err != nil {
|
||||
p.logger.Error(fmt.Sprintf("Failed to parse Jira story %s: %s\n", issueId, err))
|
||||
return
|
||||
}
|
||||
commit.Type = p.config.Options.JiraTypeMaps[issue.Fields.Type.Name]
|
||||
commit.JiraIssue = &JiraIssue{
|
||||
Type: issue.Fields.Type.Name,
|
||||
Summary: issue.Fields.Summary,
|
||||
Description: issue.Fields.Description,
|
||||
Labels: issue.Fields.Labels,
|
||||
}
|
||||
|
||||
if p.config.Options.JiraIssueDescriptionPattern != "" {
|
||||
res := p.reJiraIssueDescription.FindStringSubmatch(commit.JiraIssue.Description)
|
||||
if len(res) > 1 {
|
||||
commit.JiraIssue.Description = res[1]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
fenceTypes = []string{
|
||||
"```",
|
||||
|
|
|
|||
|
|
@ -4,10 +4,12 @@ import (
|
|||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
agjira "github.com/andygrunwald/go-jira"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
|
|
@ -27,51 +29,52 @@ func TestCommitParserParse(t *testing.T) {
|
|||
},
|
||||
}
|
||||
|
||||
parser := newCommitParser(mock, &Config{
|
||||
Options: &Options{
|
||||
CommitFilters: map[string][]string{
|
||||
"Type": []string{
|
||||
"feat",
|
||||
parser := newCommitParser(NewLogger(os.Stdout, os.Stderr, false, true),
|
||||
mock, nil, &Config{
|
||||
Options: &Options{
|
||||
CommitFilters: map[string][]string{
|
||||
"Type": []string{
|
||||
"feat",
|
||||
"fix",
|
||||
"perf",
|
||||
"refactor",
|
||||
},
|
||||
},
|
||||
HeaderPattern: "^(\\w*)(?:\\(([\\w\\$\\.\\-\\*\\s]*)\\))?\\:\\s(.*)$",
|
||||
HeaderPatternMaps: []string{
|
||||
"Type",
|
||||
"Scope",
|
||||
"Subject",
|
||||
},
|
||||
IssuePrefix: []string{
|
||||
"#",
|
||||
"gh-",
|
||||
},
|
||||
RefActions: []string{
|
||||
"close",
|
||||
"closes",
|
||||
"closed",
|
||||
"fix",
|
||||
"perf",
|
||||
"refactor",
|
||||
"fixes",
|
||||
"fixed",
|
||||
"resolve",
|
||||
"resolves",
|
||||
"resolved",
|
||||
},
|
||||
MergePattern: "^Merge pull request #(\\d+) from (.*)$",
|
||||
MergePatternMaps: []string{
|
||||
"Ref",
|
||||
"Source",
|
||||
},
|
||||
RevertPattern: "^Revert \"([\\s\\S]*)\"$",
|
||||
RevertPatternMaps: []string{
|
||||
"Header",
|
||||
},
|
||||
NoteKeywords: []string{
|
||||
"BREAKING CHANGE",
|
||||
},
|
||||
},
|
||||
HeaderPattern: "^(\\w*)(?:\\(([\\w\\$\\.\\-\\*\\s]*)\\))?\\:\\s(.*)$",
|
||||
HeaderPatternMaps: []string{
|
||||
"Type",
|
||||
"Scope",
|
||||
"Subject",
|
||||
},
|
||||
IssuePrefix: []string{
|
||||
"#",
|
||||
"gh-",
|
||||
},
|
||||
RefActions: []string{
|
||||
"close",
|
||||
"closes",
|
||||
"closed",
|
||||
"fix",
|
||||
"fixes",
|
||||
"fixed",
|
||||
"resolve",
|
||||
"resolves",
|
||||
"resolved",
|
||||
},
|
||||
MergePattern: "^Merge pull request #(\\d+) from (.*)$",
|
||||
MergePatternMaps: []string{
|
||||
"Ref",
|
||||
"Source",
|
||||
},
|
||||
RevertPattern: "^Revert \"([\\s\\S]*)\"$",
|
||||
RevertPatternMaps: []string{
|
||||
"Header",
|
||||
},
|
||||
NoteKeywords: []string{
|
||||
"BREAKING CHANGE",
|
||||
},
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
commits, err := parser.Parse("HEAD")
|
||||
assert.Nil(err)
|
||||
|
|
@ -308,3 +311,102 @@ Closes username/repository#456`, "```", "```"),
|
|||
},
|
||||
}, commits)
|
||||
}
|
||||
|
||||
type mockJiraClient struct {
|
||||
}
|
||||
|
||||
func (jira mockJiraClient) GetJiraIssue(id string) (*agjira.Issue, error) {
|
||||
return &agjira.Issue{
|
||||
ID: id,
|
||||
Fields: &agjira.IssueFields{
|
||||
Expand: "",
|
||||
Type: agjira.IssueType{Name: "Story"},
|
||||
Project: agjira.Project{},
|
||||
Resolution: nil,
|
||||
Priority: nil,
|
||||
Resolutiondate: agjira.Time{},
|
||||
Created: agjira.Time{},
|
||||
Duedate: agjira.Date{},
|
||||
Watches: nil,
|
||||
Assignee: nil,
|
||||
Updated: agjira.Time{},
|
||||
Description: fmt.Sprintf("description of %s", id),
|
||||
Summary: fmt.Sprintf("summary of %s", id),
|
||||
Creator: nil,
|
||||
Reporter: nil,
|
||||
Components: nil,
|
||||
Status: nil,
|
||||
Progress: nil,
|
||||
AggregateProgress: nil,
|
||||
TimeTracking: nil,
|
||||
TimeSpent: 0,
|
||||
TimeEstimate: 0,
|
||||
TimeOriginalEstimate: 0,
|
||||
Worklog: nil,
|
||||
IssueLinks: nil,
|
||||
Comments: nil,
|
||||
FixVersions: nil,
|
||||
AffectsVersions: nil,
|
||||
Labels: []string{"GA"},
|
||||
Subtasks: nil,
|
||||
Attachments: nil,
|
||||
Epic: nil,
|
||||
Sprint: nil,
|
||||
Parent: nil,
|
||||
AggregateTimeOriginalEstimate: 0,
|
||||
AggregateTimeSpent: 0,
|
||||
AggregateTimeEstimate: 0,
|
||||
Unknowns: nil,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func TestCommitParserParseWithJira(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
assert.True(true)
|
||||
|
||||
mock := &mockClient{
|
||||
ReturnExec: func(subcmd string, args ...string) (string, error) {
|
||||
if subcmd != "log" {
|
||||
return "", errors.New("")
|
||||
}
|
||||
|
||||
bytes, _ := ioutil.ReadFile(filepath.Join("testdata", "gitlog_jira.txt"))
|
||||
|
||||
return string(bytes), nil
|
||||
},
|
||||
}
|
||||
|
||||
parser := newCommitParser(NewLogger(os.Stdout, os.Stderr, false, true),
|
||||
mock, mockJiraClient{}, &Config{
|
||||
Options: &Options{
|
||||
CommitFilters: map[string][]string{
|
||||
"Type": []string{
|
||||
"feat",
|
||||
"fix",
|
||||
"perf",
|
||||
"refactor",
|
||||
},
|
||||
},
|
||||
HeaderPattern: "^(?:(\\w*)|(?:\\[(.*)\\])?)\\:\\s(.*)$",
|
||||
HeaderPatternMaps: []string{
|
||||
"Type",
|
||||
"JiraIssueId",
|
||||
"Subject",
|
||||
},
|
||||
JiraTypeMaps: map[string]string{
|
||||
"Story": "feat",
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
commits, err := parser.Parse("HEAD")
|
||||
assert.Nil(err)
|
||||
commit := commits[0]
|
||||
assert.Equal(commit.JiraIssueId, "JIRA-1111")
|
||||
assert.Equal(commit.JiraIssue.Type, "Story")
|
||||
assert.Equal(commit.JiraIssue.Summary, "summary of JIRA-1111")
|
||||
assert.Equal(commit.JiraIssue.Description, "description of JIRA-1111")
|
||||
assert.Equal(commit.JiraIssue.Labels, []string{"GA"})
|
||||
assert.Equal(commit.Type, "feat")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,56 +4,58 @@ import (
|
|||
"bytes"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
)
|
||||
|
||||
func Example() {
|
||||
gen := NewGenerator(&Config{
|
||||
Bin: "git",
|
||||
WorkingDir: ".",
|
||||
Template: "CHANGELOG.tpl.md",
|
||||
Info: &Info{
|
||||
Title: "CHANGELOG",
|
||||
RepositoryURL: "https://github.com/git-chglog/git-chglog",
|
||||
},
|
||||
Options: &Options{
|
||||
CommitFilters: map[string][]string{
|
||||
"Type": []string{
|
||||
"feat",
|
||||
"fix",
|
||||
gen := NewGenerator(NewLogger(os.Stdout, os.Stderr, false, true),
|
||||
&Config{
|
||||
Bin: "git",
|
||||
WorkingDir: ".",
|
||||
Template: "CHANGELOG.tpl.md",
|
||||
Info: &Info{
|
||||
Title: "CHANGELOG",
|
||||
RepositoryURL: "https://github.com/git-chglog/git-chglog",
|
||||
},
|
||||
Options: &Options{
|
||||
CommitFilters: map[string][]string{
|
||||
"Type": []string{
|
||||
"feat",
|
||||
"fix",
|
||||
},
|
||||
},
|
||||
CommitSortBy: "Scope",
|
||||
CommitGroupBy: "Type",
|
||||
CommitGroupSortBy: "Title",
|
||||
CommitGroupTitleMaps: map[string]string{
|
||||
"feat": "Features",
|
||||
"fix": "Bug Fixes",
|
||||
},
|
||||
HeaderPattern: "^(\\w*)(?:\\(([\\w\\$\\.\\-\\*\\s]*)\\))?\\:\\s(.*)$",
|
||||
HeaderPatternMaps: []string{
|
||||
"Type",
|
||||
"Scope",
|
||||
"Subject",
|
||||
},
|
||||
IssuePrefix: []string{
|
||||
"#",
|
||||
"gh-",
|
||||
},
|
||||
RefActions: []string{},
|
||||
MergePattern: "^Merge pull request #(\\d+) from (.*)$",
|
||||
MergePatternMaps: []string{
|
||||
"Ref",
|
||||
"Source",
|
||||
},
|
||||
RevertPattern: "^Revert \"([\\s\\S]*)\"$",
|
||||
RevertPatternMaps: []string{
|
||||
"Header",
|
||||
},
|
||||
NoteKeywords: []string{
|
||||
"BREAKING CHANGE",
|
||||
},
|
||||
},
|
||||
CommitSortBy: "Scope",
|
||||
CommitGroupBy: "Type",
|
||||
CommitGroupSortBy: "Title",
|
||||
CommitGroupTitleMaps: map[string]string{
|
||||
"feat": "Features",
|
||||
"fix": "Bug Fixes",
|
||||
},
|
||||
HeaderPattern: "^(\\w*)(?:\\(([\\w\\$\\.\\-\\*\\s]*)\\))?\\:\\s(.*)$",
|
||||
HeaderPatternMaps: []string{
|
||||
"Type",
|
||||
"Scope",
|
||||
"Subject",
|
||||
},
|
||||
IssuePrefix: []string{
|
||||
"#",
|
||||
"gh-",
|
||||
},
|
||||
RefActions: []string{},
|
||||
MergePattern: "^Merge pull request #(\\d+) from (.*)$",
|
||||
MergePatternMaps: []string{
|
||||
"Ref",
|
||||
"Source",
|
||||
},
|
||||
RevertPattern: "^Revert \"([\\s\\S]*)\"$",
|
||||
RevertPatternMaps: []string{
|
||||
"Header",
|
||||
},
|
||||
NoteKeywords: []string{
|
||||
"BREAKING CHANGE",
|
||||
},
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
buf := &bytes.Buffer{}
|
||||
err := gen.Generate(buf, "")
|
||||
|
|
|
|||
36
fields.go
36
fields.go
|
|
@ -52,21 +52,31 @@ type NoteGroup struct {
|
|||
Notes []*Note
|
||||
}
|
||||
|
||||
// JiraIssue
|
||||
type JiraIssue struct {
|
||||
Type string
|
||||
Summary string
|
||||
Description string
|
||||
Labels []string
|
||||
}
|
||||
|
||||
// Commit data
|
||||
type Commit struct {
|
||||
Hash *Hash
|
||||
Author *Author
|
||||
Committer *Committer
|
||||
Merge *Merge // If it is not a merge commit, `nil` is assigned
|
||||
Revert *Revert // If it is not a revert commit, `nil` is assigned
|
||||
Refs []*Ref
|
||||
Notes []*Note
|
||||
Mentions []string // Name of the user included in the commit header or body
|
||||
Header string // (e.g. `feat(core): Add new feature`)
|
||||
Type string // (e.g. `feat`)
|
||||
Scope string // (e.g. `core`)
|
||||
Subject string // (e.g. `Add new feature`)
|
||||
Body string
|
||||
Hash *Hash
|
||||
Author *Author
|
||||
Committer *Committer
|
||||
Merge *Merge // If it is not a merge commit, `nil` is assigned
|
||||
Revert *Revert // If it is not a revert commit, `nil` is assigned
|
||||
Refs []*Ref
|
||||
Notes []*Note
|
||||
Mentions []string // Name of the user included in the commit header or body
|
||||
JiraIssue *JiraIssue // If no issue id found in header, `nil` is assigned
|
||||
Header string // (e.g. `feat(core)[RNWY-310]: Add new feature`)
|
||||
Type string // (e.g. `feat`)
|
||||
Scope string // (e.g. `core`)
|
||||
Subject string // (e.g. `Add new feature`)
|
||||
JiraIssueId string // (e.g. `RNWY-310`)
|
||||
Body string
|
||||
}
|
||||
|
||||
// CommitGroup is a collection of commits grouped according to the `CommitGroupBy` option
|
||||
|
|
|
|||
1
go.mod
1
go.mod
|
|
@ -4,6 +4,7 @@ go 1.15
|
|||
|
||||
require (
|
||||
github.com/AlecAivazis/survey/v2 v2.2.8
|
||||
github.com/andygrunwald/go-jira v1.13.0
|
||||
github.com/fatih/color v1.10.0
|
||||
github.com/imdario/mergo v0.3.11
|
||||
github.com/kyokomi/emoji/v2 v2.2.8
|
||||
|
|
|
|||
15
go.sum
15
go.sum
|
|
@ -3,13 +3,23 @@ github.com/AlecAivazis/survey/v2 v2.2.8/go.mod h1:9DYvHgXtiXm6nCn+jXnOXLKbH+Yo9u
|
|||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/Netflix/go-expect v0.0.0-20180615182759-c93bf25de8e8 h1:xzYJEypr/85nBpB11F9br+3HUrpgb+fcm5iADzXXYEw=
|
||||
github.com/Netflix/go-expect v0.0.0-20180615182759-c93bf25de8e8/go.mod h1:oX5x61PbNXchhh0oikYAH+4Pcfw5LKv21+Jnpr6r6Pc=
|
||||
github.com/andygrunwald/go-jira v1.13.0 h1:vvIImGgX32bHfoiyUwkNo+/YrPnRczNarvhLOncP6dE=
|
||||
github.com/andygrunwald/go-jira v1.13.0/go.mod h1:jYi4kFDbRPZTJdJOVJO4mpMMIwdB+rcZwSO58DzPd2I=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSYgptZMwQh2aRr3LuazLJIa+Pg3Kc1ylSYVY=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
|
||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
|
||||
github.com/fatih/color v1.10.0 h1:s36xzo75JdqLaaWoiEHk767eHiwo0598uUxyfiPkDsg=
|
||||
github.com/fatih/color v1.10.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM=
|
||||
github.com/fatih/structs v1.0.0 h1:BrX964Rv5uQ3wwS+KRUAJCBBw5PQmgJfJ6v4yly5QwU=
|
||||
github.com/fatih/structs v1.0.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
|
||||
github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY=
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-querystring v0.0.0-20170111101155-53e6ce116135 h1:zLTLjkaOFEFIOxY5BWLFLwh+cL8vOBW4XJ2aqLE/Tf0=
|
||||
github.com/google/go-querystring v0.0.0-20170111101155-53e6ce116135/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
|
||||
github.com/hinshun/vt10x v0.0.0-20180616224451-1954e6464174 h1:WlZsjVhE8Af9IcZDGgJGQpNflI3+MJSBhsgT5PCtzBQ=
|
||||
github.com/hinshun/vt10x v0.0.0-20180616224451-1954e6464174/go.mod h1:DqJ97dSdRW1W22yXSB90986pcOyQ7r45iio1KN2ez1A=
|
||||
github.com/imdario/mergo v0.3.11 h1:3tnifQM4i+fbajXKBHXWEH+KvNHqojZ778UH75j3bGA=
|
||||
|
|
@ -28,6 +38,8 @@ github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHX
|
|||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b h1:j7+1HpAFS1zy5+Q4qx1fWh90gTKwiN4QCGoY9TWyyO4=
|
||||
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
|
||||
github.com/pkg/errors v0.8.0 h1:WdK/asTD0HN+q6hsWO3/vpuAkAr+tw6aNJNDFFf0+qw=
|
||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q=
|
||||
|
|
@ -38,11 +50,14 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+
|
|||
github.com/stretchr/testify v1.2.1/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/trivago/tgo v1.0.1 h1:bxatjJIXNIpV18bucU4Uk/LaoxvxuOlp/oowRHyncLQ=
|
||||
github.com/trivago/tgo v1.0.1/go.mod h1:w4dpD+3tzNIIiIfkWWa85w5/B77tlvdZckQ+6PkFnhc=
|
||||
github.com/tsuyoshiwada/go-gitcmd v0.0.0-20180205145712-5f1f5f9475df h1:Y2l28Jr3vOEeYtxfVbMtVfOdAwuUqWaP9fvNKiBVeXY=
|
||||
github.com/tsuyoshiwada/go-gitcmd v0.0.0-20180205145712-5f1f5f9475df/go.mod h1:pnyouUty/nBr/zm3GYwTIt+qFTLWbdjeLjZmJdzJOu8=
|
||||
github.com/urfave/cli/v2 v2.3.0 h1:qph92Y649prgesehzOrQjdWyxFOp/QVM+6imKHad91M=
|
||||
github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190530122614-20be4c3c3ed5 h1:8dUaAV7K4uHsF56JQWkprecIQKdPHtR9jCHF5nB8uzc=
|
||||
golang.org/x/crypto v0.0.0-20190530122614-20be4c3c3ed5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
|
|
|
|||
36
jira.go
Normal file
36
jira.go
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
package chglog
|
||||
|
||||
import (
|
||||
agjira "github.com/andygrunwald/go-jira"
|
||||
)
|
||||
|
||||
type JiraClient interface {
|
||||
GetJiraIssue(id string) (*agjira.Issue, error)
|
||||
}
|
||||
|
||||
type jiraClient struct {
|
||||
username string
|
||||
token string
|
||||
url string
|
||||
}
|
||||
|
||||
func NewJiraClient(config *Config) JiraClient {
|
||||
return jiraClient{
|
||||
username: config.Options.JiraUsername,
|
||||
token: config.Options.JiraToken,
|
||||
url: config.Options.JiraUrl,
|
||||
}
|
||||
}
|
||||
|
||||
func (jira jiraClient) GetJiraIssue(id string) (*agjira.Issue, error) {
|
||||
tp := agjira.BasicAuthTransport{
|
||||
Username: jira.username,
|
||||
Password: jira.token,
|
||||
}
|
||||
client, err := agjira.NewClient(tp.Client(), jira.url)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
issue, _, err := client.Issue.Get(id, nil)
|
||||
return issue, err
|
||||
}
|
||||
42
jira_test.go
Normal file
42
jira_test.go
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
package chglog
|
||||
|
||||
import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestJira(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
config := &Config {
|
||||
Options: &Options{
|
||||
Processor: nil,
|
||||
NextTag: "",
|
||||
TagFilterPattern: "",
|
||||
CommitFilters: nil,
|
||||
CommitSortBy: "",
|
||||
CommitGroupBy: "",
|
||||
CommitGroupSortBy: "",
|
||||
CommitGroupTitleMaps: nil,
|
||||
HeaderPattern: "",
|
||||
HeaderPatternMaps: nil,
|
||||
IssuePrefix: nil,
|
||||
RefActions: nil,
|
||||
MergePattern: "",
|
||||
MergePatternMaps: nil,
|
||||
RevertPattern: "",
|
||||
RevertPatternMaps: nil,
|
||||
NoteKeywords: nil,
|
||||
JiraUsername: "uuu",
|
||||
JiraToken: "ppp",
|
||||
JiraUrl: "http://jira.com",
|
||||
JiraTypeMaps: nil,
|
||||
JiraIssueDescriptionPattern: "",
|
||||
},
|
||||
}
|
||||
|
||||
jira := NewJiraClient(config)
|
||||
issue, err := jira.GetJiraIssue("fake")
|
||||
assert.Nil(issue)
|
||||
assert.Error(err)
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package main
|
||||
package chglog
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package main
|
||||
package chglog
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
1
testdata/gitlog_jira.txt
vendored
Normal file
1
testdata/gitlog_jira.txt
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
@@__CHGLOG__@@HASH:65cf1add9735dcc4810dda3312b0792236c97c4e 65cf1add@@__CHGLOG_DELIMITER__@@AUTHOR:tsuyoshi wada mail@example.com 1514808000@@__CHGLOG_DELIMITER__@@COMMITTER:tsuyoshi wada mail@example.com 1514808000@@__CHGLOG_DELIMITER__@@SUBJECT:[JIRA-1111]: Add new feature #123@@__CHGLOG_DELIMITER__@@BODY: This is body message.
|
||||
Loading…
Add table
Add a link
Reference in a new issue