From ff63bfbce50a6e39ba9d741d2535dd6eec85458d Mon Sep 17 00:00:00 2001 From: Justin Chadwell Date: Wed, 16 Aug 2023 16:13:19 +0100 Subject: [PATCH] wip: git: wrap upstream gitutil cli Signed-off-by: Justin Chadwell --- build/git.go | 13 ++-- tests/bake.go | 15 +++-- util/gitutil/gitutil.go | 118 ++++++++-------------------------- util/gitutil/gitutil_test.go | 32 +++++---- util/gitutil/testutil.go | 17 ++--- util/gitutil/testutilserve.go | 6 +- 6 files changed, 76 insertions(+), 125 deletions(-) diff --git a/build/git.go b/build/git.go index d80a00ba..f5208800 100644 --- a/build/git.go +++ b/build/git.go @@ -9,6 +9,7 @@ import ( "strings" "github.com/docker/buildx/util/gitutil" + bkgitutil "github.com/moby/buildkit/util/gitutil" specs "github.com/opencontainers/image-spec/specs-go/v1" "github.com/pkg/errors" ) @@ -49,7 +50,7 @@ func getGitAttributes(ctx context.Context, contextPath string, dockerfilePath st wd, _ = filepath.Abs(filepath.Join(cwd, contextPath)) } - gitc, err := gitutil.New(gitutil.WithContext(ctx), gitutil.WithWorkingDir(wd)) + gitc, err := gitutil.New(bkgitutil.WithDir(wd)) if err != nil { if st, err := os.Stat(path.Join(wd, ".git")); err == nil && st.IsDir() { return res, errors.New("buildx: git was not found in the system. Current commit information was not captured by the build") @@ -57,14 +58,14 @@ func getGitAttributes(ctx context.Context, contextPath string, dockerfilePath st return } - if !gitc.IsInsideWorkTree() { + if !gitc.IsInsideWorkTree(ctx) { if st, err := os.Stat(path.Join(wd, ".git")); err == nil && st.IsDir() { return res, errors.New("buildx: failed to read current commit information with git rev-parse --is-inside-work-tree") } return res, nil } - if sha, err := gitc.FullCommit(); err != nil && !gitutil.IsUnknownRevision(err) { + if sha, err := gitc.FullCommit(ctx); err != nil && !gitutil.IsUnknownRevision(err) { return res, errors.Wrapf(err, "buildx: failed to get git commit") } else if sha != "" { checkDirty := false @@ -73,7 +74,7 @@ func getGitAttributes(ctx context.Context, contextPath string, dockerfilePath st checkDirty = v } } - if checkDirty && gitc.IsDirty() { + if checkDirty && gitc.IsDirty(ctx) { sha += "-dirty" } if setGitLabels { @@ -84,7 +85,7 @@ func getGitAttributes(ctx context.Context, contextPath string, dockerfilePath st } } - if rurl, err := gitc.RemoteURL(); err == nil && rurl != "" { + if rurl, err := gitc.RemoteURL(ctx); err == nil && rurl != "" { if setGitLabels { res["label:"+specs.AnnotationSource] = rurl } @@ -94,7 +95,7 @@ func getGitAttributes(ctx context.Context, contextPath string, dockerfilePath st } if setGitLabels { - if root, err := gitc.RootDir(); err != nil { + if root, err := gitc.WorkTree(ctx); err != nil { return res, errors.Wrapf(err, "buildx: failed to get git root dir") } else if root != "" { if dockerfilePath == "" { diff --git a/tests/bake.go b/tests/bake.go index b76a8bb8..8ed54473 100644 --- a/tests/bake.go +++ b/tests/bake.go @@ -6,6 +6,7 @@ import ( "github.com/containerd/continuity/fs/fstest" "github.com/docker/buildx/util/gitutil" + bkgitutil "github.com/moby/buildkit/util/gitutil" "github.com/moby/buildkit/util/testutil/integration" "github.com/stretchr/testify/require" ) @@ -66,7 +67,7 @@ EOT ) dirDest := t.TempDir() - git, err := gitutil.New(gitutil.WithWorkingDir(dir)) + git, err := gitutil.New(bkgitutil.WithDir(dir)) require.NoError(t, err) gitutil.GitInit(git, t) @@ -100,7 +101,7 @@ EOT ) dirDest := t.TempDir() - git, err := gitutil.New(gitutil.WithWorkingDir(dirSpec)) + git, err := gitutil.New(bkgitutil.WithDir(dirSpec)) require.NoError(t, err) gitutil.GitInit(git, t) @@ -134,14 +135,14 @@ EOT ) dirDest := t.TempDir() - gitSpec, err := gitutil.New(gitutil.WithWorkingDir(dirSpec)) + gitSpec, err := gitutil.New(bkgitutil.WithDir(dirSpec)) require.NoError(t, err) gitutil.GitInit(gitSpec, t) gitutil.GitAdd(gitSpec, t, "docker-bake.hcl") gitutil.GitCommit(gitSpec, t, "initial commit") addrSpec := gitutil.GitServeHTTP(gitSpec, t) - gitSrc, err := gitutil.New(gitutil.WithWorkingDir(dirSrc)) + gitSrc, err := gitutil.New(bkgitutil.WithDir(dirSrc)) require.NoError(t, err) gitutil.GitInit(gitSrc, t) gitutil.GitAdd(gitSrc, t, "foo") @@ -175,7 +176,7 @@ COPY super-cool.txt / ) dirDest := t.TempDir() - git, err := gitutil.New(gitutil.WithWorkingDir(dir)) + git, err := gitutil.New(bkgitutil.WithDir(dir)) require.NoError(t, err) gitutil.GitInit(git, t) gitutil.GitAdd(git, t, "docker-bake.hcl", "bar") @@ -215,7 +216,7 @@ EOT ) dirDest := t.TempDir() - git, err := gitutil.New(gitutil.WithWorkingDir(dirSpec)) + git, err := gitutil.New(bkgitutil.WithDir(dirSpec)) require.NoError(t, err) gitutil.GitInit(git, t) @@ -262,7 +263,7 @@ EOT ) dirDest := t.TempDir() - git, err := gitutil.New(gitutil.WithWorkingDir(dirSpec)) + git, err := gitutil.New(bkgitutil.WithDir(dirSpec)) require.NoError(t, err) gitutil.GitInit(git, t) diff --git a/util/gitutil/gitutil.go b/util/gitutil/gitutil.go index 8ab76ab2..8533fd8d 100644 --- a/util/gitutil/gitutil.go +++ b/util/gitutil/gitutil.go @@ -1,111 +1,73 @@ package gitutil import ( - "bytes" "context" "net/url" - "os" - "os/exec" - "path/filepath" "strings" + bkgitutil "github.com/moby/buildkit/util/gitutil" "github.com/pkg/errors" ) -// Git represents an active git object -type Git struct { - ctx context.Context - wd string - gitpath string -} - -// Option provides a variadic option for configuring the git client. -type Option func(b *Git) - -// WithContext sets context. -func WithContext(ctx context.Context) Option { - return func(b *Git) { - b.ctx = ctx - } -} - -// WithWorkingDir sets working directory. -func WithWorkingDir(wd string) Option { - return func(b *Git) { - b.wd = wd - } +// GitCLI represents an active git object +type GitCLI struct { + bkgitutil.GitCLI } // New initializes a new git client -func New(opts ...Option) (*Git, error) { - var err error - c := &Git{ - ctx: context.Background(), - } - - for _, opt := range opts { - opt(c) +func New(opts ...bkgitutil.Option) (*GitCLI, error) { + cli, err := bkgitutil.NewGitCLI(opts...) + if err != nil { + return nil, err } - c.gitpath, err = gitPath(c.wd) + gitpath, err := gitPath(cli.Dir()) if err != nil { return nil, errors.New("git not found in PATH") } - - return c, nil + cli = cli.New(bkgitutil.WithGitBinary(gitpath)) + return &GitCLI{*cli}, nil } -func (c *Git) IsInsideWorkTree() bool { - out, err := c.clean(c.run("rev-parse", "--is-inside-work-tree")) +func (cli *GitCLI) IsInsideWorkTree(ctx context.Context) bool { + out, err := cli.clean(cli.Run(ctx, "rev-parse", "--is-inside-work-tree")) return out == "true" && err == nil } -func (c *Git) IsDirty() bool { - out, err := c.run("status", "--porcelain", "--ignored") - return strings.TrimSpace(out) != "" || err != nil -} - -func (c *Git) RootDir() (string, error) { - return c.clean(c.run("rev-parse", "--show-toplevel")) +func (cli *GitCLI) IsDirty(ctx context.Context) bool { + out, err := cli.Run(ctx, "status", "--porcelain", "--ignored") + return strings.TrimSpace(string(out)) != "" || err != nil } -func (c *Git) GitDir() (string, error) { - dir, err := c.RootDir() - if err != nil { - return "", err - } - return filepath.Join(dir, ".git"), nil -} - -func (c *Git) RemoteURL() (string, error) { +func (cli *GitCLI) RemoteURL(ctx context.Context) (string, error) { // Try to get the remote URL from the origin remote first - if ru, err := c.clean(c.run("remote", "get-url", "origin")); err == nil && ru != "" { + if ru, err := cli.clean(cli.Run(ctx, "remote", "get-url", "origin")); err == nil && ru != "" { return stripCredentials(ru), nil } // If that fails, try to get the remote URL from the upstream remote - if ru, err := c.clean(c.run("remote", "get-url", "upstream")); err == nil && ru != "" { + if ru, err := cli.clean(cli.Run(ctx, "remote", "get-url", "upstream")); err == nil && ru != "" { return stripCredentials(ru), nil } return "", errors.New("no remote URL found for either origin or upstream") } -func (c *Git) FullCommit() (string, error) { - return c.clean(c.run("show", "--format=%H", "HEAD", "--quiet", "--")) +func (cli *GitCLI) FullCommit(ctx context.Context) (string, error) { + return cli.clean(cli.Run(ctx, "show", "--format=%H", "HEAD", "--quiet", "--")) } -func (c *Git) ShortCommit() (string, error) { - return c.clean(c.run("show", "--format=%h", "HEAD", "--quiet", "--")) +func (cli *GitCLI) ShortCommit(ctx context.Context) (string, error) { + return cli.clean(cli.Run(ctx, "show", "--format=%h", "HEAD", "--quiet", "--")) } -func (c *Git) Tag() (string, error) { +func (cli *GitCLI) Tag(ctx context.Context) (string, error) { var tag string var err error for _, fn := range []func() (string, error){ func() (string, error) { - return c.clean(c.run("tag", "--points-at", "HEAD", "--sort", "-version:creatordate")) + return cli.clean(cli.Run(ctx, "tag", "--points-at", "HEAD", "--sort", "-version:creatordate")) }, func() (string, error) { - return c.clean(c.run("describe", "--tags", "--abbrev=0")) + return cli.clean(cli.Run(ctx, "describe", "--tags", "--abbrev=0")) }, } { tag, err = fn() @@ -116,32 +78,8 @@ func (c *Git) Tag() (string, error) { return tag, err } -func (c *Git) run(args ...string) (string, error) { - var extraArgs = []string{ - "-c", "log.showSignature=false", - } - - args = append(extraArgs, args...) - cmd := exec.CommandContext(c.ctx, c.gitpath, args...) - if c.wd != "" { - cmd.Dir = c.wd - } - - // Override the locale to ensure consistent output - cmd.Env = append(os.Environ(), "LC_ALL=C") - - stdout := bytes.Buffer{} - stderr := bytes.Buffer{} - cmd.Stdout = &stdout - cmd.Stderr = &stderr - - if err := cmd.Run(); err != nil { - return "", errors.New(stderr.String()) - } - return stdout.String(), nil -} - -func (c *Git) clean(out string, err error) (string, error) { +func (cli *GitCLI) clean(dt []byte, err error) (string, error) { + out := string(dt) out = strings.ReplaceAll(strings.Split(out, "\n")[0], "'", "") if err != nil { err = errors.New(strings.TrimSuffix(err.Error(), "\n")) diff --git a/util/gitutil/gitutil_test.go b/util/gitutil/gitutil_test.go index 991cd32e..61528d98 100644 --- a/util/gitutil/gitutil_test.go +++ b/util/gitutil/gitutil_test.go @@ -1,26 +1,29 @@ package gitutil import ( + "context" "testing" "github.com/stretchr/testify/require" ) func TestGit(t *testing.T) { + ctx := context.TODO() c, err := New() require.NoError(t, err) - out, err := c.run("status") + out, err := c.Run(ctx, "status") require.NoError(t, err) require.NotEmpty(t, out) - out, err = c.clean(c.run("not-exist")) + out2, err := c.clean(c.Run(ctx, "not-exist")) require.Error(t, err) - require.Empty(t, out) - require.Equal(t, "git: 'not-exist' is not a git command. See 'git --help'.", err.Error()) + require.Empty(t, out2) + require.Contains(t, err.Error(), "git: 'not-exist' is not a git command. See 'git --help'.") } func TestGitFullCommit(t *testing.T) { + ctx := context.TODO() Mktmp(t) c, err := New() require.NoError(t, err) @@ -28,12 +31,13 @@ func TestGitFullCommit(t *testing.T) { GitInit(c, t) GitCommit(c, t, "bar") - out, err := c.FullCommit() + out, err := c.FullCommit(ctx) require.NoError(t, err) require.Equal(t, 40, len(out)) } func TestGitShortCommit(t *testing.T) { + ctx := context.TODO() Mktmp(t) c, err := New() require.NoError(t, err) @@ -41,38 +45,41 @@ func TestGitShortCommit(t *testing.T) { GitInit(c, t) GitCommit(c, t, "bar") - out, err := c.ShortCommit() + out, err := c.ShortCommit(ctx) require.NoError(t, err) require.Equal(t, 7, len(out)) } func TestGitFullCommitErr(t *testing.T) { + ctx := context.TODO() Mktmp(t) c, err := New() require.NoError(t, err) GitInit(c, t) - _, err = c.FullCommit() + _, err = c.FullCommit(ctx) require.Error(t, err) require.True(t, IsUnknownRevision(err)) require.False(t, IsAmbiguousArgument(err)) } func TestGitShortCommitErr(t *testing.T) { + ctx := context.TODO() Mktmp(t) c, err := New() require.NoError(t, err) GitInit(c, t) - _, err = c.ShortCommit() + _, err = c.ShortCommit(ctx) require.Error(t, err) require.True(t, IsUnknownRevision(err)) require.False(t, IsAmbiguousArgument(err)) } func TestGitTagsPointsAt(t *testing.T) { + ctx := context.TODO() Mktmp(t) c, err := New() require.NoError(t, err) @@ -83,12 +90,13 @@ func TestGitTagsPointsAt(t *testing.T) { GitCommit(c, t, "foo") GitTag(c, t, "v0.9.0") - out, err := c.clean(c.run("tag", "--points-at", "HEAD", "--sort", "-version:creatordate")) + out, err := c.clean(c.Run(ctx, "tag", "--points-at", "HEAD", "--sort", "-version:creatordate")) require.NoError(t, err) require.Equal(t, "v0.9.0", out) } func TestGitDescribeTags(t *testing.T) { + ctx := context.TODO() Mktmp(t) c, err := New() require.NoError(t, err) @@ -99,12 +107,14 @@ func TestGitDescribeTags(t *testing.T) { GitCommit(c, t, "foo") GitTag(c, t, "v0.9.0") - out, err := c.clean(c.run("describe", "--tags", "--abbrev=0")) + out, err := c.clean(c.Run(ctx, "describe", "--tags", "--abbrev=0")) require.NoError(t, err) require.Equal(t, "v0.9.0", out) } func TestGitRemoteURL(t *testing.T) { + ctx := context.TODO() + type remote struct { name string url string @@ -179,7 +189,7 @@ func TestGitRemoteURL(t *testing.T) { GitSetRemote(c, t, r.name, r.url) } - ru, err := c.RemoteURL() + ru, err := c.RemoteURL(ctx) if tt.fail { require.Error(t, err) return diff --git a/util/gitutil/testutil.go b/util/gitutil/testutil.go index ea9dd058..58d09fef 100644 --- a/util/gitutil/testutil.go +++ b/util/gitutil/testutil.go @@ -1,6 +1,7 @@ package gitutil import ( + "context" "os" "strings" "testing" @@ -8,7 +9,7 @@ import ( "github.com/stretchr/testify/require" ) -func GitInit(c *Git, tb testing.TB) { +func GitInit(c *GitCLI, tb testing.TB) { tb.Helper() out, err := fakeGit(c, "init") require.NoError(tb, err) @@ -18,35 +19,35 @@ func GitInit(c *Git, tb testing.TB) { _, _ = fakeGit(c, "branch", "-D", "master") } -func GitCommit(c *Git, tb testing.TB, msg string) { +func GitCommit(c *GitCLI, tb testing.TB, msg string) { tb.Helper() out, err := fakeGit(c, "commit", "--allow-empty", "-m", msg) require.NoError(tb, err) require.Contains(tb, out, "main", msg) } -func GitTag(c *Git, tb testing.TB, tag string) { +func GitTag(c *GitCLI, tb testing.TB, tag string) { tb.Helper() out, err := fakeGit(c, "tag", tag) require.NoError(tb, err) require.Empty(tb, out) } -func GitCheckoutBranch(c *Git, tb testing.TB, name string) { +func GitCheckoutBranch(c *GitCLI, tb testing.TB, name string) { tb.Helper() out, err := fakeGit(c, "checkout", "-b", name) require.NoError(tb, err) require.Empty(tb, out) } -func GitAdd(c *Git, tb testing.TB, files ...string) { +func GitAdd(c *GitCLI, tb testing.TB, files ...string) { tb.Helper() args := append([]string{"add"}, files...) _, err := fakeGit(c, args...) require.NoError(tb, err) } -func GitSetRemote(c *Git, tb testing.TB, name string, url string) { +func GitSetRemote(c *GitCLI, tb testing.TB, name string, url string) { tb.Helper() _, err := fakeGit(c, "remote", "add", name, url) require.NoError(tb, err) @@ -64,7 +65,7 @@ func Mktmp(tb testing.TB) string { return folder } -func fakeGit(c *Git, args ...string) (string, error) { +func fakeGit(c *GitCLI, args ...string) (string, error) { allArgs := []string{ "-c", "user.name=buildx", "-c", "user.email=buildx@docker.com", @@ -73,7 +74,7 @@ func fakeGit(c *Git, args ...string) (string, error) { "-c", "log.showSignature=false", } allArgs = append(allArgs, args...) - return c.clean(c.run(allArgs...)) + return c.clean(c.Run(context.TODO(), allArgs...)) } func IsAmbiguousArgument(err error) bool { diff --git a/util/gitutil/testutilserve.go b/util/gitutil/testutilserve.go index 631ed693..cda37c07 100644 --- a/util/gitutil/testutilserve.go +++ b/util/gitutil/testutilserve.go @@ -10,7 +10,7 @@ import ( "github.com/stretchr/testify/require" ) -func GitServeHTTP(c *Git, t testing.TB) (url string) { +func GitServeHTTP(c *GitCLI, t testing.TB) (url string) { t.Helper() gitUpdateServerInfo(c, t) ctx, cancel := context.WithCancel(context.TODO()) @@ -19,7 +19,7 @@ func GitServeHTTP(c *Git, t testing.TB) (url string) { done := make(chan struct{}) name := "test.git" - dir, err := c.GitDir() + dir, err := c.GitDir(context.TODO()) if err != nil { cancel() } @@ -55,7 +55,7 @@ func GitServeHTTP(c *Git, t testing.TB) (url string) { return fmt.Sprintf("http://%s/%s", addr, name) } -func gitUpdateServerInfo(c *Git, tb testing.TB) { +func gitUpdateServerInfo(c *GitCLI, tb testing.TB) { tb.Helper() _, err := fakeGit(c, "update-server-info") require.NoError(tb, err)