blob: 1259439a6b79c863c7e1600b0cf569481562112d [file] [log] [blame]
// Copyright 2023 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package task
import (
"bytes"
"context"
"fmt"
"io"
"os"
"os/exec"
"golang.org/x/oauth2"
)
// A Git manages a set of Git repositories.
type Git struct {
ts oauth2.TokenSource
cookieFile string
}
// UseOAuth2Auth configures Git authentication using ts.
func (g *Git) UseOAuth2Auth(ts oauth2.TokenSource) error {
g.ts = ts
f, err := os.CreateTemp("", "gitcookies")
if err != nil {
return err
}
g.cookieFile = f.Name()
return f.Close()
}
// Clone checks out the repository at origin into a temporary directory owned
// by the resulting GitDir.
func (g *Git) Clone(ctx context.Context, origin string) (*GitDir, error) {
dir, err := os.MkdirTemp("", "relui-git-clone-*")
if err != nil {
return nil, err
}
if _, err := g.run(ctx, "", "clone", origin, dir); err != nil {
return nil, err
}
return &GitDir{g, dir}, err
}
func (g *Git) run(ctx context.Context, dir string, args ...string) ([]byte, error) {
stdout := &bytes.Buffer{}
stderr := &bytes.Buffer{}
if err := g.runGitStreamed(ctx, stdout, stderr, dir, args...); err != nil {
return stdout.Bytes(), fmt.Errorf("git command failed: %v, stderr %v", err, stderr.String())
}
return stdout.Bytes(), nil
}
func (g *Git) runGitStreamed(ctx context.Context, stdout, stderr io.Writer, dir string, args ...string) error {
if g.ts != nil {
tok, err := g.ts.Token()
if err != nil {
return err
}
// https://github.com/curl/curl/blob/master/docs/HTTP-COOKIES.md
cookieLine := fmt.Sprintf(".googlesource.com\tTRUE\t/\tTRUE\t%v\to\t%v\n", tok.Expiry.Unix(), tok.AccessToken)
if err := os.WriteFile(g.cookieFile, []byte(cookieLine), 0o700); err != nil {
return fmt.Errorf("error writing git cookies: %v", err)
}
args = append([]string{"-c", "http.cookiefile=" + g.cookieFile}, args...)
}
args = append([]string{
"-c", "user.email=gobot@golang.org",
"-c", "user.name='Gopher Robot'",
}, args...)
cmd := exec.CommandContext(ctx, "git", args...)
cmd.Dir = dir
cmd.Stdout = stdout
cmd.Stderr = stderr
return cmd.Run()
}
// A GitDir is a single Git repository.
type GitDir struct {
git *Git
dir string
}
// RunCommand runs a Git command, returning its stdout if it succeeds, or an
// error containing its stderr if it fails.
func (g *GitDir) RunCommand(ctx context.Context, args ...string) ([]byte, error) {
return g.git.run(ctx, g.dir, args...)
}
// Close cleans up the repository.
func (g *GitDir) Close() error {
return os.RemoveAll(g.dir)
}