diff --git a/README.md b/README.md
index 3e767100..c9eda7ce 100644
--- a/README.md
+++ b/README.md
@@ -574,25 +574,41 @@ This sample pattern can match both forms of commit headers:
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: "(.*)"
+ options:
+ jira:
+ info:
+ username: u
+ token: p
+ url: https://jira.com
+ issue:
+ key_pattern: "\\b(JIRA-\\d+)"
+ 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`.
+#### `options.jira.info`
-You also needs to define a issue type map. In above sample, Jira issue type `Task`
+Options to configure a Jira API client:
+
+| Key | Required | Type | Default | Description |
+|:-----------|:---------|:-------|:--------|:-----------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| `username` | N | String | none | Jira username; can also be read as an env var from `JIRA_USERNAME` |
+| `token` | N | String | none | Jira [API token](https://support.atlassian.com/atlassian-account/docs/manage-api-tokens-for-your-atlassian-account/); can also be read as an env var from `JIRA_TOKEN` |
+| `url` | N | String | none | Base URL for your Jira instance (e.g. https://myorg.atlassian.net/);
can also be read as an env var from `JIRA_URL` |
+
+#### `options.jira.issue`
+
+You can also define an issue type map. In the above sample, Jira issue type `Task`
will be mapped to `fix` and `Story` will be mapped to `feat`.
+| Key | Required | Type | Default | Description |
+|:----------------------|:---------|:--------------------|:--------|:----------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| `key_pattern` | N | String | none | A golang-compatible regular expression used to match against commit header and body to detect related Jira issues. |
+| `type_maps` | N | `map[string]string` | none | A dictionary-style map used to infer the Conventional Commit Type from the Jira Issue Type, used when the Conventional Commit Subject does not provide a Commit Type. |
+| `description_pattern` | N | String | none | A golang-compatible regular expression used to select out of a Jira Issue's Description field the subset of text to select for `.JiraIssue.Description` |
+
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 ` ... `
@@ -608,17 +624,16 @@ data. For example:
### {{ .Title }}
{{ range .Commits -}}
- {{ if .Scope }}**{{ .Scope }}:** {{ end }}{{ .Subject }}
-{{ if .JiraIssue }} {{ .JiraIssue.Description }}
-{{ end }}
+ {{ 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.Summary` - Summary of the Jira issue
+- `.JiraIssue.Description` - Description of the Jira issue
+- `.JiraIssue.Type` - Original type of the Jira issue (also available as `.Type` unless the commit provided a Conventional Commit Type.
- `.JiraIssue.Labels` - A list of strings, each is a Jira label.
## FAQ
diff --git a/chglog.go b/chglog.go
index fc5cf9e3..4f510265 100644
--- a/chglog.go
+++ b/chglog.go
@@ -39,6 +39,7 @@ type Options struct {
RevertPatternMaps []string // Similar to `HeaderPatternMaps`
NoteKeywords []string // Keyword list to find `Note`. A semicolon is a separator, like `:` (e.g. `BREAKING CHANGE`)
JiraUsername string
+ JiraKeyPattern string // Optional regex to match jira issue keys from commit message body
JiraToken string
JiraURL string
JiraTypeMaps map[string]string
diff --git a/cmd/git-chglog/config.go b/cmd/git-chglog/config.go
index abfe82e8..66486504 100644
--- a/cmd/git-chglog/config.go
+++ b/cmd/git-chglog/config.go
@@ -59,6 +59,7 @@ type JiraClientInfoOptions struct {
// JiraIssueOptions ...
type JiraIssueOptions struct {
+ KeyPattern string `yaml:"key_pattern"`
TypeMaps map[string]string `yaml:"type_maps"`
DescriptionPattern string `yaml:"description_pattern"`
}
@@ -326,6 +327,7 @@ func (config *Config) Convert(ctx *CLIContext) *chglog.Config {
RevertPattern: opts.Reverts.Pattern,
RevertPatternMaps: opts.Reverts.PatternMaps,
NoteKeywords: opts.Notes.Keywords,
+ JiraKeyPattern: opts.Jira.Issue.KeyPattern,
JiraUsername: orValue(ctx.JiraUsername, opts.Jira.ClintInfo.Username),
JiraToken: orValue(ctx.JiraToken, opts.Jira.ClintInfo.Token),
JiraURL: orValue(ctx.JiraURL, opts.Jira.ClintInfo.URL),
diff --git a/commit_parser.go b/commit_parser.go
index 0bb67f83..4d5a4290 100644
--- a/commit_parser.go
+++ b/commit_parser.go
@@ -61,6 +61,7 @@ type commitParser struct {
reMention *regexp.Regexp
reSignOff *regexp.Regexp
reCoAuthor *regexp.Regexp
+ reJiraKey *regexp.Regexp
reJiraIssueDescription *regexp.Regexp
}
@@ -71,7 +72,7 @@ func newCommitParser(logger *Logger, client gitcmd.Client, jiraClient JiraClient
joinedIssuePrefix := joinAndQuoteMeta(opts.IssuePrefix, "|")
joinedNoteKeywords := joinAndQuoteMeta(opts.NoteKeywords, "|")
- return &commitParser{
+ p := commitParser{
logger: logger,
client: client,
jiraClient: jiraClient,
@@ -87,6 +88,12 @@ func newCommitParser(logger *Logger, client gitcmd.Client, jiraClient JiraClient
reCoAuthor: regexp.MustCompile(`Co-authored-by:\s+([\p{L}\s\-\[\]]+)\s+<([\w+\-\[\].@]+)>`),
reJiraIssueDescription: regexp.MustCompile(opts.JiraIssueDescriptionPattern),
}
+
+ if opts.JiraKeyPattern != "" {
+ p.reJiraKey = regexp.MustCompile(opts.JiraKeyPattern)
+ }
+
+ return &p
}
func (p *commitParser) Parse(rev string) ([]*Commit, error) {
@@ -153,6 +160,11 @@ func (p *commitParser) parseCommit(input string) *Commit {
}
}
+ // Jira
+ if commit.JiraIssueID != "" {
+ p.processJiraIssue(commit, commit.JiraIssueID)
+ }
+
commit.Refs = p.uniqRefs(commit.Refs)
commit.Mentions = p.uniqMentions(commit.Mentions)
@@ -206,6 +218,8 @@ func (p *commitParser) processHeader(commit *Commit, input string) {
assignDynamicValues(commit, opts.HeaderPatternMaps, res[0][1:])
}
+ p.populateJiraIssueIDByJiraKeyRegex(commit, input)
+
// Merge
res = p.reMerge.FindAllStringSubmatch(input, -1)
if len(res) > 0 {
@@ -225,11 +239,6 @@ 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) extractLineMetadata(commit *Commit, line string) bool {
@@ -268,6 +277,8 @@ func (p *commitParser) processBody(commit *Commit, input string) {
// body
commit.Body = input
+ p.populateJiraIssueIDByJiraKeyRegex(commit, input)
+
// notes & refs & mentions
commit.Notes = []*Note{}
inNote := false
@@ -314,6 +325,18 @@ func (p *commitParser) processBody(commit *Commit, input string) {
p.trimSpaceInNotes(commit)
}
+func (p *commitParser) populateJiraIssueIDByJiraKeyRegex(commit *Commit, input string) {
+ if p.reJiraKey != nil {
+ m := p.reJiraKey.FindAllString(input, -1)
+ if len(m) > 0 {
+ // NOTE: a git commit may reference multiple jira issues... here we capture only the first
+ // a future change may resolve this by updating the commit structure to support
+ // multiple issues
+ commit.JiraIssueID = m[0]
+ }
+ }
+}
+
func (*commitParser) trimSpaceInNotes(commit *Commit) {
for _, note := range commit.Notes {
note.Body = strings.TrimSpace(note.Body)
@@ -432,7 +455,10 @@ func (p *commitParser) processJiraIssue(commit *Commit, issueID string) {
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]
+ if commit.Type == "" {
+ // don't override an explicit Conventional Commit Type
+ commit.Type = p.config.Options.JiraTypeMaps[issue.Fields.Type.Name]
+ }
commit.JiraIssue = &JiraIssue{
Type: issue.Fields.Type.Name,
Summary: issue.Fields.Summary,
diff --git a/commit_parser_test.go b/commit_parser_test.go
index 600d1163..00ea851e 100644
--- a/commit_parser_test.go
+++ b/commit_parser_test.go
@@ -398,6 +398,7 @@ func TestCommitParserParseWithJira(t *testing.T) {
"JiraIssueID",
"Subject",
},
+ JiraKeyPattern: "\\b(JIRA-\\d+)",
JiraTypeMaps: map[string]string{
"Story": "feat",
},
@@ -406,11 +407,28 @@ func TestCommitParserParseWithJira(t *testing.T) {
commits, err := parser.Parse("HEAD")
assert.Nil(err)
+
+ assert.Equal(2, len(commits))
+
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")
+ assert.Equal("JIRA-1111", commit.JiraIssueID)
+ if commit.JiraIssue == nil {
+ t.Fatal("did not load jira issue JIRA-1111")
+ }
+ assert.Equal("Story", commit.JiraIssue.Type)
+ assert.Equal("summary of JIRA-1111", commit.JiraIssue.Summary)
+ assert.Equal("description of JIRA-1111", commit.JiraIssue.Description)
+ assert.Equal([]string{"GA"}, commit.JiraIssue.Labels)
+ assert.Equal("feat", commit.Type)
+
+ commit = commits[1]
+ assert.Equal("JIRA-1112", commit.JiraIssueID)
+ if commit.JiraIssue == nil {
+ t.Fatal("did not load jira issue JIRA-1112")
+ }
+ assert.Equal("Story", commit.JiraIssue.Type)
+ assert.Equal("summary of JIRA-1112", commit.JiraIssue.Summary)
+ assert.Equal("description of JIRA-1112", commit.JiraIssue.Description)
+ assert.Equal([]string{"GA"}, commit.JiraIssue.Labels)
+ assert.Equal("fix", commit.Type)
}
diff --git a/testdata/gitlog_jira.txt b/testdata/gitlog_jira.txt
index f535f2e1..e3d66705 100644
--- a/testdata/gitlog_jira.txt
+++ b/testdata/gitlog_jira.txt
@@ -1 +1,5 @@
@@__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.
+
+@@__CHGLOG__@@HASH:14ef0b6d386c5432af9292eab3c8314fa3001bc7 14ef0b6d@@__CHGLOG_DELIMITER__@@AUTHOR:tsuyoshi wada mail@example.com 1515153600@@__CHGLOG_DELIMITER__@@COMMITTER:tsuyoshi wada mail@example.com 1515153600@@__CHGLOG_DELIMITER__@@SUBJECT:fix: Feature didn't work right@@__CHGLOG_DELIMITER__@@BODY:This is body message.
+
+Fixes JIRA-1112