mirror of
https://github.com/git-chglog/git-chglog.git
synced 2026-07-18 08:55:31 +00:00
As outlined in #183 it can be useful when following the conventional commit syntax to separate conventional commit type in the subject from work item references which often appear in the commit body or commit footer this commit: - adds a new config option 'jira.issue.key_pattern' which is an optional regex used to match issue keys in the commit subject and body - ensures that the Jira Issue Type does not overwrite a Conventional Commit Type; the 'jira.issue.type_maps' map is only used as a backup when the Conventional Commit Type parsed out of the Subject header is empty - runs the 'jira.issue.key_pattern' regex against both the commit Subject header and the entire commit Body NOTE: the current code assumes that only one Jira work item will be bound to a single commit, and this appears consistent with the spirit of Conventional Commits. I have sometimes found it useful to write a Conventional Commit where one 'fix' can close multiple Jira work items, so a future issue/change might consider updating the model to support multiple Jira work items in a single commit message.
434 lines
9.9 KiB
Go
434 lines
9.9 KiB
Go
package chglog
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"testing"
|
|
"time"
|
|
|
|
agjira "github.com/andygrunwald/go-jira"
|
|
"github.com/stretchr/testify/assert"
|
|
)
|
|
|
|
func TestCommitParserParse(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, _ := os.ReadFile(filepath.Join("testdata", "gitlog.txt"))
|
|
|
|
return string(bytes), nil
|
|
},
|
|
}
|
|
|
|
parser := newCommitParser(NewLogger(os.Stdout, os.Stderr, false, true),
|
|
mock, nil, &Config{
|
|
Options: &Options{
|
|
CommitFilters: map[string][]string{
|
|
"Type": {
|
|
"feat",
|
|
"fix",
|
|
"perf",
|
|
"refactor",
|
|
},
|
|
},
|
|
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)
|
|
assert.Equal([]*Commit{
|
|
{
|
|
Hash: &Hash{
|
|
Long: "65cf1add9735dcc4810dda3312b0792236c97c4e",
|
|
Short: "65cf1add",
|
|
},
|
|
Author: &Author{
|
|
Name: "tsuyoshi wada",
|
|
Email: "mail@example.com",
|
|
Date: time.Unix(int64(1514808000), 0),
|
|
},
|
|
Committer: &Committer{
|
|
Name: "tsuyoshi wada",
|
|
Email: "mail@example.com",
|
|
Date: time.Unix(int64(1514808000), 0),
|
|
},
|
|
Merge: nil,
|
|
Revert: nil,
|
|
Refs: []*Ref{
|
|
{
|
|
Action: "",
|
|
Ref: "123",
|
|
Source: "",
|
|
},
|
|
},
|
|
Notes: []*Note{},
|
|
Mentions: []string{},
|
|
Header: "feat(*): Add new feature #123",
|
|
Type: "feat",
|
|
Scope: "*",
|
|
Subject: "Add new feature #123",
|
|
Body: "",
|
|
TrimmedBody: "",
|
|
},
|
|
{
|
|
Hash: &Hash{
|
|
Long: "14ef0b6d386c5432af9292eab3c8314fa3001bc7",
|
|
Short: "14ef0b6d",
|
|
},
|
|
Author: &Author{
|
|
Name: "tsuyoshi wada",
|
|
Email: "mail@example.com",
|
|
Date: time.Unix(int64(1515153600), 0),
|
|
},
|
|
Committer: &Committer{
|
|
Name: "tsuyoshi wada",
|
|
Email: "mail@example.com",
|
|
Date: time.Unix(int64(1515153600), 0),
|
|
},
|
|
Merge: &Merge{
|
|
Ref: "3",
|
|
Source: "username/branchname",
|
|
},
|
|
Revert: nil,
|
|
Refs: []*Ref{
|
|
{
|
|
Action: "",
|
|
Ref: "3",
|
|
Source: "",
|
|
},
|
|
{
|
|
Action: "Fixes",
|
|
Ref: "3",
|
|
Source: "",
|
|
},
|
|
{
|
|
Action: "Closes",
|
|
Ref: "1",
|
|
Source: "",
|
|
},
|
|
},
|
|
Notes: []*Note{
|
|
{
|
|
Title: "BREAKING CHANGE",
|
|
Body: "This is breaking point message.",
|
|
},
|
|
},
|
|
Mentions: []string{},
|
|
Header: "Merge pull request #3 from username/branchname",
|
|
Type: "",
|
|
Scope: "",
|
|
Subject: "",
|
|
Body: `This is body message.
|
|
|
|
Fixes #3
|
|
|
|
Closes #1
|
|
|
|
BREAKING CHANGE: This is breaking point message.`,
|
|
TrimmedBody: `This is body message.`,
|
|
},
|
|
{
|
|
Hash: &Hash{
|
|
Long: "809a8280ffd0dadb0f4e7ba9fc835e63c37d6af6",
|
|
Short: "809a8280",
|
|
},
|
|
Author: &Author{
|
|
Name: "tsuyoshi wada",
|
|
Email: "mail@example.com",
|
|
Date: time.Unix(int64(1517486400), 0),
|
|
},
|
|
Committer: &Committer{
|
|
Name: "tsuyoshi wada",
|
|
Email: "mail@example.com",
|
|
Date: time.Unix(int64(1517486400), 0),
|
|
},
|
|
Merge: nil,
|
|
Revert: nil,
|
|
Refs: []*Ref{},
|
|
Notes: []*Note{},
|
|
Mentions: []string{
|
|
"tsuyoshiwada",
|
|
"hogefuga",
|
|
"FooBarBaz",
|
|
},
|
|
Header: "fix(controller): Fix cors configure",
|
|
Type: "fix",
|
|
Scope: "controller",
|
|
Subject: "Fix cors configure",
|
|
Body: `Has mention body
|
|
|
|
@tsuyoshiwada
|
|
@hogefuga
|
|
@FooBarBaz`,
|
|
TrimmedBody: `Has mention body`,
|
|
},
|
|
{
|
|
Hash: &Hash{
|
|
Long: "74824d6bd1470b901ec7123d13a76a1b8938d8d0",
|
|
Short: "74824d6b",
|
|
},
|
|
Author: &Author{
|
|
Name: "tsuyoshi wada",
|
|
Email: "mail@example.com",
|
|
Date: time.Unix(int64(1517488587), 0),
|
|
},
|
|
Committer: &Committer{
|
|
Name: "tsuyoshi wada",
|
|
Email: "mail@example.com",
|
|
Date: time.Unix(int64(1517488587), 0),
|
|
},
|
|
Merge: nil,
|
|
Revert: nil,
|
|
Refs: []*Ref{
|
|
{
|
|
Action: "Fixes",
|
|
Ref: "123",
|
|
Source: "",
|
|
},
|
|
{
|
|
Action: "Closes",
|
|
Ref: "456",
|
|
Source: "username/repository",
|
|
},
|
|
},
|
|
Notes: []*Note{
|
|
{
|
|
Title: "BREAKING CHANGE",
|
|
Body: fmt.Sprintf(`This is multiline breaking change note.
|
|
It is treated as the body of the Note until a mention or reference appears.
|
|
|
|
We also allow blank lines :)
|
|
|
|
Example:
|
|
|
|
%sjavascript
|
|
import { Controller } from 'hoge-fuga';
|
|
|
|
@autobind
|
|
class MyController extends Controller {
|
|
constructor() {
|
|
super();
|
|
}
|
|
}
|
|
%s`, "```", "```"),
|
|
},
|
|
},
|
|
Mentions: []string{},
|
|
Header: "fix(model): Remove hoge attributes",
|
|
Type: "fix",
|
|
Scope: "model",
|
|
Subject: "Remove hoge attributes",
|
|
Body: fmt.Sprintf(`This mixed body message.
|
|
|
|
BREAKING CHANGE:
|
|
This is multiline breaking change note.
|
|
It is treated as the body of the Note until a mention or reference appears.
|
|
|
|
We also allow blank lines :)
|
|
|
|
Example:
|
|
|
|
%sjavascript
|
|
import { Controller } from 'hoge-fuga';
|
|
|
|
@autobind
|
|
class MyController extends Controller {
|
|
constructor() {
|
|
super();
|
|
}
|
|
}
|
|
%s
|
|
|
|
Fixes #123
|
|
Closes username/repository#456`, "```", "```"),
|
|
TrimmedBody: `This mixed body message.`,
|
|
},
|
|
{
|
|
Hash: &Hash{
|
|
Long: "123456789735dcc4810dda3312b0792236c97c4e",
|
|
Short: "12345678",
|
|
},
|
|
Author: &Author{
|
|
Name: "tsuyoshi wada",
|
|
Email: "mail@example.com",
|
|
Date: time.Unix(int64(1517488587), 0),
|
|
},
|
|
Committer: &Committer{
|
|
Name: "tsuyoshi wada",
|
|
Email: "mail@example.com",
|
|
Date: time.Unix(int64(1517488587), 0),
|
|
},
|
|
Merge: nil,
|
|
Revert: &Revert{
|
|
Header: "fix(core): commit message",
|
|
},
|
|
Refs: []*Ref{},
|
|
Notes: []*Note{},
|
|
Mentions: []string{},
|
|
Header: "Revert \"fix(core): commit message\"",
|
|
Type: "",
|
|
Scope: "",
|
|
Subject: "",
|
|
Body: "This reverts commit f755db78dcdf461dc42e709b3ab728ceba353d1d.",
|
|
TrimmedBody: "This reverts commit f755db78dcdf461dc42e709b3ab728ceba353d1d.",
|
|
},
|
|
}, 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, _ := os.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": {
|
|
"feat",
|
|
"fix",
|
|
"perf",
|
|
"refactor",
|
|
},
|
|
},
|
|
HeaderPattern: "^(?:(\\w*)|(?:\\[(.*)\\])?)\\:\\s(.*)$",
|
|
HeaderPatternMaps: []string{
|
|
"Type",
|
|
"JiraIssueID",
|
|
"Subject",
|
|
},
|
|
JiraKeyPattern: "\\b(JIRA-\\d+)",
|
|
JiraTypeMaps: map[string]string{
|
|
"Story": "feat",
|
|
},
|
|
},
|
|
})
|
|
|
|
commits, err := parser.Parse("HEAD")
|
|
assert.Nil(err)
|
|
|
|
assert.Equal(2, len(commits))
|
|
|
|
commit := commits[0]
|
|
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)
|
|
}
|