Skip to content

Commit 5227dc8

Browse files
heschigopherbot
authored andcommitted
internal/task: skip CLs that don't make changes
In the most recent release we retried the DL CL task, and it failed because Gerrit rejected the no-op changes. Change the Gerrit client to return no change in this case, and the surrounding tasks to tolerate it. If this happens for the VERSION CL we will tag whatever the branch head happens to be at the time, which seems correct to me. For golang/go#51797. Change-Id: Ieb69a4c96c7ebe057a53d0fca4d713dca0fc6f47 Reviewed-on: https://go-review.googlesource.com/c/build/+/411897 TryBot-Result: Gopher Robot <[email protected]> Run-TryBot: Heschi Kreinick <[email protected]> Auto-Submit: Heschi Kreinick <[email protected]> Reviewed-by: Alex Rakoczy <[email protected]> Reviewed-by: Dmitri Shuralyov <[email protected]> Reviewed-by: Dmitri Shuralyov <[email protected]>
1 parent c2ca09d commit 5227dc8

File tree

4 files changed

+54
-23
lines changed

4 files changed

+54
-23
lines changed

gerrit/gerrit.go

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,10 @@ func (c *Client) httpClient() *http.Client {
6161
// It is only for use with errors.Is.
6262
var ErrResourceNotExist = errors.New("gerrit: requested resource does not exist")
6363

64+
// ErrNotModified is returned when a modification didn't result in any change.
65+
// It is only for use with errors.Is. Not all APIs return this error; check the documentation.
66+
var ErrNotModified = errors.New("gerrit: requested modification resulted in no change")
67+
6468
// HTTPError is the error type returned when a Gerrit API call does not return
6569
// the expected status.
6670
type HTTPError struct {
@@ -74,7 +78,17 @@ func (e *HTTPError) Error() string {
7478
}
7579

7680
func (e *HTTPError) Is(target error) bool {
77-
return target == ErrResourceNotExist && e.Res.StatusCode == http.StatusNotFound
81+
switch target {
82+
case ErrResourceNotExist:
83+
return e.Res.StatusCode == http.StatusNotFound
84+
case ErrNotModified:
85+
// As of writing, this error text is the only way to distinguish different Conflict errors. See
86+
// https://cs.opensource.google/gerrit/gerrit/gerrit/+/master:java/com/google/gerrit/server/restapi/change/ChangeEdits.java;l=346;drc=d338da307a518f7f28b94310c1c083c997ca3c6a
87+
// https://cs.opensource.google/gerrit/gerrit/gerrit/+/master:java/com/google/gerrit/server/edit/ChangeEditModifier.java;l=453;drc=3bc970bb3e689d1d340382c3f5e5285d44f91dbf
88+
return e.Res.StatusCode == http.StatusConflict && bytes.Contains(e.Body, []byte("no changes were made"))
89+
default:
90+
return false
91+
}
7892
}
7993

8094
// doArg is an optional argument for the Client.do method.
@@ -750,21 +764,16 @@ type ChangeInput struct {
750764
}
751765

752766
// ChangeFileContentInChangeEdit puts content of a file to a change edit.
767+
// If no change is made, an error that matches ErrNotModified is returned.
753768
//
754769
// See https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#put-edit-file.
755770
func (c *Client) ChangeFileContentInChangeEdit(ctx context.Context, changeID string, path string, content string) error {
756-
err := c.do(ctx, nil, "PUT", "/changes/"+changeID+"/edit/"+url.QueryEscape(path),
771+
return c.do(ctx, nil, "PUT", "/changes/"+changeID+"/edit/"+url.QueryEscape(path),
757772
reqBodyRaw{strings.NewReader(content)}, wantResStatus(http.StatusNoContent))
758-
if he, ok := err.(*HTTPError); ok && he.Res.StatusCode == http.StatusConflict {
759-
// The change edit was a no-op.
760-
// Note: If/when there's a need inside x/build to handle this differently,
761-
// maybe it'll be a good time to return something other than a *HTTPError
762-
// and document it as part of the API.
763-
}
764-
return err
765773
}
766774

767775
// DeleteFileInChangeEdit deletes a file from a change edit.
776+
// If no change is made, an error that matches ErrNotModified is returned.
768777
//
769778
// See https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#delete-edit-file.
770779
func (c *Client) DeleteFileInChangeEdit(ctx context.Context, changeID string, path string) error {

internal/relui/workflows.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -297,7 +297,7 @@ func addSingleReleaseWorkflow(build *BuildReleaseTasks, milestone *task.Mileston
297297
branch = "master"
298298
}
299299
branchVal := wd.Constant(branch)
300-
branchHead := wd.Task("Read branch HEAD", version.ReadBranchHead, branchVal)
300+
releaseBase := wd.Task("Pick release base commit", version.ReadBranchHead, branchVal)
301301

302302
// Select version, check milestones.
303303
nextVersion := wd.Task("Get next version", version.GetNextVersion, kindVal)
@@ -308,7 +308,7 @@ func addSingleReleaseWorkflow(build *BuildReleaseTasks, milestone *task.Mileston
308308
wd.Output("Download CL submitted", dlclCommit)
309309

310310
// Build, test, and sign release.
311-
signedAndTestedArtifacts, err := build.addBuildTasks(wd, "go1.19", nextVersion, branchHead, skipTests, checked)
311+
signedAndTestedArtifacts, err := build.addBuildTasks(wd, "go1.19", nextVersion, releaseBase, skipTests, checked)
312312
if err != nil {
313313
return err
314314
}
@@ -319,11 +319,11 @@ func addSingleReleaseWorkflow(build *BuildReleaseTasks, milestone *task.Mileston
319319
// Tag version and upload to CDN/website.
320320
uploaded := wd.Action("Upload artifacts to CDN", build.uploadArtifacts, signedAndTestedArtifacts, verified)
321321

322-
tagCommit := branchHead
322+
tagCommit := releaseBase
323323
if branch != "master" {
324-
branchHeadChecked := wd.Action("Check for modified branch head", version.CheckBranchHead, branchVal, branchHead, uploaded)
324+
branchHeadChecked := wd.Action("Check for modified branch head", version.CheckBranchHead, branchVal, releaseBase, uploaded)
325325
versionCL := wd.Task("Mail version CL", version.CreateAutoSubmitVersionCL, branchVal, nextVersion, branchHeadChecked)
326-
tagCommit = wd.Task("Wait for version CL submission", version.AwaitCL, versionCL, branchHead)
326+
tagCommit = wd.Task("Wait for version CL submission", version.AwaitCL, versionCL, releaseBase)
327327
}
328328
tagged := wd.Action("Tag version", version.TagRelease, nextVersion, tagCommit, uploaded)
329329

internal/task/gerrit.go

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,11 @@ import (
1111
)
1212

1313
type GerritClient interface {
14-
// CreateAutoSubmitChange creates a change with the given metadata and contents, sets
15-
// Run-TryBots and Auto-Submit, and returns its change ID.
14+
// CreateAutoSubmitChange creates a change with the given metadata and
15+
// contents, sets Run-TryBots and Auto-Submit, and returns its change ID.
1616
// If the content of a file is empty, that file will be deleted from the repository.
17+
// If the requested contents match the state of the repository, no change
18+
// is created and the returned change ID will be empty.
1719
CreateAutoSubmitChange(ctx context.Context, input gerrit.ChangeInput, contents map[string]string) (string, error)
1820
// AwaitSubmit waits for the specified change to be auto-submitted or fail
1921
// trybots. If the CL is submitted, returns the submitted commit hash.
@@ -37,16 +39,27 @@ func (c *RealGerritClient) CreateAutoSubmitChange(ctx context.Context, input ger
3739
return "", err
3840
}
3941
changeID := fmt.Sprintf("%s~%d", change.Project, change.ChangeNumber)
42+
anyChange := false
4043
for path, content := range files {
44+
var err error
4145
if content == "" {
42-
if err := c.Client.DeleteFileInChangeEdit(ctx, changeID, path); err != nil {
43-
return "", err
44-
}
46+
err = c.Client.DeleteFileInChangeEdit(ctx, changeID, path)
4547
} else {
46-
if err := c.Client.ChangeFileContentInChangeEdit(ctx, changeID, path, content); err != nil {
47-
return "", err
48-
}
48+
err = c.Client.ChangeFileContentInChangeEdit(ctx, changeID, path, content)
49+
}
50+
if errors.Is(err, gerrit.ErrNotModified) {
51+
continue
52+
}
53+
if err != nil {
54+
return "", err
55+
}
56+
anyChange = true
57+
}
58+
if !anyChange {
59+
if err := c.Client.AbandonChange(ctx, changeID, "no changes necessary"); err != nil {
60+
return "", err
4961
}
62+
return "", nil
5063
}
5164

5265
if err := c.Client.PublishChangeEdit(ctx, changeID); err != nil {

internal/task/version.go

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,8 +84,17 @@ func (t *VersionTasks) CreateAutoSubmitVersionCL(ctx *workflow.TaskContext, bran
8484
})
8585
}
8686

87-
// AwaitCL waits for the specified CL to be submitted.
87+
// AwaitCL waits for the specified CL to be submitted, and returns the new
88+
// branch head. Callers can pass baseCommit, the current branch head, to verify
89+
// that no CLs were submitted between when the CL was created and when it was
90+
// merged. If changeID is blank because the intended CL was a no-op, baseCommit
91+
// is returned immediately.
8892
func (t *VersionTasks) AwaitCL(ctx *workflow.TaskContext, changeID, baseCommit string) (string, error) {
93+
if changeID == "" {
94+
ctx.Printf("No CL was necessary")
95+
return baseCommit, nil
96+
}
97+
8998
ctx.Printf("Awaiting review/submit of %v", ChangeLink(changeID))
9099
return t.Gerrit.AwaitSubmit(ctx, changeID, baseCommit)
91100
}

0 commit comments

Comments
 (0)