From 1903217b5f696057b74e842499ffe5edb0db5687 Mon Sep 17 00:00:00 2001 From: MDietermann Date: Tue, 14 Apr 2026 08:32:04 +0200 Subject: [PATCH] feat(platform): add GitClient helper with idempotent EnsureRepo --- internal/platform/git.go | 23 +++++++++++++++ internal/platform/git_test.go | 55 +++++++++++++++++++++++++++++++++++ 2 files changed, 78 insertions(+) create mode 100644 internal/platform/git.go create mode 100644 internal/platform/git_test.go diff --git a/internal/platform/git.go b/internal/platform/git.go new file mode 100644 index 0000000..33bc074 --- /dev/null +++ b/internal/platform/git.go @@ -0,0 +1,23 @@ +package platform + +import ( + "context" + "os" + "path/filepath" +) + +// GitClient wraps `git` via a Runner. +type GitClient struct{ Runner Runner } + +// Clone always runs `git clone --depth=1 url dest`. +func (g GitClient) Clone(ctx context.Context, url, dest string) error { + return g.Runner.Run(ctx, "git", "clone", "--depth=1", url, dest) +} + +// EnsureRepo clones url into dest if dest/.git is missing; otherwise noop. +func (g GitClient) EnsureRepo(ctx context.Context, url, dest string) error { + if _, err := os.Stat(filepath.Join(dest, ".git")); err == nil { + return nil + } + return g.Clone(ctx, url, dest) +} diff --git a/internal/platform/git_test.go b/internal/platform/git_test.go new file mode 100644 index 0000000..34d0e10 --- /dev/null +++ b/internal/platform/git_test.go @@ -0,0 +1,55 @@ +package platform + +import ( + "context" + "os" + "path/filepath" + "testing" +) + +type gitFakeRunner struct { + calls [][]string + err error +} + +func (f *gitFakeRunner) Run(_ context.Context, name string, args ...string) error { + f.calls = append(f.calls, append([]string{name}, args...)) + return f.err +} +func (f *gitFakeRunner) Output(_ context.Context, name string, args ...string) ([]byte, error) { + return nil, nil +} + +func TestGitEnsureRepoClonesWhenMissing(t *testing.T) { + r := &gitFakeRunner{} + g := GitClient{Runner: r} + dest := filepath.Join(t.TempDir(), "repo") + if err := g.EnsureRepo(context.Background(), "https://x/y.git", dest); err != nil { + t.Fatal(err) + } + if len(r.calls) != 1 { + t.Fatalf("want 1 call, got %d: %v", len(r.calls), r.calls) + } + got := r.calls[0] + want := []string{"git", "clone", "--depth=1", "https://x/y.git", dest} + for i := range want { + if i >= len(got) || got[i] != want[i] { + t.Fatalf("call = %v, want %v", got, want) + } + } +} + +func TestGitEnsureRepoSkipsWhenPresent(t *testing.T) { + dest := t.TempDir() + if err := os.MkdirAll(filepath.Join(dest, ".git"), 0o755); err != nil { + t.Fatal(err) + } + r := &gitFakeRunner{} + g := GitClient{Runner: r} + if err := g.EnsureRepo(context.Background(), "https://x/y.git", dest); err != nil { + t.Fatal(err) + } + if len(r.calls) != 0 { + t.Fatalf("expected no calls, got %v", r.calls) + } +}