internal/task: create the branch for the first rc in minor release

A local relui screenshot is at https://github.com/golang/vscode-go/issues/3500#issuecomment-2313610047

For golang/vscode-go#3500

Change-Id: Ie6b5650eef8f84d1fe7264e35894f80043cad109
Reviewed-on: https://go-review.googlesource.com/c/build/+/608817
Reviewed-by: Dmitri Shuralyov <dmitshur@golang.org>
Reviewed-by: Dmitri Shuralyov <dmitshur@google.com>
Auto-Submit: Hongxiang Jiang <hxjiang@golang.org>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
This commit is contained in:
Hongxiang Jiang 2024-08-27 20:44:26 +00:00 коммит произвёл Gopher Robot
Родитель e049c5c65c
Коммит a15ffe2c8f
4 изменённых файлов: 135 добавлений и 1 удалений

Просмотреть файл

@ -220,9 +220,14 @@ func (g *FakeGerrit) ReadBranchHead(ctx context.Context, project, branch string)
if err != nil {
return "", err
}
// TODO: If the branch doesn't exist, return an error matching gerrit.ErrResourceNotExist.
out, err := repo.dir.RunCommand(ctx, "rev-parse", "refs/heads/"+branch)
if err != nil {
// TODO(hxjiang): switch to git show-ref --exists refs/heads/branch after
// upgrade git to 2.43.0.
// https://git-scm.com/docs/git-show-ref/2.43.0#Documentation/git-show-ref.txt---exists
if strings.Contains(err.Error(), "unknown revision or path not in the working tree") {
return "", gerrit.ErrResourceNotExist
}
// Returns empty string if the error is nil to align the same behavior with
// the real Gerrit client.
return "", err

Просмотреть файл

@ -551,6 +551,8 @@ func parseSemver(v string) (_ semversion, ok bool) {
return parsed, ok
}
// prereleaseVersion extracts the integer component from a pre-release version
// string in the format "${STRING}.${INT}".
func (s *semversion) prereleaseVersion() (int, error) {
parts := strings.Split(s.Pre, ".")
if len(parts) == 1 {

Просмотреть файл

@ -6,11 +6,13 @@ package task
import (
_ "embed"
"errors"
"fmt"
"strconv"
"strings"
"github.com/google/go-github/v48/github"
"golang.org/x/build/gerrit"
"golang.org/x/build/internal/relui/groups"
"golang.org/x/build/internal/workflow"
wf "golang.org/x/build/internal/workflow"
@ -130,6 +132,7 @@ func (r *ReleaseVSCodeGoTasks) NewPrereleaseDefinition() *wf.Definition {
approved := wf.Action1(wd, "await release coordinator's approval", r.approveVersion, semv)
_ = wf.Task1(wd, "create release milestone and issue", r.createReleaseMilestoneAndIssue, semv, wf.After(approved))
_ = wf.Action1(wd, "create release branch", r.createReleaseBranch, semv, wf.After(approved))
return wd
}
@ -173,6 +176,52 @@ func (r *ReleaseVSCodeGoTasks) createReleaseMilestoneAndIssue(ctx *wf.TaskContex
return *issue.Number, nil
}
// createReleaseBranch creates corresponding release branch only for the initial
// release candidate of a minor version.
func (r *ReleaseVSCodeGoTasks) createReleaseBranch(ctx *wf.TaskContext, semv semversion) error {
branch := fmt.Sprintf("release-v%v.%v", semv.Major, semv.Minor)
releaseHead, err := r.Gerrit.ReadBranchHead(ctx, "vscode-go", branch)
if err == nil {
ctx.Printf("Found the release branch %q with head pointing to %s\n", branch, releaseHead)
return nil
}
if !errors.Is(err, gerrit.ErrResourceNotExist) {
return fmt.Errorf("failed to read the release branch: %w", err)
}
// Require vscode release branch existence if this is a non-minor release.
if semv.Patch != 0 {
return fmt.Errorf("release branch is required for patch releases: %w", err)
}
rc, err := semv.prereleaseVersion()
if err != nil {
return err
}
// Require vscode release branch existence if this is not the first rc in
// a minor release.
if rc != 1 {
return fmt.Errorf("release branch is required for non-initial release candidates: %w", err)
}
// Create the release branch using the revision from the head of master branch.
head, err := r.Gerrit.ReadBranchHead(ctx, "vscode-go", "master")
if err != nil {
return err
}
ctx.DisableRetries() // Beyond this point we want retries to be done manually, not automatically.
_, err = r.Gerrit.CreateBranch(ctx, "vscode-go", branch, gerrit.BranchInput{Revision: head})
if err != nil {
return err
}
ctx.Printf("Created branch %q at revision %s.\n", branch, head)
return nil
}
// nextPrereleaseVersion determines the next pre-release version for the
// upcoming stable release of vscode-go by examining all existing tags in the
// repository.

Просмотреть файл

@ -6,6 +6,7 @@ package task
import (
"context"
"fmt"
"testing"
"github.com/google/go-github/v48/github"
@ -79,6 +80,83 @@ func TestCreateReleaseMilestoneAndIssue(t *testing.T) {
}
}
func TestCreateReleaseBranch(t *testing.T) {
ctx := context.Background()
testcases := []struct {
name string
version string
existingBranch bool
wantErr bool
}{
{
name: "nil if the release branch does not exist for first rc in a minor release",
version: "v0.44.0-rc.1",
existingBranch: false,
wantErr: false,
},
{
name: "nil if the release branch already exist for non-initial rc in a minor release",
version: "v0.44.0-rc.4",
existingBranch: true,
wantErr: false,
},
{
name: "fail if the release branch does not exist for non-initial rc in a minor release",
version: "v0.44.0-rc.4",
existingBranch: false,
wantErr: true,
},
{
name: "nil if the release branch already exist for a patch version",
version: "v0.44.3-rc.3",
existingBranch: true,
wantErr: false,
},
{
name: "fail if the release branch does not exist for a patch version",
version: "v0.44.3-rc.3",
existingBranch: false,
wantErr: true,
},
}
for _, tc := range testcases {
t.Run(tc.name, func(t *testing.T) {
semv, ok := parseSemver(tc.version)
if !ok {
t.Fatalf("failed to parse the want version: %q", tc.version)
}
vscodego := NewFakeRepo(t, "vscode-go")
commit := vscodego.Commit(map[string]string{
"go.mod": "module github.com/golang/vscode-go\n",
"go.sum": "\n",
})
if tc.existingBranch {
vscodego.Branch(fmt.Sprintf("release-v%v.%v", semv.Major, semv.Minor), commit)
}
gerrit := NewFakeGerrit(t, vscodego)
tasks := &ReleaseVSCodeGoTasks{
Gerrit: gerrit,
}
err := tasks.createReleaseBranch(&workflow.TaskContext{Context: ctx, Logger: &testLogger{t, ""}}, semv)
if tc.wantErr && err == nil {
t.Errorf("createReleaseBranch(%q) should return error but return nil", tc.version)
} else if !tc.wantErr && err != nil {
t.Errorf("createReleaseBranch(%q) should return nil but return err: %v", tc.version, err)
}
if !tc.wantErr {
if _, err := gerrit.ReadBranchHead(ctx, "vscode-go", fmt.Sprintf("release-v%v.%v", semv.Major, semv.Minor)); err != nil {
t.Errorf("createReleaseBranch(%q) should ensure the release branch creation: %v", tc.version, err)
}
}
})
}
}
func TestNextPrereleaseVersion(t *testing.T) {
tests := []struct {
name string