pub fn push_branch(
tx: &dyn Transport,
store: &ObjectStore,
branch: &str,
tip: Hash,
condition: RefWriteCondition,
) -> Result<(), DispatchError>Expand description
Push one branch: upload one or more delta-compressed packs carrying
every object reachable from tip that the remote lacks — split
across multiple packs when the plan’s payload exceeds a single
pack’s cap (issue #831) — durably advertise them as one node on the
refs/mkit/packmap/<branch> metadata ref, then CAS-write
refs/heads/<branch> under condition.
Objects already present at the remote’s current tip are never re-sent
(identical-object dedup), and changed FastCDC chunks are delta-encoded
against the prior version the remote already holds when that saves
bytes (see mkit_core::transfer::plan_pack). The pack is keyed by its
own BLAKE3 digest (SPEC-PACKFILE §7) — required because the digest-
checking storage server rejects a delta stored under the reconstructed
object’s hash.
The packmap is advanced and confirmed before the branch ref moves: if the packmap can’t be durably established the push aborts without touching the head, so the head never points past a packmap that fails to reconstruct it (even under concurrent pushers to the same branch).
Plans the pack FIRST (diffing against the remote’s current tip). A no-op
push (empty plan — the remote already holds this closure) takes the cheap
head-only path and walks NO packmap chain (mkit #521 perf). Only when the
plan is non-empty does it resolve the branch’s current packmap chain depth
(walking it exactly once, see packmap::probe_chain) and, if the chain
would grow past the re-baseline threshold (#406, see
packmap::rebaseline_depth) AND the transport’s advance_refs is
transactional (Transport::supports_atomic_advance, mkit #521) AND the
head write is CAS-conditioned (a force push’s Any head condition takes
the safe append path — an Any condition makes even an atomic transport
fall back to the ordered two-PUT advance_refs, so a reset there is not
safe), re-plans as a full closure (diffs against no remote tip) and
carries that decision down to advance_packmap as
ChainAction::ResetSelfContained so it resets the chain to a single
fresh node instead of appending to it — bounding clone cost, which
otherwise grows with chain length.
On a transport WITHOUT transactional advance_refs (the default used by
file/S3/SSH/memory), crossing the threshold never triggers a reset: the
default advance_refs commits the packmap write before the head CAS, and
a reset (unlike an append) is not a superset of the prior chain, so a
lost head-CAS race after a committed reset would strand the (unmoved)
head pointing at a commit the packmap can no longer reconstruct. Such a
transport keeps appending — ChainAction::Append — past the
threshold; packmap::MAX_PACK_CHAIN_DEPTH (the pure runaway/cycle guard)
remains the only bound on chain growth there, unchanged by this gate.
A chain read that fails with DispatchError::PackChainInvalid, or a
missing prior packmap (first push), is left alone here — depth is only
defined for a resolvable chain, and a broken chain already has its own
reset path in advance_packmap (the broken-chain escape hatch, gated on
self_contained alone, independent of this transactional-advance gate —
see ChainAction::Append’s doc comment).
The already-resolved chain from this probe (when not discarded by a
re-baseline decision) is threaded into advance_packmap so its first
CAS attempt does not have to walk the chain a second time (#521 perf
fix).
On a CAS failure (TransportError::RefConflict) this returns
DispatchError::NonFastForwardPush so callers can render an
actionable fetch-then-retry hint. Does NOT touch local
remote-tracking refs — the caller decides when to advance them.