aboutsummaryrefslogtreecommitdiff
path: root/git-codereview/pending.go
diff options
context:
space:
mode:
authorAndrew Gerrand <adg@golang.org>2014-12-18 11:25:48 +1100
committerAndrew Gerrand <adg@golang.org>2014-12-18 00:35:19 +0000
commitf473ce13dd1bba7ce531e7800fdf018f60aa2454 (patch)
treefdc20fb8b555be78ca5a4f251e04de5e77d338ea /git-codereview/pending.go
parent6a0c83f0c935e49b841a3a880579cb07918bcb57 (diff)
downloadgo-x-review-f473ce13dd1bba7ce531e7800fdf018f60aa2454.tar.xz
git-codereview: rename from 'git-review' to 'git-codereview'
Mostly trivial search and replace, except for hooks.go which includes a special case to remove the old git-review hooks. Change-Id: Ic0792bb3e26607e5e0ead88958e46c3ac08288cd Reviewed-on: https://go-review.googlesource.com/1741 Reviewed-by: Russ Cox <rsc@golang.org>
Diffstat (limited to 'git-codereview/pending.go')
-rw-r--r--git-codereview/pending.go249
1 files changed, 249 insertions, 0 deletions
diff --git a/git-codereview/pending.go b/git-codereview/pending.go
new file mode 100644
index 0000000..63a2cdb
--- /dev/null
+++ b/git-codereview/pending.go
@@ -0,0 +1,249 @@
+// Copyright 2014 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// TODO(rsc): Tests
+
+package main
+
+import (
+ "bytes"
+ "fmt"
+ "net/http"
+ "os"
+ "sort"
+ "strings"
+ "time"
+)
+
+var pendingLocal bool // -l flag, use only local operations (no network)
+
+// A pendingBranch collects information about a single pending branch.
+// We overlap the reading of this information for each branch.
+type pendingBranch struct {
+ *Branch // standard Branch functionality
+ g *GerritChange // state loaded from Gerrit
+ gerr error // error loading state from Gerrit
+ current bool // is this the current branch?
+ committed []string // files committed on this branch
+ staged []string // files in staging area, only if current==true
+ unstaged []string // files unstaged in local directory, only if current==true
+ untracked []string // files untracked in local directory, only if current==true
+}
+
+// load populates b with information about the branch.
+func (b *pendingBranch) load() {
+ b.loadPending()
+ if !b.current && b.commitsAhead == 0 {
+ // Won't be displayed, don't bother looking any closer.
+ return
+ }
+ b.OriginBranch() // cache result
+ if b.current {
+ b.staged, b.unstaged, b.untracked = LocalChanges()
+ }
+ b.committed = getLines("git", "diff", "--name-only", b.parentHash, b.commitHash)
+ if !pendingLocal {
+ b.g, b.gerr = b.GerritChange("DETAILED_LABELS", "CURRENT_REVISION", "MESSAGES", "DETAILED_ACCOUNTS")
+ }
+}
+
+func pending(args []string) {
+ flags.BoolVar(&pendingLocal, "l", false, "use only local information - no network operations")
+ flags.Parse(args)
+ if len(flags.Args()) > 0 {
+ fmt.Fprintf(os.Stderr, "Usage: %s pending %s [-l]\n", os.Args[0], globalFlags)
+ os.Exit(2)
+ }
+
+ // Fetch info about remote changes, so that we can say which branches need sync.
+ if !pendingLocal {
+ run("git", "fetch", "-q")
+ http.DefaultClient.Timeout = 5 * time.Second
+ }
+
+ // Build list of pendingBranch structs to be filled in.
+ current := CurrentBranch().Name
+ var branches []*pendingBranch
+ for _, b := range LocalBranches() {
+ branches = append(branches, &pendingBranch{Branch: b, current: b.Name == current})
+ }
+
+ // The various data gathering is a little slow,
+ // especially run in serial with a lot of branches.
+ // Overlap inspection of multiple branches.
+ // Each branch is only accessed by a single worker.
+
+ // Build work queue.
+ work := make(chan *pendingBranch, len(branches))
+ done := make(chan bool, len(branches))
+ for _, b := range branches {
+ work <- b
+ }
+ close(work)
+
+ // Kick off goroutines to do work.
+ n := len(branches)
+ if n > 10 {
+ n = 10
+ }
+ for i := 0; i < n; i++ {
+ go func() {
+ for b := range work {
+ b.load()
+ done <- true
+ }
+ }()
+ }
+
+ // Wait for goroutines to finish.
+ // Note: Counting work items, not goroutines (there may be fewer goroutines).
+ for range branches {
+ <-done
+ }
+
+ // Print output, like:
+ // pending d8fcb99 https://go-review.googlesource.com/1620 (current branch, 1 behind)
+ // git-codereview: expand pending output
+ //
+ // for pending:
+ // - show full commit message
+ // - show information about being behind upstream
+ // - show list of modified files
+ // - for current branch, show staged, unstaged, untracked files
+ // - warn about being ahead of upstream on master
+ // - warn about being multiple commits ahead of upstream
+ //
+ // - add same warnings to change
+ // - add change -a (mostly unrelated, but prompted by this work)
+ //
+ // Change-Id: Ie480ba5b66cc07faffca421ee6c9623d35204696
+ //
+ // Code-Review:
+ // +2 Andrew Gerrand, Rob Pike
+ // Files in this change:
+ // git-codereview/api.go
+ // git-codereview/branch.go
+ // git-codereview/change.go
+ // git-codereview/pending.go
+ // git-codereview/review.go
+ // git-codereview/submit.go
+ // git-codereview/sync.go
+ // Files untracked:
+ // git-codereview/doc.go
+ // git-codereview/savedmail.go.txt
+ //
+ var buf bytes.Buffer
+ for _, b := range branches {
+ if !b.current && b.commitsAhead == 0 {
+ // Hide branches with no work on them.
+ continue
+ }
+
+ fmt.Fprintf(&buf, "%s", b.Name)
+ if b.shortCommitHash != "" {
+ fmt.Fprintf(&buf, " %s", b.shortCommitHash)
+ }
+ if b.g != nil && b.g.Number != 0 {
+ fmt.Fprintf(&buf, " %s/%d", auth.url, b.g.Number)
+ }
+ var tags []string
+ if b.current {
+ tags = append(tags, "current branch")
+ }
+ if b.g != nil && b.g.CurrentRevision == b.commitHash {
+ tags = append(tags, "mailed")
+ }
+ if b.g != nil && b.g.Status == "MERGED" {
+ tags = append(tags, "submitted")
+ }
+ if b.commitsBehind > 0 {
+ tags = append(tags, fmt.Sprintf("%d behind", b.commitsBehind))
+ }
+ if len(tags) > 0 {
+ fmt.Fprintf(&buf, " (%s)", strings.Join(tags, ", "))
+ }
+ fmt.Fprintf(&buf, "\n")
+ if text := b.errors(); text != "" {
+ // TODO(rsc): Test
+ fmt.Fprintf(&buf, "\tERROR: %s", strings.Replace(text, "\n", "\n\t", -1))
+ }
+ if b.message != "" {
+ msg := strings.TrimRight(b.message, "\r\n")
+ fmt.Fprintf(&buf, "\t%s\n", strings.Replace(msg, "\n", "\n\t", -1))
+ fmt.Fprintf(&buf, "\n")
+ }
+ if b.g != nil {
+ for _, name := range b.g.LabelNames() {
+ label := b.g.Labels[name]
+ minValue := 10000
+ maxValue := -10000
+ byScore := map[int][]string{}
+ for _, x := range label.All {
+ // Hide CL owner unless owner score is nonzero.
+ if b.g.Owner != nil && x.ID == b.g.Owner.ID && x.Value == 0 {
+ continue
+ }
+ byScore[x.Value] = append(byScore[x.Value], x.Name)
+ if minValue > x.Value {
+ minValue = x.Value
+ }
+ if maxValue < x.Value {
+ maxValue = x.Value
+ }
+ }
+ fmt.Fprintf(&buf, "\t%s:\n", name)
+ for score := maxValue; score >= minValue; score-- {
+ who := byScore[score]
+ if len(who) == 0 {
+ continue
+ }
+ sort.Strings(who)
+ fmt.Fprintf(&buf, "\t\t%+d %s\n", score, strings.Join(who, ", "))
+ }
+ }
+ }
+ printFileList := func(name string, list []string) {
+ if len(list) == 0 {
+ return
+ }
+ fmt.Fprintf(&buf, "\tFiles %s:\n", name)
+ for _, file := range list {
+ fmt.Fprintf(&buf, "\t\t%s\n", file)
+ }
+ }
+ printFileList("in this change", b.committed)
+ printFileList("staged", b.staged)
+ printFileList("unstaged", b.unstaged)
+ printFileList("untracked", b.untracked)
+
+ fmt.Fprintf(&buf, "\n")
+ }
+
+ os.Stdout.Write(buf.Bytes())
+}
+
+// errors returns any errors that should be displayed
+// about the state of the current branch, diagnosing common mistakes.
+func (b *Branch) errors() string {
+ b.loadPending()
+ var buf bytes.Buffer
+ if !b.IsLocalOnly() && b.commitsAhead > 0 {
+ fmt.Fprintf(&buf, "Branch contains %d commit%s not on origin/%s.\n", b.commitsAhead, suffix(b.commitsAhead, "s"), b.Name)
+ fmt.Fprintf(&buf, "\tDo not commit directly to %s branch.\n", b.Name)
+ } else if b.commitsAhead > 1 {
+ fmt.Fprintf(&buf, "Branch contains %d commits not on origin/%s.\n", b.commitsAhead, b.OriginBranch())
+ fmt.Fprintf(&buf, "\tThere should be at most one.\n", b.commitsAhead, b.OriginBranch())
+ fmt.Fprintf(&buf, "\tUse 'git change', not 'git commit'.\n", b.Name)
+ fmt.Fprintf(&buf, "\tRun 'git log %s..%s' to list commits.\n", b.OriginBranch(), b.Name)
+ }
+ return buf.String()
+}
+
+// suffix returns an empty string if n == 1, s otherwise.
+func suffix(n int, s string) string {
+ if n == 1 {
+ return ""
+ }
+ return s
+}