git-codereview: allow multiple commit hashes for submit

This adds support to the submit subcommand for passing and submitting
multiple commit hashes.

Change-Id: Id030b07cac21d11acbeb8015fde9cb9e1acb1479
Reviewed-on: https://go-review.googlesource.com/16674
Reviewed-by: Andrew Gerrand <adg@golang.org>
diff --git a/git-codereview/review.go b/git-codereview/review.go
index 8ebb8d9..1898463 100644
--- a/git-codereview/review.go
+++ b/git-codereview/review.go
@@ -95,7 +95,7 @@
 		If -l is specified, only use locally available information.
 		If -s is specified, show short output.
 
-	submit
+	submit [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 df43148..209f14b 100644
--- a/git-codereview/submit.go
+++ b/git-codereview/submit.go
@@ -14,20 +14,18 @@
 
 func cmdSubmit(args []string) {
 	flags.Usage = func() {
-		fmt.Fprintf(stderr(), "Usage: %s submit %s [commit-hash]\n", os.Args[0], globalFlags)
+		fmt.Fprintf(stderr(), "Usage: %s submit %s [commit-hash...]\n", os.Args[0], globalFlags)
 	}
 	flags.Parse(args)
-	if n := len(flags.Args()); n > 1 {
-		flags.Usage()
-		os.Exit(2)
-	}
 
 	b := CurrentBranch()
-	var c *Commit
-	if len(flags.Args()) == 1 {
-		c = b.CommitByHash("submit", flags.Arg(0))
+	var cs []*Commit
+	if args := flags.Args(); len(args) >= 1 {
+		for _, arg := range args {
+			cs = append(cs, b.CommitByHash("submit", arg))
+		}
 	} else {
-		c = b.DefaultCommit("submit")
+		cs = append(cs, b.DefaultCommit("submit"))
 	}
 
 	// No staged changes.
@@ -37,13 +35,17 @@
 	checkStaged("submit")
 	checkUnstaged("submit")
 
-	// Submit the change.
-	g := submit(b, c)
+	// Submit the changes.
+	var g *GerritChange
+	for _, c := range cs {
+		printf("submitting %s %s", c.ShortHash, c.Subject)
+		g = submit(b, c)
+	}
 
 	// Sync client to revision that Gerrit committed, but only if we can do it cleanly.
 	// Otherwise require user to run 'git sync' themselves (if they care).
 	run("git", "fetch", "-q")
-	if len(b.Pending()) == 1 {
+	if len(cs) == 1 && len(b.Pending()) == 1 {
 		if err := runErr("git", "checkout", "-q", "-B", b.Name, g.CurrentRevision, "--"); err != nil {
 			dief("submit succeeded, but cannot sync local branch\n"+
 				"\trun 'git sync' to sync, or\n"+
@@ -119,7 +121,8 @@
 	}
 
 	if *noRun {
-		dief("stopped before submit")
+		printf("stopped before submit")
+		return g
 	}
 
 	// Otherwise, try the submit. Sends back updated GerritChange,
diff --git a/git-codereview/submit_test.go b/git-codereview/submit_test.go
index 8e91e42..ead9157 100644
--- a/git-codereview/submit_test.go
+++ b/git-codereview/submit_test.go
@@ -163,3 +163,60 @@
 		"git fetch -q",
 		"git checkout -q -B work "+serverHead+" --")
 }
+
+func TestSubmitMultiple(t *testing.T) {
+	gt := newGitTest(t)
+	defer gt.done()
+
+	srv := newGerritServer(t)
+	defer srv.done()
+
+	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")
+	hash1 := strings.TrimSpace(trun(t, gt.client, "git", "log", "-n", "1", "--format=format:%H"))
+
+	write(t, gt.client+"/file2", "")
+	trun(t, gt.client, "git", "add", "file2")
+	trun(t, gt.client, "git", "commit", "-m", "msg\n\nChange-Id: I0000002\n")
+	hash2 := strings.TrimSpace(trun(t, gt.client, "git", "log", "-n", "1", "--format=format:%H"))
+
+	testMainDied(t, "submit")
+	testPrintedStderr(t, "cannot submit: multiple changes pending")
+
+	cl1 := GerritChange{
+		Status:          "NEW",
+		Mergeable:       true,
+		CurrentRevision: hash1,
+		Labels:          map[string]*GerritLabel{"Code-Review": &GerritLabel{Approved: new(GerritAccount)}},
+	}
+	cl2 := GerritChange{
+		Status:          "NEW",
+		Mergeable:       false,
+		CurrentRevision: hash2,
+		Labels:          map[string]*GerritLabel{"Code-Review": &GerritLabel{Approved: new(GerritAccount)}},
+	}
+
+	srv.setReply("/a/changes/proj~master~I0000001", gerritReply{f: func() gerritReply {
+		return gerritReply{json: cl1}
+	}})
+	srv.setReply("/a/changes/proj~master~I0000001/submit", gerritReply{f: func() gerritReply {
+		if cl1.Status != "NEW" {
+			return gerritReply{status: 409}
+		}
+		cl1.Status = "MERGED"
+		cl2.Mergeable = true
+		return gerritReply{json: cl1}
+	}})
+	srv.setReply("/a/changes/proj~master~I0000002", gerritReply{f: func() gerritReply {
+		return gerritReply{json: cl2}
+	}})
+	srv.setReply("/a/changes/proj~master~I0000002/submit", gerritReply{f: func() gerritReply {
+		if cl2.Status != "NEW" || !cl2.Mergeable {
+			return gerritReply{status: 409}
+		}
+		cl2.Status = "MERGED"
+		return gerritReply{json: cl2}
+	}})
+	testMain(t, "submit", hash1, hash2)
+}
diff --git a/git-codereview/util_test.go b/git-codereview/util_test.go
index 400beb4..b010f08 100644
--- a/git-codereview/util_test.go
+++ b/git-codereview/util_test.go
@@ -6,6 +6,7 @@
 
 import (
 	"bytes"
+	"encoding/json"
 	"fmt"
 	"io/ioutil"
 	"net"
@@ -386,6 +387,7 @@
 type gerritReply struct {
 	status int
 	body   string
+	json   interface{}
 	f      func() gerritReply
 }
 
@@ -413,6 +415,13 @@
 	if reply.status != 0 {
 		w.WriteHeader(reply.status)
 	}
+	if reply.json != nil {
+		body, err := json.Marshal(reply.json)
+		if err != nil {
+			dief("%v", err)
+		}
+		reply.body = ")]}'\n" + string(body)
+	}
 	if len(reply.body) > 0 {
 		w.Write([]byte(reply.body))
 	}