Skip to content

Commit dcbe063

Browse files
authored
fix(internal/conventionalcommits): relax footer regex to allow line broke footers to be properly recognized (#2388)
The original logic expect footer to follow `key: value` (exactly one space before value) format as conventionalcommit specs. For multi-line footers, when parsing ([logic](https://github.com/googleapis/librarian/blob/9b28aa4a3619579dfa59c430afa612589b353598/internal/conventionalcommits/conventional_commits.go#L336)), the line content "Source-Link:" would fail the regex match and thus not properly recognized as footer, and gets appended to the value of the previous footer. By relaxing the regex a bit to allow any number of spaces before "value" in footer, the multiline footer can be recognized. note: this format below from owlbot PR ([example](googleapis/google-cloud-python@34a7916)) is technically not valid footer according to conventionalcommits.org spec [here](https://www.conventionalcommits.org/en/v1.0.0/#:~:text=Each%20footer%20MUST%20consist%20of%20a%20word%20token%2C%20followed%20by%20either%20a%20%3A%3Cspace%3E%20or%20%3Cspace%3E%23%20separator%2C%20followed%20by%20a%20string%20value). But we are relaxing to allow it for onboarding libraries. ``` Source-Link: googleapis/googleapis@d300b15 ``` This should solve the first issue causing this bug, see [comment](#2080 (comment)) For #2080
1 parent 2e11004 commit dcbe063

File tree

2 files changed

+86
-2
lines changed

2 files changed

+86
-2
lines changed

internal/conventionalcommits/conventional_commits.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,9 +38,9 @@ var (
3838
commitRegex = regexp.MustCompile(`^(?P<type>\w+)(?:\((?P<scope>.*)\))?(?P<breaking>!)?:\s(?P<description>.*)`)
3939
// footerRegex defines the format for a conventional commit footer.
4040
// A footer key consists of letters and hyphens, or is the "BREAKING CHANGE"
41-
// literal. The key is followed by ": " and then the value.
41+
// literal. The key is followed by ":" and then the value.
4242
// e.g., "Reviewed-by: G. Gemini" or "BREAKING CHANGE: an API was changed".
43-
footerRegex = regexp.MustCompile(`^([A-Za-z-]+|` + breakingChangeKey + `):\s(.*)`)
43+
footerRegex = regexp.MustCompile(`^([A-Za-z-]+|` + breakingChangeKey + `):(.*)`)
4444
sourceLinkRegex = regexp.MustCompile(`^\[googleapis\/googleapis@(?P<shortSHA>.*)\]\(https:\/\/github\.com\/googleapis\/googleapis\/commit\/(?P<sha>.*)\)$`)
4545
// libraryIDRegex extracts the libraryID from the commit message in a generation PR.
4646
// For a generation PR, each commit is expected to have the libraryID in brackets
@@ -298,6 +298,8 @@ func parseHeader(headerLine string) (*parsedHeader, bool) {
298298
}
299299

300300
// separateBodyAndFooters splits the lines after the header into body and footer sections.
301+
// It identifies the footer section by looking for a blank line followed by a
302+
// line that matches the conventional commit footer format.
301303
func separateBodyAndFooters(lines []string) (bodyLines, footerLines []string) {
302304
inFooterSection := false
303305
for i, line := range lines {

internal/conventionalcommits/conventional_commits_test.go

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -693,3 +693,85 @@ func TestConventionalCommit_MarshalJSON(t *testing.T) {
693693
t.Errorf("MarshalJSON() mismatch (-want +got):\n%s", diff)
694694
}
695695
}
696+
697+
func TestParseFooters(t *testing.T) {
698+
for _, test := range []struct {
699+
name string
700+
footerLines []string
701+
wantFooters map[string]string
702+
wantIsBreaking bool
703+
}{
704+
{
705+
name: "single footer",
706+
footerLines: []string{
707+
"Reviewed-by: G. Gemini",
708+
},
709+
wantFooters: map[string]string{
710+
"Reviewed-by": "G. Gemini",
711+
},
712+
},
713+
{
714+
name: "multiple footers",
715+
footerLines: []string{
716+
"Reviewed-by: G. Gemini",
717+
"Co-authored-by: Another Person <another@person.com>",
718+
},
719+
wantFooters: map[string]string{
720+
"Reviewed-by": "G. Gemini",
721+
"Co-authored-by": "Another Person <another@person.com>",
722+
},
723+
},
724+
{
725+
name: "multiline footer",
726+
footerLines: []string{
727+
"BREAKING CHANGE: something broke",
728+
" and it was a big deal",
729+
},
730+
wantFooters: map[string]string{
731+
"BREAKING CHANGE": "something broke\n and it was a big deal",
732+
},
733+
wantIsBreaking: true,
734+
},
735+
{
736+
name: "empty lines",
737+
footerLines: []string{
738+
"Reviewed-by: G. Gemini",
739+
"",
740+
"",
741+
"Co-authored-by: Another Person <another@person.com>",
742+
},
743+
wantFooters: map[string]string{
744+
"Reviewed-by": "G. Gemini",
745+
"Co-authored-by": "Another Person <another@person.com>",
746+
},
747+
},
748+
{
749+
name: "multi-line footers with key on one line, value on the next",
750+
footerLines: []string{
751+
"PiperOrigin-RevId: 123456",
752+
"",
753+
"Source-Link:",
754+
"",
755+
"googleapis/googleapis@a12b345",
756+
"",
757+
"",
758+
"Copy-Tag:",
759+
"eyJwIjoic",
760+
},
761+
wantFooters: map[string]string{
762+
"PiperOrigin-RevId": "123456",
763+
"Source-Link": "\ngoogleapis/googleapis@a12b345",
764+
"Copy-Tag": "\neyJwIjoic"},
765+
},
766+
} {
767+
t.Run(test.name, func(t *testing.T) {
768+
gotFooters, gotIsBreaking := parseFooters(test.footerLines)
769+
if diff := cmp.Diff(test.wantFooters, gotFooters); diff != "" {
770+
t.Errorf("parseFooters() footers mismatch (-want +got):%s", diff)
771+
}
772+
if gotIsBreaking != test.wantIsBreaking {
773+
t.Errorf("parseFooters() isBreaking = %v, want %v", gotIsBreaking, test.wantIsBreaking)
774+
}
775+
})
776+
}
777+
}

0 commit comments

Comments
 (0)