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
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
//! One consistent view of the reference namespace, and how a symbolic
//! reference is followed inside it.
//!
//! # Why this exists, and why it is not a resurrected `RefStore`
//!
//! `RefStore` was a *trait*: `read`, `iter`, `resolve`, `transaction`, `head`,
//! `set_head`, implemented per backend. It is deleted, and re-declaring it
//! anywhere in gunnar is the duplication the 2026-08-10 demolition removes —
//! `GitOps::refs` / `update_ref` / `put_refs` already say all of it.
//!
//! This is not that. It is a **value**: the `Vec<RefRow>` that
//! [`GitOps::refs`](gunnar_store::git_storage_trait::GitOps::refs) hands back,
//! with the two questions the wire asks of it. Nothing here can be implemented
//! by a backend, nothing here is a seam, and no engine can behave differently
//! through it.
//!
//! # It also fixes something the trait made easy to get wrong
//!
//! `RefStore::iter` followed by `RefStore::resolve` per symbolic row was N+1
//! round-trips into the store **and N+1 snapshots**: a ref could move between
//! the listing and the resolution, so an advertisement could name an oid for a
//! reference that had already been deleted. One `refs()` call is one snapshot,
//! and every name in an advertisement is resolved against the same one.
//!
//! # `HEAD` is not in here
//!
//! `locked_on_target.md`'s `HEAD`-is-not-a-row question, settled: `refs()` walks `refs/` and
//! **deliberately excludes the pseudo-refs**, which is the contract every
//! backend honours. `HEAD` has its own accessor pair on `GitServe`
//! (`head`/`set_head`) and [`RefSnapshot::resolve`] will therefore answer
//! `None` for it. That is the correct answer here and the reason
//! `UploadPack::head_ref` asks the store rather than this snapshot.
//!
//! # It is `pub`, and it is the only symref walk in the tree
//!
//! **2026-08-10, `locked_on_target.md`'s checklist, diff (B): one implementation.**
//! `gunnar-server` carried a second copy of this walk — free functions over
//! `&[RefRow]` with the same [`MAX_SYMREF_DEPTH`] cap, written for the control
//! plane because it could not reach this one. That file is deleted and its
//! four call sites come here.
//!
//! **The two had drifted, and the copy was the wrong one.** `MAX_SYMREF_DEPTH`
//! is documented as *"how many symbolic **hops** a resolver will follow"*.
//! [`RefSnapshot::resolve_row`] spends its loop iterations on hops, having
//! taken the first row before the loop, so it follows the documented five.
//! gunnar-server's copy spent its first iteration on the initial lookup and
//! followed **four**, refusing a five-hop chain the advertisement resolves. One
//! walk, one cap, and now they cannot disagree.
//!
//! [`MAX_SYMREF_DEPTH`]: gunnar_store::refname::MAX_SYMREF_DEPTH
use ObjectId;
use ;
use MAX_SYMREF_DEPTH;
use crate;
use crate::;
/// A raw oid from the contract, as a `gix_hash::ObjectId`.
///
/// A wrong-length id is an error and never a truncation: the contract carries
/// oids as `Vec<u8>` and a store that answered with the wrong hash's width has
/// said something no caller must paper over.
/// The reference namespace as of one call to `refs()`.