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 }