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.
5 // The go-contrib-init command helps new Go contributors get their development
6 // environment set up for the Go contribution process.
8 // It aims to be a complement or alternative to https://golang.org/doc/contribute.html.
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.")
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")
45 func detectrepo() string {
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))
66 var googleSourceRx = regexp.MustCompile(`(?m)^(go|go-review)?\.googlesource.com\b`)
69 slurp, err := ioutil.ReadFile(cookiesFile())
70 if err != nil && !os.IsNotExist(err) {
73 if googleSourceRx.Match(slurp) {
77 log.Fatal("Your .gitcookies file isn't configured.\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")
85 func expandUser(s string) string {
87 if runtime.GOOS == "windows" {
89 } else if runtime.GOOS == "plan9" {
92 home := os.Getenv(env)
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:]))
101 s = filepath.Join(home, s[2:])
104 return os.Expand(s, func(env string) string {
108 return os.Getenv(env)
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, "~") {
120 if runtime.GOOS == "windows" {
121 return filepath.Join(os.Getenv("USERPROFILE"), ".gitcookies")
123 return filepath.Join(os.Getenv("HOME"), ".gitcookies")
127 v := os.Getenv("GOROOT")
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"+
138 slurp, err := ioutil.ReadFile(filepath.Join(v, "VERSION"))
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",
149 func checkWorkingDir() {
150 wd, err := os.Getwd()
156 log.Fatalf(`You can't work on Go from within your GOPATH. Please checkout Go outside of your GOPATH
158 Current directory: %s
160 `, wd, os.Getenv("GOPATH"))
165 gopath := firstGoPath()
167 log.Fatal("Your GOPATH is not set, please set it")
170 rightdir := filepath.Join(gopath, "src", "golang.org", "x", *repo)
171 if !strings.HasPrefix(wd, rightdir) {
172 dirExists, err := exists(rightdir)
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)
182 log.Fatalf("Your current directory is:%q\n"+
183 "Working on golang/x/%v requires you be in %q\n",
188 func firstGoPath() string {
189 list := filepath.SplitList(build.Default.GOPATH)
196 func exists(path string) (bool, error) {
197 _, err := os.Stat(path)
198 if os.IsNotExist(err) {
204 func inGoPath(wd string) bool {
205 if os.Getenv("GOPATH") == "" {
209 for _, path := range filepath.SplitList(os.Getenv("GOPATH")) {
210 if strings.HasPrefix(wd, filepath.Join(path, "src")) {
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.")
223 wantRemote := "https://go.googlesource.com/" + *repo
224 remotes, err := exec.Command("git", "remote", "-v").Output()
227 if strings.Contains(msg, "Not a git repository") {
228 log.Fatalf("Your current directory is not in a git checkout of %s", wantRemote)
230 log.Fatalf("Error running git remote -v: %v", msg)
233 for _, line := range strings.Split(string(remotes), "\n") {
234 line = strings.TrimSpace(line)
235 if !strings.HasPrefix(line, "origin") {
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)
246 log.Fatalf("git remote -v output didn't contain expected %q. Got:\n%s", wantRemote, remotes)
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)
254 return fmt.Sprint(err)
257 func checkGitCodeReview() {
258 if _, err := exec.LookPath("git-codereview"); err != nil {
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)")
264 err := exec.Command("go", "get", "golang.org/x/review/git-codereview").Run()
266 log.Fatalf("Error running go get golang.org/x/review/git-codereview: %v", cmdErr(err))
268 log.Printf("Installed git-codereview (ran `go get golang.org/x/review/git-codereview`)")
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") {
277 log.Printf("Missing alias. Run:\n\t$ git config alias.%s \"codereview %s\"", cmd, cmd)
280 err := exec.Command("git", "config", "alias."+cmd, "codereview "+cmd).Run()
282 log.Fatalf("Error setting alias.%s: %v", cmd, cmdErr(err))
287 log.Fatalf("Missing aliases. (While optional, this tool assumes you use them.)")