github.com/cockroachdb/tools@v0.0.0-20230222021103-a6d27438930d/cmd/go-contrib-init/contrib.go (about) 1 // Copyright 2017 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 // The go-contrib-init command helps new Go contributors get their development 6 // environment set up for the Go contribution process. 7 // 8 // It aims to be a complement or alternative to https://golang.org/doc/contribute.html. 9 package main 10 11 import ( 12 "bytes" 13 "flag" 14 "fmt" 15 "go/build" 16 exec "golang.org/x/sys/execabs" 17 "io/ioutil" 18 "log" 19 "os" 20 "path/filepath" 21 "regexp" 22 "runtime" 23 "strings" 24 ) 25 26 var ( 27 repo = flag.String("repo", detectrepo(), "Which go repo you want to contribute to. Use \"go\" for the core, or e.g. \"net\" for golang.org/x/net/*") 28 dry = flag.Bool("dry-run", false, "Fail with problems instead of trying to fix things.") 29 ) 30 31 func main() { 32 log.SetFlags(0) 33 flag.Parse() 34 35 checkCLA() 36 checkGoroot() 37 checkWorkingDir() 38 checkGitOrigin() 39 checkGitCodeReview() 40 fmt.Print("All good. Happy hacking!\n" + 41 "Remember to squash your revised commits and preserve the magic Change-Id lines.\n" + 42 "Next steps: https://golang.org/doc/contribute.html#commit_changes\n") 43 } 44 45 func detectrepo() string { 46 wd, err := os.Getwd() 47 if err != nil { 48 return "go" 49 } 50 51 for _, path := range filepath.SplitList(build.Default.GOPATH) { 52 rightdir := filepath.Join(path, "src", "golang.org", "x") + string(os.PathSeparator) 53 if strings.HasPrefix(wd, rightdir) { 54 tail := wd[len(rightdir):] 55 end := strings.Index(tail, string(os.PathSeparator)) 56 if end > 0 { 57 repo := tail[:end] 58 return repo 59 } 60 } 61 } 62 63 return "go" 64 } 65 66 var googleSourceRx = regexp.MustCompile(`(?m)^(go|go-review)?\.googlesource.com\b`) 67 68 func checkCLA() { 69 slurp, err := ioutil.ReadFile(cookiesFile()) 70 if err != nil && !os.IsNotExist(err) { 71 log.Fatal(err) 72 } 73 if googleSourceRx.Match(slurp) { 74 // Probably good. 75 return 76 } 77 log.Fatal("Your .gitcookies file isn't configured.\n" + 78 "Next steps:\n" + 79 " * Submit a CLA (https://golang.org/doc/contribute.html#cla) if not done\n" + 80 " * Go to https://go.googlesource.com/ and click \"Generate Password\" at the top,\n" + 81 " then follow instructions.\n" + 82 " * Run go-contrib-init again.\n") 83 } 84 85 func expandUser(s string) string { 86 env := "HOME" 87 if runtime.GOOS == "windows" { 88 env = "USERPROFILE" 89 } else if runtime.GOOS == "plan9" { 90 env = "home" 91 } 92 home := os.Getenv(env) 93 if home == "" { 94 return s 95 } 96 97 if len(s) >= 2 && s[0] == '~' && os.IsPathSeparator(s[1]) { 98 if runtime.GOOS == "windows" { 99 s = filepath.ToSlash(filepath.Join(home, s[2:])) 100 } else { 101 s = filepath.Join(home, s[2:]) 102 } 103 } 104 return os.Expand(s, func(env string) string { 105 if env == "HOME" { 106 return home 107 } 108 return os.Getenv(env) 109 }) 110 } 111 112 func cookiesFile() string { 113 out, _ := exec.Command("git", "config", "http.cookiefile").Output() 114 if s := strings.TrimSpace(string(out)); s != "" { 115 if strings.HasPrefix(s, "~") { 116 s = expandUser(s) 117 } 118 return s 119 } 120 if runtime.GOOS == "windows" { 121 return filepath.Join(os.Getenv("USERPROFILE"), ".gitcookies") 122 } 123 return filepath.Join(os.Getenv("HOME"), ".gitcookies") 124 } 125 126 func checkGoroot() { 127 v := os.Getenv("GOROOT") 128 if v == "" { 129 return 130 } 131 if *repo == "go" { 132 if strings.HasPrefix(v, "/usr/") { 133 log.Fatalf("Your GOROOT environment variable is set to %q\n"+ 134 "This is almost certainly not what you want. Either unset\n"+ 135 "your GOROOT or set it to the path of your development version\n"+ 136 "of Go.", v) 137 } 138 slurp, err := ioutil.ReadFile(filepath.Join(v, "VERSION")) 139 if err == nil { 140 slurp = bytes.TrimSpace(slurp) 141 log.Fatalf("Your GOROOT environment variable is set to %q\n"+ 142 "But that path is to a binary release of Go, with VERSION file %q.\n"+ 143 "You should hack on Go in a fresh checkout of Go. Fix or unset your GOROOT.\n", 144 v, slurp) 145 } 146 } 147 } 148 149 func checkWorkingDir() { 150 wd, err := os.Getwd() 151 if err != nil { 152 log.Fatal(err) 153 } 154 if *repo == "go" { 155 if inGoPath(wd) { 156 log.Fatalf(`You can't work on Go from within your GOPATH. Please checkout Go outside of your GOPATH 157 158 Current directory: %s 159 GOPATH: %s 160 `, wd, os.Getenv("GOPATH")) 161 } 162 return 163 } 164 165 gopath := firstGoPath() 166 if gopath == "" { 167 log.Fatal("Your GOPATH is not set, please set it") 168 } 169 170 rightdir := filepath.Join(gopath, "src", "golang.org", "x", *repo) 171 if !strings.HasPrefix(wd, rightdir) { 172 dirExists, err := exists(rightdir) 173 if err != nil { 174 log.Fatal(err) 175 } 176 if !dirExists { 177 log.Fatalf("The repo you want to work on is currently not on your system.\n"+ 178 "Run %q to obtain this repo\n"+ 179 "then go to the directory %q\n", 180 "go get -d golang.org/x/"+*repo, rightdir) 181 } 182 log.Fatalf("Your current directory is:%q\n"+ 183 "Working on golang/x/%v requires you be in %q\n", 184 wd, *repo, rightdir) 185 } 186 } 187 188 func firstGoPath() string { 189 list := filepath.SplitList(build.Default.GOPATH) 190 if len(list) < 1 { 191 return "" 192 } 193 return list[0] 194 } 195 196 func exists(path string) (bool, error) { 197 _, err := os.Stat(path) 198 if os.IsNotExist(err) { 199 return false, nil 200 } 201 return true, err 202 } 203 204 func inGoPath(wd string) bool { 205 if os.Getenv("GOPATH") == "" { 206 return false 207 } 208 209 for _, path := range filepath.SplitList(os.Getenv("GOPATH")) { 210 if strings.HasPrefix(wd, filepath.Join(path, "src")) { 211 return true 212 } 213 } 214 215 return false 216 } 217 218 // mostly check that they didn't clone from github 219 func checkGitOrigin() { 220 if _, err := exec.LookPath("git"); err != nil { 221 log.Fatalf("You don't appear to have git installed. Do that.") 222 } 223 wantRemote := "https://go.googlesource.com/" + *repo 224 remotes, err := exec.Command("git", "remote", "-v").Output() 225 if err != nil { 226 msg := cmdErr(err) 227 if strings.Contains(msg, "Not a git repository") { 228 log.Fatalf("Your current directory is not in a git checkout of %s", wantRemote) 229 } 230 log.Fatalf("Error running git remote -v: %v", msg) 231 } 232 matches := 0 233 for _, line := range strings.Split(string(remotes), "\n") { 234 line = strings.TrimSpace(line) 235 if !strings.HasPrefix(line, "origin") { 236 continue 237 } 238 if !strings.Contains(line, wantRemote) { 239 curRemote := strings.Fields(strings.TrimPrefix(line, "origin"))[0] 240 // TODO: if not in dryRun mode, just fix it? 241 log.Fatalf("Current directory's git was cloned from %q; origin should be %q", curRemote, wantRemote) 242 } 243 matches++ 244 } 245 if matches == 0 { 246 log.Fatalf("git remote -v output didn't contain expected %q. Got:\n%s", wantRemote, remotes) 247 } 248 } 249 250 func cmdErr(err error) string { 251 if ee, ok := err.(*exec.ExitError); ok && len(ee.Stderr) > 0 { 252 return fmt.Sprintf("%s: %s", err, ee.Stderr) 253 } 254 return fmt.Sprint(err) 255 } 256 257 func checkGitCodeReview() { 258 if _, err := exec.LookPath("git-codereview"); err != nil { 259 if *dry { 260 log.Fatalf("You don't appear to have git-codereview tool. While this is technically optional,\n" + 261 "almost all Go contributors use it. Our documentation and this tool assume it is used.\n" + 262 "To install it, run:\n\n\t$ go get golang.org/x/review/git-codereview\n\n(Then run go-contrib-init again)") 263 } 264 err := exec.Command("go", "get", "golang.org/x/review/git-codereview").Run() 265 if err != nil { 266 log.Fatalf("Error running go get golang.org/x/review/git-codereview: %v", cmdErr(err)) 267 } 268 log.Printf("Installed git-codereview (ran `go get golang.org/x/review/git-codereview`)") 269 } 270 missing := false 271 for _, cmd := range []string{"change", "gofmt", "mail", "pending", "submit", "sync"} { 272 v, _ := exec.Command("git", "config", "alias."+cmd).Output() 273 if strings.Contains(string(v), "codereview") { 274 continue 275 } 276 if *dry { 277 log.Printf("Missing alias. Run:\n\t$ git config alias.%s \"codereview %s\"", cmd, cmd) 278 missing = true 279 } else { 280 err := exec.Command("git", "config", "alias."+cmd, "codereview "+cmd).Run() 281 if err != nil { 282 log.Fatalf("Error setting alias.%s: %v", cmd, cmdErr(err)) 283 } 284 } 285 } 286 if missing { 287 log.Fatalf("Missing aliases. (While optional, this tool assumes you use them.)") 288 } 289 }