golang.org/x/build@v0.0.0-20240506185731-218518f32b70/internal/task/git.go (about) 1 // Copyright 2023 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 task 6 7 import ( 8 "bytes" 9 "context" 10 "fmt" 11 "io" 12 "os" 13 "os/exec" 14 15 "golang.org/x/oauth2" 16 ) 17 18 // A Git manages a set of Git repositories. 19 type Git struct { 20 ts oauth2.TokenSource 21 cookieFile string 22 } 23 24 // UseOAuth2Auth configures Git authentication using ts. 25 func (g *Git) UseOAuth2Auth(ts oauth2.TokenSource) error { 26 g.ts = ts 27 f, err := os.CreateTemp("", "gitcookies") 28 if err != nil { 29 return err 30 } 31 g.cookieFile = f.Name() 32 return f.Close() 33 } 34 35 // Clone checks out the repository at origin into a temporary directory owned 36 // by the resulting GitDir. 37 func (g *Git) Clone(ctx context.Context, origin string) (*GitDir, error) { 38 dir, err := os.MkdirTemp("", "relui-git-clone-*") 39 if err != nil { 40 return nil, err 41 } 42 if _, err := g.run(ctx, "", "clone", origin, dir); err != nil { 43 return nil, err 44 } 45 return &GitDir{g, dir}, err 46 } 47 48 func (g *Git) run(ctx context.Context, dir string, args ...string) ([]byte, error) { 49 stdout := &bytes.Buffer{} 50 stderr := &bytes.Buffer{} 51 if err := g.runGitStreamed(ctx, stdout, stderr, dir, args...); err != nil { 52 return stdout.Bytes(), fmt.Errorf("git command failed: %v, stderr %v", err, stderr.String()) 53 } 54 return stdout.Bytes(), nil 55 } 56 57 func (g *Git) runGitStreamed(ctx context.Context, stdout, stderr io.Writer, dir string, args ...string) error { 58 if g.ts != nil { 59 tok, err := g.ts.Token() 60 if err != nil { 61 return err 62 } 63 // https://github.com/curl/curl/blob/master/docs/HTTP-COOKIES.md 64 cookieLine := fmt.Sprintf(".googlesource.com\tTRUE\t/\tTRUE\t%v\to\t%v\n", tok.Expiry.Unix(), tok.AccessToken) 65 if err := os.WriteFile(g.cookieFile, []byte(cookieLine), 0o700); err != nil { 66 return fmt.Errorf("error writing git cookies: %v", err) 67 } 68 args = append([]string{"-c", "http.cookiefile=" + g.cookieFile}, args...) 69 } 70 args = append([]string{ 71 "-c", "user.email=gobot@golang.org", 72 "-c", "user.name='Gopher Robot'", 73 }, args...) 74 75 cmd := exec.CommandContext(ctx, "git", args...) 76 cmd.Dir = dir 77 cmd.Stdout = stdout 78 cmd.Stderr = stderr 79 return cmd.Run() 80 } 81 82 // A GitDir is a single Git repository. 83 type GitDir struct { 84 git *Git 85 dir string 86 } 87 88 // RunCommand runs a Git command, returning its stdout if it succeeds, or an 89 // error containing its stderr if it fails. 90 func (g *GitDir) RunCommand(ctx context.Context, args ...string) ([]byte, error) { 91 return g.git.run(ctx, g.dir, args...) 92 } 93 94 // Close cleans up the repository. 95 func (g *GitDir) Close() error { 96 return os.RemoveAll(g.dir) 97 }