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  }