diff options
| author | Austin Clements <austin@google.com> | 2015-11-05 16:02:35 -0500 |
|---|---|---|
| committer | Austin Clements <austin@google.com> | 2015-11-11 00:38:01 +0000 |
| commit | 7cc8d7720ed2e3ca6aba0cea191b9e19690dc9f7 (patch) | |
| tree | f44204abf453f4c17c7f8e9ea6c6411d433983f0 | |
| parent | a993a2d94a08a2ae58d984e48613d4ff387adf57 (diff) | |
| download | go-x-review-7cc8d7720ed2e3ca6aba0cea191b9e19690dc9f7.tar.xz | |
git-codereview: add interactive mode to submit
This adds a -i option to submit that brings up a list of commits to
submit in an editor (a la git rebase -i), lets the user edit the list,
and then submits the specified commits in the specified order.
Change-Id: I88149140527c987ae856aac2598f0a992fe5654d
Reviewed-on: https://go-review.googlesource.com/16677
Reviewed-by: Andrew Gerrand <adg@golang.org>
| -rw-r--r-- | git-codereview/branch.go | 6 | ||||
| -rw-r--r-- | git-codereview/editor.go | 50 | ||||
| -rw-r--r-- | git-codereview/review.go | 2 | ||||
| -rw-r--r-- | git-codereview/submit.go | 75 | ||||
| -rw-r--r-- | git-codereview/submit_test.go | 26 |
5 files changed, 154 insertions, 5 deletions
diff --git a/git-codereview/branch.go b/git-codereview/branch.go index 5eacb10..b451507 100644 --- a/git-codereview/branch.go +++ b/git-codereview/branch.go @@ -344,7 +344,11 @@ func (b *Branch) DefaultCommit(action string) *Commit { for _, c := range work { fmt.Fprintf(&buf, "\n\t%s %s", c.ShortHash, c.Subject) } - dief("cannot %s: multiple changes pending; must specify commit hash on command line:%s", action, buf.String()) + extra := "" + if action == "submit" { + extra = " or use submit -i" + } + dief("cannot %s: multiple changes pending; must specify commit hash on command line%s:%s", action, extra, buf.String()) } return work[0] } diff --git a/git-codereview/editor.go b/git-codereview/editor.go new file mode 100644 index 0000000..c3de65f --- /dev/null +++ b/git-codereview/editor.go @@ -0,0 +1,50 @@ +// Copyright 2015 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. + +package main + +import ( + "io" + "io/ioutil" + "os" + "os/exec" +) + +// editor invokes an interactive editor on a temporary file containing +// initial, blocks until the editor exits, and returns the (possibly +// edited) contents of the temporary file. It follows the conventions +// of git for selecting and invoking the editor (see git-var(1)). +func editor(initial string) string { + // Query the git editor command. + gitEditor := trim(cmdOutput("git", "var", "GIT_EDITOR")) + + // Create temporary file. + temp, err := ioutil.TempFile("", "git-codereview") + if err != nil { + dief("creating temp file: %v", err) + } + tempName := temp.Name() + defer os.Remove(tempName) + if _, err := io.WriteString(temp, initial); err != nil { + dief("%v", err) + } + if err := temp.Close(); err != nil { + dief("%v", err) + } + + // Invoke the editor. See git's prepare_shell_cmd. + cmd := exec.Command("sh", "-c", gitEditor+" \"$@\"", gitEditor, tempName) + cmd.Stdin, cmd.Stdout, cmd.Stderr = os.Stdin, os.Stdout, os.Stderr + if err := cmd.Run(); err != nil { + os.Remove(tempName) + dief("editor exited with: %v", err) + } + + // Read the edited file. + b, err := ioutil.ReadFile(tempName) + if err != nil { + dief("%v", err) + } + return string(b) +} diff --git a/git-codereview/review.go b/git-codereview/review.go index 1898463..f4db94a 100644 --- a/git-codereview/review.go +++ b/git-codereview/review.go @@ -95,7 +95,7 @@ Available commands: If -l is specified, only use locally available information. If -s is specified, show short output. - submit [commit-hash...] + submit [-i | commit-hash...] Push the pending change to the Gerrit server and tell Gerrit to submit it to the master branch. diff --git a/git-codereview/submit.go b/git-codereview/submit.go index f6b48d4..809bbe7 100644 --- a/git-codereview/submit.go +++ b/git-codereview/submit.go @@ -5,22 +5,39 @@ package main import ( + "bytes" "fmt" "os" + "strings" "time" ) // TODO(rsc): Add -tbr, along with standard exceptions (doc/go1.5.txt) func cmdSubmit(args []string) { + var interactive bool + flags.BoolVar(&interactive, "i", false, "interactively select commits to submit") flags.Usage = func() { - fmt.Fprintf(stderr(), "Usage: %s submit %s [commit-hash...]\n", os.Args[0], globalFlags) + fmt.Fprintf(stderr(), "Usage: %s submit %s [-i | commit-hash...]\n", os.Args[0], globalFlags) } flags.Parse(args) + if interactive && flags.NArg() > 0 { + flags.Usage() + os.Exit(2) + } b := CurrentBranch() var cs []*Commit - if args := flags.Args(); len(args) >= 1 { + if interactive { + hashes := submitHashes(b) + if len(hashes) == 0 { + printf("nothing to submit") + return + } + for _, hash := range hashes { + cs = append(cs, b.CommitByHash("submit", hash)) + } + } else if args := flags.Args(); len(args) >= 1 { for _, arg := range args { cs = append(cs, b.CommitByHash("submit", arg)) } @@ -179,3 +196,57 @@ func submitCheck(g *GerritChange) error { return nil } + +// submitHashes interactively prompts for commits to submit. +func submitHashes(b *Branch) []string { + // Get pending commits on b. + pending := b.Pending() + for _, c := range pending { + // Note that DETAILED_LABELS does not imply LABELS. + c.g, c.gerr = b.GerritChange(c, "CURRENT_REVISION", "LABELS", "DETAILED_LABELS") + if c.g == nil { + c.g = new(GerritChange) + } + } + + // Construct submit script. + var script bytes.Buffer + for i := len(pending) - 1; i >= 0; i-- { + c := pending[i] + + if c.g.ID == "" { + fmt.Fprintf(&script, "# change not on Gerrit:\n#") + } else if err := submitCheck(c.g); err != nil { + fmt.Fprintf(&script, "# %v:\n#", err) + } + + formatCommit(&script, c, true) + } + + fmt.Fprintf(&script, ` +# The above commits will be submitted in order from top to bottom +# when you exit the editor. +# +# These lines can be re-ordered, removed, and commented out. +# +# If you remove all lines, the submit will be aborted. +`) + + // Edit the script. + final := editor(script.String()) + + // Parse the final script. + var hashes []string + for _, line := range lines(final) { + line := strings.TrimSpace(line) + if len(line) == 0 || line[0] == '#' { + continue + } + if i := strings.Index(line, " "); i >= 0 { + line = line[:i] + } + hashes = append(hashes, line) + } + + return hashes +} diff --git a/git-codereview/submit_test.go b/git-codereview/submit_test.go index ead9157..02c8a12 100644 --- a/git-codereview/submit_test.go +++ b/git-codereview/submit_test.go @@ -5,6 +5,7 @@ package main import ( + "os" "strings" "testing" ) @@ -171,6 +172,29 @@ func TestSubmitMultiple(t *testing.T) { srv := newGerritServer(t) defer srv.done() + cl1, cl2 := testSubmitMultiple(t, gt, srv) + testMain(t, "submit", cl1.CurrentRevision, cl2.CurrentRevision) +} + +func TestSubmitInteractive(t *testing.T) { + gt := newGitTest(t) + defer gt.done() + + srv := newGerritServer(t) + defer srv.done() + + cl1, cl2 := testSubmitMultiple(t, gt, srv) + os.Setenv("GIT_EDITOR", "echo "+cl1.CurrentRevision+" > ") + testMain(t, "submit", "-i") + if cl1.Status != "MERGED" { + t.Fatalf("want cl1.Status == MERGED; got %v", cl1.Status) + } + if cl2.Status != "NEW" { + t.Fatalf("want cl2.Status == NEW; got %v", cl1.Status) + } +} + +func testSubmitMultiple(t *testing.T, gt *gitTest, srv *gerritServer) (*GerritChange, *GerritChange) { write(t, gt.client+"/file1", "") trun(t, gt.client, "git", "add", "file1") trun(t, gt.client, "git", "commit", "-m", "msg\n\nChange-Id: I0000001\n") @@ -218,5 +242,5 @@ func TestSubmitMultiple(t *testing.T) { cl2.Status = "MERGED" return gerritReply{json: cl2} }}) - testMain(t, "submit", hash1, hash2) + return &cl1, &cl2 } |
