Skip to content

Commit

Permalink
internal/task: add step to update gopls version in vscode-go project
Browse files Browse the repository at this point in the history
1. Add new method ListBranches in gerrit client interface returns all
   the branches in a project.
   a. implement the real gerrit client follow rest api doc.
   b. implement the fake gerrit client using git for-each-ref.
2. Add util function returns the current active release branch from
   vscode-go.
3. Add step at the end of gopls pre-release process to update gopls
   version info in vscode-go project.

A local relui screenshot is at golang/vscode-go#3500 (comment)

For golang/vscode-go#3500

Change-Id: Ib88b950df729a2f28133809f5a54311c420b447b
Reviewed-on: https://go-review.googlesource.com/c/build/+/608416
LUCI-TryBot-Result: Go LUCI <[email protected]>
Reviewed-by: Dmitri Shuralyov <[email protected]>
Reviewed-by: Robert Findley <[email protected]>
Reviewed-by: Hyang-Ah Hana Kim <[email protected]>
Auto-Submit: Hongxiang Jiang <[email protected]>
  • Loading branch information
h9jiang authored and gopherbot committed Aug 29, 2024
1 parent d7fe890 commit e049c5c
Show file tree
Hide file tree
Showing 7 changed files with 277 additions and 20 deletions.
9 changes: 9 additions & 0 deletions gerrit/gerrit.go
Original file line number Diff line number Diff line change
Expand Up @@ -867,6 +867,15 @@ func (c *Client) GetProjectBranches(ctx context.Context, name string) (map[strin
return m, nil
}

// ListBranches lists all branches in the project.
//
// See https://gerrit-review.googlesource.com/Documentation/rest-api-projects.html#list-branches.
func (c *Client) ListBranches(ctx context.Context, project string) ([]BranchInfo, error) {
var res []BranchInfo
err := c.do(ctx, &res, "GET", fmt.Sprintf("/projects/%s/branches", url.PathEscape(project)))
return res, err
}

// GetBranch gets a particular branch in project.
//
// See https://gerrit-review.googlesource.com/Documentation/rest-api-projects.html#get-branch.
Expand Down
17 changes: 17 additions & 0 deletions internal/task/fakes.go
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,23 @@ func (g *FakeGerrit) ReadBranchHead(ctx context.Context, project, branch string)
return strings.TrimSpace(string(out)), nil
}

func (g *FakeGerrit) ListBranches(ctx context.Context, project string) ([]gerrit.BranchInfo, error) {
repo, err := g.repo(project)
if err != nil {
return nil, err
}
out, err := repo.dir.RunCommand(ctx, "for-each-ref", "--format=%(refname) %(objectname:short)", "refs/heads/")
if err != nil {
return nil, err
}
var infos []gerrit.BranchInfo
for _, line := range strings.Split(strings.TrimSpace(string(out)), "\n") {
branchCommit := strings.Fields(line)
infos = append(infos, gerrit.BranchInfo{Ref: branchCommit[0], Revision: branchCommit[1]})
}
return infos, nil
}

func (g *FakeGerrit) CreateBranch(ctx context.Context, project, branch string, input gerrit.BranchInput) (string, error) {
repo, err := g.repo(project)
if err != nil {
Expand Down
6 changes: 6 additions & 0 deletions internal/task/gerrit.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ type GerritClient interface {
// ReadBranchHead returns the head of a branch in project.
// If the branch doesn't exist, it returns an error matching gerrit.ErrResourceNotExist.
ReadBranchHead(ctx context.Context, project, branch string) (string, error)
// ListBranches returns the branch info for all the branch in a project.
ListBranches(ctx context.Context, project string) ([]gerrit.BranchInfo, error)
// CreateBranch create the given branch and returns the created branch's revision.
CreateBranch(ctx context.Context, project, branch string, input gerrit.BranchInput) (string, error)
// ListProjects lists all the projects on the server.
Expand Down Expand Up @@ -196,6 +198,10 @@ func (c *RealGerritClient) ReadBranchHead(ctx context.Context, project, branch s
return branchInfo.Revision, nil
}

func (c *RealGerritClient) ListBranches(ctx context.Context, project string) ([]gerrit.BranchInfo, error) {
return c.Client.ListBranches(ctx, project)
}

func (c *RealGerritClient) CreateBranch(ctx context.Context, project, branch string, input gerrit.BranchInput) (string, error) {
branchInfo, err := c.Client.CreateBranch(ctx, project, branch, input)
if err != nil {
Expand Down
91 changes: 82 additions & 9 deletions internal/task/releasegopls.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,9 @@ func (r *ReleaseGoplsTasks) NewPrereleaseDefinition() *wf.Definition {
prereleaseVerified := wf.Action1(wd, "verify installing latest gopls using release branch pre-release version", r.verifyGoplsInstallation, prereleaseVersion)
wf.Action4(wd, "mail announcement", r.mailAnnouncement, semv, prereleaseVersion, dependencyCommit, issue, wf.After(prereleaseVerified))

vsCodeGoChanges := wf.Task3(wd, "update gopls version in vscode-go project", r.updateGoplsVersionInVSCodeGo, reviewers, issue, prereleaseVersion, wf.After(prereleaseVerified))
_ = wf.Task1(wd, "await gopls version update CL submission in vscode-go project", clAwaiter{r.Gerrit}.awaitSubmissions, vsCodeGoChanges)

wf.Output(wd, "version", prereleaseVersion)

return wd
Expand Down Expand Up @@ -221,9 +224,9 @@ func (r *ReleaseGoplsTasks) createBranchIfMinor(ctx *wf.TaskContext, semv semver
//
// It returns an empty string if no such CL is found, otherwise it returns the
// CL's change ID.
func openCL(ctx *wf.TaskContext, gerrit GerritClient, branch, title string) (string, error) {
func openCL(ctx *wf.TaskContext, gerrit GerritClient, repo, branch, title string) (string, error) {
// Query for an existing pending config CL, to avoid duplication.
query := fmt.Sprintf(`message:%q status:open owner:[email protected] repo:tools branch:%q -age:7d`, title, branch)
query := fmt.Sprintf(`message:%q status:open owner:[email protected] repo:%s branch:%q -age:7d`, title, repo, branch)
changes, err := gerrit.QueryChanges(ctx, query)
if err != nil {
return "", err
Expand All @@ -245,7 +248,7 @@ func (r *ReleaseGoplsTasks) updateCodeReviewConfig(ctx *wf.TaskContext, semv sem
branch := goplsReleaseBranchName(semv)
clTitle := fmt.Sprintf("all: update %s for %s", configFile, branch)

openCL, err := openCL(ctx, r.Gerrit, branch, clTitle)
openCL, err := openCL(ctx, r.Gerrit, "tools", branch, clTitle)
if err != nil {
return "", fmt.Errorf("failed to find the open CL of title %q in branch %q: %w", clTitle, branch, err)
}
Expand Down Expand Up @@ -342,7 +345,7 @@ func (r *ReleaseGoplsTasks) updateXToolsDependency(ctx *wf.TaskContext, semv sem

branch := goplsReleaseBranchName(semv)
clTitle := fmt.Sprintf("gopls: update go.mod for v%v.%v.%v-%s", semv.Major, semv.Minor, semv.Patch, pre)
openCL, err := openCL(ctx, r.Gerrit, branch, clTitle)
openCL, err := openCL(ctx, r.Gerrit, "tools", branch, clTitle)
if err != nil {
return "", fmt.Errorf("failed to find the open CL of title %q in branch %q: %w", clTitle, branch, err)
}
Expand All @@ -363,7 +366,7 @@ go get golang.org/x/tools@%s
go mod tidy -compat=1.19
`, head)

changedFiles, err := executeAndMonitorChange(ctx, r.CloudBuild, branch, script, []string{"gopls/go.mod", "gopls/go.sum"})
changedFiles, err := executeAndMonitorChange(ctx, r.CloudBuild, "tools", branch, script, []string{"gopls/go.mod", "gopls/go.sum"})
if err != nil {
return "", err
}
Expand Down Expand Up @@ -467,6 +470,53 @@ func (r *ReleaseGoplsTasks) mailAnnouncement(ctx *wf.TaskContext, semv semversio
return r.SendMail(r.AnnounceMailHeader, content)
}

func (r *ReleaseGoplsTasks) updateGoplsVersionInVSCodeGo(ctx *wf.TaskContext, reviewers []string, issue int64, version string) ([]string, error) {
releaseBranch, err := vsCodeGoActiveReleaseBranch(ctx, r.Gerrit)
if err != nil {
return nil, err
}
var changes []string
for _, branch := range []string{"master", releaseBranch} {
clTitle := fmt.Sprintf(`extension/src/goToolsInformation: update gopls version %s`, version)
if branch != "master" {
clTitle = "[" + branch + "] " + clTitle
}
openCL, err := openCL(ctx, r.Gerrit, "vscode-go", branch, clTitle)
if err != nil {
return nil, fmt.Errorf("failed to find the open CL of title %q in branch %q: %w", clTitle, branch, err)
}
if openCL != "" {
ctx.Printf("not creating CL: found existing CL %s", openCL)
changes = append(changes, openCL)
continue
}
const script = `go run -C extension tools/generate.go -tools`
changedFiles, err := executeAndMonitorChange(ctx, r.CloudBuild, "vscode-go", branch, script, []string{"extension/src/goToolsInformation.ts"})
if err != nil {
return nil, err
}

// Skip CL creation as nothing changed.
if len(changedFiles) == 0 {
return nil, nil
}

changeInput := gerrit.ChangeInput{
Project: "vscode-go",
Branch: branch,
Subject: fmt.Sprintf("%s\n\nThis is an automated CL which updates the gopls version.\n\nFor golang/go#%v", clTitle, issue),
}

ctx.Printf("creating auto-submit change under branch %q in vscode-go repo.", branch)
changeID, err := r.Gerrit.CreateAutoSubmitChange(ctx, changeInput, reviewers, changedFiles)
if err != nil {
return nil, err
}
changes = append(changes, changeID)
}
return changes, nil
}

func (r *ReleaseGoplsTasks) isValidReleaseVersion(ctx *wf.TaskContext, ver string) error {
if !semver.IsValid(ver) {
return fmt.Errorf("the input %q version does not follow semantic version schema", ver)
Expand Down Expand Up @@ -706,7 +756,7 @@ func (r *ReleaseGoplsTasks) updateDependencyIfMinor(ctx *wf.TaskContext, reviewe
}

clTitle := fmt.Sprintf("gopls/go.mod: update dependencies following the v%v.%v.%v release", semv.Major, semv.Minor, semv.Patch)
openCL, err := openCL(ctx, r.Gerrit, "master", clTitle)
openCL, err := openCL(ctx, r.Gerrit, "tools", "master", clTitle)
if err != nil {
return "", fmt.Errorf("failed to find the open CL of title %q in master branch: %w", clTitle, err)
}
Expand All @@ -722,7 +772,7 @@ pwd
go get -u all
go mod tidy -compat=1.19
`
changed, err := executeAndMonitorChange(ctx, r.CloudBuild, "master", script, []string{"gopls/go.mod", "gopls/go.sum"})
changed, err := executeAndMonitorChange(ctx, r.CloudBuild, "tools", "master", script, []string{"gopls/go.mod", "gopls/go.sum"})
if err != nil {
return "", err
}
Expand All @@ -747,7 +797,7 @@ go mod tidy -compat=1.19
//
// Returns a map where keys are the filenames of modified files and values are
// their corresponding content after script execution.
func executeAndMonitorChange(ctx *wf.TaskContext, cloudBuild CloudBuildClient, branch, script string, watchFiles []string) (map[string]string, error) {
func executeAndMonitorChange(ctx *wf.TaskContext, cloudBuild CloudBuildClient, project, branch, script string, watchFiles []string) (map[string]string, error) {
// Checkout to the provided branch.
fullScript := fmt.Sprintf(`git checkout %s
git rev-parse --abbrev-ref HEAD
Expand Down Expand Up @@ -775,7 +825,7 @@ fi
outputFiles = append(outputFiles, file+".before")
outputFiles = append(outputFiles, file)
}
build, err := cloudBuild.RunScript(ctx, fullScript, "tools", outputFiles)
build, err := cloudBuild.RunScript(ctx, fullScript, project, outputFiles)
if err != nil {
return nil, err
}
Expand All @@ -801,6 +851,8 @@ type clAwaiter struct {
GerritClient
}

// awaitSubmission waits for the specified change to be submitted, then returns
// the corresponding commit hash.
func (c clAwaiter) awaitSubmission(ctx *wf.TaskContext, changeID string) (string, error) {
if changeID == "" {
ctx.Printf("not awaiting: no CL was created")
Expand All @@ -812,3 +864,24 @@ func (c clAwaiter) awaitSubmission(ctx *wf.TaskContext, changeID string) (string
return c.Submitted(ctx, changeID, "")
})
}

// awaitSubmissions waits for the specified changes to be submitted, then
// returns a slice of commit hashes corresponding to the input change IDs,
// maintaining the original input order.
func (c clAwaiter) awaitSubmissions(ctx *wf.TaskContext, changeIDs []string) ([]string, error) {
if len(changeIDs) == 0 {
ctx.Printf("not awaiting: no CL was created")
return nil, nil
}

var commits []string
for _, changeID := range changeIDs {
commit, err := c.awaitSubmission(ctx, changeID)
if err != nil {
return nil, err
}
commits = append(commits, commit)
}

return commits, nil
}
60 changes: 49 additions & 11 deletions internal/task/releasegopls_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -628,6 +628,10 @@ parent-branch: master
ctx, cancel := context.WithCancel(context.Background())
defer cancel()

vscodego := NewFakeRepo(t, "vscode-go")
initial := vscodego.Commit(map[string]string{"extension/src/goToolsInformation.ts": "foo"})
vscodego.Branch("release-v0.44", initial)

tools := NewFakeRepo(t, "tools")
beforeHead := tools.Commit(map[string]string{
"gopls/go.mod": "module golang.org/x/tools\n",
Expand Down Expand Up @@ -655,7 +659,7 @@ parent-branch: master
tools.Branch(goplsReleaseBranchName(tc.semv), beforeHead)
}

gerrit := NewFakeGerrit(t, tools)
gerrit := NewFakeGerrit(t, tools, vscodego)

// fakeGo handles multiple arguments in gopls pre-release flow.
// - go get will write fake go.sum and go.mod to simulate pining the
Expand All @@ -664,6 +668,8 @@ parent-branch: master
// permission to it to simulate gopls installation.
// - go env will return the current dir so gopls will point to the fake
// script that is written by go install.
// - go run will write "bar" content to file in vscode-go project
// containing gopls versions.
// - go mod will exit without any error.
var fakeGo = fmt.Sprintf(`#!/bin/bash -exu
Expand Down Expand Up @@ -702,6 +708,10 @@ EOF
"mod")
exit 0
;;
"run")
echo -n "bar" > extension/src/goToolsInformation.ts
exit 0
;;
*)
echo unexpected command $@
exit 1
Expand Down Expand Up @@ -744,24 +754,48 @@ esac`, tc.wantVersion)

// Verify the content of following files are expected.
contentChecks := []struct {
path string
want string
repo string
branch string
path string
want string
}{
{
path: "codereview.cfg",
want: tc.wantConfig,
repo: "tools",
branch: goplsReleaseBranchName(tc.semv),
path: "codereview.cfg",
want: tc.wantConfig,
},
{
repo: "tools",
branch: goplsReleaseBranchName(tc.semv),
path: "gopls/go.sum",
want: "test go sum",
},
{
path: "gopls/go.sum",
want: "test go sum",
repo: "tools",
branch: goplsReleaseBranchName(tc.semv),
path: "gopls/go.mod",
want: "test go mod",
},
{
path: "gopls/go.mod",
want: "test go mod",
repo: "vscode-go",
branch: "master",
path: "extension/src/goToolsInformation.ts",
want: "bar",
},
{
repo: "vscode-go",
branch: "release-v0.44",
path: "extension/src/goToolsInformation.ts",
want: "bar",
},
}
for _, check := range contentChecks {
got, err := gerrit.ReadFile(ctx, "tools", afterHead, check.path)
commit, err := gerrit.ReadBranchHead(ctx, check.repo, check.branch)
if err != nil {
t.Fatal(err)
}
got, err := gerrit.ReadFile(ctx, check.repo, commit, check.path)
if err != nil {
t.Fatal(err)
}
Expand Down Expand Up @@ -1058,7 +1092,11 @@ echo -n "foo" > file_b
}

cloudBuild := NewFakeCloudBuild(t, NewFakeGerrit(t, tools), "", nil, fakeGo)
got, err := executeAndMonitorChange(&workflow.TaskContext{Context: context.Background(), Logger: &testLogger{t, ""}}, cloudBuild, tc.branch, tc.script, tc.watch)
ctx := &workflow.TaskContext{
Context: context.Background(),
Logger: &testLogger{t, ""},
}
got, err := executeAndMonitorChange(ctx, cloudBuild, "tools", tc.branch, tc.script, tc.watch)
if err != nil {
t.Fatal(err)
}
Expand Down
Loading

0 comments on commit e049c5c

Please sign in to comment.