Skip to content

Commit 16df43c

Browse files
refactor: commands provide PR body builder (#2574)
`commitAndPush` should not be responsible for building the PR body. Because building the PR body could be a relatively expensive operation, we lazily build it. The caller of `commitAndPush` provides a callback func that returns the PR body that is used for creating the GitHub pull request. An added benefit is that the PR body building logic can live with the code that is opening the PR. --------- Signed-off-by: Jeff Ching <[email protected]> Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
1 parent 6a4aead commit 16df43c

File tree

9 files changed

+1297
-1181
lines changed

9 files changed

+1297
-1181
lines changed

internal/librarian/command.go

Lines changed: 18 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,8 @@ For each failed library, open a ticket in that library’s repository and then y
4444
`
4545
)
4646

47+
var errBuilderNotProvided = fmt.Errorf("no prBodyBuilder provided")
48+
4749
type pullRequestType int
4850

4951
const (
@@ -98,9 +100,7 @@ type commitInfo struct {
98100
branch string
99101
commit bool
100102
commitMessage string
101-
failedLibraries []string
102103
ghClient GitHubClient
103-
idToCommits map[string]string
104104
prType pullRequestType
105105
pullRequestLabels []string
106106
push bool
@@ -112,7 +112,8 @@ type commitInfo struct {
112112
// api is the api path of a library, only set this value during api onboarding.
113113
api string
114114
// library is the ID of a library, only set this value during api onboarding.
115-
library string
115+
library string
116+
prBodyBuilder func() (string, error)
116117
}
117118

118119
type commandRunner struct {
@@ -359,8 +360,7 @@ func getDirectoryFilenames(dir string) ([]string, error) {
359360
func commitAndPush(ctx context.Context, info *commitInfo) error {
360361
if !info.push && !info.commit {
361362
slog.Info("Push flag and Commit flag are not specified, skipping committing")
362-
writePRBody(info)
363-
return nil
363+
return writePRBody(info)
364364
}
365365

366366
repo := info.languageRepo
@@ -389,8 +389,7 @@ func commitAndPush(ctx context.Context, info *commitInfo) error {
389389

390390
if !info.push {
391391
slog.Info("Push flag is not specified, skipping pull request creation")
392-
writePRBody(info)
393-
return nil
392+
return writePRBody(info)
394393
}
395394

396395
if err := repo.Push(branch); err != nil {
@@ -403,7 +402,7 @@ func commitAndPush(ctx context.Context, info *commitInfo) error {
403402
}
404403

405404
title := fmt.Sprintf("chore: librarian %s pull request: %s", info.prType, datetimeNow)
406-
prBody, err := createPRBody(info, gitHubRepo)
405+
prBody, err := info.prBodyBuilder()
407406
if err != nil {
408407
return fmt.Errorf("failed to create pull request body: %w", err)
409408
}
@@ -423,19 +422,17 @@ func commitAndPush(ctx context.Context, info *commitInfo) error {
423422
}
424423

425424
// writePRBody attempts to log the body of a PR that would have been created if the
426-
// -push flag had been specified. This logs any errors (e.g. if the GitHub repo can't be determined)
427-
// but deliberately does not return them, as a failure here should not interfere with the flow.
428-
func writePRBody(info *commitInfo) {
429-
gitHubRepo, err := GetGitHubRepositoryFromGitRepo(info.languageRepo)
430-
if err != nil {
431-
slog.Warn("Unable to create PR body; could not determine GitHub repo", "error", err)
432-
return
425+
// -push flag had been specified. This logs any errors and returns them to the
426+
// caller.
427+
func writePRBody(info *commitInfo) error {
428+
if info.prBodyBuilder == nil {
429+
return errBuilderNotProvided
433430
}
434431

435-
prBody, err := createPRBody(info, gitHubRepo)
432+
prBody, err := info.prBodyBuilder()
436433
if err != nil {
437434
slog.Warn("Unable to create PR body", "error", err)
438-
return
435+
return err
439436
}
440437
// Note: we can't accurately predict whether a PR would have been created,
441438
// as we're not checking whether the repo is clean or not. The intention is to be
@@ -444,11 +441,12 @@ func writePRBody(info *commitInfo) {
444441
// Ensure that "cat [path-to-pr-body.txt]" gives useful output.
445442
prBody = prBody + "\n"
446443
err = os.WriteFile(fullPath, []byte(prBody), 0644)
447-
if err == nil {
448-
slog.Info("Wrote body of pull request that might have been created", "file", fullPath)
449-
} else {
444+
if err != nil {
450445
slog.Warn("Unable to save PR body", "error", err)
446+
return err
451447
}
448+
slog.Info("Wrote body of pull request that might have been created", "file", fullPath)
449+
return nil
452450
}
453451

454452
// addLabelsToPullRequest adds a list of labels to a single pull request (specified by the id number).
@@ -468,32 +466,6 @@ func addLabelsToPullRequest(ctx context.Context, ghClient GitHubClient, pullRequ
468466
return nil
469467
}
470468

471-
func createPRBody(info *commitInfo, gitHubRepo *github.Repository) (string, error) {
472-
switch info.prType {
473-
case pullRequestOnboard:
474-
req := &onboardPRRequest{
475-
sourceRepo: info.sourceRepo,
476-
state: info.state,
477-
api: info.api,
478-
library: info.library,
479-
}
480-
return formatOnboardPRBody(req)
481-
case pullRequestGenerate:
482-
req := &generationPRRequest{
483-
sourceRepo: info.sourceRepo,
484-
languageRepo: info.languageRepo,
485-
state: info.state,
486-
idToCommits: info.idToCommits,
487-
failedLibraries: info.failedLibraries,
488-
}
489-
return formatGenerationPRBody(req)
490-
case pullRequestRelease:
491-
return formatReleaseNotes(info.state, gitHubRepo)
492-
default:
493-
return "", fmt.Errorf("unrecognized pull request type: %s", info.prType)
494-
}
495-
}
496-
497469
// copyGlobalAllowlist copies files in the global file allowlist from src to dst.
498470
func copyGlobalAllowlist(cfg *config.LibrarianConfig, dst, src string, copyReadOnly bool) error {
499471
if cfg == nil {

internal/librarian/command_test.go

Lines changed: 39 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ package librarian
1717
import (
1818
"context"
1919
"errors"
20+
"fmt"
2021
"os"
2122
"os/exec"
2223
"path/filepath"
@@ -1302,6 +1303,7 @@ func TestCommitAndPush(t *testing.T) {
13021303
expectedErrMsg string
13031304
check func(t *testing.T, repo gitrepo.Repository)
13041305
wantPRBodyFile bool
1306+
prBodyBuilder func() (string, error)
13051307
}{
13061308
{
13071309
name: "Push flag and Commit flag are not specified",
@@ -1520,6 +1522,7 @@ func TestCommitAndPush(t *testing.T) {
15201522
push: true,
15211523
wantErr: true,
15221524
expectedErrMsg: "failed to create pull request body",
1525+
prBodyBuilder: func() (string, error) { return "", fmt.Errorf("failed to create pull request body") },
15231526
},
15241527
{
15251528
name: "Create pull request error",
@@ -1593,6 +1596,11 @@ func TestCommitAndPush(t *testing.T) {
15931596
repo := test.setupMockRepo(t)
15941597
client := test.setupMockClient(t)
15951598

1599+
// set default PR body builder
1600+
if test.prBodyBuilder == nil {
1601+
test.prBodyBuilder = func() (string, error) { return "some pr body", nil }
1602+
}
1603+
15961604
commitInfo := &commitInfo{
15971605
commit: test.commit,
15981606
commitMessage: "",
@@ -1603,6 +1611,7 @@ func TestCommitAndPush(t *testing.T) {
16031611
state: test.state,
16041612
failedGenerations: test.failedGenerations,
16051613
workRoot: t.TempDir(),
1614+
prBodyBuilder: test.prBodyBuilder,
16061615
}
16071616

16081617
err := commitAndPush(context.Background(), commitInfo)
@@ -1637,6 +1646,7 @@ func TestWritePRBody(t *testing.T) {
16371646
for _, test := range []struct {
16381647
name string
16391648
info *commitInfo
1649+
wantErr bool
16401650
wantFile bool
16411651
}{
16421652
{
@@ -1651,14 +1661,15 @@ func TestWritePRBody(t *testing.T) {
16511661
},
16521662
},
16531663
},
1654-
prType: pullRequestRelease,
1655-
state: &config.LibrarianState{},
1656-
workRoot: t.TempDir(),
1664+
prType: pullRequestRelease,
1665+
state: &config.LibrarianState{},
1666+
workRoot: t.TempDir(),
1667+
prBodyBuilder: func() (string, error) { return "some pr body", nil },
16571668
},
16581669
wantFile: true,
16591670
},
16601671
{
1661-
name: "invalid repo",
1672+
name: "unable to build PR body",
16621673
info: &commitInfo{
16631674
languageRepo: &MockRepository{
16641675
Dir: t.TempDir(),
@@ -1669,12 +1680,14 @@ func TestWritePRBody(t *testing.T) {
16691680
},
16701681
},
16711682
},
1672-
prType: pullRequestRelease,
1673-
workRoot: t.TempDir(),
1683+
prType: pullRequestRelease,
1684+
workRoot: t.TempDir(),
1685+
prBodyBuilder: func() (string, error) { return "", fmt.Errorf("some error") },
16741686
},
1687+
wantErr: true,
16751688
},
16761689
{
1677-
name: "invalid-pr-type",
1690+
name: "unable to save file",
16781691
info: &commitInfo{
16791692
languageRepo: &MockRepository{
16801693
Dir: t.TempDir(),
@@ -1685,12 +1698,15 @@ func TestWritePRBody(t *testing.T) {
16851698
},
16861699
},
16871700
},
1688-
prType: 4,
1689-
workRoot: t.TempDir(),
1701+
prType: pullRequestRelease,
1702+
state: &config.LibrarianState{},
1703+
workRoot: filepath.Join(t.TempDir(), "missing-directory"),
1704+
prBodyBuilder: func() (string, error) { return "some pr body", nil },
16901705
},
1706+
wantErr: true,
16911707
},
16921708
{
1693-
name: "unable to save file",
1709+
name: "no body builder",
16941710
info: &commitInfo{
16951711
languageRepo: &MockRepository{
16961712
Dir: t.TempDir(),
@@ -1705,10 +1721,22 @@ func TestWritePRBody(t *testing.T) {
17051721
state: &config.LibrarianState{},
17061722
workRoot: filepath.Join(t.TempDir(), "missing-directory"),
17071723
},
1724+
wantErr: true,
17081725
},
17091726
} {
17101727
t.Run(test.name, func(t *testing.T) {
1711-
writePRBody(test.info)
1728+
err := writePRBody(test.info)
1729+
1730+
if test.wantErr {
1731+
if err == nil {
1732+
t.Fatalf("writePRBody() expected error, but no error returned")
1733+
}
1734+
return
1735+
}
1736+
if err != nil {
1737+
t.Fatalf("unexpected error %v", err)
1738+
}
1739+
17121740
gotFile := gotPRBodyFile(t, test.info.workRoot)
17131741
if test.wantFile != gotFile {
17141742
t.Errorf("writePRBody() wantFile = %t, gotFile = %t", test.wantFile, gotFile)

internal/librarian/generate_command.go

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -149,13 +149,38 @@ func (r *generateRunner) run(ctx context.Context) error {
149149
return err
150150
}
151151

152+
var prBodyBuilder func() (string, error)
153+
switch prType {
154+
case pullRequestGenerate:
155+
prBodyBuilder = func() (string, error) {
156+
req := &generationPRRequest{
157+
sourceRepo: r.sourceRepo,
158+
languageRepo: r.repo,
159+
state: r.state,
160+
idToCommits: idToCommits,
161+
failedLibraries: failedLibraries,
162+
}
163+
return formatGenerationPRBody(req)
164+
}
165+
case pullRequestOnboard:
166+
prBodyBuilder = func() (string, error) {
167+
req := &onboardPRRequest{
168+
sourceRepo: r.sourceRepo,
169+
state: r.state,
170+
api: r.api,
171+
library: r.library,
172+
}
173+
return formatOnboardPRBody(req)
174+
}
175+
default:
176+
return fmt.Errorf("unexpected prType %s", prType)
177+
}
178+
152179
commitInfo := &commitInfo{
153180
branch: r.branch,
154181
commit: r.commit,
155182
commitMessage: "feat: generate libraries",
156-
failedLibraries: failedLibraries,
157183
ghClient: r.ghClient,
158-
idToCommits: idToCommits,
159184
prType: prType,
160185
push: r.push,
161186
languageRepo: r.repo,
@@ -165,6 +190,7 @@ func (r *generateRunner) run(ctx context.Context) error {
165190
api: r.api,
166191
library: r.library,
167192
failedGenerations: failedGenerations,
193+
prBodyBuilder: prBodyBuilder,
168194
}
169195

170196
return commitAndPush(ctx, commitInfo)

0 commit comments

Comments
 (0)