Skip to content

Commit cb44b42

Browse files
authored
fix: handle multi-line commit message (#2244)
Fixes #2234
1 parent ca32437 commit cb44b42

File tree

3 files changed

+74
-8
lines changed

3 files changed

+74
-8
lines changed

internal/conventionalcommits/conventional_commits.go

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -190,17 +190,25 @@ func parseSimpleCommit(commitPart commitPart, commit *gitrepo.Commit, libraryID
190190
processFooters(footers)
191191

192192
var commits []*ConventionalCommit
193-
// If the body lines have multiple headers, separate them into different conventional
194-
// commit, all associated with the same commit sha.
195-
// Note that we assume single line headers, the 2nd line of headers, if exists,
196-
// will be discarded.
193+
// Hold the subjects of each commit.
194+
var subjects [][]string
195+
// If the body lines have multiple headers, separate them into different conventional commit, all associated with
196+
// the same commit sha.
197197
for _, bodyLine := range bodyLines {
198198
header, ok := parseHeader(bodyLine)
199199
if !ok {
200200
slog.Warn("bodyLine is not a header", "bodyLine", bodyLine, "hash", commit.Hash.String())
201+
if len(commits) == 0 {
202+
// This should not happen as we expect a conventional commit message inside a nested commit.
203+
continue
204+
}
205+
206+
// This might be a multi-line header, append the line to the subject of the last commit.
207+
subjects[len(subjects)-1] = append(subjects[len(subjects)-1], strings.TrimSpace(bodyLine))
201208
continue
202209
}
203210

211+
subjects = append(subjects, []string{})
204212
commits = append(commits, &ConventionalCommit{
205213
Type: header.Type,
206214
Scope: header.Scope,
@@ -214,10 +222,17 @@ func parseSimpleCommit(commitPart commitPart, commit *gitrepo.Commit, libraryID
214222
})
215223
}
216224

217-
// If only one conventional commit is found, i.e., only one header line is
218-
// in the commit message, assign the body field.
219225
if len(commits) == 1 {
226+
// If only one conventional commit is found, i.e., only one header line is
227+
// in the commit message, assign the body field.
220228
commits[0].Body = strings.TrimSpace(strings.Join(bodyLines[1:], "\n"))
229+
} else {
230+
// Otherwise, concatenate all lines as the subject of the corresponding commit.
231+
// This is a workaround when GitHub inserts line breaks in the middle of a long line after squash and merge.
232+
for i, commit := range commits {
233+
sub := fmt.Sprintf("%s %s", commit.Subject, strings.Join(subjects[i], " "))
234+
commit.Subject = strings.TrimSpace(sub)
235+
}
221236
}
222237

223238
return commits, nil

internal/conventionalcommits/conventional_commits_test.go

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -393,7 +393,7 @@ END_NESTED_COMMIT`,
393393
},
394394
},
395395
{
396-
name: "parse multiple lines message inside nested commit",
396+
name: "parse multiple lines message inside nested commit, one line header",
397397
message: `
398398
chore: Update generation configuration at Tue Aug 26 02:31:23 UTC 2025 (#11734)
399399
@@ -453,6 +453,57 @@ END_COMMIT_OVERRIDE`,
453453
},
454454
},
455455
},
456+
{
457+
name: "parse_multiple_lines_message_inside_nested_commit_multi_line_headers",
458+
message: `
459+
chore: Update generation configuration at Tue Aug 26 02:31:23 UTC 2025 (#11734)
460+
461+
This pull request is generated with proto changes between
462+
[googleapis/googleapis@525c95a](https://github.com/googleapis/googleapis/commit/525c95a7a122ec2869ae06cd02fa5013819463f6)
463+
(exclusive) and
464+
[googleapis/googleapis@b738e78](https://github.com/googleapis/googleapis/commit/b738e78ed63effb7d199ed2d61c9e03291b6077f)
465+
(inclusive).
466+
467+
BEGIN_COMMIT_OVERRIDE
468+
BEGIN_NESTED_COMMIT
469+
feat: [texttospeech] Support promptable voices by specifying a model
470+
name and a prompt
471+
feat: [texttospeech] Add enum value M4A to enum AudioEncoding
472+
docs: [texttospeech] A comment for method 'StreamingSynthesize' in
473+
service 'TextToSpeech' is changed
474+
475+
END_NESTED_COMMIT
476+
END_COMMIT_OVERRIDE`,
477+
want: []*ConventionalCommit{
478+
{
479+
Type: "feat",
480+
Subject: "[texttospeech] Support promptable voices by specifying a model name and a prompt",
481+
LibraryID: "example-id",
482+
IsNested: true,
483+
Footers: map[string]string{},
484+
SHA: sha.String(),
485+
When: now,
486+
},
487+
{
488+
Type: "feat",
489+
Subject: "[texttospeech] Add enum value M4A to enum AudioEncoding",
490+
LibraryID: "example-id",
491+
IsNested: true,
492+
Footers: map[string]string{},
493+
SHA: sha.String(),
494+
When: now,
495+
},
496+
{
497+
Type: "docs",
498+
Subject: "[texttospeech] A comment for method 'StreamingSynthesize' in service 'TextToSpeech' is changed",
499+
LibraryID: "example-id",
500+
IsNested: true,
501+
Footers: map[string]string{},
502+
SHA: sha.String(),
503+
When: now,
504+
},
505+
},
506+
},
456507
} {
457508
t.Run(test.name, func(t *testing.T) {
458509
commit := &gitrepo.Commit{

internal/librarian/release_notes.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -208,7 +208,7 @@ func formatGenerationPRBody(repo gitrepo.Repository, state *config.LibrarianStat
208208

209209
// findLatestGenerationCommit returns the latest commit among the last generated
210210
// commit of all the libraries.
211-
// A libray is skipped if the last generated commit is empty.
211+
// A library is skipped if the last generated commit is empty.
212212
//
213213
// Note that it is possible that the returned commit is nil.
214214
func findLatestGenerationCommit(repo gitrepo.Repository, state *config.LibrarianState, idToCommits map[string]string) (*gitrepo.Commit, error) {

0 commit comments

Comments
 (0)