Documentation
¶
Overview ¶
Package batch resolves Manifests into scheduling Members: the Chains and Ticket-to-branch derivation that no fleet tool has an equivalent for. It is the deep module for treepad's batch orchestration feature — every scheduling predicate that can be dangerously wrong lands here as a pure, table-testable function.
Index ¶
- func CommonDir(ctx context.Context, r Runner) (string, error)
- func DeriveBranch(prefix, ref string) string
- func LinkArgs(chain []Member, prs map[string]PR) []string
- func Resolve(m Manifest, ticketURLTmpl string) ([][]Member, error)
- func ResolveTicket(ticketURLTmpl, input string) (ticketURL, ref string, err error)
- type Chain
- type Manifest
- type Member
- type PR
- type RestackAction
- type Runner
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func CommonDir ¶
CommonDir resolves the git common dir for the repository containing the process's current directory. In a linked worktree .git is a file, not a directory, so this must be used instead of filepath.Join(path, ".git").
func DeriveBranch ¶
DeriveBranch computes a branch name from prefix + slug(ref). Refs are unique within a Tracker, so no collision suffix is needed. It must stay deterministic and total: reconcile re-derives every branch name on every tick and stores none. ponytail: a Linear ref is a title slug (feat/silent-refresh), a GitHub ref is bare digits (feat/42) — readability trade-off accepted in ADR 0001.
func LinkArgs ¶
LinkArgs returns the longest prefix of chain whose branches all have an open pull request, bottom to top — the argument list for `gh stack link`. It is a prefix, not a filter: stopping at the first member without an open pull request, rather than skipping it, because `gh stack link` sets each argument's base to the one before it — skipping a member would silently base the next one on the wrong branch.
gh stack link pushes every branch it is given and creates a pull request for any that lack one, so a member past the prefix must never be included — passing the whole Chain "for tidiness" would push work-in-progress and open unwanted pull requests. This is load-bearing: do not widen the filter to include members without an open pull request.
A Chain of fewer than two PR-having members produces no Stack, so the result is nil in that case too.
func Resolve ¶
Resolve is pure: no context.Context, no runner, no filesystem. It turns a Manifest's Chains into resolved Members, one slice per Chain.
func ResolveTicket ¶
ResolveTicket turns user input into a Ticket URL and the Ref it carries. Input matching http(s):// is used verbatim; anything else is a Ref rendered through ticketURLTmpl.
Types ¶
type Chain ¶
type Chain struct {
Tickets []string `toml:"tickets"`
}
Chain is an ordered run of Tickets, each worktree branched from the one before it.
type Manifest ¶
type Manifest struct {
Name string `toml:"name"`
BranchPrefix string `toml:"branch_prefix"`
Base string `toml:"base"`
Chains []Chain `toml:"chain"`
}
Manifest declares one Batch: a named collection of Chains. Written by an agent that read the Tracker — never by treepad, never by hand.
type Member ¶
type Member struct {
Ticket string `json:"ticket"`
Ref string `json:"ref"`
TicketURL string `json:"ticket_url"`
Branch string `json:"branch"`
Base string `json:"base"` // Chain position 0 -> Manifest.Base; otherwise the previous member's Branch
Batch string `json:"batch"`
Chain int `json:"chain"`
Position int `json:"position"`
}
Member is a Chain member resolved against config: one Ticket, its Ref, the branch it seeds, and the base that branch is created from.
func ReadyToMaterialise ¶
ReadyToMaterialise returns the leading prefix of chain eligible for this tick: position 0 is always ready; position i is ready when member i-1's branch has an open (or merged) pull request. A pushed branch alone is not enough — an agent pushing a work-in-progress commit must not unblock the layer above against nothing.
An already-materialised member (existing[m.Branch] true) stays ready regardless of its parent's current pull request state, so a pull request closing after materialisation never un-reports something already created.
type PR ¶
type PR struct {
Number int
HeadRefName string
BaseRefName string
State string // OPEN | MERGED | CLOSED
URL string
}
PR is a pull request as `gh pr list` reports it — plain data, no gh behaviour. It lives here rather than in internal/gh so batch's exported predicates (ReadyToMaterialise, LinkArgs) can take PR state as an argument: an exported signature can't name a type from an internal package.
type RestackAction ¶
type RestackAction int
RestackAction is what Reconcile's restack step does to one member's worktree after `git fetch origin <branch>`.
const ( RestackNone RestackAction = iota // nothing to do: behind == 0 RestackFastForward // clean, plainly behind: git merge --ff-only origin/<branch> RestackReset // clean, diverged, patch-equivalent: git reset --hard origin/<branch> RestackStale // dirty, or diverged with a genuinely local commit: wait for a human )
func RestackDecision ¶
func RestackDecision(clean bool, ahead, behind int, patchEquivalent bool) RestackAction
RestackDecision applies the restack safety predicate (issue #140, correcting ADR 0003's "fast-forward only" mechanism: a server-side rebase leaves the branch diverged from origin, not merely behind, and `merge --ff-only` refuses a diverged branch).
behind == 0 means origin has nothing this branch lacks — whether the branch is ahead with unpushed commits or fully in sync, there is nothing to restack, regardless of clean. Only when behind > 0 does dirty become disqualifying: a dirty worktree, or a diverged one holding a commit git cherry says is not yet upstream, never auto-repairs — treepad never stashes or discards work on an agent's behalf.