From a1c84d7a0d1ed30644b3295aeeae1129c0e635e8 Mon Sep 17 00:00:00 2001 From: Chao Li Date: Thu, 11 Mar 2021 15:40:32 +0800 Subject: [PATCH] feat: add Jira integration (#52) --- Makefile | 2 +- README.md | 83 +++++ chglog.go | 49 +-- chglog_test.go | 312 +++++++++--------- cmd/git-chglog/cli.go | 6 +- cmd/git-chglog/config.go | 70 ++-- cmd/git-chglog/context.go | 5 + cmd/git-chglog/generator.go | 6 +- cmd/git-chglog/generator_mock.go | 2 +- cmd/git-chglog/initializer.go | 5 +- cmd/git-chglog/main.go | 46 ++- commit_parser.go | 74 +++-- commit_parser_test.go | 186 ++++++++--- example_test.go | 92 +++--- fields.go | 36 +- go.mod | 1 + go.sum | 15 + jira.go | 36 ++ jira_test.go | 42 +++ cmd/git-chglog/logger.go => logger.go | 2 +- .../logger_test.go => logger_test.go | 2 +- testdata/gitlog_jira.txt | 1 + 22 files changed, 742 insertions(+), 331 deletions(-) create mode 100644 jira.go create mode 100644 jira_test.go rename cmd/git-chglog/logger.go => logger.go (98%) rename cmd/git-chglog/logger_test.go => logger_test.go (99%) create mode 100644 testdata/gitlog_jira.txt diff --git a/Makefile b/Makefile index 01fc1824..1f27d92a 100644 --- a/Makefile +++ b/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: diff --git a/README.md b/README.md index e504d930..8b5e1c76 100644 --- a/README.md +++ b/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: "(.*)" + ``` + +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 ` ... ` 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
diff --git a/chglog.go b/chglog.go index d0afac53..968b65cf 100644 --- a/chglog.go +++ b/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 `:` (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 `:` (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), } } diff --git a/chglog_test.go b/chglog_test.go index 7a1fd595..8e2a3e9e 100644 --- a/chglog_test.go +++ b/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, "") diff --git a/cmd/git-chglog/cli.go b/cmd/git-chglog/cli.go index dcb44b60..2f2b4c1b 100644 --- a/cmd/git-chglog/cli.go +++ b/cmd/git-chglog/cli.go @@ -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 diff --git a/cmd/git-chglog/config.go b/cmd/git-chglog/config.go index 7baeca4a..d2e4844d 100644 --- a/cmd/git-chglog/config.go +++ b/cmd/git-chglog/config.go @@ -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, }, } } diff --git a/cmd/git-chglog/context.go b/cmd/git-chglog/context.go index a33cc6d9..3f2e1aa8 100644 --- a/cmd/git-chglog/context.go +++ b/cmd/git-chglog/context.go @@ -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 ... diff --git a/cmd/git-chglog/generator.go b/cmd/git-chglog/generator.go index be229a33..583ea5d7 100644 --- a/cmd/git-chglog/generator.go +++ b/cmd/git-chglog/generator.go @@ -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) } diff --git a/cmd/git-chglog/generator_mock.go b/cmd/git-chglog/generator_mock.go index 467c7d31..5120c995 100644 --- a/cmd/git-chglog/generator_mock.go +++ b/cmd/git-chglog/generator_mock.go @@ -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) } diff --git a/cmd/git-chglog/initializer.go b/cmd/git-chglog/initializer.go index 7356f8b0..c0a8489a 100644 --- a/cmd/git-chglog/initializer.go +++ b/cmd/git-chglog/initializer.go @@ -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, diff --git a/cmd/git-chglog/main.go b/cmd/git-chglog/main.go index 6f262476..28005207 100644 --- a/cmd/git-chglog/main.go +++ b/cmd/git-chglog/main.go @@ -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(), diff --git a/commit_parser.go b/commit_parser.go index 5054b227..83d38a45 100644 --- a/commit_parser.go +++ b/commit_parser.go @@ -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{ "```", diff --git a/commit_parser_test.go b/commit_parser_test.go index 2df79531..defc7357 100644 --- a/commit_parser_test.go +++ b/commit_parser_test.go @@ -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") +} diff --git a/example_test.go b/example_test.go index fd0f1adb..931b3bb1 100644 --- a/example_test.go +++ b/example_test.go @@ -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, "") diff --git a/fields.go b/fields.go index 46cf8cc3..123da6c7 100644 --- a/fields.go +++ b/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 diff --git a/go.mod b/go.mod index 4f0140ec..daef7e59 100644 --- a/go.mod +++ b/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 diff --git a/go.sum b/go.sum index de399e24..e141f10b 100644 --- a/go.sum +++ b/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= diff --git a/jira.go b/jira.go new file mode 100644 index 00000000..78afbdd7 --- /dev/null +++ b/jira.go @@ -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 +} diff --git a/jira_test.go b/jira_test.go new file mode 100644 index 00000000..30936e29 --- /dev/null +++ b/jira_test.go @@ -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) +} diff --git a/cmd/git-chglog/logger.go b/logger.go similarity index 98% rename from cmd/git-chglog/logger.go rename to logger.go index be64fc6e..52003ecb 100644 --- a/cmd/git-chglog/logger.go +++ b/logger.go @@ -1,4 +1,4 @@ -package main +package chglog import ( "fmt" diff --git a/cmd/git-chglog/logger_test.go b/logger_test.go similarity index 99% rename from cmd/git-chglog/logger_test.go rename to logger_test.go index 17bb8598..77f5ff4d 100644 --- a/cmd/git-chglog/logger_test.go +++ b/logger_test.go @@ -1,4 +1,4 @@ -package main +package chglog import ( "bytes" diff --git a/testdata/gitlog_jira.txt b/testdata/gitlog_jira.txt new file mode 100644 index 00000000..f535f2e1 --- /dev/null +++ b/testdata/gitlog_jira.txt @@ -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.