fix: add proper instrumentation to git clone for debugging
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed

- Log clone request with work_dir, URL, and token presence
- Log workspace state (is_git_repo, existing remote)
- Log all decision points (pull vs clone, clear workspace)
- Detect and clear non-empty non-git directories before clone
- Capture both stdout and stderr for clone failures
- Include exit code in error messages
This commit is contained in:
jordan 2026-02-07 07:59:53 -07:00
parent 83b5d1ebb4
commit 9cca5cc41b

View File

@ -68,50 +68,97 @@ type CloneResult struct {
func (g *GitOperations) CloneRepo(ctx context.Context, workDir, cloneURL string) *CloneResult { func (g *GitOperations) CloneRepo(ctx context.Context, workDir, cloneURL string) *CloneResult {
result := &CloneResult{} result := &CloneResult{}
g.logger.Info("git clone request",
"work_dir", workDir,
"clone_url", g.redactToken(cloneURL),
"has_token", g.giteaToken != "")
if cloneURL == "" { if cloneURL == "" {
result.Error = fmt.Errorf("git clone URL is required") result.Error = fmt.Errorf("git clone URL is required")
g.logger.Error("git clone failed: empty URL")
return result return result
} }
// Check if already a git repo with the correct remote // Check if already a git repo with the correct remote
if g.isGitRepo(ctx, workDir) { isRepo := g.isGitRepo(ctx, workDir)
g.logger.Info("workspace check", "is_git_repo", isRepo, "work_dir", workDir)
if isRepo {
currentRemote, err := g.runGitOutput(ctx, workDir, "config", "--get", "remote.origin.url") currentRemote, err := g.runGitOutput(ctx, workDir, "config", "--get", "remote.origin.url")
currentRemote = strings.TrimSpace(currentRemote) currentRemote = strings.TrimSpace(currentRemote)
g.logger.Info("existing repo detected",
"current_remote", g.redactToken(currentRemote),
"expected_remote", g.redactToken(cloneURL),
"remote_match", currentRemote == cloneURL,
"remote_err", err)
if err == nil && currentRemote == cloneURL { if err == nil && currentRemote == cloneURL {
// Pull latest changes // Pull latest changes
g.logger.Info("pulling latest changes", "work_dir", workDir)
if err := g.runGit(ctx, workDir, "pull", "--ff-only"); err != nil { if err := g.runGit(ctx, workDir, "pull", "--ff-only"); err != nil {
// Pull failed but repo exists - continue with existing state // Pull failed but repo exists - continue with existing state
g.logger.Debug("git pull failed, continuing with existing state", "error", err, "work_dir", workDir) g.logger.Warn("git pull failed, continuing with existing state",
"error", err,
"work_dir", workDir)
} }
g.logger.Info("git clone complete (existing repo)", "cloned", false)
return result return result
} }
// Different remote - clear and re-clone // Different remote - clear and re-clone
g.logger.Info("clearing workspace for re-clone", "work_dir", workDir)
if err := g.clearDir(ctx, workDir); err != nil { if err := g.clearDir(ctx, workDir); err != nil {
result.Error = fmt.Errorf("clear workspace: %w", err) result.Error = fmt.Errorf("clear workspace: %w", err)
g.logger.Error("failed to clear workspace", "error", result.Error)
return result return result
} }
} }
// Check if directory exists and is non-empty (would cause clone to fail)
if !isRepo {
checkCmd := exec.CommandContext(ctx, "sh", "-c", fmt.Sprintf("ls -A %s 2>/dev/null | head -1", workDir))
output, _ := checkCmd.Output()
if len(strings.TrimSpace(string(output))) > 0 {
g.logger.Warn("workspace not empty but not a git repo, clearing",
"work_dir", workDir,
"first_file", strings.TrimSpace(string(output)))
if err := g.clearDir(ctx, workDir); err != nil {
result.Error = fmt.Errorf("clear non-empty workspace: %w", err)
g.logger.Error("failed to clear non-empty workspace", "error", result.Error)
return result
}
}
}
// Inject token for authentication // Inject token for authentication
authCloneURL := cloneURL authCloneURL := cloneURL
if g.giteaToken != "" { if g.giteaToken != "" {
authCloneURL = strings.Replace(cloneURL, "https://", "https://token:"+g.giteaToken+"@", 1) authCloneURL = strings.Replace(cloneURL, "https://", "https://token:"+g.giteaToken+"@", 1)
} else {
g.logger.Warn("no gitea token configured, clone may fail for private repos")
} }
// Clone the repository // Clone the repository
g.logger.Info("executing git clone", "work_dir", workDir)
cmd := exec.CommandContext(ctx, "git", "clone", authCloneURL, workDir) cmd := exec.CommandContext(ctx, "git", "clone", authCloneURL, workDir)
var stderr bytes.Buffer var stdout, stderr bytes.Buffer
cmd.Stdout = &stdout
cmd.Stderr = &stderr cmd.Stderr = &stderr
if err := cmd.Run(); err != nil { if err := cmd.Run(); err != nil {
errMsg := g.redactToken(stderr.String()) errMsg := g.redactToken(stderr.String())
result.Error = fmt.Errorf("git clone failed: %s: %s", err, errMsg) stdoutMsg := g.redactToken(stdout.String())
result.Error = fmt.Errorf("git clone exit %v: %s", err, errMsg)
g.logger.Error("git clone failed",
"error", err,
"stderr", errMsg,
"stdout", stdoutMsg,
"work_dir", workDir)
return result return result
} }
result.Cloned = true result.Cloned = true
g.logger.Info("git clone complete", "cloned", true, "work_dir", workDir)
return result return result
} }