Expand description
Followers (D21): after a ref lands, push it to the remotes the operator added to the bare repository.
A follower is any remote a bare repository under the root carries. A
bare repository on a node has no origin, it is the origin, so
every remote on one was added on purpose, and enumerating them is
what keeps this list-free: adding a mirror is git remote add on the
node host (choir repo follower add), and nothing here to edit.
§The node is canonical, and this only ever pushes
D21’s single-canonical invariant: upstream is the node, a follower
copies it, and nothing flows back. So this pushes refs/heads/* and
refs/tags/*, never fetches, and never forces. A follower that
diverged from the node is a fault to surface, not a state to
overwrite, and a push that refuses is the whole of that surfacing. A
deleted ref is not deleted on the follower either: a follower is a
copy that may hold more, and pruning is a policy nobody has asked for.
§Invariant 5, the same way webhooks keep it
A push goes to a host somebody else runs, so its latency is unbounded.
The writer thread therefore does one thing with a landed ref:
Followers::offer, a non-blocking try_send of the repository’s
name into a bounded queue. The pushes run on the thread this module
spawns, and a full queue drops the offer and counts it. Dropping is
safe here in a way it is not for a webhook: the next landing on the
same repository pushes everything, so a dropped offer is delay, not
loss.
§The log leads, so the push waits for git
The offer is made when the sequencer accepts the ref, and git does
not hold it yet: a push is still inside pre-receive, and the queue
writes its ref after the round. A push made at the offer reads the
repository as it was, sends nothing new, and nothing sends it again,
which leaves the follower one landing behind until the next one. So
an offer carries the value the view now holds, and the push thread
waits until git reads the same before it pushes.
The wait is bounded by SETTLE, because git may never get there: a
push refused on a later ref is retracted, and its objects go out with
the quarantine. Offers that arrive during the wait replace what is
waited for, so a retraction ends it early instead of running it out.
§Consent is the flag
The daemon pushes only when started with --followers. Without it a
remote on a bare repository is inert, which matters for a mirror
choir-bridge keeps: that repository carries an origin pointing
upstream, and a node that pushed to every remote it found would
push a mirror back at the thing it mirrors.
§Examples
use choir_node::followers::repository_of;
// A landed ref names its repository ahead of the colon.
assert_eq!(repository_of("owner/repo.git:refs/heads/main"), Some("owner/repo.git"));
assert_eq!(repository_of("refs/heads/main"), None);Structs§
Functions§
- push
- Pushes every branch and tag of one bare repository to one remote. No force, no prune, no fetch.
- push_
all - Pushes one repository to each of its remotes, one outcome per remote,
or one
no_remoteoutcome when it has none. - remotes
- The remotes a bare repository carries,
(name, url)each. - repository_
of - The repository half of a view ref key,
<repo>:<refname>.