mirror of
https://gitea.com/Lydanne/buildx.git
synced 2025-05-18 00:47:48 +08:00

Update buildkit dependency to v0.14.0-rc1. Update the tracing infrastructure to use the new detect API which updates how the delegated exporter is configured. Signed-off-by: Jonathan A. Sternberg <jonathan.sternberg@docker.com>
254 lines
5.9 KiB
Go
254 lines
5.9 KiB
Go
package gitutil
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"io"
|
|
"os"
|
|
"os/exec"
|
|
"strings"
|
|
|
|
"github.com/pkg/errors"
|
|
)
|
|
|
|
// GitCLI carries config to pass to the git cli to make running multiple
|
|
// commands less repetitive.
|
|
type GitCLI struct {
|
|
git string
|
|
exec func(context.Context, *exec.Cmd) error
|
|
|
|
args []string
|
|
dir string
|
|
streams StreamFunc
|
|
|
|
workTree string
|
|
gitDir string
|
|
|
|
sshAuthSock string
|
|
sshKnownHosts string
|
|
}
|
|
|
|
// Option provides a variadic option for configuring the git client.
|
|
type Option func(b *GitCLI)
|
|
|
|
// WithGitBinary sets the git binary path.
|
|
func WithGitBinary(path string) Option {
|
|
return func(b *GitCLI) {
|
|
b.git = path
|
|
}
|
|
}
|
|
|
|
// WithExec sets the command exec function.
|
|
func WithExec(exec func(context.Context, *exec.Cmd) error) Option {
|
|
return func(b *GitCLI) {
|
|
b.exec = exec
|
|
}
|
|
}
|
|
|
|
// WithArgs sets extra args.
|
|
func WithArgs(args ...string) Option {
|
|
return func(b *GitCLI) {
|
|
b.args = append(b.args, args...)
|
|
}
|
|
}
|
|
|
|
// WithDir sets working directory.
|
|
//
|
|
// This should be a path to any directory within a standard git repository.
|
|
func WithDir(dir string) Option {
|
|
return func(b *GitCLI) {
|
|
b.dir = dir
|
|
}
|
|
}
|
|
|
|
// WithWorkTree sets the --work-tree arg.
|
|
//
|
|
// This should be the path to the top-level directory of the checkout. When
|
|
// setting this, you also likely need to set WithGitDir.
|
|
func WithWorkTree(workTree string) Option {
|
|
return func(b *GitCLI) {
|
|
b.workTree = workTree
|
|
}
|
|
}
|
|
|
|
// WithGitDir sets the --git-dir arg.
|
|
//
|
|
// This should be the path to the .git directory. When setting this, you may
|
|
// also need to set WithWorkTree, unless you are working with a bare
|
|
// repository.
|
|
func WithGitDir(gitDir string) Option {
|
|
return func(b *GitCLI) {
|
|
b.gitDir = gitDir
|
|
}
|
|
}
|
|
|
|
// WithSSHAuthSock sets the ssh auth sock.
|
|
func WithSSHAuthSock(sshAuthSock string) Option {
|
|
return func(b *GitCLI) {
|
|
b.sshAuthSock = sshAuthSock
|
|
}
|
|
}
|
|
|
|
// WithSSHKnownHosts sets the known hosts file.
|
|
func WithSSHKnownHosts(sshKnownHosts string) Option {
|
|
return func(b *GitCLI) {
|
|
b.sshKnownHosts = sshKnownHosts
|
|
}
|
|
}
|
|
|
|
type StreamFunc func(context.Context) (io.WriteCloser, io.WriteCloser, func())
|
|
|
|
// WithStreams configures a callback for getting the streams for a command. The
|
|
// stream callback will be called once for each command, and both writers will
|
|
// be closed after the command has finished.
|
|
func WithStreams(streams StreamFunc) Option {
|
|
return func(b *GitCLI) {
|
|
b.streams = streams
|
|
}
|
|
}
|
|
|
|
// New initializes a new git client
|
|
func NewGitCLI(opts ...Option) *GitCLI {
|
|
c := &GitCLI{}
|
|
for _, opt := range opts {
|
|
opt(c)
|
|
}
|
|
return c
|
|
}
|
|
|
|
// New returns a new git client with the same config as the current one, but
|
|
// with the given options applied on top.
|
|
func (cli *GitCLI) New(opts ...Option) *GitCLI {
|
|
c := &GitCLI{
|
|
git: cli.git,
|
|
dir: cli.dir,
|
|
workTree: cli.workTree,
|
|
gitDir: cli.gitDir,
|
|
args: append([]string{}, cli.args...),
|
|
streams: cli.streams,
|
|
sshAuthSock: cli.sshAuthSock,
|
|
sshKnownHosts: cli.sshKnownHosts,
|
|
}
|
|
for _, opt := range opts {
|
|
opt(c)
|
|
}
|
|
return c
|
|
}
|
|
|
|
// Run executes a git command with the given args.
|
|
func (cli *GitCLI) Run(ctx context.Context, args ...string) (_ []byte, err error) {
|
|
gitBinary := "git"
|
|
if cli.git != "" {
|
|
gitBinary = cli.git
|
|
}
|
|
|
|
for {
|
|
var cmd *exec.Cmd
|
|
if cli.exec == nil {
|
|
cmd = exec.CommandContext(ctx, gitBinary)
|
|
} else {
|
|
cmd = exec.Command(gitBinary)
|
|
}
|
|
|
|
cmd.Dir = cli.dir
|
|
if cmd.Dir == "" {
|
|
cmd.Dir = cli.workTree
|
|
}
|
|
|
|
// Block sneaky repositories from using repos from the filesystem as submodules.
|
|
cmd.Args = append(cmd.Args, "-c", "protocol.file.allow=user")
|
|
if cli.workTree != "" {
|
|
cmd.Args = append(cmd.Args, "--work-tree", cli.workTree)
|
|
}
|
|
if cli.gitDir != "" {
|
|
cmd.Args = append(cmd.Args, "--git-dir", cli.gitDir)
|
|
}
|
|
cmd.Args = append(cmd.Args, cli.args...)
|
|
cmd.Args = append(cmd.Args, args...)
|
|
|
|
buf := bytes.NewBuffer(nil)
|
|
errbuf := bytes.NewBuffer(nil)
|
|
cmd.Stdin = nil
|
|
cmd.Stdout = buf
|
|
cmd.Stderr = errbuf
|
|
if cli.streams != nil {
|
|
stdout, stderr, flush := cli.streams(ctx)
|
|
if stdout != nil {
|
|
cmd.Stdout = io.MultiWriter(stdout, cmd.Stdout)
|
|
}
|
|
if stderr != nil {
|
|
cmd.Stderr = io.MultiWriter(stderr, cmd.Stderr)
|
|
}
|
|
defer stdout.Close()
|
|
defer stderr.Close()
|
|
defer func() {
|
|
if err != nil {
|
|
flush()
|
|
}
|
|
}()
|
|
}
|
|
|
|
cmd.Env = []string{
|
|
"PATH=" + os.Getenv("PATH"),
|
|
"GIT_TERMINAL_PROMPT=0",
|
|
"GIT_SSH_COMMAND=" + getGitSSHCommand(cli.sshKnownHosts),
|
|
// "GIT_TRACE=1",
|
|
"GIT_CONFIG_NOSYSTEM=1", // Disable reading from system gitconfig.
|
|
"HOME=/dev/null", // Disable reading from user gitconfig.
|
|
"LC_ALL=C", // Ensure consistent output.
|
|
}
|
|
if cli.sshAuthSock != "" {
|
|
cmd.Env = append(cmd.Env, "SSH_AUTH_SOCK="+cli.sshAuthSock)
|
|
}
|
|
|
|
if cli.exec != nil {
|
|
// remote git commands spawn helper processes that inherit FDs and don't
|
|
// handle parent death signal so exec.CommandContext can't be used
|
|
err = cli.exec(ctx, cmd)
|
|
} else {
|
|
err = cmd.Run()
|
|
}
|
|
|
|
if err != nil {
|
|
select {
|
|
case <-ctx.Done():
|
|
cerr := context.Cause(ctx)
|
|
if cerr != nil {
|
|
return buf.Bytes(), errors.Wrapf(cerr, "context completed: git stderr:\n%s", errbuf.String())
|
|
}
|
|
default:
|
|
}
|
|
|
|
if strings.Contains(errbuf.String(), "--depth") || strings.Contains(errbuf.String(), "shallow") {
|
|
if newArgs := argsNoDepth(args); len(args) > len(newArgs) {
|
|
args = newArgs
|
|
continue
|
|
}
|
|
}
|
|
|
|
return buf.Bytes(), errors.Wrapf(err, "git stderr:\n%s", errbuf.String())
|
|
}
|
|
return buf.Bytes(), nil
|
|
}
|
|
}
|
|
|
|
func getGitSSHCommand(knownHosts string) string {
|
|
gitSSHCommand := "ssh -F /dev/null"
|
|
if knownHosts != "" {
|
|
gitSSHCommand += " -o UserKnownHostsFile=" + knownHosts
|
|
} else {
|
|
gitSSHCommand += " -o StrictHostKeyChecking=no"
|
|
}
|
|
return gitSSHCommand
|
|
}
|
|
|
|
func argsNoDepth(args []string) []string {
|
|
out := make([]string, 0, len(args))
|
|
for _, a := range args {
|
|
if a != "--depth=1" {
|
|
out = append(out, a)
|
|
}
|
|
}
|
|
return out
|
|
}
|