feat: add Jira integration (#52)

This commit is contained in:
Chao Li 2021-03-11 15:40:32 +08:00 committed by GitHub
parent 8713d96856
commit a1c84d7a0d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
22 changed files with 742 additions and 331 deletions

View file

@ -7,7 +7,7 @@ clean:
.PHONY: build .PHONY: build
build: build:
go build -i -o git-chglog ./cmd/git-chglog go build -o git-chglog ./cmd/git-chglog
.PHONY: test .PHONY: test
test: test:

View file

@ -44,6 +44,7 @@
- [`options.notes`](#optionsnotes) - [`options.notes`](#optionsnotes)
- [Templates](#templates) - [Templates](#templates)
- [Supported Styles](#supported-styles) - [Supported Styles](#supported-styles)
- [Jira Integration](#jira-integration)
- [FAQ](#faq) - [FAQ](#faq)
- [TODO](#todo) - [TODO](#todo)
- [Thanks](#thanks) - [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. > :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 ## FAQ
<details> <details>

View file

@ -16,25 +16,30 @@ import (
// Options is an option used to process commits // Options is an option used to process commits
type Options struct { type Options struct {
Processor Processor Processor Processor
NextTag string // Treat unreleased commits as specified tags (EXPERIMENTAL) NextTag string // Treat unreleased commits as specified tags (EXPERIMENTAL)
TagFilterPattern string // Filter tag by regexp TagFilterPattern string // Filter tag by regexp
NoCaseSensitive bool // Filter commits in a case insensitive way 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 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`) 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`) 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`) 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` 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 CommitGroupTitleMaps map[string]string // Map for `CommitGroup` title conversion
HeaderPattern string // A regular expression to use for parsing the commit header 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` HeaderPatternMaps []string // A rule for mapping the result of `HeaderPattern` to the property of `Commit`
IssuePrefix []string // Prefix used for issues (e.g. `#`, `gh-`) IssuePrefix []string // Prefix used for issues (e.g. `#`, `gh-`)
RefActions []string // Word list of `Ref.Action` RefActions []string // Word list of `Ref.Action`
MergePattern string // A regular expression to use for parsing the merge commit MergePattern string // A regular expression to use for parsing the merge commit
MergePatternMaps []string // Similar to `HeaderPatternMaps` MergePatternMaps []string // Similar to `HeaderPatternMaps`
RevertPattern string // A regular expression to use for parsing the revert commit RevertPattern string // A regular expression to use for parsing the revert commit
RevertPatternMaps []string // Similar to `HeaderPatternMaps` RevertPatternMaps []string // Similar to `HeaderPatternMaps`
NoteKeywords []string // Keyword list to find `Note`. A semicolon is a separator, like `<keyword>:` (e.g. `BREAKING CHANGE`) 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 // Info is metadata related to CHANGELOG
@ -97,11 +102,13 @@ type Generator struct {
} }
// NewGenerator receives `Config` and create an new `Generator` // 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{ client := gitcmd.New(&gitcmd.Config{
Bin: config.Bin, Bin: config.Bin,
}) })
jiraClient := NewJiraClient(config)
if config.Options.Processor != nil { if config.Options.Processor != nil {
config.Options.Processor.Bootstrap(config) config.Options.Processor.Bootstrap(config)
} }
@ -113,7 +120,7 @@ func NewGenerator(config *Config) *Generator {
config: config, config: config,
tagReader: newTagReader(client, config.Options.TagFilterPattern), tagReader: newTagReader(client, config.Options.TagFilterPattern),
tagSelector: newTagSelector(), tagSelector: newTagSelector(),
commitParser: newCommitParser(client, config), commitParser: newCommitParser(logger, client, jiraClient, config),
commitExtractor: newCommitExtractor(config.Options), commitExtractor: newCommitExtractor(config.Options),
} }
} }

View file

@ -76,15 +76,16 @@ func TestGeneratorNotFoundTags(t *testing.T) {
commit("2018-01-01 00:00:00", "feat(*): New feature", "") commit("2018-01-01 00:00:00", "feat(*): New feature", "")
}) })
gen := NewGenerator(&Config{ gen := NewGenerator(NewLogger(os.Stdout, os.Stderr, false, true),
Bin: "git", &Config{
WorkingDir: filepath.Join(testRepoRoot, testName), Bin: "git",
Template: filepath.Join(cwd, "testdata", testName+".md"), WorkingDir: filepath.Join(testRepoRoot, testName),
Info: &Info{ Template: filepath.Join(cwd, "testdata", testName+".md"),
RepositoryURL: "https://github.com/git-chglog/git-chglog", Info: &Info{
}, RepositoryURL: "https://github.com/git-chglog/git-chglog",
Options: &Options{}, },
}) Options: &Options{},
})
buf := &bytes.Buffer{} buf := &bytes.Buffer{}
err := gen.Generate(buf, "") err := gen.Generate(buf, "")
@ -102,15 +103,16 @@ func TestGeneratorNotFoundCommits(t *testing.T) {
tag("1.0.0") tag("1.0.0")
}) })
gen := NewGenerator(&Config{ gen := NewGenerator(NewLogger(os.Stdout, os.Stderr, false, true),
Bin: "git", &Config{
WorkingDir: filepath.Join(testRepoRoot, testName), Bin: "git",
Template: filepath.Join(cwd, "testdata", testName+".md"), WorkingDir: filepath.Join(testRepoRoot, testName),
Info: &Info{ Template: filepath.Join(cwd, "testdata", testName+".md"),
RepositoryURL: "https://github.com/git-chglog/git-chglog", Info: &Info{
}, RepositoryURL: "https://github.com/git-chglog/git-chglog",
Options: &Options{}, },
}) Options: &Options{},
})
buf := &bytes.Buffer{} buf := &bytes.Buffer{}
err := gen.Generate(buf, "foo") err := gen.Generate(buf, "foo")
@ -127,44 +129,45 @@ func TestGeneratorNotFoundCommitsOne(t *testing.T) {
tag("1.0.0") tag("1.0.0")
}) })
gen := NewGenerator(&Config{ gen := NewGenerator(NewLogger(os.Stdout, os.Stderr, false, true),
Bin: "git", &Config{
WorkingDir: filepath.Join(testRepoRoot, testName), Bin: "git",
Template: filepath.Join(cwd, "testdata", testName+".md"), WorkingDir: filepath.Join(testRepoRoot, testName),
Info: &Info{ Template: filepath.Join(cwd, "testdata", testName+".md"),
RepositoryURL: "https://github.com/git-chglog/git-chglog", 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",
}, },
IssuePrefix: []string{ Options: &Options{
"#", CommitFilters: map[string][]string{},
"gh-", 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{} buf := &bytes.Buffer{}
err := gen.Generate(buf, "foo") 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.", "") commit("2018-01-04 00:01:00", "fix(core): Fix commit\n\nThis is body message.", "")
}) })
gen := NewGenerator(&Config{ gen := NewGenerator(NewLogger(os.Stdout, os.Stderr, false, true),
Bin: "git", &Config{
WorkingDir: filepath.Join(testRepoRoot, testName), Bin: "git",
Template: filepath.Join(cwd, "testdata", testName+".md"), WorkingDir: filepath.Join(testRepoRoot, testName),
Info: &Info{ Template: filepath.Join(cwd, "testdata", testName+".md"),
Title: "CHANGELOG Example", Info: &Info{
RepositoryURL: "https://github.com/git-chglog/git-chglog", Title: "CHANGELOG Example",
}, RepositoryURL: "https://github.com/git-chglog/git-chglog",
Options: &Options{ },
CommitFilters: map[string][]string{ Options: &Options{
"Type": []string{ CommitFilters: map[string][]string{
"feat", "Type": []string{
"fix", "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{} buf := &bytes.Buffer{}
err := gen.Generate(buf, "") 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", "") commit("2018-03-01 00:00:00", "feat(core): version 3.0.0", "")
}) })
gen := NewGenerator(&Config{ gen := NewGenerator(NewLogger(os.Stdout, os.Stderr, false, true),
Bin: "git", &Config{
WorkingDir: filepath.Join(testRepoRoot, testName), Bin: "git",
Template: filepath.Join(cwd, "testdata", testName+".md"), WorkingDir: filepath.Join(testRepoRoot, testName),
Info: &Info{ Template: filepath.Join(cwd, "testdata", testName+".md"),
Title: "CHANGELOG Example", Info: &Info{
RepositoryURL: "https://github.com/git-chglog/git-chglog", Title: "CHANGELOG Example",
}, RepositoryURL: "https://github.com/git-chglog/git-chglog",
Options: &Options{ },
NextTag: "3.0.0", Options: &Options{
CommitFilters: map[string][]string{ NextTag: "3.0.0",
"Type": []string{ CommitFilters: map[string][]string{
"feat", "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{} buf := &bytes.Buffer{}
err := gen.Generate(buf, "") err := gen.Generate(buf, "")
@ -407,35 +412,36 @@ func TestGeneratorWithTagFiler(t *testing.T) {
tag("v1.0.0") tag("v1.0.0")
}) })
gen := NewGenerator(&Config{ gen := NewGenerator(NewLogger(os.Stdout, os.Stderr, false, true),
Bin: "git", &Config{
WorkingDir: filepath.Join(testRepoRoot, testName), Bin: "git",
Template: filepath.Join(cwd, "testdata", testName+".md"), WorkingDir: filepath.Join(testRepoRoot, testName),
Info: &Info{ Template: filepath.Join(cwd, "testdata", testName+".md"),
Title: "CHANGELOG Example", Info: &Info{
RepositoryURL: "https://github.com/git-chglog/git-chglog", Title: "CHANGELOG Example",
}, RepositoryURL: "https://github.com/git-chglog/git-chglog",
Options: &Options{ },
TagFilterPattern: "^v", Options: &Options{
CommitFilters: map[string][]string{ TagFilterPattern: "^v",
"Type": []string{ CommitFilters: map[string][]string{
"feat", "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{} buf := &bytes.Buffer{}
err := gen.Generate(buf, "") err := gen.Generate(buf, "")

View file

@ -14,7 +14,7 @@ import (
type CLI struct { type CLI struct {
ctx *CLIContext ctx *CLIContext
fs FileSystem fs FileSystem
logger *Logger logger *chglog.Logger
configLoader ConfigLoader configLoader ConfigLoader
generator Generator generator Generator
processorFactory *ProcessorFactory processorFactory *ProcessorFactory
@ -34,7 +34,7 @@ func NewCLI(
return &CLI{ return &CLI{
ctx: ctx, ctx: ctx,
fs: fs, fs: fs,
logger: NewLogger(ctx.Stdout, ctx.Stderr, silent, ctx.NoEmoji), logger: chglog.NewLogger(ctx.Stdout, ctx.Stderr, silent, ctx.NoEmoji),
configLoader: configLoader, configLoader: configLoader,
generator: generator, generator: generator,
processorFactory: NewProcessorFactory(), processorFactory: NewProcessorFactory(),
@ -69,7 +69,7 @@ func (c *CLI) Run() int {
return ExitCodeError 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 { if err != nil {
c.logger.Error(err.Error()) c.logger.Error(err.Error())
return ExitCodeError return ExitCodeError

View file

@ -49,6 +49,24 @@ type NoteOptions struct {
Keywords []string `yaml:"keywords"` 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 ... // Options ...
type Options struct { type Options struct {
TagFilterPattern string `yaml:"tag_filter_pattern"` TagFilterPattern string `yaml:"tag_filter_pattern"`
@ -60,6 +78,7 @@ type Options struct {
Merges PatternOptions `yaml:"merges"` Merges PatternOptions `yaml:"merges"`
Reverts PatternOptions `yaml:"reverts"` Reverts PatternOptions `yaml:"reverts"`
Notes NoteOptions `yaml:"notes"` Notes NoteOptions `yaml:"notes"`
Jira JiraOptions `yaml:"jira"`
} }
// Config ... // Config ...
@ -245,6 +264,13 @@ func (config *Config) normalizeStyleOfBitbucket() {
config.Options = opts config.Options = opts
} }
func orValue(str1 string, str2 string) string {
if str1 != "" {
return str1
}
return str2
}
// Convert ... // Convert ...
func (config *Config) Convert(ctx *CLIContext) *chglog.Config { func (config *Config) Convert(ctx *CLIContext) *chglog.Config {
info := config.Info info := config.Info
@ -257,30 +283,34 @@ func (config *Config) Convert(ctx *CLIContext) *chglog.Config {
return &chglog.Config{ return &chglog.Config{
Bin: config.Bin, Bin: config.Bin,
WorkingDir: ctx.WorkingDir, WorkingDir: ctx.WorkingDir,
Template: config.Template, Template: orValue(ctx.Template, config.Template),
Info: &chglog.Info{ Info: &chglog.Info{
Title: info.Title, Title: info.Title,
RepositoryURL: info.RepositoryURL, RepositoryURL: orValue(ctx.RepositoryUrl, info.RepositoryURL),
}, },
Options: &chglog.Options{ Options: &chglog.Options{
NextTag: ctx.NextTag, NextTag: ctx.NextTag,
TagFilterPattern: ctx.TagFilterPattern, TagFilterPattern: ctx.TagFilterPattern,
NoCaseSensitive: ctx.NoCaseSensitive, NoCaseSensitive: ctx.NoCaseSensitive,
CommitFilters: opts.Commits.Filters, CommitFilters: opts.Commits.Filters,
CommitSortBy: opts.Commits.SortBy, CommitSortBy: opts.Commits.SortBy,
CommitGroupBy: opts.CommitGroups.GroupBy, CommitGroupBy: opts.CommitGroups.GroupBy,
CommitGroupSortBy: opts.CommitGroups.SortBy, CommitGroupSortBy: opts.CommitGroups.SortBy,
CommitGroupTitleMaps: opts.CommitGroups.TitleMaps, CommitGroupTitleMaps: opts.CommitGroups.TitleMaps,
CommitGroupTitleOrder: opts.CommitGroups.TitleOrder, HeaderPattern: opts.Header.Pattern,
HeaderPattern: opts.Header.Pattern, HeaderPatternMaps: opts.Header.PatternMaps,
HeaderPatternMaps: opts.Header.PatternMaps, IssuePrefix: opts.Issues.Prefix,
IssuePrefix: opts.Issues.Prefix, RefActions: opts.Refs.Actions,
RefActions: opts.Refs.Actions, MergePattern: opts.Merges.Pattern,
MergePattern: opts.Merges.Pattern, MergePatternMaps: opts.Merges.PatternMaps,
MergePatternMaps: opts.Merges.PatternMaps, RevertPattern: opts.Reverts.Pattern,
RevertPattern: opts.Reverts.Pattern, RevertPatternMaps: opts.Reverts.PatternMaps,
RevertPatternMaps: opts.Reverts.PatternMaps, NoteKeywords: opts.Notes.Keywords,
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,
}, },
} }
} }

View file

@ -10,6 +10,8 @@ type CLIContext struct {
Stdout io.Writer Stdout io.Writer
Stderr io.Writer Stderr io.Writer
ConfigPath string ConfigPath string
Template string
RepositoryUrl string
OutputPath string OutputPath string
Silent bool Silent bool
NoColor bool NoColor bool
@ -18,6 +20,9 @@ type CLIContext struct {
Query string Query string
NextTag string NextTag string
TagFilterPattern string TagFilterPattern string
JiraUsername string
JiraToken string
JiraUrl string
} }
// InitContext ... // InitContext ...

View file

@ -8,7 +8,7 @@ import (
// Generator ... // Generator ...
type Generator interface { type Generator interface {
Generate(io.Writer, string, *chglog.Config) error Generate(*chglog.Logger, io.Writer, string, *chglog.Config) error
} }
type generatorImpl struct{} type generatorImpl struct{}
@ -19,6 +19,6 @@ func NewGenerator() Generator {
} }
// Generate ... // Generate ...
func (*generatorImpl) Generate(w io.Writer, query string, config *chglog.Config) error { func (*generatorImpl) Generate(logger *chglog.Logger, w io.Writer, query string, config *chglog.Config) error {
return chglog.NewGenerator(config).Generate(w, query) return chglog.NewGenerator(logger, config).Generate(w, query)
} }

View file

@ -10,6 +10,6 @@ type mockGeneratorImpl struct {
ReturnGenerate func(io.Writer, string, *chglog.Config) error 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) return m.ReturnGenerate(w, query, config)
} }

View file

@ -2,6 +2,7 @@ package main
import ( import (
"fmt" "fmt"
"github.com/git-chglog/git-chglog"
"path/filepath" "path/filepath"
"github.com/fatih/color" "github.com/fatih/color"
@ -13,7 +14,7 @@ type Initializer struct {
ctx *InitContext ctx *InitContext
client gitcmd.Client client gitcmd.Client
fs FileSystem fs FileSystem
logger *Logger logger *chglog.Logger
questioner Questioner questioner Questioner
configBuilder ConfigBuilder configBuilder ConfigBuilder
templateBuilderFactory TemplateBuilderFactory templateBuilderFactory TemplateBuilderFactory
@ -29,7 +30,7 @@ func NewInitializer(
return &Initializer{ return &Initializer{
ctx: ctx, ctx: ctx,
fs: fs, fs: fs,
logger: NewLogger(ctx.Stdout, ctx.Stderr, false, false), logger: chglog.NewLogger(ctx.Stdout, ctx.Stderr, false, false),
questioner: questioner, questioner: questioner,
configBuilder: configBuilder, configBuilder: configBuilder,
templateBuilderFactory: tplBuilderFactory, templateBuilderFactory: tplBuilderFactory,

View file

@ -83,6 +83,18 @@ func CreateApp(actionFunc cli.ActionFunc) *cli.App {
Value: ".chglog/config.yml", 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 // output
&cli.StringFlag{ &cli.StringFlag{
Name: "output, o", Name: "output, o",
@ -102,15 +114,15 @@ func CreateApp(actionFunc cli.ActionFunc) *cli.App {
// no-color // no-color
&cli.BoolFlag{ &cli.BoolFlag{
Name: "no-color", Name: "no-color",
Usage: "disable color output", Usage: "disable color output",
EnvVars: []string{"NO_COLOR"}, EnvVars: []string{"NO_COLOR"},
}, },
// no-emoji // no-emoji
&cli.BoolFlag{ &cli.BoolFlag{
Name: "no-emoji", Name: "no-emoji",
Usage: "disable emoji output", Usage: "disable emoji output",
EnvVars: []string{"NO_EMOJI"}, EnvVars: []string{"NO_EMOJI"},
}, },
@ -122,10 +134,31 @@ func CreateApp(actionFunc cli.ActionFunc) *cli.App {
// tag-filter-pattern // tag-filter-pattern
&cli.StringFlag{ &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", 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 // help & version
cli.HelpFlag, cli.HelpFlag,
cli.VersionFlag, cli.VersionFlag,
@ -180,6 +213,9 @@ func AppAction(c *cli.Context) error {
Query: c.Args().First(), Query: c.Args().First(),
NextTag: c.String("next-tag"), NextTag: c.String("next-tag"),
TagFilterPattern: c.String("tag-filter-pattern"), TagFilterPattern: c.String("tag-filter-pattern"),
JiraUsername: c.String("jira-username"),
JiraToken: c.String("jira-token"),
JiraUrl: c.String("jira-url"),
}, },
fs, fs,
NewConfigLoader(), NewConfigLoader(),

View file

@ -1,12 +1,13 @@
package chglog package chglog
import ( import (
"fmt"
"regexp" "regexp"
"strconv" "strconv"
"strings" "strings"
"time" "time"
gitcmd "github.com/tsuyoshiwada/go-gitcmd" "github.com/tsuyoshiwada/go-gitcmd"
) )
var ( var (
@ -47,18 +48,21 @@ func joinAndQuoteMeta(list []string, sep string) string {
} }
type commitParser struct { type commitParser struct {
client gitcmd.Client logger *Logger
config *Config client gitcmd.Client
reHeader *regexp.Regexp jiraClient JiraClient
reMerge *regexp.Regexp config *Config
reRevert *regexp.Regexp reHeader *regexp.Regexp
reRef *regexp.Regexp reMerge *regexp.Regexp
reIssue *regexp.Regexp reRevert *regexp.Regexp
reNotes *regexp.Regexp reRef *regexp.Regexp
reMention *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 opts := config.Options
joinedRefActions := joinAndQuoteMeta(opts.RefActions, "|") joinedRefActions := joinAndQuoteMeta(opts.RefActions, "|")
@ -66,15 +70,18 @@ func newCommitParser(client gitcmd.Client, config *Config) *commitParser {
joinedNoteKeywords := joinAndQuoteMeta(opts.NoteKeywords, "|") joinedNoteKeywords := joinAndQuoteMeta(opts.NoteKeywords, "|")
return &commitParser{ return &commitParser{
client: client, logger: logger,
config: config, client: client,
reHeader: regexp.MustCompile(opts.HeaderPattern), jiraClient: jiraClient,
reMerge: regexp.MustCompile(opts.MergePattern), config: config,
reRevert: regexp.MustCompile(opts.RevertPattern), reHeader: regexp.MustCompile(opts.HeaderPattern),
reRef: regexp.MustCompile("(?i)(" + joinedRefActions + ")\\s?([\\w/\\.\\-]+)?(?:" + joinedIssuePrefix + ")(\\d+)"), reMerge: regexp.MustCompile(opts.MergePattern),
reIssue: regexp.MustCompile("(?:" + joinedIssuePrefix + ")(\\d+)"), reRevert: regexp.MustCompile(opts.RevertPattern),
reNotes: regexp.MustCompile("^(?i)\\s*(" + joinedNoteKeywords + ")[:\\s]+(.*)"), reRef: regexp.MustCompile("(?i)(" + joinedRefActions + ")\\s?([\\w/\\.\\-]+)?(?:" + joinedIssuePrefix + ")(\\d+)"),
reMention: regexp.MustCompile("@([\\w-]+)"), 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 // refs & mentions
commit.Refs = p.parseRefs(input) commit.Refs = p.parseRefs(input)
commit.Mentions = p.parseMentions(input) commit.Mentions = p.parseMentions(input)
// Jira
if commit.JiraIssueId != "" {
p.processJiraIssue(commit, commit.JiraIssueId)
}
} }
func (p *commitParser) processBody(commit *Commit, input string) { func (p *commitParser) processBody(commit *Commit, input string) {
@ -344,6 +356,28 @@ func (p *commitParser) uniqMentions(mentions []string) []string {
return arr 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 ( var (
fenceTypes = []string{ fenceTypes = []string{
"```", "```",

View file

@ -4,10 +4,12 @@ import (
"errors" "errors"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"os"
"path/filepath" "path/filepath"
"testing" "testing"
"time" "time"
agjira "github.com/andygrunwald/go-jira"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
@ -27,51 +29,52 @@ func TestCommitParserParse(t *testing.T) {
}, },
} }
parser := newCommitParser(mock, &Config{ parser := newCommitParser(NewLogger(os.Stdout, os.Stderr, false, true),
Options: &Options{ mock, nil, &Config{
CommitFilters: map[string][]string{ Options: &Options{
"Type": []string{ CommitFilters: map[string][]string{
"feat", "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", "fix",
"perf", "fixes",
"refactor", "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") commits, err := parser.Parse("HEAD")
assert.Nil(err) assert.Nil(err)
@ -308,3 +311,102 @@ Closes username/repository#456`, "```", "```"),
}, },
}, commits) }, 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")
}

View file

@ -4,56 +4,58 @@ import (
"bytes" "bytes"
"fmt" "fmt"
"log" "log"
"os"
) )
func Example() { func Example() {
gen := NewGenerator(&Config{ gen := NewGenerator(NewLogger(os.Stdout, os.Stderr, false, true),
Bin: "git", &Config{
WorkingDir: ".", Bin: "git",
Template: "CHANGELOG.tpl.md", WorkingDir: ".",
Info: &Info{ Template: "CHANGELOG.tpl.md",
Title: "CHANGELOG", Info: &Info{
RepositoryURL: "https://github.com/git-chglog/git-chglog", Title: "CHANGELOG",
}, RepositoryURL: "https://github.com/git-chglog/git-chglog",
Options: &Options{ },
CommitFilters: map[string][]string{ Options: &Options{
"Type": []string{ CommitFilters: map[string][]string{
"feat", "Type": []string{
"fix", "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{} buf := &bytes.Buffer{}
err := gen.Generate(buf, "") err := gen.Generate(buf, "")

View file

@ -52,21 +52,31 @@ type NoteGroup struct {
Notes []*Note Notes []*Note
} }
// JiraIssue
type JiraIssue struct {
Type string
Summary string
Description string
Labels []string
}
// Commit data // Commit data
type Commit struct { type Commit struct {
Hash *Hash Hash *Hash
Author *Author Author *Author
Committer *Committer Committer *Committer
Merge *Merge // If it is not a merge commit, `nil` is assigned Merge *Merge // If it is not a merge commit, `nil` is assigned
Revert *Revert // If it is not a revert commit, `nil` is assigned Revert *Revert // If it is not a revert commit, `nil` is assigned
Refs []*Ref Refs []*Ref
Notes []*Note Notes []*Note
Mentions []string // Name of the user included in the commit header or body Mentions []string // Name of the user included in the commit header or body
Header string // (e.g. `feat(core): Add new feature`) JiraIssue *JiraIssue // If no issue id found in header, `nil` is assigned
Type string // (e.g. `feat`) Header string // (e.g. `feat(core)[RNWY-310]: Add new feature`)
Scope string // (e.g. `core`) Type string // (e.g. `feat`)
Subject string // (e.g. `Add new feature`) Scope string // (e.g. `core`)
Body string 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 // CommitGroup is a collection of commits grouped according to the `CommitGroupBy` option

1
go.mod
View file

@ -4,6 +4,7 @@ go 1.15
require ( require (
github.com/AlecAivazis/survey/v2 v2.2.8 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/fatih/color v1.10.0
github.com/imdario/mergo v0.3.11 github.com/imdario/mergo v0.3.11
github.com/kyokomi/emoji/v2 v2.2.8 github.com/kyokomi/emoji/v2 v2.2.8

15
go.sum
View file

@ -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/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 h1:xzYJEypr/85nBpB11F9br+3HUrpgb+fcm5iADzXXYEw=
github.com/Netflix/go-expect v0.0.0-20180615182759-c93bf25de8e8/go.mod h1:oX5x61PbNXchhh0oikYAH+4Pcfw5LKv21+Jnpr6r6Pc= 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 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/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.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 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 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 h1:s36xzo75JdqLaaWoiEHk767eHiwo0598uUxyfiPkDsg=
github.com/fatih/color v1.10.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM= 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 h1:WlZsjVhE8Af9IcZDGgJGQpNflI3+MJSBhsgT5PCtzBQ=
github.com/hinshun/vt10x v0.0.0-20180616224451-1954e6464174/go.mod h1:DqJ97dSdRW1W22yXSB90986pcOyQ7r45iio1KN2ez1A= github.com/hinshun/vt10x v0.0.0-20180616224451-1954e6464174/go.mod h1:DqJ97dSdRW1W22yXSB90986pcOyQ7r45iio1KN2ez1A=
github.com/imdario/mergo v0.3.11 h1:3tnifQM4i+fbajXKBHXWEH+KvNHqojZ778UH75j3bGA= 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/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 h1:j7+1HpAFS1zy5+Q4qx1fWh90gTKwiN4QCGoY9TWyyO4=
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= 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 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 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= 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.2.1/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 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 h1:Y2l28Jr3vOEeYtxfVbMtVfOdAwuUqWaP9fvNKiBVeXY=
github.com/tsuyoshiwada/go-gitcmd v0.0.0-20180205145712-5f1f5f9475df/go.mod h1:pnyouUty/nBr/zm3GYwTIt+qFTLWbdjeLjZmJdzJOu8= 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 h1:qph92Y649prgesehzOrQjdWyxFOp/QVM+6imKHad91M=
github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI= 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-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 h1:8dUaAV7K4uHsF56JQWkprecIQKdPHtR9jCHF5nB8uzc=
golang.org/x/crypto v0.0.0-20190530122614-20be4c3c3ed5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 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= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=

36
jira.go Normal file
View 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
View 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)
}

View file

@ -1,4 +1,4 @@
package main package chglog
import ( import (
"fmt" "fmt"

View file

@ -1,4 +1,4 @@
package main package chglog
import ( import (
"bytes" "bytes"

1
testdata/gitlog_jira.txt vendored Normal file
View 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.