blob: 3bc4262c91e302de0403fdd81ff0284392592870 [file] [log] [blame]
Eric Borend75fdc62018-01-11 13:45:17 -05001// Copyright 2018 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5package main
6
7/*
8 Tool for bisecting failed rolls.
9*/
10
11import (
12 "bufio"
13 "context"
14 "flag"
15 "fmt"
16 "os"
17 "os/exec"
18 "os/user"
19 "path"
20 "strings"
21 "time"
22
23 "go.skia.org/infra/autoroll/go/repo_manager"
24 "go.skia.org/infra/go/autoroll"
25 "go.skia.org/infra/go/common"
26 "go.skia.org/infra/go/gerrit"
27 "go.skia.org/infra/go/util"
28)
29
30var (
31 // Flags.
32 autoRollerAccount = flag.String("autoroller_account", "skia-deps-roller@chromium.org", "Email address of the autoroller.")
33 childPath = flag.String("childPath", "src/third_party/skia", "Path within parent repo of the project to roll.")
34 gerritUrl = flag.String("gerrit", "https://chromium-review.googlesource.com", "URL of the Gerrit server.")
35 parentRepoUrl = flag.String("parent_repo_url", common.REPO_CHROMIUM, "URL of the parent repo (the child repo rolls into this repo).")
36 workdir = flag.String("workdir", path.Join(os.TempDir(), "autoroll_bisect"), "Working directory.")
37)
38
39func log(tmpl string, a ...interface{}) {
40 fmt.Println(fmt.Sprintf(tmpl, a...))
41}
42
43func bail(a ...interface{}) {
44 fmt.Fprintln(os.Stderr, a...)
45 os.Exit(1)
46}
47
48func main() {
49 // Setup.
50 common.Init()
51 ctx := context.Background()
52
53 log("Updating repos and finding roll attempts; this can take a few minutes...")
54
55 // Create the working directory if necessary.
56 if err := os.MkdirAll(*workdir, os.ModePerm); err != nil {
57 bail(err)
58 }
59
60 // Create the RepoManager.
61 gclient, err := exec.LookPath("gclient")
62 if err != nil {
63 bail(err)
64 }
65 depotTools := path.Dir(gclient)
66 user, err := user.Current()
67 if err != nil {
68 bail(err)
69 }
70 gitcookiesPath := path.Join(user.HomeDir, ".gitcookies")
71 g, err := gerrit.NewGerrit(*gerritUrl, gitcookiesPath, nil)
72 if err != nil {
73 bail("Failed to create Gerrit client:", err)
74 }
75 g.TurnOnAuthenticatedGets()
76 childBranch := "master"
77 strat, err := repo_manager.GetNextRollStrategy(repo_manager.ROLL_STRATEGY_BATCH, childBranch, "")
78 if err != nil {
79 bail(err)
80 }
81 rm, err := repo_manager.NewDEPSRepoManager(ctx, *workdir, *parentRepoUrl, "master", *childPath, childBranch, depotTools, g, strat, nil, true, nil, "(local run)")
82 if err != nil {
83 bail(err)
84 }
85
86 // Determine the set of not-yet-rolled commits.
87 lastRoll := rm.LastRollRev()
88 nextRoll := rm.NextRollRev()
89 commits, err := rm.ChildRevList(ctx, fmt.Sprintf("%s..%s", lastRoll, nextRoll))
90 if err != nil {
91 bail(err)
92 }
93 if len(commits) == 0 {
94 log("Repo is up-to-date.")
95 os.Exit(0)
96 } else if len(commits) == 1 {
97 log("Recommend reverting commit %s", commits[0])
98 os.Exit(0)
99 }
100
101 // Next, find any failed roll CLs.
102 // TODO(borenet): Use the timestamp of the last-rolled commit.
103 lastRollTime := time.Now().Add(-24 * time.Hour)
104 modAfter := gerrit.SearchModifiedAfter(lastRollTime)
105 cls, err := g.Search(500, modAfter, gerrit.SearchOwner(*autoRollerAccount))
106 if err != nil {
107 bail(err)
108 }
109 cls2, err := g.Search(500, modAfter, gerrit.SearchOwner("self"))
110 if err != nil {
111 bail(err)
112 }
113 cls = append(cls, cls2...)
114
115 // Filter out CLs which don't look like rolls, de-duplicate CLs which
116 // roll to the same commit, taking the most recent.
117 rollCls := make(map[string]*autoroll.AutoRollIssue, len(cls))
118 fullHashFn := func(hash string) (string, error) {
119 return rm.FullChildHash(ctx, hash)
120 }
121 for _, cl := range cls {
122 issue, err := autoroll.FromGerritChangeInfo(cl, fullHashFn, false)
123 if err == nil {
124 if old, ok := rollCls[issue.RollingTo]; !ok || ok && issue.Modified.After(old.Modified) {
125 rollCls[issue.RollingTo] = issue
126 }
127 }
128 }
129
130 // Report the summary of the not-rolled commits and their associated
131 // roll results to the user.
132 log("%d commits have not yet rolled:", len(commits))
133 earliestFail := -1
134 latestFail := -1
135 latestSuccess := -1 // eg. dry runs.
136 for idx, commit := range commits {
137 if cl, ok := rollCls[commit]; ok {
138 log("%s roll %s", commit[:12], cl.Result)
139 if util.In(cl.Result, autoroll.FAILURE_RESULTS) {
140 earliestFail = idx
141 if latestFail == -1 {
142 latestFail = idx
143 }
144 } else if util.In(cl.Result, autoroll.SUCCESS_RESULTS) && latestSuccess == -1 {
145 latestSuccess = idx
146 }
147 } else {
148 log(commit[:12])
149 }
150 }
151
152 // Suggest a commit to try rolling. The user may choose a different one.
153 suggestedCommit := ""
154 if latestSuccess != -1 {
155 suggestedCommit = commits[latestSuccess]
156 log("Recommend landing successful roll %s/%d", *gerritUrl, rollCls[suggestedCommit].Issue)
157 } else if latestFail != 0 {
158 suggestedCommit = commits[0]
159 if issue, ok := rollCls[suggestedCommit]; ok && issue.Result == autoroll.ROLL_RESULT_IN_PROGRESS {
160 log("Recommend waiting for the current in-progress roll to finish: %s/%d", *gerritUrl, issue.Issue)
161 suggestedCommit = ""
162 } else {
163 log("Recommend trying a roll at %s which has not yet been tried.", suggestedCommit)
164 }
165 } else if earliestFail == 0 {
166 log("Recommend reverting commit %s", commits[earliestFail])
167 } else {
168 // Bisect the commits which have not yet failed.
169 remaining := commits[earliestFail+1:]
170 idx := len(remaining) / 2
171 suggestedCommit = remaining[idx]
172 log("Recommend trying a roll at %s", suggestedCommit)
173 }
174
175 // Ask the user what commit to roll.
176 msg := "Type a commit hash to roll"
177 if suggestedCommit != "" {
178 msg += fmt.Sprintf(" (press enter to roll at suggested commit %s)", suggestedCommit[:12])
179 }
180 log("%s:", msg)
181 reader := bufio.NewReader(os.Stdin)
182 text, err := reader.ReadString('\n')
183 if err != nil {
184 bail(err)
185 }
186 text = strings.TrimSpace(text)
187 if text == "" && suggestedCommit != "" {
188 text = suggestedCommit
189 }
190 if text == "" {
191 bail("You must enter a commit hash.")
192 }
193 log("Attempting a roll at %q", text)
194 rollTo, err := rm.FullChildHash(ctx, text)
195 if err != nil {
196 bail(text, "is not a valid commit hash:", text, err)
197 }
198
199 // Upload a roll.
200 email, err := g.GetUserEmail()
201 if err != nil {
202 bail(err)
203 }
204 issue, err := rm.CreateNewRoll(ctx, lastRoll, rollTo, []string{email}, "", false)
205 if err != nil {
206 bail(err)
207 }
208 log("Uploaded %s/%d", *gerritUrl, issue)
209}