github.com/graybobo/golang.org-package-offline-cache@v0.0.0-20200626051047-6608995c132f/x/review/git-codereview/pending.go (about) 1 // Copyright 2014 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package main 6 7 import ( 8 "bytes" 9 "fmt" 10 "io" 11 "net/http" 12 "os" 13 "sort" 14 "strings" 15 "time" 16 ) 17 18 var ( 19 pendingLocal bool // -l flag, use only local operations (no network) 20 pendingCurrentOnly bool // -c flag, show only current branch 21 pendingShort bool // -s flag, short display 22 ) 23 24 // A pendingBranch collects information about a single pending branch. 25 // We overlap the reading of this information for each branch. 26 type pendingBranch struct { 27 *Branch // standard Branch functionality 28 current bool // is this the current branch? 29 staged []string // files in staging area, only if current==true 30 unstaged []string // files unstaged in local directory, only if current==true 31 untracked []string // files untracked in local directory, only if current==true 32 } 33 34 // load populates b with information about the branch. 35 func (b *pendingBranch) load() { 36 b.loadPending() 37 if !b.current && b.commitsAhead == 0 { 38 // Won't be displayed, don't bother looking any closer. 39 return 40 } 41 b.OriginBranch() // cache result 42 if b.current { 43 b.staged, b.unstaged, b.untracked = LocalChanges() 44 } 45 for _, c := range b.Pending() { 46 c.committed = nonBlankLines(cmdOutput("git", "diff", "--name-only", c.Parent, c.Hash, "--")) 47 if !pendingLocal { 48 c.g, c.gerr = b.GerritChange(c, "DETAILED_LABELS", "CURRENT_REVISION", "MESSAGES", "DETAILED_ACCOUNTS") 49 } 50 if c.g == nil { 51 c.g = new(GerritChange) // easier for formatting code 52 } 53 } 54 } 55 56 func cmdPending(args []string) { 57 flags.BoolVar(&pendingCurrentOnly, "c", false, "show only current branch") 58 flags.BoolVar(&pendingLocal, "l", false, "use only local information - no network operations") 59 flags.BoolVar(&pendingShort, "s", false, "show short listing") 60 flags.Parse(args) 61 if len(flags.Args()) > 0 { 62 fmt.Fprintf(stderr(), "Usage: %s pending %s [-c] [-l] [-s]\n", os.Args[0], globalFlags) 63 os.Exit(2) 64 } 65 66 // Fetch info about remote changes, so that we can say which branches need sync. 67 if !pendingLocal { 68 run("git", "fetch", "-q") 69 http.DefaultClient.Timeout = 60 * time.Second 70 } 71 72 // Build list of pendingBranch structs to be filled in. 73 // The current branch is always first. 74 var branches []*pendingBranch 75 branches = []*pendingBranch{{Branch: CurrentBranch(), current: true}} 76 if !pendingCurrentOnly { 77 current := CurrentBranch().Name 78 for _, b := range LocalBranches() { 79 if b.Name != current { 80 branches = append(branches, &pendingBranch{Branch: b}) 81 } 82 } 83 } 84 85 // The various data gathering is a little slow, 86 // especially run in serial with a lot of branches. 87 // Overlap inspection of multiple branches. 88 // Each branch is only accessed by a single worker. 89 90 // Build work queue. 91 work := make(chan *pendingBranch, len(branches)) 92 done := make(chan bool, len(branches)) 93 for _, b := range branches { 94 work <- b 95 } 96 close(work) 97 98 // Kick off goroutines to do work. 99 n := len(branches) 100 if n > 10 { 101 n = 10 102 } 103 for i := 0; i < n; i++ { 104 go func() { 105 for b := range work { 106 b.load() 107 done <- true 108 } 109 }() 110 } 111 112 // Wait for goroutines to finish. 113 // Note: Counting work items, not goroutines (there may be fewer goroutines). 114 for range branches { 115 <-done 116 } 117 118 // Print output. 119 // If there are multiple changes in the current branch, the output splits them out into separate sections, 120 // in reverse commit order, to match git log output. 121 // 122 // wbshadow 7a524a1..a496c1e (current branch, all mailed, 23 behind, tracking master) 123 // + uncommitted changes 124 // Files unstaged: 125 // src/runtime/proc1.go 126 // 127 // + a496c1e https://go-review.googlesource.com/2064 (mailed) 128 // runtime: add missing write barriers in append's copy of slice data 129 // 130 // Found with GODEBUG=wbshadow=1 mode. 131 // Eventually that will run automatically, but right now 132 // it still detects other missing write barriers. 133 // 134 // Change-Id: Ic8624401d7c8225a935f719f96f2675c6f5c0d7c 135 // 136 // Code-Review: 137 // +0 Austin Clements, Rick Hudson 138 // Files in this change: 139 // src/runtime/slice.go 140 // 141 // + 95390c7 https://go-review.googlesource.com/2061 (mailed) 142 // runtime: add GODEBUG wbshadow for finding missing write barriers 143 // 144 // This is the detection code. It works well enough that I know of 145 // a handful of missing write barriers. However, those are subtle 146 // enough that I'll address them in separate followup CLs. 147 // 148 // Change-Id: If863837308e7c50d96b5bdc7d65af4969bf53a6e 149 // 150 // Code-Review: 151 // +0 Austin Clements, Rick Hudson 152 // Files in this change: 153 // src/runtime/extern.go 154 // src/runtime/malloc1.go 155 // src/runtime/malloc2.go 156 // src/runtime/mgc.go 157 // src/runtime/mgc0.go 158 // src/runtime/proc1.go 159 // src/runtime/runtime1.go 160 // src/runtime/runtime2.go 161 // src/runtime/stack1.go 162 // 163 // The first line only gives information that applies to the entire branch: 164 // the name, the commit range, whether this is the current branch, whether 165 // all the commits are mailed/submitted, how far behind, what remote branch 166 // it is tracking. 167 // The individual change sections have per-change information: the hash of that 168 // commit, the URL on the Gerrit server, whether it is mailed/submitted, the list of 169 // files in that commit. The uncommitted file modifications are shown as a separate 170 // section, at the beginning, to fit better into the reverse commit order. 171 // 172 // The short view compresses the listing down to two lines per commit: 173 // wbshadow 7a524a1..a496c1e (current branch, all mailed, 23 behind, tracking master) 174 // + uncommitted changes 175 // Files unstaged: 176 // src/runtime/proc1.go 177 // + a496c1e runtime: add missing write barriers in append's copy of slice data (CL 2064, mailed) 178 // + 95390c7 runtime: add GODEBUG wbshadow for finding missing write barriers (CL 2061, mailed) 179 180 var buf bytes.Buffer 181 printFileList := func(name string, list []string) { 182 if len(list) == 0 { 183 return 184 } 185 fmt.Fprintf(&buf, "\tFiles %s:\n", name) 186 for _, file := range list { 187 fmt.Fprintf(&buf, "\t\t%s\n", file) 188 } 189 } 190 191 for _, b := range branches { 192 if !b.current && b.commitsAhead == 0 { 193 // Hide branches with no work on them. 194 continue 195 } 196 197 fmt.Fprintf(&buf, "%s", b.Name) 198 work := b.Pending() 199 if len(work) > 0 { 200 fmt.Fprintf(&buf, " %.7s..%s", b.branchpoint, work[0].ShortHash) 201 } 202 var tags []string 203 if b.current { 204 tags = append(tags, "current branch") 205 } 206 if allMailed(work) && len(work) > 0 { 207 tags = append(tags, "all mailed") 208 } 209 if allSubmitted(work) && len(work) > 0 { 210 tags = append(tags, "all submitted") 211 } 212 if b.commitsBehind > 0 { 213 tags = append(tags, fmt.Sprintf("%d behind", b.commitsBehind)) 214 } 215 if b.OriginBranch() != "origin/master" { 216 tags = append(tags, "tracking "+strings.TrimPrefix(b.OriginBranch(), "origin/")) 217 } 218 if len(tags) > 0 { 219 fmt.Fprintf(&buf, " (%s)", strings.Join(tags, ", ")) 220 } 221 fmt.Fprintf(&buf, "\n") 222 printed := false 223 if text := b.errors(); text != "" { 224 fmt.Fprintf(&buf, "\tERROR: %s\n", strings.Replace(strings.TrimSpace(text), "\n", "\n\t", -1)) 225 if !pendingShort { 226 printed = true 227 fmt.Fprintf(&buf, "\n") 228 } 229 } 230 231 if b.current && len(b.staged)+len(b.unstaged)+len(b.untracked) > 0 { 232 printed = true 233 fmt.Fprintf(&buf, "+ uncommitted changes\n") 234 printFileList("untracked", b.untracked) 235 printFileList("unstaged", b.unstaged) 236 printFileList("staged", b.staged) 237 if !pendingShort { 238 fmt.Fprintf(&buf, "\n") 239 } 240 } 241 242 for _, c := range work { 243 printed = true 244 fmt.Fprintf(&buf, "+ ") 245 formatCommit(&buf, c, pendingShort) 246 if !pendingShort { 247 printFileList("in this change", c.committed) 248 fmt.Fprintf(&buf, "\n") 249 } 250 } 251 if pendingShort || !printed { 252 fmt.Fprintf(&buf, "\n") 253 } 254 } 255 256 stdout().Write(buf.Bytes()) 257 } 258 259 // formatCommit writes detailed information about c to w. c.g must 260 // have the "CURRENT_REVISION" (or "ALL_REVISIONS") and 261 // "DETAILED_LABELS" options set. 262 // 263 // If short is true, this writes a single line overview. 264 // 265 // If short is false, this writes detailed information about the 266 // commit and its Gerrit state. 267 func formatCommit(w io.Writer, c *Commit, short bool) { 268 g := c.g 269 msg := strings.TrimRight(c.Message, "\r\n") 270 fmt.Fprintf(w, "%s", c.ShortHash) 271 var tags []string 272 if short { 273 if i := strings.Index(msg, "\n"); i >= 0 { 274 msg = msg[:i] 275 } 276 fmt.Fprintf(w, " %s", msg) 277 if g.Number != 0 { 278 tags = append(tags, fmt.Sprintf("CL %d%s", g.Number, codeReviewScores(g))) 279 } 280 } else { 281 if g.Number != 0 { 282 fmt.Fprintf(w, " %s/%d", auth.url, g.Number) 283 } 284 } 285 if g.CurrentRevision == c.Hash { 286 tags = append(tags, "mailed") 287 } 288 if g.Status == "MERGED" { 289 tags = append(tags, "submitted") 290 } 291 if len(tags) > 0 { 292 fmt.Fprintf(w, " (%s)", strings.Join(tags, ", ")) 293 } 294 fmt.Fprintf(w, "\n") 295 if short { 296 return 297 } 298 299 fmt.Fprintf(w, "\t%s\n", strings.Replace(msg, "\n", "\n\t", -1)) 300 fmt.Fprintf(w, "\n") 301 302 for _, name := range g.LabelNames() { 303 label := g.Labels[name] 304 minValue := 10000 305 maxValue := -10000 306 byScore := map[int][]string{} 307 for _, x := range label.All { 308 // Hide CL owner unless owner score is nonzero. 309 if g.Owner != nil && x.ID == g.Owner.ID && x.Value == 0 { 310 continue 311 } 312 byScore[x.Value] = append(byScore[x.Value], x.Name) 313 if minValue > x.Value { 314 minValue = x.Value 315 } 316 if maxValue < x.Value { 317 maxValue = x.Value 318 } 319 } 320 // Unless there are scores to report, do not show labels other than Code-Review. 321 // This hides Run-TryBot and TryBot-Result. 322 if minValue >= 0 && maxValue <= 0 && name != "Code-Review" { 323 continue 324 } 325 fmt.Fprintf(w, "\t%s:\n", name) 326 for score := maxValue; score >= minValue; score-- { 327 who := byScore[score] 328 if len(who) == 0 || score == 0 && name != "Code-Review" { 329 continue 330 } 331 sort.Strings(who) 332 fmt.Fprintf(w, "\t\t%+d %s\n", score, strings.Join(who, ", ")) 333 } 334 } 335 } 336 337 // codeReviewScores reports the code review scores as tags for the short output. 338 // 339 // g must have the "DETAILED_LABELS" option set. 340 func codeReviewScores(g *GerritChange) string { 341 label := g.Labels["Code-Review"] 342 if label == nil { 343 return "" 344 } 345 minValue := 10000 346 maxValue := -10000 347 for _, x := range label.All { 348 if minValue > x.Value { 349 minValue = x.Value 350 } 351 if maxValue < x.Value { 352 maxValue = x.Value 353 } 354 } 355 var scores string 356 if minValue < 0 { 357 scores += fmt.Sprintf(" %d", minValue) 358 } 359 if maxValue > 0 { 360 scores += fmt.Sprintf(" %+d", maxValue) 361 } 362 return scores 363 } 364 365 // allMailed reports whether all commits in work have been posted to Gerrit. 366 func allMailed(work []*Commit) bool { 367 for _, c := range work { 368 if c.Hash != c.g.CurrentRevision { 369 return false 370 } 371 } 372 return true 373 } 374 375 // allSubmitted reports whether all commits in work have been submitted to the origin branch. 376 func allSubmitted(work []*Commit) bool { 377 for _, c := range work { 378 if c.g.Status != "MERGED" { 379 return false 380 } 381 } 382 return true 383 } 384 385 // errors returns any errors that should be displayed 386 // about the state of the current branch, diagnosing common mistakes. 387 func (b *Branch) errors() string { 388 b.loadPending() 389 var buf bytes.Buffer 390 if !b.IsLocalOnly() && b.commitsAhead > 0 { 391 fmt.Fprintf(&buf, "Branch contains %d commit%s not on origin/%s.\n", b.commitsAhead, suffix(b.commitsAhead, "s"), b.Name) 392 fmt.Fprintf(&buf, "\tDo not commit directly to %s branch.\n", b.Name) 393 } 394 return buf.String() 395 } 396 397 // suffix returns an empty string if n == 1, s otherwise. 398 func suffix(n int, s string) string { 399 if n == 1 { 400 return "" 401 } 402 return s 403 }