aboutsummaryrefslogtreecommitdiff
path: root/git-codereview/mail.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/mail.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/mail.go')
-rw-r--r--git-codereview/mail.go119
1 files changed, 119 insertions, 0 deletions
diff --git a/git-codereview/mail.go b/git-codereview/mail.go
new file mode 100644
index 0000000..01dd4b8
--- /dev/null
+++ b/git-codereview/mail.go
@@ -0,0 +1,119 @@
+// 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.
+
+package main
+
+import (
+ "fmt"
+ "os"
+ "regexp"
+ "strings"
+)
+
+func mail(args []string) {
+ 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")
+ rList = new(stringList) // installed below
+ ccList = new(stringList) // installed below
+ )
+ flags.Var(rList, "r", "comma-separated list of reviewers")
+ flags.Var(ccList, "cc", "comma-separated list of people to CC:")
+
+ flags.Usage = func() {
+ fmt.Fprintf(os.Stderr, "Usage: %s mail %s [-r reviewer,...] [-cc mail,...]\n", os.Args[0], globalFlags)
+ }
+ flags.Parse(args)
+ if len(flags.Args()) != 0 {
+ flags.Usage()
+ os.Exit(2)
+ }
+
+ b := CurrentBranch()
+ if b.ChangeID() == "" {
+ dief("no pending change; can't mail.")
+ }
+
+ if *diff {
+ run("git", "diff", b.Branchpoint()+"..HEAD")
+ return
+ }
+
+ if !*force && HasStagedChanges() {
+ dief("there are staged changes; aborting.\n" +
+ "Use 'review change' to include them or 'review mail -f' to force it.")
+ }
+
+ // for side effect of dying with a good message if origin is GitHub
+ loadGerritOrigin()
+
+ refSpec := b.PushSpec()
+ start := "%"
+ if *rList != "" {
+ refSpec += mailList(start, "r", string(*rList))
+ start = ","
+ }
+ if *ccList != "" {
+ refSpec += mailList(start, "cc", string(*ccList))
+ }
+ run("git", "push", "-q", "origin", refSpec)
+
+ // Create local tag for mailed change.
+ // If in the 'work' branch, this creates or updates work.mailed.
+ // Older mailings are in the reflog, so work.mailed is newest,
+ // work.mailed@{1} is the one before that, work.mailed@{2} before that,
+ // and so on.
+ // Git doesn't actually have a concept of a local tag,
+ // but Gerrit won't let people push tags to it, so the tag
+ // can't propagate out of the local client into the official repo.
+ // There is no conflict with the branch names people are using
+ // for work, because git change rejects any name containing a dot.
+ // The space of names with dots is ours (the Go team's) to define.
+ run("git", "tag", "-f", b.Name+".mailed")
+}
+
+// PushSpec returns the spec for a Gerrit push command to publish the change in b.
+func (b *Branch) PushSpec() string {
+ return "HEAD:refs/for/" + strings.TrimPrefix(b.OriginBranch(), "origin/")
+}
+
+// mailAddressRE matches the mail addresses we admit. It's restrictive but admits
+// all the addresses in the Go CONTRIBUTORS file at time of writing (tested separately).
+var mailAddressRE = regexp.MustCompile(`^[a-zA-Z0-9][-_.a-zA-Z0-9]*@[-_.a-zA-Z0-9]+$`)
+
+// mailList turns the list of mail addresses from the flag value into the format
+// expected by gerrit. The start argument is a % or , depending on where we
+// are in the processing sequence.
+func mailList(start, tag string, flagList string) string {
+ spec := start
+ for i, addr := range strings.Split(flagList, ",") {
+ if !mailAddressRE.MatchString(addr) {
+ dief("%q is not a valid reviewer mail address", addr)
+ }
+ if i > 0 {
+ spec += ","
+ }
+ spec += tag + "=" + addr
+ }
+ return spec
+}
+
+// stringList is a flag.Value that is like flag.String, but if repeated
+// keeps appending to the old value, inserting commas as separators.
+// This allows people to write -r rsc,adg (like the old hg command)
+// but also -r rsc -r adg (like standard git commands).
+// This does change the meaning of -r rsc -r adg (it used to mean just adg).
+type stringList string
+
+func (x *stringList) String() string {
+ return string(*x)
+}
+
+func (x *stringList) Set(s string) error {
+ if *x != "" && s != "" {
+ *x += ","
+ }
+ *x += stringList(s)
+ return nil
+}