branchsync

package
v1.55.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 11, 2026 License: MIT Imports: 15 Imported by: 0

Documentation

Index

Constants

View Source
const (
	StatePipelineOwned        = "pipeline_owned"
	StatePushInProgress       = "push_in_progress"
	StateBehind               = "behind"
	StateSynchronized         = "synchronized"
	StateLocalAhead           = "local_ahead"
	StateDiverged             = "diverged"
	StateDirty                = "dirty"
	StateRemoteAdvanced       = "remote_advanced"
	StateRemoteRewritten      = "remote_rewritten"
	StateRemoteMissing        = "remote_missing"
	StateMergedRemoteRetained = "merged_remote_retained"
	StateMergedRemoteRemoved  = "merged_remote_removed"
	StateClosed               = "closed"
	StateOffline              = "offline"
	StateTargetChanged        = "target_changed"
	StateAmbiguousContext     = "ambiguous_context"
	StateLegacyUnbound        = "legacy_unbound"
	StateCustodyReturned      = "custody_returned"
	// StateUserOwned reports a branch released by its terminal outcome: the
	// run ended before the pipeline changed the submitted head, so no
	// pipeline-created content exists to recover and the exact branch and head
	// are the operator's, immediately usable with no sync action.
	StateUserOwned = "user_owned"
)
View Source
const (
	RelationEqual    = "equal"
	RelationBehind   = "behind"
	RelationAhead    = "ahead"
	RelationDiverged = "diverged"
	RelationUnknown  = "unknown"
)
View Source
const (
	SafetySafeFastForward       = "safe_fast_forward"
	SafetySafeEquivalentAdvance = "safe_equivalent_advance"
)

Variables

This section is empty.

Functions

func CanApply

func CanApply(state State) bool

CanApply reports whether Apply may advance the clean checked-out branch for a freshly verified plan. It includes strict fast-forwards and the narrower equivalent-diverged advance that first anchors the pre-sync head.

func RunHeadUnmoved

func RunHeadUnmoved(state State) bool

RunHeadUnmoved reports whether the classified run's pipeline head still equals the submitted head, i.e. the run holds no pipeline-authored commits whose loss a fresh gate push could cause.

func TargetFingerprint

func TargetFingerprint(raw string) string

TargetFingerprint returns a stable one-way identity for a credential-free, canonical target. No URL is persisted by callers.

Types

type LocalState

type LocalState struct {
	Branch string
	Head   string
	Clean  bool
	Reason string
}

type NextAction

type NextAction struct {
	Code    string
	Command string
}

type PipelineState

type PipelineState struct {
	RunID          string
	Status         string
	Phase          string
	SubmittedHead  string
	CurrentHead    string
	PushedHead     string
	PushedAt       int64
	PushGeneration int64
}

type RemoteState

type RemoteState struct {
	ObservedHead string
	Freshness    string
	ObservedAt   int64
}

type Service

type Service struct {
	DB      *db.DB
	Repo    *db.Repo
	WorkDir string
	GateDir string
	Paths   *paths.Paths
	// contains filtered or unexported fields
}

Service synchronizes only the invoking worktree. Repo is the registered repository record, while WorkDir may be its main or a linked worktree. GateDir is the repo's local bare gate; selection may read its exact branch head and ancestry as provenance evidence, while Recover is the only method that mutates it.

func OpenCurrent

func OpenCurrent() (*Service, func(), error)

OpenCurrent opens a service for the invoking registered worktree. The caller owns the returned close function.

func (*Service) Apply

func (s *Service) Apply(ctx context.Context) State

Apply repeats remote and mutable-precondition checks, then advances the clean checked-out branch to the exact pipeline-bound SHA. Ordinary behind branches use a strict fast-forward. Equivalent-diverged branches first anchor the pre-sync head, then move to the verified equivalent pipeline head.

func (*Service) InspectCached

func (s *Service) InspectCached(ctx context.Context) State

InspectCached reads local Git, persisted provenance, and read-only gate ancestry evidence without fetching or mutating refs, the index, or the worktree.

func (*Service) Recover

func (s *Service) Recover(ctx context.Context, keepLocal bool) State

Recover returns custody of a branch stranded by a TERMINAL run whose MOVED pipeline head was never published: cancelled or failed before the push with pipeline commits in the gate, or terminal after a push with additional unpublished commits. While such a run was active the pipeline_owned block was correct; once it is terminal nothing will ever publish the head, so an explicit guarded exit must exist. A terminal run whose verified worktree head never changed from the submitted head needs no recovery at all, so Recover treats that user_owned state as an idempotent no-op success.

The decision matrix, by worktree relation to the preserved pipeline head P (the gate branch head recorded as the run's head_sha):

relation   worktree  default                        --keep-local
equal      any       anchor locally; return custody same
ahead      any       anchor locally; return custody same
behind     clean     strict fast-forward to P,      custody at local head;
                     then return custody            gate reset to it (CAS)
behind     dirty     refuse (commit/stash first)    custody at local head;
                                                    gate reset to it (CAS)
diverged,  clean     anchor the pre-recovery local  custody at local head;
P contains           head, then move to P with      gate reset to it (CAS)
all local            fail-closed ops; return custody
work
diverged,  dirty     refuse (commit/stash first)    custody at local head;
P contains                                          gate reset to it (CAS)
all local
work
diverged   any       refuse (anchor named, manual   custody at local head;
                     reconcile / rerun offered)     gate reset to it (CAS)
P missing  any       refuse                         refuse

A gate branch still frozen at the run's submitted head is its own row: the pipeline adopted nothing past submission, so P was never published and can never be fetched from the gate branch. P is still anchored when the gate's object store or the invoking worktree can still reach it, and custody then returns as a bookkeeping move (or, under --keep-local, at the local head with the usual gate CAS) instead of deadlocking on a head the gate never held.

The containment row exists because a cancelled validation routinely leaves P as a REBASE of the local branch onto a newer base: the same logical commits with new SHAs, so equality and ancestry alone see only divergence and escalated a case where nothing could be lost. The row applies only where preservedContainsLocalWork proves, by executable three-way merge, that P already carries every local change. That proof is deliberately narrow, and everything it cannot decide - including a rebase whose fix rounds also rewrote the operator's lines - falls through to the plain diverged refusal. No-data-loss outranks convenience here: when nothing can distinguish a deliberate pipeline fix from a dropped change, the operator decides.

Fail-safe rules, in the same spirit as Refresh/Apply:

  • An active run always refuses: only terminal runs are recoverable.
  • The preserved commits must be provably safe before custody moves: when already reachable from the local branch (equal/ahead), recovery pins the private anchor ref refs/no-slop/recover/<runID> locally without gate access; otherwise the preserved head is verified at the gate branch head and fetched into that anchor. The anchor keeps them reachable locally no matter what later happens to the gate.
  • The only possible worktree mutation is a guarded move of a clean checked-out branch: a strict fast-forward, or an anchored move to a proven-containing head performed by Git operations that refuse on their own rather than by a preceding observation (see recoverAdoptPreserved). When the operator explicitly keeps a behind or diverged local head instead of taking P, --keep-local never touches the worktree and moves the gate branch to the kept head with an atomic compare-and-swap, so a concurrent gate push wins and recovery refuses.
  • That compare-and-swap abandons whatever the gate branch pointed at, so unless the kept head already contains it or the recover anchor already pins it, it is first pinned at refs/no-slop/recover-abandoned/<runID>. The frozen-gate row is where the two differ: the anchor holds P while the commit being let go is the submitted head.
  • Anything unverifiable (missing gate where required, moved gate branch, failed anchor write or fetch, changed assumptions) refuses with a reason and leaves the worktree, the branch, and the gate branch exactly as they were. The only thing a refusal can leave behind is a private anchor ref holding a commit that already existed, and every refusal that can follow an anchor write names that ref rather than claiming nothing was written.

Recovery ends with a persisted custody-return stamp on the run; inspection then reports custody_returned (never-pushed runs) or the ordinary classification against the last push binding (pushed runs), both pointing at run_pipeline as the next step. `no-slop rerun` remains the alternative exit: it starts a fresh run from the current gate branch head, which is the pipeline head only when the pipeline adopted one there.

Whenever a recovered branch does NOT carry a commit this recovery anchored (the frozen-gate row, and --keep-local anywhere), the holding ref is reported as PreservedAnchorRef or AbandonedAnchorRef so the success output names the only remaining reference to that work; a recorded pipeline head that survived nowhere is reported as LostPipelineHead instead of silently absent. Re-running the idempotent recovery reports exactly the same facts.

func (*Service) Refresh

func (s *Service) Refresh(ctx context.Context) State

Refresh explicitly verifies the exact configured push ref into a private no-slop ref. It never updates an ordinary remote-tracking ref.

type State

type State struct {
	State    string
	Changed  bool
	Local    LocalState
	Pipeline PipelineState
	Target   TargetState
	Remote   RemoteState
	Relation string
	Safety   string
	PRState  string
	// Recovered is set only by Recover and reports that the operator owns the
	// branch when the call returns: custody of a stranded terminal run was
	// returned (by this call or an earlier, idempotent one), or the terminal
	// outcome had already released the branch (user_owned), making recovery an
	// idempotent no-op.
	Recovered bool
	// PreservedAnchorRef and AbandonedAnchorRef name the private refs holding
	// commits the returned branch does NOT contain, and are set only when
	// custody returns that way: the frozen-gate row anchors a stranded pipeline
	// head the branch never reaches, --keep-local deliberately abandons the
	// preserved head, and its gate compare-and-swap separately abandons the
	// commit the gate branch pointed at. Those returns report changed: false, so
	// without these the only record of that work (an agent's conflict
	// resolutions, or a submitted head the operator reset past) is a ref no
	// output names. Each stays empty whenever the branch carries that commit.
	PreservedAnchorRef string
	AbandonedAnchorRef string
	// LostPipelineHead is the recorded pipeline head of a returned run that the
	// branch does not contain and that no object store still holds. The
	// frozen-gate row rescues that commit whenever it survives; when it does
	// not, an empty anchor set alone cannot distinguish a pipeline that produced
	// nothing from one whose work was destroyed, so the lost commit is named.
	LostPipelineHead string
	NextAction       *NextAction
	Error            string
}

State is the shared branch synchronization contract rendered by CLI, AXI, and TUI presenters. Cached inspection never contacts a remote.

type TargetState

type TargetState struct {
	Kind   string
	Remote string
	URL    string
	Ref    string
}

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL