From 12ad99911a557348660af5d3513dc0659587f1fb Mon Sep 17 00:00:00 2001 From: Jim Myhrberg Date: Sat, 31 Oct 2020 20:43:46 +0000 Subject: [PATCH] wip: horrible hack for parsing commit footers --- pkg/commit/commit.go | 5 +- pkg/commit/parser.go | 67 ++++++++++++++++++- pkg/commit/parser_test.go | 133 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 201 insertions(+), 4 deletions(-) diff --git a/pkg/commit/commit.go b/pkg/commit/commit.go index 6d0b76b..0f31f73 100644 --- a/pkg/commit/commit.go +++ b/pkg/commit/commit.go @@ -12,6 +12,7 @@ type Commit struct { } type Footer struct { - Name string - Body string + Name string + Body string + Reference bool } diff --git a/pkg/commit/parser.go b/pkg/commit/parser.go index 5adacb5..bb6ca2d 100644 --- a/pkg/commit/parser.go +++ b/pkg/commit/parser.go @@ -12,8 +12,16 @@ const ( crlf = "\r\n" ) -var rHeader = regexp.MustCompile( - `^([\w\-]*)(?:\(([\w\$\.\/\-\* ]*)\))?(!)?\: (.*)$`, +var ( + rHeader = regexp.MustCompile( + `^([\w\-]*)(?:\(([\w\$\.\/\-\* ]*)\))?(!)?\: (.*)$`, + ) + rFooterToken = regexp.MustCompile( + `^([\w-]+|BREAKING CHANGE):\s\s*(.*)$`, + ) + rFooterTicket = regexp.MustCompile( + `^([\w-]+)\s+(#\S.*)$`, + ) ) func parseHeader(header []byte) (*Commit, error) { @@ -34,6 +42,61 @@ func parseHeader(header []byte) (*Commit, error) { }, nil } +func footers(paragraph []byte) []*Footer { + footers := []*Footer{} + lines := bytes.Split(paragraph, []byte{lf}) + + if !rFooterToken.Match(lines[0]) && !rFooterTicket.Match(lines[0]) { + return footers + } + + var cName string + var cBody []byte + var cRef bool + for _, line := range lines { + if m := rFooterToken.FindSubmatch(line); m != nil { + if cName != "" { + footers = append(footers, &Footer{ + Name: cName, + Body: string(bytes.TrimSpace(cBody)), + Reference: cRef, + }) + cName = "" + cRef = false + } + cName = string(m[1]) + cBody = m[2] + cRef = false + } else if m := rFooterTicket.FindSubmatch(line); m != nil { + if cName != "" { + footers = append(footers, &Footer{ + Name: cName, + Body: string(bytes.TrimSpace(cBody)), + Reference: cRef, + }) + cName = "" + cRef = false + } + cName = string(m[1]) + cBody = m[2] + cRef = true + } else { + cBody = append(cBody, []byte{lf}...) + cBody = append(cBody, line...) + } + } + + if cName != "" { + footers = append(footers, &Footer{ + Name: cName, + Body: string(bytes.TrimSpace(cBody)), + Reference: cRef, + }) + } + + return footers +} + func paragraphs(commitMsg []byte) [][]byte { paras := bytes.Split( bytes.TrimSpace(normlizeLinefeeds(commitMsg)), diff --git a/pkg/commit/parser_test.go b/pkg/commit/parser_test.go index e781538..9d5200f 100644 --- a/pkg/commit/parser_test.go +++ b/pkg/commit/parser_test.go @@ -175,6 +175,139 @@ func Test_parseHeader(t *testing.T) { } } +func Test_footers(t *testing.T) { + type args struct { + paragraph []byte + } + tests := []struct { + name string + args args + want []*Footer + }{ + { + name: "without footer", + args: args{[]byte("this is not a fooder")}, + want: []*Footer{}, + }, + { + name: "token footer on second line", + args: args{[]byte("this is not a fooder\nDone-By: John")}, + want: []*Footer{}, + }, + { + name: "ticket footer on second line", + args: args{[]byte("this is not a fooder\nFixes #42")}, + want: []*Footer{}, + }, + { + name: "breaking change footer on second line", + args: args{[]byte("this is not a fooder\nBREAKING CHANGE: Oops")}, + want: []*Footer{}, + }, + { + name: "token footer", + args: args{[]byte("Reviewed-By: John Smith")}, + want: []*Footer{{Name: "Reviewed-By", Body: "John Smith"}}, + }, + { + name: "breaking change footer", + args: args{[]byte("BREAKING CHANGE: Oopsy")}, + want: []*Footer{{Name: "BREAKING CHANGE", Body: "Oopsy"}}, + }, + { + name: "ticket footer", + args: args{[]byte("Fixes #82")}, + want: []*Footer{{Name: "Fixes", Body: "#82", Reference: true}}, + }, + { + name: "multiple token footers", + args: args{[]byte( + "Reviewed-By: John\n" + + "Committer: Smith\n", + )}, + want: []*Footer{ + {Name: "Reviewed-By", Body: "John"}, + {Name: "Committer", Body: "Smith"}, + }, + }, + { + name: "multiple ticket footers", + args: args{[]byte("Fixes #82\nFixes #74")}, + want: []*Footer{ + {Name: "Fixes", Body: "#82", Reference: true}, + {Name: "Fixes", Body: "#74", Reference: true}, + }, + }, + { + name: "multiple breaking change footers", + args: args{[]byte( + "BREAKING CHANGE: Oopsy\n" + + "BREAKING CHANGE: Again!", + )}, + want: []*Footer{ + {Name: "BREAKING CHANGE", Body: "Oopsy"}, + {Name: "BREAKING CHANGE", Body: "Again!"}, + }, + }, + { + name: "mixture of footer types", + args: args{[]byte( + "Fixes #930\n" + + "BREAKING CHANGE: Careful!\n" + + "Reviewed-By: Maria\n", + )}, + want: []*Footer{ + {Name: "Fixes", Body: "#930", Reference: true}, + {Name: "BREAKING CHANGE", Body: "Careful!"}, + {Name: "Reviewed-By", Body: "Maria"}, + }, + }, + { + name: "multi-line footers", + args: args{[]byte( + "Description: Lorem ipsum dolor sit amet, consectetur\n" + + "adipiscing elit.Praesent eleifend lorem non purus\n" + + "finibus, interdum hendrerit sem bibendum.\n" + + "Fixes #94\n" + + "Misc-Other: Etiam porttitor mollis nulla, egestas\n" + + "facilisis nisi molestie ut. Quisque mi mi, commodo\n" + + "ut mattis a, scelerisque eu elit.\n" + + "BREAKING CHANGE: Duis id nulla eget velit maximus\n" + + "varius et egestas sem. Ut mi risus, pretium quis\n" + + "cursus quis, porttitor in ipsum.\n", + )}, + want: []*Footer{ + { + Name: "Description", + Body: "Lorem ipsum dolor sit amet, consectetur\n" + + "adipiscing elit.Praesent eleifend lorem non purus\n" + + "finibus, interdum hendrerit sem bibendum.", + }, + {Name: "Fixes", Body: "#94", Reference: true}, + { + Name: "Misc-Other", + Body: "Etiam porttitor mollis nulla, egestas\n" + + "facilisis nisi molestie ut. Quisque mi mi, commodo\n" + + "ut mattis a, scelerisque eu elit.", + }, + { + Name: "BREAKING CHANGE", + Body: "Duis id nulla eget velit maximus\n" + + "varius et egestas sem. Ut mi risus, pretium quis\n" + + "cursus quis, porttitor in ipsum.", + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := footers(tt.args.paragraph) + + assert.Equal(t, tt.want, got) + }) + } +} + func Test_paragraph(t *testing.T) { type args struct { input []byte