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  }