diff --git a/chglog_test.go b/chglog_test.go index 01eae8ab..6c4e2552 100644 --- a/chglog_test.go +++ b/chglog_test.go @@ -70,7 +70,7 @@ func TestGeneratorWithTypeScopeSubject(t *testing.T) { gen := NewGenerator(&Config{ Bin: "git", WorkingDir: filepath.Join(testRepoRoot, testName), - Template: filepath.Join("fixtures", testName+".md"), + Template: filepath.Join("testdata", testName+".md"), Info: &Info{ Title: "CHANGELOG Example", RepositoryURL: "https://github.com/git-chglog/git-chglog", diff --git a/commit_extractor_test.go b/commit_extractor_test.go index 6b42eea2..59f80dce 100644 --- a/commit_extractor_test.go +++ b/commit_extractor_test.go @@ -73,9 +73,7 @@ func TestCommitExtractor(t *testing.T) { Header: "Revert1", Notes: []*Note{}, Revert: &Revert{ - Raw: "revert1", - Subject: "REVERT1", - Hash: "1", + Header: "REVERT1", }, }, } diff --git a/commit_parser.go b/commit_parser.go index 591cbc8f..5054b227 100644 --- a/commit_parser.go +++ b/commit_parser.go @@ -38,6 +38,14 @@ var ( }, delimiter) ) +func joinAndQuoteMeta(list []string, sep string) string { + arr := make([]string, len(list)) + for i, s := range list { + arr[i] = regexp.QuoteMeta(s) + } + return strings.Join(arr, sep) +} + type commitParser struct { client gitcmd.Client config *Config @@ -53,9 +61,9 @@ type commitParser struct { func newCommitParser(client gitcmd.Client, config *Config) *commitParser { opts := config.Options - joinedRefActions := strings.Join(opts.RefActions, "|") - joinedIssuePrefix := strings.Join(opts.IssuePrefix, "|") - joinedNoteKeywords := strings.Join(opts.NoteKeywords, "|") + joinedRefActions := joinAndQuoteMeta(opts.RefActions, "|") + joinedIssuePrefix := joinAndQuoteMeta(opts.IssuePrefix, "|") + joinedNoteKeywords := joinAndQuoteMeta(opts.NoteKeywords, "|") return &commitParser{ client: client, @@ -65,7 +73,7 @@ func newCommitParser(client gitcmd.Client, config *Config) *commitParser { 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]+(.*)"), + reNotes: regexp.MustCompile("^(?i)\\s*(" + joinedNoteKeywords + ")[:\\s]+(.*)"), reMention: regexp.MustCompile("@([\\w-]+)"), } } @@ -76,8 +84,6 @@ func (p *commitParser) Parse(rev string) ([]*Commit, error) { rev, "--no-decorate", "--pretty="+logFormat, - "-n", - "10", ) if err != nil { @@ -203,25 +209,32 @@ func (p *commitParser) processHeader(commit *Commit, input string) { } func (p *commitParser) processBody(commit *Commit, input string) { + input = convNewline(input, "\n") + // body commit.Body = input // notes & refs & mentions commit.Notes = []*Note{} inNote := false + fenceDetector := newMdFenceDetector() lines := strings.Split(input, "\n") for _, line := range lines { - refs := p.parseRefs(line) - if len(refs) > 0 { - inNote = false - commit.Refs = append(commit.Refs, refs...) - } + fenceDetector.Update(line) - mentions := p.parseMentions(line) - if len(mentions) > 0 { - inNote = false - commit.Mentions = append(commit.Mentions, mentions...) + if !fenceDetector.InCodeblock() { + refs := p.parseRefs(line) + if len(refs) > 0 { + inNote = false + commit.Refs = append(commit.Refs, refs...) + } + + mentions := p.parseMentions(line) + if len(mentions) > 0 { + inNote = false + commit.Mentions = append(commit.Mentions, mentions...) + } } res := p.reNotes.FindAllStringSubmatch(line, -1) @@ -240,6 +253,10 @@ func (p *commitParser) processBody(commit *Commit, input string) { } } + p.trimSpaceInNotes(commit) +} + +func (*commitParser) trimSpaceInNotes(commit *Commit) { for _, note := range commit.Notes { note.Body = strings.TrimSpace(note.Body) } @@ -326,3 +343,42 @@ func (p *commitParser) uniqMentions(mentions []string) []string { return arr } + +var ( + fenceTypes = []string{ + "```", + "~~~", + " ", + "\t", + } +) + +type mdFenceDetector struct { + fence int +} + +func newMdFenceDetector() *mdFenceDetector { + return &mdFenceDetector{ + fence: -1, + } +} + +func (d *mdFenceDetector) InCodeblock() bool { + return d.fence > -1 +} + +func (d *mdFenceDetector) Update(input string) { + for i, s := range fenceTypes { + if d.fence < 0 { + if strings.Index(input, s) == 0 { + d.fence = i + break + } + } else { + if strings.Index(input, s) == 0 && i == d.fence { + d.fence = -1 + break + } + } + } +} diff --git a/commit_parser_test.go b/commit_parser_test.go index 7b2928ba..2df79531 100644 --- a/commit_parser_test.go +++ b/commit_parser_test.go @@ -2,6 +2,7 @@ package chglog import ( "errors" + "fmt" "io/ioutil" "path/filepath" "testing" @@ -20,7 +21,7 @@ func TestCommitParserParse(t *testing.T) { return "", errors.New("") } - bytes, _ := ioutil.ReadFile(filepath.Join("fixtures", "gitlog.txt")) + bytes, _ := ioutil.ReadFile(filepath.Join("testdata", "gitlog.txt")) return string(bytes), nil }, @@ -62,10 +63,9 @@ func TestCommitParserParse(t *testing.T) { "Ref", "Source", }, - RevertPattern: "^Revert\\s\"([\\s\\S]*)\"\\s*This reverts commit (\\w*)\\.", + RevertPattern: "^Revert \"([\\s\\S]*)\"$", RevertPatternMaps: []string{ - "Subject", - "Hash", + "Header", }, NoteKeywords: []string{ "BREAKING CHANGE", @@ -230,10 +230,23 @@ BREAKING CHANGE: This is breaking point message.`, Notes: []*Note{ &Note{ Title: "BREAKING CHANGE", - Body: `This is multiline breaking change note. + 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 :)`, +We also allow blank lines :) + +Example: + +%sjavascript +import { Controller } from 'hoge-fuga'; + +@autobind +class MyController extends Controller { + constructor() { + super(); + } +} +%s`, "```", "```"), }, }, Mentions: []string{}, @@ -241,7 +254,7 @@ We also allow blank lines :)`, Type: "fix", Scope: "model", Subject: "Remove hoge attributes", - Body: `This mixed body message. + Body: fmt.Sprintf(`This mixed body message. BREAKING CHANGE: This is multiline breaking change note. @@ -249,8 +262,49 @@ 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`, +Closes username/repository#456`, "```", "```"), + }, + &Commit{ + 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.", }, }, commits) } diff --git a/fields.go b/fields.go index 414e9503..a348aba6 100644 --- a/fields.go +++ b/fields.go @@ -30,9 +30,7 @@ type Merge struct { // Revert ... type Revert struct { - Raw string - Subject string - Hash string + Header string } // Ref ... @@ -96,6 +94,7 @@ type Tag struct { type Version struct { Tag *Tag CommitGroups []*CommitGroup + Commits []*Commit MergeCommits []*Commit RevertCommits []*Commit NoteGroups []*NoteGroup diff --git a/fixtures/gitlog.txt b/testdata/gitlog.txt similarity index 75% rename from fixtures/gitlog.txt rename to testdata/gitlog.txt index 68fcdf7e..42ebbf68 100644 --- a/fixtures/gitlog.txt +++ b/testdata/gitlog.txt @@ -20,5 +20,20 @@ It is treated as the body of the Note until a mention or reference appears. We also allow blank lines :) +Example: + +```javascript +import { Controller } from 'hoge-fuga'; + +@autobind +class MyController extends Controller { + constructor() { + super(); + } +} +``` + Fixes #123 Closes username/repository#456 + +@@__CHGLOG__@@HASH:123456789735dcc4810dda3312b0792236c97c4e 12345678@@__CHGLOG_DELIMITER__@@AUTHOR:tsuyoshi wada mail@example.com 1517488587@@__CHGLOG_DELIMITER__@@COMMITTER:tsuyoshi wada mail@example.com 1517488587@@__CHGLOG_DELIMITER__@@SUBJECT:Revert "fix(core): commit message"@@__CHGLOG_DELIMITER__@@BODY:This reverts commit f755db78dcdf461dc42e709b3ab728ceba353d1d. diff --git a/fixtures/type_scope_subject.md b/testdata/type_scope_subject.md similarity index 100% rename from fixtures/type_scope_subject.md rename to testdata/type_scope_subject.md diff --git a/utils.go b/utils.go index f025d972..01aea857 100644 --- a/utils.go +++ b/utils.go @@ -104,3 +104,11 @@ func compareTime(a time.Time, operator string, b time.Time) bool { return false } } + +func convNewline(str, nlcode string) string { + return strings.NewReplacer( + "\r\n", nlcode, + "\r", nlcode, + "\n", nlcode, + ).Replace(str) +}