blob: 690b7707bdb4299e89d05a1c2940b89d0608199f [file] [log] [blame]
package helper
import (
"bytes"
"context"
"fmt"
"io/fs"
"io/ioutil"
"os"
"path/filepath"
"regexp"
"strings"
"github.com/bazelbuild/buildtools/build"
"go.skia.org/infra/go/auth"
"go.skia.org/infra/go/cq"
"go.skia.org/infra/go/gerrit"
"go.skia.org/infra/go/git"
"go.skia.org/infra/go/gitiles"
"go.skia.org/infra/go/httputils"
"go.skia.org/infra/go/skerr"
"go.skia.org/infra/go/supported_branches"
"go.skia.org/infra/go/util"
)
const DefaultMainStarFile = "main.star"
// AddSupportedBranch adds a new supported branch, optionally deleting an old
// supported branch.
func AddSupportedBranch(repoUrl, branch, owner, deleteBranch string, excludeTrybots []string, submit bool) error {
newRef := git.FullyQualifiedBranchName(branch)
excludeTrybotRegexp := make([]*regexp.Regexp, 0, len(excludeTrybots))
for _, excludeTrybot := range excludeTrybots {
re, err := regexp.Compile(excludeTrybot)
if err != nil {
return skerr.Wrapf(err, "failed to compile regular expression from %q", excludeTrybot)
}
excludeTrybotRegexp = append(excludeTrybotRegexp, re)
}
// Setup.
wd, err := ioutil.TempDir("", "new-branch")
if err != nil {
return skerr.Wrap(err)
}
defer util.RemoveAll(wd)
ts, err := auth.NewDefaultTokenSource(true, auth.SCOPE_GERRIT)
if err != nil {
return skerr.Wrap(err)
}
client := httputils.DefaultClientConfig().WithTokenSource(ts).Client()
gUrl := strings.Split(repoUrl, ".googlesource.com")[0] + "-review.googlesource.com"
g, err := gerrit.NewGerrit(gUrl, client)
if err != nil {
return skerr.Wrap(err)
}
repo := gitiles.NewRepo(repoUrl, client)
ctx := context.Background()
baseCommitInfo, err := repo.Details(ctx, cq.CQ_CFG_REF)
if err != nil {
return skerr.Wrap(err)
}
baseCommit := baseCommitInfo.Hash
tmp, err := ioutil.TempDir("", "")
if err != nil {
return skerr.Wrap(err)
}
defer util.RemoveAll(tmp)
// Download the CQ config file and modify it.
mainStarFile := filepath.Join(tmp, DefaultMainStarFile)
if err := repo.DownloadFileAtRef(ctx, DefaultMainStarFile, baseCommit, mainStarFile); err != nil {
return skerr.Wrapf(err, "failed to download %q", DefaultMainStarFile)
}
if err := cq.WithUpdateCQConfig(ctx, mainStarFile, filepath.Join(tmp, "generated"), func(f *build.File) error {
_, _, err := cq.FindExprForBranch(f, branch)
if err == nil {
_, _ = fmt.Fprintf(os.Stderr, "Already have %s in %s; not adding a duplicate.\n", newRef, cq.CQ_CFG_FILE)
} else {
if err := cq.CloneBranch(f, git.MainBranch, git.BranchBaseName(branch), false, false, excludeTrybotRegexp); err != nil {
return skerr.Wrap(err)
}
}
if deleteBranch != "" {
if _, _, err := cq.FindExprForBranch(f, deleteBranch); err == nil {
if err := cq.DeleteBranch(f, deleteBranch); err != nil {
return skerr.Wrap(err)
}
}
}
return nil
}); err != nil {
return skerr.Wrap(err)
}
// Download and modify the supported-branches.json file.
branchesContents, err := repo.ReadFileAtRef(ctx, supported_branches.SUPPORTED_BRANCHES_FILE, baseCommit)
if err != nil {
return skerr.Wrap(err)
}
sbc, err := supported_branches.DecodeConfig(bytes.NewReader(branchesContents))
if err != nil {
return skerr.Wrap(err)
}
deleteRef := ""
if deleteBranch != "" {
deleteRef = git.FullyQualifiedBranchName(deleteBranch)
}
foundNewRef := false
newBranches := make([]*supported_branches.SupportedBranch, 0, len(sbc.Branches)+1)
for _, sb := range sbc.Branches {
if deleteRef == "" || deleteRef != sb.Ref {
newBranches = append(newBranches, sb)
}
if sb.Ref == newRef {
foundNewRef = true
}
}
if foundNewRef {
_, _ = fmt.Fprintf(os.Stderr, "Already have %s in %s; not adding a duplicate.\n", newRef, supported_branches.SUPPORTED_BRANCHES_FILE)
} else {
newBranches = append(newBranches, &supported_branches.SupportedBranch{
Ref: newRef,
Owner: owner,
})
}
sbc.Branches = newBranches
buf := bytes.Buffer{}
if err := supported_branches.EncodeConfig(&buf, sbc); err != nil {
return skerr.Wrap(err)
}
// Create the Gerrit CL.
commitMsg := fmt.Sprintf("Add supported branch %s", branch)
if deleteBranch != "" {
commitMsg += fmt.Sprintf(", remove %s", deleteBranch)
}
repoSplit := strings.Split(repoUrl, "/")
project := strings.TrimSuffix(repoSplit[len(repoSplit)-1], ".git")
changes := map[string]string{
supported_branches.SUPPORTED_BRANCHES_FILE: string(buf.Bytes()),
}
if err := filepath.Walk(tmp, func(path string, info fs.FileInfo, err error) error {
if !info.IsDir() {
relpath, err := filepath.Rel(tmp, path)
if err != nil {
return skerr.Wrap(err)
}
contents, err := ioutil.ReadFile(path)
if err != nil {
return skerr.Wrap(err)
}
remoteContents, err := repo.ReadFileAtRef(ctx, relpath, cq.CQ_CFG_REF)
if err != nil {
return skerr.Wrapf(err, "failed to retrieve %q", relpath)
}
if string(contents) != string(remoteContents) {
changes[relpath] = string(contents)
}
}
return nil
}); err != nil {
return skerr.Wrap(err)
}
ci, err := gerrit.CreateCLWithChanges(ctx, g, project, cq.CQ_CFG_REF, commitMsg, baseCommit, changes, submit)
if ci != nil {
fmt.Println(fmt.Sprintf("Uploaded change %s", g.Url(ci.Issue)))
}
return skerr.Wrap(err)
}