blob: b5bcaa28ec06884e5ff5f32b62102f5c7378ce7d [file] [log] [blame]
// 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 (
var changeAuto bool
var changeQuick bool
func cmdChange(args []string) {
flags.BoolVar(&changeAuto, "a", false, "add changes to any tracked files")
flags.BoolVar(&changeQuick, "q", false, "do not edit pending commit msg")
if len(flags.Args()) > 1 {
fmt.Fprintf(stderr(), "Usage: %s change %s [branch]\n", os.Args[0], globalFlags)
// Checkout or create branch, if specified.
target := flags.Arg(0)
if target != "" {
b := CurrentBranch()
if HasStagedChanges() && b.IsLocalOnly() && !b.HasPendingCommit() {
// Create or amend change commit.
b := CurrentBranch()
if !b.IsLocalOnly() {
dief("can't commit to %s branch (use '%s change branchname').", b.Name, os.Args[0])
amend := b.HasPendingCommit()
b.loadedPending = false // force reload after commitChanges
func (b *Branch) check() {
// TODO(rsc): Test
staged, unstaged, _ := LocalChanges()
if len(staged) == 0 && len(unstaged) == 0 {
// No staged changes, no unstaged changes.
// If the branch is behind upstream, now is a good time to point that out.
// This applies to both local work branches and tracking branches.
// TODO(rsc): Test.
if b.commitsBehind > 0 {
printf("warning: %d commit%s behind %s; run 'git sync' to update.", b.commitsBehind, suffix(b.commitsBehind, "s"), b.OriginBranch())
// TODO(rsc): Test
if text := b.errors(); text != "" {
printf("error: %s\n", text)
var testCommitMsg string
func commitChanges(amend bool) {
// git commit will run the gofmt hook.
// Run it now to give a better error (won't show a git commit command failing).
if HasUnstagedChanges() && !HasStagedChanges() && !changeAuto {
printf("warning: unstaged changes and no staged changes; use 'git add' or 'git change -a'")
commit := func(amend bool) {
args := []string{"commit", "-q", "--allow-empty"}
if amend {
args = append(args, "--amend")
if changeQuick {
args = append(args, "--no-edit")
if testCommitMsg != "" {
args = append(args, "-m", testCommitMsg)
if changeAuto {
args = append(args, "-a")
run("git", args...)
for !commitMessageOK() {
fmt.Print("re-edit commit message (y/n)? ")
if !scanYes() {
printf("change updated.")
func checkoutOrCreate(target string) {
if strings.ToUpper(target) == "HEAD" {
// Git gets very upset and confused if you 'git change head'
// on systems with case-insensitive file names: the branch
// head conflicts with the usual HEAD.
dief("invalid branch name %q: ref name HEAD is reserved for git.", target)
// If local branch exists, check it out.
for _, b := range LocalBranches() {
if b.Name == target {
run("git", "checkout", "-q", target)
printf("changed to branch %v.", target)
// If origin branch exists, create local branch tracking it.
for _, name := range OriginBranches() {
if name == "origin/"+target {
run("git", "checkout", "-q", "-t", "-b", target, name)
printf("created branch %v tracking %s.", target, name)
// Otherwise, this is a request to create a local work branch.
// Check for reserved names. We take everything with a dot.
if strings.Contains(target, ".") {
dief("invalid branch name %v: branch names with dots are reserved for git-codereview.", target)
// If the current branch has a pending commit, building
// on top of it will not help. Don't allow that.
// Otherwise, inherit HEAD and upstream from the current branch.
b := CurrentBranch()
if b.HasPendingCommit() {
if !b.IsLocalOnly() {
dief("bad repo state: branch %s is ahead of origin/%s", b.Name, b.Name)
dief("cannot branch from work branch; change back to %v first.", strings.TrimPrefix(b.OriginBranch(), "origin/"))
origin := b.OriginBranch()
// NOTE: This is different from git checkout -q -t -b branch. It does not move HEAD.
run("git", "checkout", "-q", "-b", target)
run("git", "branch", "-q", "--set-upstream-to", origin)
printf("created branch %v tracking %s.", target, origin)
var (
messageRE = regexp.MustCompile(`^(\[[a-zA-Z0-9.-]+\] )?[a-zA-Z0-9-/,. ]+: `)
oldFixesRE = regexp.MustCompile(`Fixes +(issue +#?)?([0-9]+)`)
func commitMessageOK() bool {
body := cmdOutput("git", "log", "--format=format:%B", "-n", "1")
ok := true
if !messageRE.MatchString(body) {
ok = false
if m := oldFixesRE.FindStringSubmatch(body); m != nil {
fmt.Printf(fixesIssueWarning, m[0], m[2])
ok = false
return ok
const commitMessageWarning = `
Your CL description appears not to use the standard form.
The first line of your change description is conventionally a one-line summary
of the change, prefixed by the primary affected package, and is used as the
subject for code review mail; the rest of the description elaborates.
encoding/rot13: new package
math: add IsInf, IsNaN
net: fix cname in LookupHost
unicode: update to Unicode 5.0.2
const fixesIssueWarning = `
Your CL description contains the string %q, which is
the old Google Code way of linking commits to issues.
You should rewrite it to use the GitHub convention: "Fixes #%v".
func scanYes() bool {
var s string
return strings.HasPrefix(strings.ToLower(s), "y")