From 67c4b521682cc1cc0214ba49c1518421408e2bc3 Mon Sep 17 00:00:00 2001 From: Andrew Nesbitt Date: Fri, 4 Sep 2026 13:18:39 +0100 Subject: [PATCH] Enable Git long paths for clone and fetch --- cache.go | 4 ++-- cache_test.go | 35 +++++++++++++++++++++++++++++++++++ ensure.go | 4 ++-- ensure_test.go | 23 +++++++++++++++++++++++ run.go | 4 ++++ 5 files changed, 66 insertions(+), 4 deletions(-) diff --git a/cache.go b/cache.go index 61cde78..7049016 100644 --- a/cache.go +++ b/cache.go @@ -83,8 +83,8 @@ func (c *Cache) EnsureCommit(ctx context.Context, url, commit string) error { return nil } out, err := doPinnedURL(ctx, policy, url, Command{ - Args: []string{"fetch", "--unshallow", "--quiet", "origin"}, //nolint:goconst // Git argv is clearer with literal subcommands and flags. - Label: "fetch", //nolint:goconst // Retry notices use the literal Git subcommand. + Args: longPathArgs("fetch", "--unshallow", "--quiet", "origin"), //nolint:goconst // Git argv is clearer with literal subcommands and flags. + Label: "fetch", //nolint:goconst // Retry notices use the literal Git subcommand. Dir: cacheSrc, Env: remoteEnv(), }) diff --git a/cache_test.go b/cache_test.go index 9d53256..a22bc76 100644 --- a/cache_test.go +++ b/cache_test.go @@ -5,6 +5,7 @@ import ( "errors" "os" "path/filepath" + "slices" "strings" "testing" "time" @@ -191,6 +192,40 @@ func TestCacheEnsureCommitRetriesUnshallowFetch(t *testing.T) { } } +func TestCacheEnsureCommitConfiguresLongPathsForFetch(t *testing.T) { + cache := Cache{Root: t.TempDir()} + url := "https://example.invalid/repo" + cacheSrc := filepath.Join(cache.Dir(url), "src") + if err := os.MkdirAll(filepath.Join(cacheSrc, ".git"), 0o755); err != nil { + t.Fatal(err) + } + + var fetchArgs []string + cache.Retry = Retry{ + Run: func(_ context.Context, _ string, _ []string, args ...string) (string, error) { + switch subcommand(args) { + case "cat-file": + return "", errors.New("missing object") + case "rev-parse": + return "true\n", nil + case "fetch": + fetchArgs = append([]string(nil), args...) + return "", nil + default: + return "", errors.New("unexpected Git command") + } + }, + } + + if err := cache.EnsureCommit(context.Background(), url, "deadbeef"); err != nil { + t.Fatalf("EnsureCommit: %v", err) + } + want := []string{"-c", "core.longpaths=true"} + if len(fetchArgs) < len(want) || !slices.Equal(fetchArgs[:len(want)], want) { + t.Errorf("fetch args = %v, want prefix %v", fetchArgs, want) + } +} + func TestCacheEnsureCommitSkipsUnreachableCommitInFullClone(t *testing.T) { cache := Cache{Root: t.TempDir()} url := "https://example.invalid/repo" diff --git a/ensure.go b/ensure.go index fddc134..22d4365 100644 --- a/ensure.go +++ b/ensure.go @@ -84,7 +84,7 @@ func ensure(ctx context.Context, retry Retry, url, dst, ref string, options Ensu out, err := doPinnedURL(ctx, retry, url, Command{ Label: "clone", Env: remoteEnv(), - Args: args, + Args: longPathArgs(args...), Reset: DestReset(dst), }) if err != nil { @@ -141,7 +141,7 @@ func fetchRef(ctx context.Context, retry Retry, url, dst, ref string, full bool) out, err := doPinnedURL(ctx, policy, url, Command{ Label: "fetch", //nolint:goconst // Retry notices use the literal Git subcommand. Env: remoteEnv(), - Args: args, + Args: longPathArgs(args...), }) if err != nil { return fmt.Errorf("%s: %w", strings.TrimSpace(out), err) diff --git a/ensure_test.go b/ensure_test.go index f27aa4e..0863acc 100644 --- a/ensure_test.go +++ b/ensure_test.go @@ -379,6 +379,29 @@ func TestEnsureRetriesCloneAndResetsPartialDestination(t *testing.T) { } } +func TestEnsureConfiguresLongPathsForCloneAndFetch(t *testing.T) { + dst := filepath.Join(t.TempDir(), "checkout") + commands := make(map[string][]string) + retry := Retry{ + Run: func(_ context.Context, _ string, _ []string, args ...string) (string, error) { + commands[subcommand(args)] = append([]string(nil), args...) + return "", nil + }, + } + + err := Ensure(context.Background(), retry, "https://example.invalid/repo", dst, "main", false) + if err != nil { + t.Fatalf("Ensure: %v", err) + } + for _, command := range []string{"clone", "fetch"} { + args := commands[command] + want := []string{"-c", "core.longpaths=true"} + if len(args) < len(want) || !slices.Equal(args[:len(want)], want) { + t.Errorf("%s args = %v, want prefix %v", command, args, want) + } + } +} + func TestEnsureUnknownRefReturnsUnreachableError(t *testing.T) { origin := newOriginFixture(t) dst := filepath.Join(t.TempDir(), "checkout") diff --git a/run.go b/run.go index 5000a0d..15ea8cb 100644 --- a/run.go +++ b/run.go @@ -28,6 +28,10 @@ func remoteEnv() []string { return env } +func longPathArgs(args ...string) []string { + return append([]string{"-c", "core.longpaths=true"}, args...) +} + // Runner runs one Git invocation and returns its combined output. type Runner func(ctx context.Context, dir string, env []string, args ...string) (string, error)