210 lines
6.0 KiB
Go
210 lines
6.0 KiB
Go
|
// Copyright 2018 The Chromium Authors. All rights reserved.
|
||
|
// Use of this source code is governed by a BSD-style license that can be
|
||
|
// found in the LICENSE file.
|
||
|
|
||
|
package main
|
||
|
|
||
|
/*
|
||
|
Tool for bisecting failed rolls.
|
||
|
*/
|
||
|
|
||
|
import (
|
||
|
"bufio"
|
||
|
"context"
|
||
|
"flag"
|
||
|
"fmt"
|
||
|
"os"
|
||
|
"os/exec"
|
||
|
"os/user"
|
||
|
"path"
|
||
|
"strings"
|
||
|
"time"
|
||
|
|
||
|
"go.skia.org/infra/autoroll/go/repo_manager"
|
||
|
"go.skia.org/infra/go/autoroll"
|
||
|
"go.skia.org/infra/go/common"
|
||
|
"go.skia.org/infra/go/gerrit"
|
||
|
"go.skia.org/infra/go/util"
|
||
|
)
|
||
|
|
||
|
var (
|
||
|
// Flags.
|
||
|
autoRollerAccount = flag.String("autoroller_account", "skia-deps-roller@chromium.org", "Email address of the autoroller.")
|
||
|
childPath = flag.String("childPath", "src/third_party/skia", "Path within parent repo of the project to roll.")
|
||
|
gerritUrl = flag.String("gerrit", "https://chromium-review.googlesource.com", "URL of the Gerrit server.")
|
||
|
parentRepoUrl = flag.String("parent_repo_url", common.REPO_CHROMIUM, "URL of the parent repo (the child repo rolls into this repo).")
|
||
|
workdir = flag.String("workdir", path.Join(os.TempDir(), "autoroll_bisect"), "Working directory.")
|
||
|
)
|
||
|
|
||
|
func log(tmpl string, a ...interface{}) {
|
||
|
fmt.Println(fmt.Sprintf(tmpl, a...))
|
||
|
}
|
||
|
|
||
|
func bail(a ...interface{}) {
|
||
|
fmt.Fprintln(os.Stderr, a...)
|
||
|
os.Exit(1)
|
||
|
}
|
||
|
|
||
|
func main() {
|
||
|
// Setup.
|
||
|
common.Init()
|
||
|
ctx := context.Background()
|
||
|
|
||
|
log("Updating repos and finding roll attempts; this can take a few minutes...")
|
||
|
|
||
|
// Create the working directory if necessary.
|
||
|
if err := os.MkdirAll(*workdir, os.ModePerm); err != nil {
|
||
|
bail(err)
|
||
|
}
|
||
|
|
||
|
// Create the RepoManager.
|
||
|
gclient, err := exec.LookPath("gclient")
|
||
|
if err != nil {
|
||
|
bail(err)
|
||
|
}
|
||
|
depotTools := path.Dir(gclient)
|
||
|
user, err := user.Current()
|
||
|
if err != nil {
|
||
|
bail(err)
|
||
|
}
|
||
|
gitcookiesPath := path.Join(user.HomeDir, ".gitcookies")
|
||
|
g, err := gerrit.NewGerrit(*gerritUrl, gitcookiesPath, nil)
|
||
|
if err != nil {
|
||
|
bail("Failed to create Gerrit client:", err)
|
||
|
}
|
||
|
g.TurnOnAuthenticatedGets()
|
||
|
childBranch := "master"
|
||
|
strat, err := repo_manager.GetNextRollStrategy(repo_manager.ROLL_STRATEGY_BATCH, childBranch, "")
|
||
|
if err != nil {
|
||
|
bail(err)
|
||
|
}
|
||
|
rm, err := repo_manager.NewDEPSRepoManager(ctx, *workdir, *parentRepoUrl, "master", *childPath, childBranch, depotTools, g, strat, nil, true, nil, "(local run)")
|
||
|
if err != nil {
|
||
|
bail(err)
|
||
|
}
|
||
|
|
||
|
// Determine the set of not-yet-rolled commits.
|
||
|
lastRoll := rm.LastRollRev()
|
||
|
nextRoll := rm.NextRollRev()
|
||
|
commits, err := rm.ChildRevList(ctx, fmt.Sprintf("%s..%s", lastRoll, nextRoll))
|
||
|
if err != nil {
|
||
|
bail(err)
|
||
|
}
|
||
|
if len(commits) == 0 {
|
||
|
log("Repo is up-to-date.")
|
||
|
os.Exit(0)
|
||
|
} else if len(commits) == 1 {
|
||
|
log("Recommend reverting commit %s", commits[0])
|
||
|
os.Exit(0)
|
||
|
}
|
||
|
|
||
|
// Next, find any failed roll CLs.
|
||
|
// TODO(borenet): Use the timestamp of the last-rolled commit.
|
||
|
lastRollTime := time.Now().Add(-24 * time.Hour)
|
||
|
modAfter := gerrit.SearchModifiedAfter(lastRollTime)
|
||
|
cls, err := g.Search(500, modAfter, gerrit.SearchOwner(*autoRollerAccount))
|
||
|
if err != nil {
|
||
|
bail(err)
|
||
|
}
|
||
|
cls2, err := g.Search(500, modAfter, gerrit.SearchOwner("self"))
|
||
|
if err != nil {
|
||
|
bail(err)
|
||
|
}
|
||
|
cls = append(cls, cls2...)
|
||
|
|
||
|
// Filter out CLs which don't look like rolls, de-duplicate CLs which
|
||
|
// roll to the same commit, taking the most recent.
|
||
|
rollCls := make(map[string]*autoroll.AutoRollIssue, len(cls))
|
||
|
fullHashFn := func(hash string) (string, error) {
|
||
|
return rm.FullChildHash(ctx, hash)
|
||
|
}
|
||
|
for _, cl := range cls {
|
||
|
issue, err := autoroll.FromGerritChangeInfo(cl, fullHashFn, false)
|
||
|
if err == nil {
|
||
|
if old, ok := rollCls[issue.RollingTo]; !ok || ok && issue.Modified.After(old.Modified) {
|
||
|
rollCls[issue.RollingTo] = issue
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Report the summary of the not-rolled commits and their associated
|
||
|
// roll results to the user.
|
||
|
log("%d commits have not yet rolled:", len(commits))
|
||
|
earliestFail := -1
|
||
|
latestFail := -1
|
||
|
latestSuccess := -1 // eg. dry runs.
|
||
|
for idx, commit := range commits {
|
||
|
if cl, ok := rollCls[commit]; ok {
|
||
|
log("%s roll %s", commit[:12], cl.Result)
|
||
|
if util.In(cl.Result, autoroll.FAILURE_RESULTS) {
|
||
|
earliestFail = idx
|
||
|
if latestFail == -1 {
|
||
|
latestFail = idx
|
||
|
}
|
||
|
} else if util.In(cl.Result, autoroll.SUCCESS_RESULTS) && latestSuccess == -1 {
|
||
|
latestSuccess = idx
|
||
|
}
|
||
|
} else {
|
||
|
log(commit[:12])
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Suggest a commit to try rolling. The user may choose a different one.
|
||
|
suggestedCommit := ""
|
||
|
if latestSuccess != -1 {
|
||
|
suggestedCommit = commits[latestSuccess]
|
||
|
log("Recommend landing successful roll %s/%d", *gerritUrl, rollCls[suggestedCommit].Issue)
|
||
|
} else if latestFail != 0 {
|
||
|
suggestedCommit = commits[0]
|
||
|
if issue, ok := rollCls[suggestedCommit]; ok && issue.Result == autoroll.ROLL_RESULT_IN_PROGRESS {
|
||
|
log("Recommend waiting for the current in-progress roll to finish: %s/%d", *gerritUrl, issue.Issue)
|
||
|
suggestedCommit = ""
|
||
|
} else {
|
||
|
log("Recommend trying a roll at %s which has not yet been tried.", suggestedCommit)
|
||
|
}
|
||
|
} else if earliestFail == 0 {
|
||
|
log("Recommend reverting commit %s", commits[earliestFail])
|
||
|
} else {
|
||
|
// Bisect the commits which have not yet failed.
|
||
|
remaining := commits[earliestFail+1:]
|
||
|
idx := len(remaining) / 2
|
||
|
suggestedCommit = remaining[idx]
|
||
|
log("Recommend trying a roll at %s", suggestedCommit)
|
||
|
}
|
||
|
|
||
|
// Ask the user what commit to roll.
|
||
|
msg := "Type a commit hash to roll"
|
||
|
if suggestedCommit != "" {
|
||
|
msg += fmt.Sprintf(" (press enter to roll at suggested commit %s)", suggestedCommit[:12])
|
||
|
}
|
||
|
log("%s:", msg)
|
||
|
reader := bufio.NewReader(os.Stdin)
|
||
|
text, err := reader.ReadString('\n')
|
||
|
if err != nil {
|
||
|
bail(err)
|
||
|
}
|
||
|
text = strings.TrimSpace(text)
|
||
|
if text == "" && suggestedCommit != "" {
|
||
|
text = suggestedCommit
|
||
|
}
|
||
|
if text == "" {
|
||
|
bail("You must enter a commit hash.")
|
||
|
}
|
||
|
log("Attempting a roll at %q", text)
|
||
|
rollTo, err := rm.FullChildHash(ctx, text)
|
||
|
if err != nil {
|
||
|
bail(text, "is not a valid commit hash:", text, err)
|
||
|
}
|
||
|
|
||
|
// Upload a roll.
|
||
|
email, err := g.GetUserEmail()
|
||
|
if err != nil {
|
||
|
bail(err)
|
||
|
}
|
||
|
issue, err := rm.CreateNewRoll(ctx, lastRoll, rollTo, []string{email}, "", false)
|
||
|
if err != nil {
|
||
|
bail(err)
|
||
|
}
|
||
|
log("Uploaded %s/%d", *gerritUrl, issue)
|
||
|
}
|