diff options
| author | Andrew Gerrand <adg@golang.org> | 2014-12-18 11:25:48 +1100 |
|---|---|---|
| committer | Andrew Gerrand <adg@golang.org> | 2014-12-18 00:35:19 +0000 |
| commit | f473ce13dd1bba7ce531e7800fdf018f60aa2454 (patch) | |
| tree | fdc20fb8b555be78ca5a4f251e04de5e77d338ea /git-codereview/pending.go | |
| parent | 6a0c83f0c935e49b841a3a880579cb07918bcb57 (diff) | |
| download | go-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.go | 249 |
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 +} |
