1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
//! §12/§13 remote ops: `sync` (import) and `push` (publish). `sync` imports on
//! the explicit `bl sync` (and inside prime); `push` publishes after every
//! mutating op. Currency is OPTIMISTIC (mutate → push, bl-336a): there is no
//! pre-pull — a stale store surfaces atomically as the push's non-ff reject
//! (E5), and recovery is `bl sync` + retry. Both are no-ops in a stealth
//! (no-remote) repo: with no remote there is nothing to talk to, which is the
//! structural opt-out (§12).
use git;
use Binding;
use cratereject_option_like;
use io;
use Path;
/// §13 `sync/pre`: the general rule — fetch the branch's UPSTREAM, **if any**,
/// then **fast-forward** THAT branch. "If any" is read from the remote
/// ([`remote_has_branch`], the same ls-remote that decides prime's
/// adopt-vs-found): an upstream-less branch — the landing by construction (§4),
/// any local-only branch — yields a no-op *for free*, no name special-cased.
/// The ff target is the branch the binding NAMES, never whatever the store
/// checkout happens to have checked out: the store's own branch integrates by
/// `merge --ff-only FETCH_HEAD` (the working tree moves with it); any other
/// branch is a pure ref move via the `<branch>:<branch>` refspec (ff-only by
/// git's own default). Either way the ff is atomically detect-and-act — a
/// non-ff IS the contention signal (git's non-zero exit becomes ours: "remote
/// wins, re-run"), so there is no separate contention probe. Nothing is pushed,
/// so a partial sync leaves the branch at the old or the new tip, never wedged
/// (§13 rollback).
/// Is `remote`'s `branch` tip NOT a store — no `tasks/` tree at its root? That
/// is the §16 migration window (bl-868d): a hub still carrying the
/// PRE-greenfield legacy JSON store on the (colliding, §16) store-branch name,
/// awaiting the runbook's one-time human cutover. Such a tip is no upstream at
/// all — every store TIP carries `tasks/` by construction (§2, the founding
/// `.gitkeep`; the §16 cutover join keeps the greenfield tree) — so a failed
/// integrate/publish against it is the window, not contention: warn (the §12
/// diagnostic-never-authority pattern) and report `true` so the caller skips,
/// keeping work local and the legacy ref intact (cutover is the runbook's
/// explicit history join + fast-forward push, never a rewrite).
/// Identification must be POSITIVE: the tip is re-fetched here (`FETCH_HEAD`),
/// and any failure to read it reports `false` — the caller's own error stands.
pub
/// The §16 migration-window warning — one spelling, shared by every site that
/// positively identified a legacy (non-store) tip.
/// Is `remote`'s `branch` tip a greenfield STORE (`tasks/` at its root, §2)?
/// `None` = the tip could not be read at all (unreachable remote, absent
/// branch) — the caller's own error stands; `Some(false)` is the §16 legacy
/// window [`not_yet_cut_over`] warns about; `Some(true)` is an ESTABLISHED
/// store, the E5 precondition. Positive identification by re-fetch
/// (`FETCH_HEAD`), shared by both reject-interpretation sites.
/// Does `remote` already carry `branch`? `git ls-remote --heads` is the one
/// round-trip that answers "an upstream, if any" — sync's no-op gate and
/// prime's adopt-vs-found / clone-vs-bootstrap signal (§12/§13).
pub
/// §12 `*/post`: publish the just-sealed balls branch to the remote — always to
/// an ESTABLISHED store (founding is `prime`'s alone, §12). A rejected push
/// (non-ff, perms revoked mid-life, a server-hook reject) means the mutation did
/// NOT land while the caller believes it is federated, so the non-zero exit
/// ABORTS the op (the push IS the optimistic mutate → push contention check;
/// re-run after `bl sync`) — it is NEVER silently degraded to stealth, which
/// would be split-brain (contrast `prime`'s founding-miss fallback, where
/// nothing existed to land on). ONE carve-out, positively identified: a reject
/// against a remote tip that is NOT a store ([`not_yet_cut_over`], bl-868d) is
/// the §16 migration window — warn and keep the work local; the legacy ref is
/// never rewritten (cutover is the runbook's explicit history join, published
/// as an ordinary fast-forward).
/// §6/§13 `install/pre`: fetch the center's config branch (`balls/config`,
/// [`crate::LANDING_BRANCH`]) into the LANDING repo so core can MATERIALIZE it
/// locally and copy it in. The tracker is balls' only remote-talker — core never
/// fetches (§0) — so `prime --install`'s remote read rides this hook. It leaves
/// the config at the landing's `FETCH_HEAD` (a git-standard ref, so no invented
/// core↔plugin convention); core reads it from the same checkout. This is a READ
/// only — config adoption is destructive on the LANDING, never a push to the
/// center (publishing is `install --to`, a separate direction). Stealth (no
/// remote) is a no-op, like every handler — and so is a present remote that
/// simply LACKS the ref (bl-45fd): bl never publishes the landing (§4
/// single-owner), so a stock hub carries no `balls/config`, and a purely local
/// install must not depend on remote state. The gate is sync's own
/// [`remote_has_branch`] ("an upstream, if any", §13); an adopt that really
/// needs the center's config fails at point-of-use (no `FETCH_HEAD`).