in git-codereview/mail.go [16:173]
func cmdMail(args []string) {
// NOTE: New flags should be added to the usage message below as well as doc.go.
var (
rList = new(stringList) // installed below
ccList = new(stringList) // installed below
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")
hashtagList = new(stringList) // installed below
noKeyCheck = flags.Bool("nokeycheck", false, "set 'git push -o nokeycheck', to prevent Gerrit from checking for private keys")
topic = flags.String("topic", "", "set Gerrit topic")
trust = flags.Bool("trust", false, "add a Trust+1 vote to the CL")
trybot = flags.Bool("trybot", false, "run trybots on the uploaded CLs")
wip = flags.Bool("wip", false, "set the status of a change to Work-in-Progress")
)
flags.Var(rList, "r", "comma-separated list of reviewers")
flags.Var(ccList, "cc", "comma-separated list of people to CC:")
flags.Var(hashtagList, "hashtag", "comma-separated list of tags to set")
flags.Usage = func() {
fmt.Fprintf(stderr(),
"Usage: %s mail %s [-r reviewer,...] [-cc mail,...]\n"+
"\t[-f] [-diff] [-hashtag tag,...] [-nokeycheck] [-topic topic]\n"+
"\t[-trust] [-trybot] [-wip] [commit]\n", progName, globalFlags)
exit(2)
}
flags.Parse(args)
if len(flags.Args()) > 1 {
flags.Usage()
exit(2)
}
b := CurrentBranch()
var c *Commit
if len(flags.Args()) == 1 {
c = b.CommitByRev("mail", flags.Arg(0))
} else {
c = b.DefaultCommit("mail", "must specify commit on command line; use HEAD to mail all pending changes")
}
if *diff {
run("git", "diff", b.Branchpoint()[:7]+".."+c.ShortHash, "--")
return
}
if len(ListFiles(c)) == 0 && len(c.Parents) == 1 {
dief("cannot mail: commit %s is empty", c.ShortHash)
}
foundCommit := false
for _, c1 := range b.Pending() {
if c1 == c {
foundCommit = true
}
if !foundCommit {
continue
}
if strings.Contains(strings.ToLower(c1.Message), "do not mail") {
dief("%s: CL says DO NOT MAIL", c1.ShortHash)
}
if strings.HasPrefix(c1.Message, "fixup!") {
dief("%s: CL is a fixup! commit", c1.ShortHash)
}
if strings.HasPrefix(c1.Message, "squash!") {
dief("%s: CL is a squash! commit", c1.ShortHash)
}
for _, f := range ListFiles(c1) {
if strings.HasPrefix(f, ".#") || strings.HasSuffix(f, "~") ||
(strings.HasPrefix(f, "#") && strings.HasSuffix(f, "#")) {
dief("cannot mail temporary files: %s", f)
}
}
}
if !foundCommit {
// b.CommitByRev and b.DefaultCommit both return a commit on b.
dief("internal error: did not find chosen commit on current branch")
}
if !*force && HasStagedChanges() {
dief("there are staged changes; aborting.\n"+
"Use '%s change' to include them or '%s mail -f' to force it.", progName, progName)
}
if !utf8.ValidString(c.Message) {
dief("cannot mail message with invalid UTF-8")
}
for _, r := range c.Message {
if !unicode.IsPrint(r) && !unicode.IsSpace(r) {
dief("cannot mail message with non-printable rune %q", r)
}
}
// for side effect of dying with a good message if origin is GitHub
loadGerritOrigin()
refSpec := b.PushSpec(c)
start := "%"
if *rList != "" {
refSpec += mailList(start, "r", string(*rList))
start = ","
}
if *ccList != "" {
refSpec += mailList(start, "cc", string(*ccList))
start = ","
}
if *hashtagList != "" {
for _, tag := range strings.Split(string(*hashtagList), ",") {
if tag == "" {
dief("hashtag may not contain empty tags")
}
refSpec += start + "hashtag=" + tag
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
start = ","
}
if *trust {
refSpec += start + "l=Trust"
start = ","
}
if *trybot {
refSpec += start + "l=Run-TryBot"
start = ","
}
if *wip {
refSpec += start + "wip"
start = ","
}
args = []string{"push", "-q"}
if *noKeyCheck {
args = append(args, "-o", "nokeycheck")
}
args = append(args, "origin", refSpec)
run("git", args...)
// 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", "--no-sign", "-f", b.Name+".mailed", c.ShortHash)
}