Skip to content

Commit 7cb6469

Browse files
h9jianggopherbot
authored andcommitted
internal/task: update codereview.cfg and create a CL for review
1. Add optional parameter reviewers to gopls release flow. 2. Add a Task to update code review configuration in release branch. 3. Add a Action to wait for CL to be submitted. A local relui screenshot is at https://go.dev/issue/57643#issuecomment-2259049777 For golang/go#57643 Change-Id: Iae3ebd5dd7bc9040979185aa353e2a4afed8cb54 Reviewed-on: https://go-review.googlesource.com/c/build/+/601241 Reviewed-by: Dmitri Shuralyov <[email protected]> LUCI-TryBot-Result: Go LUCI <[email protected]> Auto-Submit: Hongxiang Jiang <[email protected]> Reviewed-by: Dmitri Shuralyov <[email protected]>
1 parent 0e131e6 commit 7cb6469

File tree

3 files changed

+229
-5
lines changed

3 files changed

+229
-5
lines changed

cmd/relui/main.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -309,7 +309,8 @@ func main() {
309309
dh.RegisterDefinition("Tag a new version of x/telemetry/config (if necessary)", tagTelemetryTasks.NewDefinition())
310310

311311
releaseGoplsTasks := task.ReleaseGoplsTasks{
312-
Gerrit: gerritClient,
312+
Gerrit: gerritClient,
313+
CloudBuild: cloudBuildClient,
313314
}
314315
dh.RegisterDefinition("Release a new version of gopls", releaseGoplsTasks.NewDefinition())
315316

internal/task/releasegopls.go

Lines changed: 85 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,11 @@
55
package task
66

77
import (
8+
"errors"
89
"fmt"
910
"slices"
1011
"strings"
12+
"time"
1113

1214
"golang.org/x/build/gerrit"
1315
wf "golang.org/x/build/internal/workflow"
@@ -17,7 +19,8 @@ import (
1719
// ReleaseGoplsTasks implements a new workflow definition include all the tasks
1820
// to release a gopls.
1921
type ReleaseGoplsTasks struct {
20-
Gerrit GerritClient
22+
Gerrit GerritClient
23+
CloudBuild CloudBuildClient
2124
}
2225

2326
// NewDefinition create a new workflow definition for releasing gopls.
@@ -27,16 +30,24 @@ func (r *ReleaseGoplsTasks) NewDefinition() *wf.Definition {
2730
// TODO(hxjiang): provide potential release versions in the relui where the
2831
// coordinator can choose which version to release instead of manual input.
2932
version := wf.Param(wd, wf.ParamDef[string]{Name: "version"})
33+
reviewers := wf.Param(wd, reviewersParam)
3034
semversion := wf.Task1(wd, "validating input version", r.isValidVersion, version)
31-
_ = wf.Action1(wd, "creating new branch if minor release", r.createBranchIfMinor, semversion)
35+
branchCreated := wf.Action1(wd, "creating new branch if minor release", r.createBranchIfMinor, semversion)
36+
changeID := wf.Task2(wd, "updating branch's codereview.cfg", r.updateCodeReviewConfig, semversion, reviewers, wf.After(branchCreated))
37+
_ = wf.Action1(wd, "await config CL submission", r.AwaitSubmission, changeID)
3238
return wd
3339
}
3440

41+
// goplsReleaseBranchName returns the branch name for given input release version.
42+
func goplsReleaseBranchName(semv semversion) string {
43+
return fmt.Sprintf("gopls-release-branch.%v.%v", semv.Major, semv.Minor)
44+
}
45+
3546
// createBranchIfMinor create the release branch if the input version is a minor
3647
// release.
3748
// All patch releases under the same minor version share the same release branch.
3849
func (r *ReleaseGoplsTasks) createBranchIfMinor(ctx *wf.TaskContext, semv semversion) error {
39-
branch := fmt.Sprintf("gopls-release-branch.%v.%v", semv.Major, semv.Minor)
50+
branch := goplsReleaseBranchName(semv)
4051

4152
// Require gopls release branch existence if this is a non-minor release.
4253
if semv.Patch != 0 {
@@ -62,6 +73,77 @@ func (r *ReleaseGoplsTasks) createBranchIfMinor(ctx *wf.TaskContext, semv semver
6273
return err
6374
}
6475

76+
// updateCodeReviewConfig ensures codereview.cfg contains the expected
77+
// configuration.
78+
//
79+
// It returns the change ID, or "" if the CL was not created.
80+
func (r *ReleaseGoplsTasks) updateCodeReviewConfig(ctx *wf.TaskContext, semv semversion, reviewers []string) (string, error) {
81+
const configFile = "codereview.cfg"
82+
const configFmt = `issuerepo: golang/go
83+
branch: %s
84+
parent-branch: master
85+
`
86+
87+
branch := goplsReleaseBranchName(semv)
88+
clTitle := fmt.Sprintf("all: update %s for %s", configFile, branch)
89+
90+
// Query for an existing pending config CL, to avoid duplication.
91+
query := fmt.Sprintf(`message:%q status:open owner:[email protected] repo:tools branch:%q -age:7d`, clTitle, branch)
92+
changes, err := r.Gerrit.QueryChanges(ctx, query)
93+
if err != nil {
94+
return "", err
95+
}
96+
if len(changes) > 0 {
97+
ctx.Printf("not creating CL: found existing CL %d", changes[0].ChangeNumber)
98+
return changes[0].ChangeID, nil
99+
}
100+
101+
head, err := r.Gerrit.ReadBranchHead(ctx, "tools", branch)
102+
if err != nil {
103+
return "", err
104+
}
105+
106+
before, err := r.Gerrit.ReadFile(ctx, "tools", head, configFile)
107+
if err != nil && !errors.Is(err, gerrit.ErrResourceNotExist) {
108+
return "", err
109+
}
110+
111+
after := fmt.Sprintf(configFmt, branch)
112+
// Skip CL creation as config has not changed.
113+
if string(before) == after {
114+
return "", nil
115+
}
116+
117+
changeInput := gerrit.ChangeInput{
118+
Project: "tools",
119+
Subject: fmt.Sprintf("%s\n\nThis is an automated CL which updates the %s.", clTitle, configFile),
120+
Branch: branch,
121+
}
122+
123+
files := map[string]string{
124+
configFile: string(after),
125+
}
126+
127+
ctx.Printf("creating auto-submit change to %s under branch %q in x/tools repo.", configFile, branch)
128+
return r.Gerrit.CreateAutoSubmitChange(ctx, changeInput, reviewers, files)
129+
}
130+
131+
// AwaitSubmission waits for the CL with the given change ID to be submitted.
132+
//
133+
// The return value is the submitted commit hash, or "" if changeID is "".
134+
func (r *ReleaseGoplsTasks) AwaitSubmission(ctx *wf.TaskContext, changeID string) error {
135+
if changeID == "" {
136+
ctx.Printf("not awaiting: no CL was created")
137+
return nil
138+
}
139+
140+
ctx.Printf("awaiting review/submit of %v", ChangeLink(changeID))
141+
_, err := AwaitCondition(ctx, 10*time.Second, func() (string, bool, error) {
142+
return r.Gerrit.Submitted(ctx, changeID, "")
143+
})
144+
return err
145+
}
146+
65147
func (r *ReleaseGoplsTasks) isValidVersion(ctx *wf.TaskContext, ver string) (semversion, error) {
66148
if !semver.IsValid(ver) {
67149
return semversion{}, fmt.Errorf("the input %q version does not follow semantic version schema", ver)

internal/task/releasegopls_test.go

Lines changed: 142 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,7 @@ func TestCreateBranchIfMinor(t *testing.T) {
158158
t.Errorf("createBranchIfMinor() should return nil but return err: %v", err)
159159
}
160160

161-
// Created branch should have same revision as master branch's HEAD.
161+
// Created branch should have same revision as master branch's head.
162162
if tc.wantBranch != "" {
163163
gotRevision, err := gerritClient.ReadBranchHead(ctx, "tools", tc.wantBranch)
164164
if err != nil {
@@ -171,3 +171,144 @@ func TestCreateBranchIfMinor(t *testing.T) {
171171
})
172172
}
173173
}
174+
175+
func TestUpdateCodeReviewConfig(t *testing.T) {
176+
ctx := context.Background()
177+
testcases := []struct {
178+
name string
179+
version string
180+
config string
181+
wantCommit bool
182+
wantConfig string
183+
}{
184+
{
185+
name: "should update the codereview.cfg with version 1.2 for input minor release 1.2.0",
186+
version: "v1.2.0",
187+
config: "foo",
188+
wantCommit: true,
189+
wantConfig: `issuerepo: golang/go
190+
branch: gopls-release-branch.1.2
191+
parent-branch: master
192+
`,
193+
},
194+
{
195+
name: "should update the codereview.cfg with version 1.2 for input patch release 1.2.3",
196+
version: "v1.2.3",
197+
config: "foo",
198+
wantCommit: true,
199+
wantConfig: `issuerepo: golang/go
200+
branch: gopls-release-branch.1.2
201+
parent-branch: master
202+
`,
203+
},
204+
{
205+
name: "no need to update the config for a minor release 1.3.0",
206+
version: "v1.3.0",
207+
config: `issuerepo: golang/go
208+
branch: gopls-release-branch.1.3
209+
parent-branch: master
210+
`,
211+
wantCommit: false,
212+
wantConfig: `issuerepo: golang/go
213+
branch: gopls-release-branch.1.3
214+
parent-branch: master
215+
`,
216+
},
217+
{
218+
name: "no need to update the config for a patch release 1.3.3",
219+
version: "v1.3.3",
220+
config: `issuerepo: golang/go
221+
branch: gopls-release-branch.1.3
222+
parent-branch: master
223+
`,
224+
wantCommit: false,
225+
wantConfig: `issuerepo: golang/go
226+
branch: gopls-release-branch.1.3
227+
parent-branch: master
228+
`,
229+
},
230+
}
231+
for _, tc := range testcases {
232+
t.Run(tc.name, func(t *testing.T) {
233+
tools := NewFakeRepo(t, "tools")
234+
_ = tools.Commit(map[string]string{
235+
"go.mod": "module golang.org/x/tools\n",
236+
"go.sum": "\n",
237+
})
238+
_ = tools.Commit(map[string]string{
239+
"codereview.cfg": tc.config,
240+
})
241+
242+
gerritClient := NewFakeGerrit(t, tools)
243+
244+
headMaster, err := gerritClient.ReadBranchHead(ctx, "tools", "master")
245+
if err != nil {
246+
t.Fatalf("ReadBranchHead should be able to get revision of master branch's head: %v", err)
247+
}
248+
249+
configMaster, err := gerritClient.ReadFile(ctx, "tools", headMaster, "codereview.cfg")
250+
if err != nil {
251+
t.Fatalf("ReadFile should be able to read the codereview.cfg file from master branch head: %v", err)
252+
}
253+
254+
semv, _ := parseSemver(tc.version)
255+
releaseBranch := goplsReleaseBranchName(semv)
256+
if _, err := gerritClient.CreateBranch(ctx, "tools", releaseBranch, gerrit.BranchInput{Revision: headMaster}); err != nil {
257+
t.Fatalf("failed to create the branch %q: %v", releaseBranch, err)
258+
}
259+
260+
headRelease, err := gerritClient.ReadBranchHead(ctx, "tools", releaseBranch)
261+
if err != nil {
262+
t.Fatalf("ReadBranchHead should be able to get revision of release branch's head: %v", err)
263+
}
264+
265+
tasks := &ReleaseGoplsTasks{
266+
Gerrit: gerritClient,
267+
CloudBuild: NewFakeCloudBuild(t, gerritClient, "", nil, fakeGo),
268+
}
269+
270+
_, err = tasks.updateCodeReviewConfig(&workflow.TaskContext{Context: ctx, Logger: &testLogger{t, ""}}, semv, nil)
271+
if err != nil {
272+
t.Fatalf("updateCodeReviewConfig() returns error: %v", err)
273+
}
274+
275+
// master branch's head commit should not change.
276+
headMasterAfter, err := gerritClient.ReadBranchHead(ctx, "tools", "master")
277+
if err != nil {
278+
t.Fatalf("ReadBranchHead() should be able to get revision of master branch's head: %v", err)
279+
}
280+
if headMasterAfter != headMaster {
281+
t.Errorf("updateCodeReviewConfig() should not change master branch's head, got = %s want = %s", headMasterAfter, headMaster)
282+
}
283+
284+
// master branch's head codereview.cfg content should not change.
285+
configMasterAfter, err := gerritClient.ReadFile(ctx, "tools", headMasterAfter, "codereview.cfg")
286+
if err != nil {
287+
t.Fatalf("ReadFile() should be able to read the codereview.cfg file from master branch head: %v", err)
288+
}
289+
if diff := cmp.Diff(configMaster, configMasterAfter); diff != "" {
290+
t.Errorf("updateCodeReviewConfig() should not change codereview.cfg content in master branch (-want +got) \n %s", diff)
291+
}
292+
293+
// verify the release branch commit have the expected behavior.
294+
headReleaseAfter, err := gerritClient.ReadBranchHead(ctx, "tools", releaseBranch)
295+
if err != nil {
296+
t.Fatalf("ReadBranchHead() should be able to get revision of master branch's head: %v", err)
297+
}
298+
if tc.wantCommit && headReleaseAfter == headRelease {
299+
t.Errorf("updateCodeReviewConfig() should have one commit to release branch, head of branch got = %s want = %s", headRelease, headReleaseAfter)
300+
} else if !tc.wantCommit && headReleaseAfter != headRelease {
301+
t.Errorf("updateCodeReviewConfig() should have not change release branch's head, got = %s want = %s", headRelease, headReleaseAfter)
302+
}
303+
304+
// verify the release branch configreview.cfg have the expected content.
305+
configReleaseAfter, err := gerritClient.ReadFile(ctx, "tools", headReleaseAfter, "codereview.cfg")
306+
if err != nil {
307+
t.Fatalf("ReadFile() should be able to read the codereview.cfg file from release branch head: %v", err)
308+
}
309+
if diff := cmp.Diff(tc.wantConfig, string(configReleaseAfter)); diff != "" {
310+
t.Errorf("codereview.cfg mismatch (-want +got) \n %s", diff)
311+
}
312+
})
313+
}
314+
}

0 commit comments

Comments
 (0)