diff --git a/internal/gitclone/manager.go b/internal/gitclone/manager.go index c05ef3d..9632671 100644 --- a/internal/gitclone/manager.go +++ b/internal/gitclone/manager.go @@ -164,8 +164,8 @@ func (m *Manager) GetOrCreate(_ context.Context, upstreamURL string) (*Repositor credentialProvider: m.credentialProvider, } - gitDir := filepath.Join(clonePath, ".git") - if _, err := os.Stat(gitDir); err == nil { + headFile := filepath.Join(clonePath, "HEAD") + if _, err := os.Stat(headFile); err == nil { repo.state = StateReady } @@ -192,14 +192,7 @@ func (m *Manager) DiscoverExisting(_ context.Context) ([]*Repository, error) { return nil } - gitDir := filepath.Join(path, ".git") - headPath := filepath.Join(path, ".git", "HEAD") - if _, statErr := os.Stat(gitDir); statErr != nil { - if errors.Is(statErr, os.ErrNotExist) { - return nil - } - return errors.Wrap(statErr, "stat .git directory") - } + headPath := filepath.Join(path, "HEAD") if _, statErr := os.Stat(headPath); statErr != nil { if errors.Is(statErr, os.ErrNotExist) { return nil @@ -329,7 +322,7 @@ func (r *Repository) executeClone(ctx context.Context) error { config := DefaultGitTuningConfig() // #nosec G204 - r.upstreamURL and r.path are controlled by us args := []string{ - "clone", + "clone", "--mirror", "-c", "http.postBuffer=" + strconv.Itoa(config.PostBuffer), "-c", "http.lowSpeedLimit=" + strconv.Itoa(config.LowSpeedLimit), "-c", "http.lowSpeedTime=" + strconv.Itoa(int(config.LowSpeedTime.Seconds())), @@ -342,27 +335,7 @@ func (r *Repository) executeClone(ctx context.Context) error { } output, err := cmd.CombinedOutput() if err != nil { - return errors.Wrapf(err, "git clone: %s", string(output)) - } - - // #nosec G204 - r.path is controlled by us - cmd = exec.CommandContext(ctx, "git", "-C", r.path, "config", "remote.origin.fetch", "+refs/heads/*:refs/remotes/origin/*") - output, err = cmd.CombinedOutput() - if err != nil { - return errors.Wrapf(err, "configure fetch refspec: %s", string(output)) - } - - cmd, err = r.gitCommand(ctx, "-C", r.path, - "-c", "http.postBuffer="+strconv.Itoa(config.PostBuffer), - "-c", "http.lowSpeedLimit="+strconv.Itoa(config.LowSpeedLimit), - "-c", "http.lowSpeedTime="+strconv.Itoa(int(config.LowSpeedTime.Seconds())), - "fetch", "--all") - if err != nil { - return errors.Wrap(err, "create git command for fetch") - } - output, err = cmd.CombinedOutput() - if err != nil { - return errors.Wrapf(err, "fetch all branches: %s", string(output)) + return errors.Wrapf(err, "git clone --mirror: %s", string(output)) } // Enable partial clone support (e.g. --filter=blob:none) when serving via git http-backend. @@ -403,13 +376,13 @@ func (r *Repository) Fetch(ctx context.Context) error { "-c", "http.postBuffer="+strconv.Itoa(config.PostBuffer), "-c", "http.lowSpeedLimit="+strconv.Itoa(config.LowSpeedLimit), "-c", "http.lowSpeedTime="+strconv.Itoa(int(config.LowSpeedTime.Seconds())), - "remote", "update", "--prune") + "fetch", "--prune", "--prune-tags") if err != nil { return errors.Wrap(err, "create git command") } output, err := cmd.CombinedOutput() if err != nil { - return errors.Wrapf(err, "git remote update: %s", string(output)) + return errors.Wrapf(err, "git fetch: %s", string(output)) } r.lastFetch = time.Now() @@ -444,8 +417,7 @@ func (r *Repository) EnsureRefsUpToDate(ctx context.Context) error { if !strings.HasPrefix(ref, "refs/heads/") { continue } - localRef := "refs/remotes/origin/" + strings.TrimPrefix(ref, "refs/heads/") - localSHA, exists := localRefs[localRef] + localSHA, exists := localRefs[ref] if !exists || localSHA != upstreamSHA { needsFetch = true break diff --git a/internal/gitclone/manager_test.go b/internal/gitclone/manager_test.go index e7e55dd..9768b7c 100644 --- a/internal/gitclone/manager_test.go +++ b/internal/gitclone/manager_test.go @@ -82,9 +82,8 @@ func TestManager_GetOrCreate_ExistingClone(t *testing.T) { assert.NoError(t, err) repoPath := filepath.Join(tmpDir, "github.com", "user", "repo") - gitDir := filepath.Join(repoPath, ".git") - assert.NoError(t, os.MkdirAll(gitDir, 0o755)) - assert.NoError(t, os.WriteFile(filepath.Join(gitDir, "HEAD"), []byte("ref: refs/heads/main\n"), 0o644)) + assert.NoError(t, os.MkdirAll(repoPath, 0o755)) + assert.NoError(t, os.WriteFile(filepath.Join(repoPath, "HEAD"), []byte("ref: refs/heads/main\n"), 0o644)) upstreamURL := "https://github.com/user/repo" repo, err := manager.GetOrCreate(context.Background(), upstreamURL) @@ -138,9 +137,8 @@ func TestManager_DiscoverExisting(t *testing.T) { } for _, repoPath := range repos { - gitDir := filepath.Join(repoPath, ".git") - assert.NoError(t, os.MkdirAll(gitDir, 0o755)) - assert.NoError(t, os.WriteFile(filepath.Join(gitDir, "HEAD"), []byte("ref: refs/heads/main\n"), 0o644)) + assert.NoError(t, os.MkdirAll(repoPath, 0o755)) + assert.NoError(t, os.WriteFile(filepath.Join(repoPath, "HEAD"), []byte("ref: refs/heads/main\n"), 0o644)) } discovered, err := manager.DiscoverExisting(context.Background()) diff --git a/internal/strategy/git/backend.go b/internal/strategy/git/backend.go index 7d8f5f6..7c9d799 100644 --- a/internal/strategy/git/backend.go +++ b/internal/strategy/git/backend.go @@ -38,7 +38,6 @@ func (s *Strategy) serveFromBackend(w http.ResponseWriter, r *http.Request, repo host := r.PathValue("host") pathValue := r.PathValue("path") - // Insert /.git before the git protocol paths to match the filesystem layout var gitOperation string var repoPathWithSuffix string @@ -51,7 +50,7 @@ func (s *Strategy) serveFromBackend(w http.ResponseWriter, r *http.Request, repo } repoPath := strings.TrimSuffix(repoPathWithSuffix, ".git") - backendPath := "/" + host + "/" + repoPath + "/.git" + gitOperation + backendPath := "/" + host + "/" + repoPath + gitOperation logger.DebugContext(r.Context(), "Serving with git http-backend", slog.String("original_path", r.URL.Path), diff --git a/internal/strategy/git/git_test.go b/internal/strategy/git/git_test.go index cc37699..553295d 100644 --- a/internal/strategy/git/git_test.go +++ b/internal/strategy/git/git_test.go @@ -135,15 +135,13 @@ func TestNewWithExistingCloneOnDisk(t *testing.T) { _, ctx := logging.Configure(context.Background(), logging.Config{}) tmpDir := t.TempDir() - // Create a fake clone directory on disk before initializing strategy - // For regular clones, we need a .git subdirectory with HEAD file + // Create a fake bare clone directory on disk before initializing strategy clonePath := filepath.Join(tmpDir, "github.com", "org", "repo") - gitDir := filepath.Join(clonePath, ".git") - err := os.MkdirAll(gitDir, 0o750) + err := os.MkdirAll(clonePath, 0o750) assert.NoError(t, err) - // Create HEAD file to make it look like a valid git repo - headPath := filepath.Join(gitDir, "HEAD") + // Create HEAD file to make it look like a valid bare git repo + headPath := filepath.Join(clonePath, "HEAD") err = os.WriteFile(headPath, []byte("ref: refs/heads/main\n"), 0o640) assert.NoError(t, err) diff --git a/internal/strategy/git/integration_test.go b/internal/strategy/git/integration_test.go index e6b6d88..b3151b3 100644 --- a/internal/strategy/git/integration_test.go +++ b/internal/strategy/git/integration_test.go @@ -112,11 +112,10 @@ func TestIntegrationGitCloneViaProxy(t *testing.T) { assert.NoError(t, err) assert.True(t, info.IsDir()) - // Verify it has a .git directory (regular clone) - gitDir := filepath.Join(clonePath, ".git") - gitInfo, err := os.Stat(gitDir) + // Verify it has a HEAD file (bare mirror clone) + headFile := filepath.Join(clonePath, "HEAD") + _, err = os.Stat(headFile) assert.NoError(t, err) - assert.True(t, gitInfo.IsDir()) } // TestIntegrationGitFetchViaProxy tests fetching updates through the proxy.