github.com/graybobo/golang.org-package-offline-cache@v0.0.0-20200626051047-6608995c132f/x/review/git-codereview/change.go (about)

     1  // Copyright 2014 The Go Authors.  All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package main
     6  
     7  import (
     8  	"fmt"
     9  	"os"
    10  	"regexp"
    11  	"strings"
    12  )
    13  
    14  var changeAuto bool
    15  var changeQuick bool
    16  
    17  func cmdChange(args []string) {
    18  	flags.BoolVar(&changeAuto, "a", false, "add changes to any tracked files")
    19  	flags.BoolVar(&changeQuick, "q", false, "do not edit pending commit msg")
    20  	flags.Parse(args)
    21  	if len(flags.Args()) > 1 {
    22  		fmt.Fprintf(stderr(), "Usage: %s change %s [branch]\n", os.Args[0], globalFlags)
    23  		os.Exit(2)
    24  
    25  	}
    26  
    27  	// Checkout or create branch, if specified.
    28  	target := flags.Arg(0)
    29  	if target != "" {
    30  		checkoutOrCreate(target)
    31  		b := CurrentBranch()
    32  		if HasStagedChanges() && b.IsLocalOnly() && !b.HasPendingCommit() {
    33  			commitChanges(false)
    34  		}
    35  		b.check()
    36  		return
    37  	}
    38  
    39  	// Create or amend change commit.
    40  	b := CurrentBranch()
    41  	if !b.IsLocalOnly() {
    42  		dief("can't commit to %s branch (use '%s change branchname').", b.Name, os.Args[0])
    43  	}
    44  
    45  	amend := b.HasPendingCommit()
    46  	commitChanges(amend)
    47  	b.loadedPending = false // force reload after commitChanges
    48  	b.check()
    49  }
    50  
    51  func (b *Branch) check() {
    52  	// TODO(rsc): Test
    53  	staged, unstaged, _ := LocalChanges()
    54  	if len(staged) == 0 && len(unstaged) == 0 {
    55  		// No staged changes, no unstaged changes.
    56  		// If the branch is behind upstream, now is a good time to point that out.
    57  		// This applies to both local work branches and tracking branches.
    58  		// TODO(rsc): Test.
    59  		b.loadPending()
    60  		if b.commitsBehind > 0 {
    61  			printf("warning: %d commit%s behind %s; run 'git sync' to update.", b.commitsBehind, suffix(b.commitsBehind, "s"), b.OriginBranch())
    62  		}
    63  	}
    64  
    65  	// TODO(rsc): Test
    66  	if text := b.errors(); text != "" {
    67  		printf("error: %s\n", text)
    68  	}
    69  }
    70  
    71  var testCommitMsg string
    72  
    73  func commitChanges(amend bool) {
    74  	// git commit will run the gofmt hook.
    75  	// Run it now to give a better error (won't show a git commit command failing).
    76  	hookGofmt()
    77  
    78  	if HasUnstagedChanges() && !HasStagedChanges() && !changeAuto {
    79  		printf("warning: unstaged changes and no staged changes; use 'git add' or 'git change -a'")
    80  	}
    81  	commit := func(amend bool) {
    82  		args := []string{"commit", "-q", "--allow-empty"}
    83  		if amend {
    84  			args = append(args, "--amend")
    85  			if changeQuick {
    86  				args = append(args, "--no-edit")
    87  			}
    88  		}
    89  		if testCommitMsg != "" {
    90  			args = append(args, "-m", testCommitMsg)
    91  		}
    92  		if changeAuto {
    93  			args = append(args, "-a")
    94  		}
    95  		run("git", args...)
    96  	}
    97  	commit(amend)
    98  	for !commitMessageOK() {
    99  		fmt.Print("re-edit commit message (y/n)? ")
   100  		if !scanYes() {
   101  			break
   102  		}
   103  		commit(true)
   104  	}
   105  	printf("change updated.")
   106  }
   107  
   108  func checkoutOrCreate(target string) {
   109  	if strings.ToUpper(target) == "HEAD" {
   110  		// Git gets very upset and confused if you 'git change head'
   111  		// on systems with case-insensitive file names: the branch
   112  		// head conflicts with the usual HEAD.
   113  		dief("invalid branch name %q: ref name HEAD is reserved for git.", target)
   114  	}
   115  
   116  	// If local branch exists, check it out.
   117  	for _, b := range LocalBranches() {
   118  		if b.Name == target {
   119  			run("git", "checkout", "-q", target)
   120  			printf("changed to branch %v.", target)
   121  			return
   122  		}
   123  	}
   124  
   125  	// If origin branch exists, create local branch tracking it.
   126  	for _, name := range OriginBranches() {
   127  		if name == "origin/"+target {
   128  			run("git", "checkout", "-q", "-t", "-b", target, name)
   129  			printf("created branch %v tracking %s.", target, name)
   130  			return
   131  		}
   132  	}
   133  
   134  	// Otherwise, this is a request to create a local work branch.
   135  	// Check for reserved names. We take everything with a dot.
   136  	if strings.Contains(target, ".") {
   137  		dief("invalid branch name %v: branch names with dots are reserved for git-codereview.", target)
   138  	}
   139  
   140  	// If the current branch has a pending commit, building
   141  	// on top of it will not help. Don't allow that.
   142  	// Otherwise, inherit HEAD and upstream from the current branch.
   143  	b := CurrentBranch()
   144  	if b.HasPendingCommit() {
   145  		if !b.IsLocalOnly() {
   146  			dief("bad repo state: branch %s is ahead of origin/%s", b.Name, b.Name)
   147  		}
   148  		dief("cannot branch from work branch; change back to %v first.", strings.TrimPrefix(b.OriginBranch(), "origin/"))
   149  	}
   150  
   151  	origin := b.OriginBranch()
   152  
   153  	// NOTE: This is different from git checkout -q -t -b branch. It does not move HEAD.
   154  	run("git", "checkout", "-q", "-b", target)
   155  	run("git", "branch", "-q", "--set-upstream-to", origin)
   156  	printf("created branch %v tracking %s.", target, origin)
   157  }
   158  
   159  var messageRE = regexp.MustCompile(`^(\[[a-zA-Z0-9.-]+\] )?[a-zA-Z0-9-/,. ]+: `)
   160  
   161  func commitMessageOK() bool {
   162  	body := cmdOutput("git", "log", "--format=format:%B", "-n", "1")
   163  	ok := true
   164  	if !messageRE.MatchString(body) {
   165  		fmt.Print(commitMessageWarning)
   166  		ok = false
   167  	}
   168  	return ok
   169  }
   170  
   171  const commitMessageWarning = `
   172  Your CL description appears not to use the standard form.
   173  
   174  The first line of your change description is conventionally a one-line summary
   175  of the change, prefixed by the primary affected package, and is used as the
   176  subject for code review mail; the rest of the description elaborates.
   177  
   178  Examples:
   179  
   180  	encoding/rot13: new package
   181  
   182  	math: add IsInf, IsNaN
   183  
   184  	net: fix cname in LookupHost
   185  
   186  	unicode: update to Unicode 5.0.2
   187  
   188  `
   189  
   190  const fixesIssueWarning = `
   191  Your CL description contains the string %q, which is
   192  the old Google Code way of linking commits to issues.
   193  
   194  You should rewrite it to use the GitHub convention: "Fixes #%v".
   195  
   196  `
   197  
   198  func scanYes() bool {
   199  	var s string
   200  	fmt.Scan(&s)
   201  	return strings.HasPrefix(strings.ToLower(s), "y")
   202  }