Skip to main content

Module followers

Module followers 

Source
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.

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§

Followers
The push handle the writer thread holds.
Outcome
One push, as it went.

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_remote outcome 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>.