Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 37 additions & 31 deletions internal/gitclone/manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -164,9 +164,22 @@ func (m *Manager) GetOrCreate(_ context.Context, upstreamURL string) (*Repositor
credentialProvider: m.credentialProvider,
}

// Check if repository exists (either bare or non-bare)
bareHeadPath := filepath.Join(clonePath, "HEAD")
bareConfigPath := filepath.Join(clonePath, "config")
gitDir := filepath.Join(clonePath, ".git")
if _, err := os.Stat(gitDir); err == nil {
repo.state = StateReady

// Bare repository check
if _, err := os.Stat(bareHeadPath); err == nil {
if _, err := os.Stat(bareConfigPath); err == nil {
repo.state = StateReady
}
}
// Non-bare repository check (old format)
if repo.state == StateEmpty {
if _, err := os.Stat(gitDir); err == nil {
repo.state = StateReady
}
}

repo.fetchSem <- struct{}{}
Expand All @@ -192,19 +205,29 @@ func (m *Manager) DiscoverExisting(_ context.Context) ([]*Repository, error) {
return nil
}

// Check for bare repository (mirror clone) - has HEAD and config at root
bareHeadPath := filepath.Join(path, "HEAD")
bareConfigPath := filepath.Join(path, "config")
isBare := false
if _, err := os.Stat(bareHeadPath); err == nil {
if _, err := os.Stat(bareConfigPath); err == nil {
isBare = true
}
}

// Check for non-bare repository (old format) - has .git subdirectory
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
isNonBare := false
if _, err := os.Stat(gitDir); err == nil {
if _, err := os.Stat(headPath); err == nil {
isNonBare = true
}
return errors.Wrap(statErr, "stat .git directory")
}
if _, statErr := os.Stat(headPath); statErr != nil {
if errors.Is(statErr, os.ErrNotExist) {
return nil
}
return errors.Wrap(statErr, "stat HEAD file")

// Skip if not a git repository (neither bare nor non-bare)
if !isBare && !isNonBare {
return nil
}

relPath, err := filepath.Rel(m.config.MirrorRoot, path)
Expand Down Expand Up @@ -327,9 +350,12 @@ func (r *Repository) executeClone(ctx context.Context) error {
}

config := DefaultGitTuningConfig()
// Use --mirror to create a bare repository with correct ref structure (refs/heads/*)
// This ensures git clients see the correct ref paths when querying via ls-remote
// #nosec G204 - r.upstreamURL and r.path are controlled by us
args := []string{
"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())),
Expand All @@ -345,26 +371,6 @@ func (r *Repository) executeClone(ctx context.Context) error {
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 nil
}

Expand Down
4 changes: 2 additions & 2 deletions internal/strategy/git/backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ 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
// Extract git operation from path (for bare repositories, no /.git subdirectory)
var gitOperation string
var repoPathWithSuffix string

Expand All @@ -50,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),
Expand Down
4 changes: 2 additions & 2 deletions internal/strategy/git/bundle.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,9 @@ func (s *Strategy) generateAndUploadBundle(ctx context.Context, repo *gitclone.R

err = errors.Wrap(repo.WithReadLock(func() error {
var stderr bytes.Buffer
// Use --branches --remotes to include all branches but exclude tags (which can be massive)
// Use --all to include all refs (branches and tags) for mirror repositories
// #nosec G204 - repo.Path() is controlled by us
cmd := exec.CommandContext(ctx, "git", "-C", repo.Path(), "bundle", "create", "-", "--branches", "--remotes")
cmd := exec.CommandContext(ctx, "git", "-C", repo.Path(), "bundle", "create", "-", "--all")
cmd.Stdout = w
cmd.Stderr = &stderr

Expand Down
Loading