Documentation
¶
Overview ¶
Package handoff decides whether work can safely move to another machine.
It answers a different question than status or doctor. Those report how healthy a repository is; handoff reports whether anything in it exists only on this machine. Uncommitted files, unpushed commits, and stash entries are all invisible to every other device and to every agent, so leaving them behind is how work gets lost or duplicated.
Assess is a pure function over the results of a bulk status scan, so the classification is testable without touching git.
Blockers are split into two kinds. Auto-fixable ones (uncommitted work, unpushed commits) are exactly what "handoff end" resolves by committing and pushing. The rest — conflicts, an interrupted rebase or merge, a detached HEAD, a stash, a repository with no remote — need a decision that only the person at this machine can make, and no automation should guess at.
Index ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func Paths ¶
func Paths(repos []RepoAssessment) []string
Paths returns the absolute paths of the repositories in a set, in scan order.
Types ¶
type Assessment ¶
type Assessment struct {
Verdict Verdict `json:"verdict"`
// Repositories holds every scanned repository, ready ones included, so
// callers can render a complete picture without a second scan.
Repositories []RepoAssessment `json:"repositories"`
TotalScanned int `json:"total_scanned"`
}
Assessment is the verdict across a scanned directory.
func Assess ¶
func Assess(results []repository.RepositoryStatusResult) *Assessment
Assess classifies the results of a bulk status scan into a handoff verdict.
func (*Assessment) Blocked ¶
func (a *Assessment) Blocked() []RepoAssessment
Blocked returns the repositories that "handoff end" cannot resolve.
func (*Assessment) NotReady ¶
func (a *Assessment) NotReady() []RepoAssessment
NotReady returns the repositories with at least one blocker, preserving the order they were scanned in.
func (*Assessment) ReasonCounts ¶
func (a *Assessment) ReasonCounts() map[Reason]int
ReasonCounts tallies blockers by reason across all repositories.
type Blocker ¶
type Blocker struct {
Reason Reason `json:"reason"`
Detail string `json:"detail"`
// AutoFixable reports whether "handoff end" clears this blocker on its own.
// It is false whenever clearing it needs a decision only the person at this
// machine can make.
AutoFixable bool `json:"auto_fixable"`
}
Blocker is one reason work in a repository would not survive the move.
func FirstHardBlocker ¶
func FirstHardBlocker(r RepoAssessment) (Blocker, bool)
FirstHardBlocker returns the blocker that disqualifies a repository from an automatic checkpoint, in the order the blockers were recorded.
func FirstStartBlocker ¶
func FirstStartBlocker(r RepoAssessment) (Blocker, bool)
FirstStartBlocker returns the blocker that disqualifies a repository from an automatic rebase on arrival.
type Finding ¶
type Finding struct {
Kind FindingKind `json:"kind"`
File string `json:"file"`
Detail string `json:"detail"`
}
Finding is one file the guard refuses to commit unattended.
type FindingKind ¶
type FindingKind string
FindingKind classifies why a file should not be swept into a checkpoint commit without someone looking at it first.
const ( // FindingSecret marks a file that looks like it holds a credential. FindingSecret FindingKind = "secret" // FindingLargeFile marks a file too big to be source. FindingLargeFile FindingKind = "large-file" // FindingArtifact marks generated output that .gitignore does not cover. FindingArtifact FindingKind = "artifact" )
type Plan ¶
type Plan struct {
// Checkpoint holds the repositories that will be committed and pushed.
Checkpoint []RepoAssessment `json:"checkpoint"`
// Skipped holds the repositories with a blocker no automatic step can clear.
Skipped []RepoAssessment `json:"skipped,omitempty"`
}
Plan is the split of a scanned workspace into what "handoff end" will move and what it must leave for a person.
func PlanCheckpoint ¶
func PlanCheckpoint(a *Assessment) Plan
PlanCheckpoint decides, per repository, whether "handoff end" may act.
A repository qualifies when it has work that a commit and a push would move and nothing that makes those steps unsafe. A stash does not disqualify it: the stash is untouched either way, and the committed work still deserves to reach the remote.
type Reason ¶
type Reason string
Reason identifies why a repository is not ready to be left behind.
const ( // ReasonUncommitted marks changes that exist only in the working tree. ReasonUncommitted Reason = "uncommitted" // ReasonUnpushed marks commits that exist only in the local repository. ReasonUnpushed Reason = "unpushed" // ReasonStashed marks stash entries, which are never transferred by git. ReasonStashed Reason = "stashed" // ReasonStranded marks a stash old enough to have outlived the task that // created it. It is the same blocker as ReasonStashed with a worse prognosis: // nobody is coming back for it on their own. ReasonStranded Reason = "stranded" // ReasonConflict marks unresolved merge conflicts. ReasonConflict Reason = "conflict" // ReasonInProgress marks an interrupted rebase or merge. ReasonInProgress Reason = "in-progress" // ReasonDetached marks a detached HEAD, where new commits belong to no branch. ReasonDetached Reason = "detached-head" // ReasonNoRemote marks a repository with nowhere to push. ReasonNoRemote Reason = "no-remote" // ReasonNoUpstream marks a branch that has no upstream to push to yet. ReasonNoUpstream Reason = "no-upstream" // ReasonError marks a repository whose state could not be read. ReasonError Reason = "error" )
type RepoAssessment ¶
type RepoAssessment struct {
Path string `json:"path"`
RelativePath string `json:"relative_path"`
Branch string `json:"branch,omitempty"`
Blockers []Blocker `json:"blockers,omitempty"`
}
RepoAssessment is the verdict for a single repository.
func (RepoAssessment) AutoFixable ¶
func (r RepoAssessment) AutoFixable() bool
AutoFixable reports whether "handoff end" would clear every blocker here.
func (RepoAssessment) Ready ¶
func (r RepoAssessment) Ready() bool
Ready reports whether nothing in this repository exists only locally.
type StartPlan ¶
type StartPlan struct {
// Update holds the repositories that will be pulled with a rebase.
Update []RepoAssessment `json:"update"`
// Skipped holds the repositories a rebase would endanger.
Skipped []RepoAssessment `json:"skipped,omitempty"`
}
StartPlan is the split of a scanned workspace into what "handoff start" will bring up to date and what it must leave for a person.
func PlanStart ¶
func PlanStart(a *Assessment) StartPlan
PlanStart decides, per repository, whether "handoff start" may rebase it.
Unpushed commits do not disqualify a repository: replaying them onto the updated remote branch is the point of arriving with a rebase. A stash does not either, since a rebase never touches one.
type Verdict ¶
type Verdict string
Verdict summarizes an assessment across all repositories.
const ( // VerdictReady means nothing exists only on this machine. VerdictReady Verdict = "ready" // VerdictFixable means "handoff end" would make everything ready. VerdictFixable Verdict = "fixable" // VerdictBlocked means at least one repository needs a decision made here. VerdictBlocked Verdict = "blocked" )