1 // 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 5 package main 6 7 /* 8 Tool for bisecting failed rolls. 9 */ 10 11 import ( 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 30 var ( 31 // Flags. 32 autoRollerAccount = flag.String("autoroller_account", "skia-deps-roller (a] 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 39 func log(tmpl string, a ...interface{}) { 40 fmt.Println(fmt.Sprintf(tmpl, a...)) 41 } 42 43 func bail(a ...interface{}) { 44 fmt.Fprintln(os.Stderr, a...) 45 os.Exit(1) 46 } 47 48 func 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 } 210