git-codereview: add mail -topic support

This adds a "-topic" flag to the mail subcommand that sets the Gerrit
topic for the pushed changes.  Gerrit topics make it easier to group
related changes such as in a multi-change review.

Note that, like reviewers, the topic is sticky, so it is only
necessary to use mail -topic once for a given change.

Change-Id: I30cf9a88092cc9270b4643d432dec7e5f967b922
Reviewed-on: https://go-review.googlesource.com/5301
Reviewed-by: Russ Cox <rsc@golang.org>
diff --git a/git-codereview/mail.go b/git-codereview/mail.go
index 3eabd47..3e63203 100644
--- a/git-codereview/mail.go
+++ b/git-codereview/mail.go
@@ -16,6 +16,7 @@
 	var (
 		diff   = flags.Bool("diff", false, "show change commit diff and don't upload or mail")
 		force  = flags.Bool("f", false, "mail even if there are staged changes")
+		topic  = flags.String("topic", "", "set Gerrit topic")
 		rList  = new(stringList) // installed below
 		ccList = new(stringList) // installed below
 	)
@@ -23,7 +24,7 @@
 	flags.Var(ccList, "cc", "comma-separated list of people to CC:")
 
 	flags.Usage = func() {
-		fmt.Fprintf(stderr(), "Usage: %s mail %s [-r reviewer,...] [-cc mail,...] [commit-hash]\n", os.Args[0], globalFlags)
+		fmt.Fprintf(stderr(), "Usage: %s mail %s [-r reviewer,...] [-cc mail,...] [-topic topic] [commit-hash]\n", os.Args[0], globalFlags)
 	}
 	flags.Parse(args)
 	if len(flags.Args()) > 1 {
@@ -61,6 +62,16 @@
 	}
 	if *ccList != "" {
 		refSpec += mailList(start, "cc", string(*ccList))
+		start = ","
+	}
+	if *topic != "" {
+		// There's no way to escape the topic, but the only
+		// ambiguous character is ',' (though other characters
+		// like ' ' will be rejected outright by git).
+		if strings.Contains(*topic, ",") {
+			dief("topic may not contain a comma")
+		}
+		refSpec += start + "topic=" + *topic
 	}
 	run("git", "push", "-q", "origin", refSpec)
 
diff --git a/git-codereview/mail_test.go b/git-codereview/mail_test.go
index f2baafe..72a3f4e 100644
--- a/git-codereview/mail_test.go
+++ b/git-codereview/mail_test.go
@@ -105,3 +105,25 @@
 	testMainDied(t, "mail", "-r", "other", "-r", "anon,r1,missing")
 	testPrintedStderr(t, "unknown reviewer: missing")
 }
+
+func TestMailTopic(t *testing.T) {
+	gt := newGitTest(t)
+	defer gt.done()
+	gt.work(t)
+
+	// fake auth information to avoid Gerrit error
+	auth.host = "gerrit.fake"
+	auth.user = "not-a-user"
+	defer func() {
+		auth.host = ""
+		auth.user = ""
+	}()
+
+	testMainDied(t, "mail", "-topic", "contains,comma")
+	testPrintedStderr(t, "topic may not contain a comma")
+
+	testMain(t, "mail", "-topic", "test-topic")
+	testRan(t,
+		"git push -q origin HEAD:refs/for/master%topic=test-topic",
+		"git tag -f work.mailed")
+}