// Package git wraps the subprocess `git` calls the harness needs. // Every call gracefully degrades: a non-git directory returns // HasGit=false rather than an error, so the pipeline can mark the // git phase degraded without halting. package git import ( "context" "os/exec" "path/filepath" "strings" "time" ) // Info is the git metadata bundle for repo-intake. type Info struct { HasGit bool `json:"has_git"` CurrentBranch string `json:"current_branch,omitempty"` LatestCommit string `json:"latest_commit,omitempty"` Status string `json:"status,omitempty"` // raw `git status -s` output Errors []string `json:"errors,omitempty"` } // Inspect runs the read-only git probes. Times out after 5s per call // so a hung git process can't stall the pipeline. Never mutates the // target repo. func Inspect(ctx context.Context, repoPath string) Info { out := Info{} abs, _ := filepath.Abs(repoPath) gitDir := filepath.Join(abs, ".git") if _, err := exec.LookPath("git"); err != nil { out.Errors = append(out.Errors, "git binary not in PATH") return out } // `.git` may be a file (worktree pointer) or a dir. `git rev-parse` // is the canonical "is this a repo?" probe. cctx, cancel := context.WithTimeout(ctx, 5*time.Second) defer cancel() cmd := exec.CommandContext(cctx, "git", "-C", abs, "rev-parse", "--git-dir") if err := cmd.Run(); err != nil { // Only annotate when .git looked present — otherwise it's just // a non-git target, not an error. if _, statErr := exec.LookPath("git"); statErr == nil { _ = gitDir } return out } out.HasGit = true out.CurrentBranch = runGit(ctx, abs, "rev-parse", "--abbrev-ref", "HEAD") out.LatestCommit = runGit(ctx, abs, "rev-parse", "HEAD") out.Status = runGit(ctx, abs, "status", "-s") return out } func runGit(ctx context.Context, dir string, args ...string) string { cctx, cancel := context.WithTimeout(ctx, 5*time.Second) defer cancel() full := append([]string{"-C", dir}, args...) out, err := exec.CommandContext(cctx, "git", full...).Output() if err != nil { return "" } return strings.TrimSpace(string(out)) }