Files
kandev/docs/plans/git-stale-base-refresh/task-01-git-operator-is-ancestor.md
T
2026-08-24 11:09:03 +00:00

77 lines
3.0 KiB
Markdown

---
id: "01-git-operator-is-ancestor"
title: "Add GitOperator.IsAncestor helper"
status: done
wave: 1
depends_on: []
plan: "plan.md"
spec: "../../specs/platform/requirements/workspace-git-status.md"
---
# Task 01: Add `GitOperator.IsAncestor` helper
Add a thin `GitOperator` method that answers "is A a strict ancestor of B?" so the
commits-panel base resolver (task 02) can detect a stale stored base without shelling out
inline.
## Context / Inputs
- Spec: `docs/specs/platform/requirements/workspace-git-status.md` → "Base-commit staleness and refresh"
(a stored base is stale when it is a strict ancestor of `merge-base(HEAD, origin/<base_branch>)`).
- Existing helpers to mirror for style/error handling: `GetMergeBase` and `GetRevParse` in
`apps/backend/internal/agentctl/server/process/git_log.go` (both use `runGitCommand`).
- `git merge-base --is-ancestor A B` exits 0 (true), 1 (false), or >1 (real error). The current
`runGitCommand` returns a non-nil error on any non-zero exit, so this helper MUST inspect the
exit code to distinguish "false" (exit 1) from a genuine failure.
## Implementation
- Add to `apps/backend/internal/agentctl/server/process/git_log.go`:
```go
// IsAncestor reports whether `ancestor` is a strict ancestor of `descendant`
// (git merge-base --is-ancestor). Exit 0 => true, exit 1 => false, anything
// else => error. Note: git treats a commit as an ancestor of itself, so an
// equal SHA returns true; callers wanting a STRICT check compare SHAs first.
func (g *GitOperator) IsAncestor(ctx context.Context, ancestor, descendant string) (bool, error)
```
- Use the lowest-level command runner that exposes exit status (check how `runGitCommand`
surfaces `*exec.ExitError`; if it collapses exit codes, add a sibling that returns the
exit code, or use `errors.As` on the returned error to read `ExitError.ExitCode()`).
- Empty `ancestor` or `descendant` returns `(false, nil)` without invoking git.
## Acceptance
- `IsAncestor` returns `(true, nil)` for a real ancestor, `(false, nil)` for a
non-ancestor / diverged commit, and a non-nil error only for genuine git failures
(bad repo, unknown SHA that git reports as error).
- Equal SHAs return `(true, nil)` (documented; strictness is enforced by the caller).
## Verification
```shell
cd apps/backend && go test -run 'IsAncestor' ./internal/agentctl/server/process/...
```
Add a table-driven test in
`apps/backend/internal/agentctl/server/process/git_log_test.go` using `setupTestRepo`
covering: ancestor, non-ancestor (sibling branch), equal SHA, and unknown-SHA error.
## Files likely touched
- `apps/backend/internal/agentctl/server/process/git_log.go`
- `apps/backend/internal/agentctl/server/process/git_log_test.go`
## Dependencies
None.
## Parallelism
`sequential`. (Task 02 imports this helper.)
## Output contract
Report: helper signature added, exit-code handling approach, test cases, `go test` result,
any blockers. Update this file's `status` to `in_progress` at start and `done` at finish, and
tick the task-01 checkbox in `plan.md`.