1use std::collections::{BTreeMap, HashMap};
4
5use serde::{Deserialize, Serialize};
6
7use crate::format::{BranchId, Metadata, OwnedStoredRecord};
8use crate::{Result, SalamanderError};
9
10pub const DEFAULT_BRANCH_NAME: &str = "main";
12pub const MAX_LINEAGE_DEPTH: usize = 64;
14
15#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
17pub struct BranchName(String);
18
19impl BranchName {
20 pub const MAX_BYTES: usize = 255;
22
23 pub fn new(value: impl Into<String>) -> Result<Self> {
26 let value = value.into();
27 if value.is_empty() || value.as_bytes().contains(&0) {
28 return Err(SalamanderError::InvalidArgument(
29 "branch name must be nonempty and contain no NUL".into(),
30 ));
31 }
32 if value.len() > Self::MAX_BYTES {
33 return Err(SalamanderError::ResourceLimit {
34 resource: "branch name bytes",
35 actual: value.len() as u64,
36 maximum: Self::MAX_BYTES as u64,
37 });
38 }
39 Ok(Self(value))
40 }
41
42 pub fn as_str(&self) -> &str {
44 &self.0
45 }
46}
47
48#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
50pub enum BranchStatus {
51 Active,
53 Archived,
55}
56
57#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
59pub struct BranchInfo {
60 pub id: BranchId,
62 pub name: BranchName,
64 pub parent: Option<BranchId>,
66 pub fork_position: Option<u64>,
68 pub created_at_unix_nanos: i64,
70 pub metadata: BTreeMap<String, Vec<u8>>,
72 pub status: BranchStatus,
74}
75
76#[derive(Clone, Serialize, Deserialize)]
77pub(crate) struct BranchCatalog {
78 by_id: HashMap<BranchId, BranchInfo>,
79 by_name: HashMap<String, BranchId>,
80}
81
82impl BranchCatalog {
83 pub(crate) fn all(&self) -> impl Iterator<Item = &BranchInfo> {
84 self.by_id.values()
85 }
86
87 pub(crate) fn rebuild(
88 system_records: impl Iterator<Item = Result<OwnedStoredRecord>>,
89 ) -> Result<Self> {
90 let default = BranchInfo {
91 id: BranchId::ZERO,
92 name: BranchName::new(DEFAULT_BRANCH_NAME)?,
93 parent: None,
94 fork_position: None,
95 created_at_unix_nanos: 0,
96 metadata: Metadata::new(),
97 status: BranchStatus::Active,
98 };
99 let mut catalog = Self {
100 by_id: HashMap::from([(default.id, default.clone())]),
101 by_name: HashMap::from([(default.name.as_str().to_string(), default.id)]),
102 };
103 for item in system_records {
104 let record = item?;
105 let event_type = record.envelope.event_type.as_str();
106 if event_type != "salamander.branch.created"
107 && event_type != "salamander.branch.archived"
108 {
109 continue;
110 }
111 let info: BranchInfo = serde_json::from_slice(&record.payload).map_err(|error| {
112 SalamanderError::Corrupt {
113 offset: record.position,
114 reason: format!("branch metadata decode: {error}"),
115 }
116 })?;
117 if event_type == "salamander.branch.created" {
118 catalog.insert(info)?;
119 } else {
120 catalog.archive(info)?;
121 }
122 }
123 Ok(catalog)
124 }
125
126 pub(crate) fn insert(&mut self, info: BranchInfo) -> Result<()> {
127 if self.by_id.contains_key(&info.id) {
128 return Err(SalamanderError::BranchExists(
129 info.name.as_str().to_string(),
130 ));
131 }
132 if self.by_name.contains_key(info.name.as_str()) {
133 return Err(SalamanderError::BranchExists(
134 info.name.as_str().to_string(),
135 ));
136 }
137 if let Some(parent) = info.parent {
138 if !self.by_id.contains_key(&parent) {
139 return Err(SalamanderError::BranchNotFound(format!("{parent:?}")));
140 }
141 }
142 self.validate_depth(&info)?;
143 self.by_name.insert(info.name.as_str().to_string(), info.id);
144 self.by_id.insert(info.id, info);
145 Ok(())
146 }
147
148 pub(crate) fn archive(&mut self, archived: BranchInfo) -> Result<()> {
149 let current = self
150 .by_id
151 .get_mut(&archived.id)
152 .ok_or_else(|| SalamanderError::BranchNotFound(format!("{:?}", archived.id)))?;
153 if current.id == BranchId::ZERO {
154 return Err(SalamanderError::InvalidArgument(
155 "the default branch cannot be archived".into(),
156 ));
157 }
158 let mut expected = current.clone();
159 expected.status = BranchStatus::Archived;
160 if archived != expected {
161 return Err(SalamanderError::InvalidBranchAncestry(
162 "archive metadata may only change branch status".into(),
163 ));
164 }
165 *current = archived;
166 Ok(())
167 }
168
169 fn validate_depth(&self, info: &BranchInfo) -> Result<()> {
170 let mut parent = info.parent;
171 for _ in 0..MAX_LINEAGE_DEPTH {
172 let Some(id) = parent else {
173 return Ok(());
174 };
175 if id == info.id {
176 return Err(SalamanderError::InvalidBranchAncestry(
177 "cycle detected".into(),
178 ));
179 }
180 parent = self.by_id.get(&id).and_then(|branch| branch.parent);
181 }
182 Err(SalamanderError::InvalidBranchAncestry(format!(
183 "lineage exceeds maximum depth {MAX_LINEAGE_DEPTH}"
184 )))
185 }
186
187 pub(crate) fn get(&self, id: BranchId) -> Option<&BranchInfo> {
188 self.by_id.get(&id)
189 }
190
191 pub(crate) fn named(&self, name: &str) -> Option<&BranchInfo> {
192 self.by_name.get(name).and_then(|id| self.by_id.get(id))
193 }
194
195 pub(crate) fn ancestry(&self, id: BranchId) -> Result<Vec<BranchInfo>> {
196 let mut result = Vec::new();
197 let mut current = Some(id);
198 while let Some(branch_id) = current {
199 let branch = self
200 .by_id
201 .get(&branch_id)
202 .ok_or_else(|| SalamanderError::BranchNotFound(format!("{branch_id:?}")))?;
203 result.push(branch.clone());
204 current = branch.parent;
205 if result.len() > MAX_LINEAGE_DEPTH {
206 return Err(SalamanderError::InvalidBranchAncestry(
207 "lineage depth exceeded".into(),
208 ));
209 }
210 }
211 result.reverse();
212 Ok(result)
213 }
214
215 pub(crate) fn children(&self, id: BranchId) -> Vec<BranchInfo> {
216 let mut children: Vec<_> = self
217 .by_id
218 .values()
219 .filter(|branch| branch.parent == Some(id))
220 .cloned()
221 .collect();
222 children.sort_by(|left, right| left.name.cmp(&right.name));
223 children
224 }
225
226 pub(crate) fn replay_scopes(&self, id: BranchId, upto: u64) -> Result<Vec<(BranchId, u64)>> {
227 let ancestry = self.ancestry(id)?;
228 let mut upper = upto;
236 let mut scopes: Vec<(BranchId, u64)> = ancestry
237 .iter()
238 .enumerate()
239 .rev()
240 .map(|(index, branch)| {
241 if let Some(child) = ancestry.get(index + 1) {
242 upper = upper.min(child.fork_position.unwrap_or(upto));
243 }
244 (branch.id, upper)
245 })
246 .collect();
247 scopes.reverse();
248 Ok(scopes)
249 }
250
251 pub(crate) fn divergence(
257 &self,
258 left: BranchId,
259 left_until: u64,
260 right: BranchId,
261 right_until: u64,
262 ) -> Result<(BranchInfo, u64)> {
263 let left_path = self.ancestry(left)?;
264 let right_path = self.ancestry(right)?;
265 Ok(divergence_of(
266 &left_path,
267 left_until,
268 &right_path,
269 right_until,
270 ))
271 }
272
273 pub(crate) fn common_ancestor(&self, left: BranchId, right: BranchId) -> Result<BranchInfo> {
274 let left = self.ancestry(left)?;
275 let right = self.ancestry(right)?;
276 left.into_iter()
277 .zip(right)
278 .take_while(|(a, b)| a.id == b.id)
279 .map(|(branch, _)| branch)
280 .last()
281 .ok_or_else(|| {
282 SalamanderError::InvalidBranchAncestry("branches have no common root".into())
283 })
284 }
285}
286
287fn divergence_of(
298 left_path: &[BranchInfo],
299 left_until: u64,
300 right_path: &[BranchInfo],
301 right_until: u64,
302) -> (BranchInfo, u64) {
303 let shared = left_path
304 .iter()
305 .zip(right_path)
306 .take_while(|(a, b)| a.id == b.id)
307 .count();
308 debug_assert!(shared >= 1, "ancestries always share the default branch");
309 let ancestor = left_path[shared - 1].clone();
310 let side_min = |path: &[BranchInfo], until: u64| {
311 path[shared..]
312 .iter()
313 .filter_map(|branch| branch.fork_position)
314 .fold(until, u64::min)
315 };
316 let divergence = side_min(left_path, left_until).min(side_min(right_path, right_until));
317 (ancestor, divergence)
318}
319
320#[cfg(test)]
321mod tests {
322 use super::*;
323
324 fn node(id: u8, parent: Option<u8>, fork_position: Option<u64>) -> BranchInfo {
325 BranchInfo {
326 id: BranchId::from_bytes([id; 16]),
327 name: BranchName::new(format!("branch-{id}")).unwrap(),
328 parent: parent.map(|p| BranchId::from_bytes([p; 16])),
329 fork_position,
330 created_at_unix_nanos: 0,
331 metadata: Metadata::new(),
332 status: BranchStatus::Active,
333 }
334 }
335
336 fn root() -> BranchInfo {
337 BranchInfo {
338 id: BranchId::ZERO,
339 name: BranchName::new(DEFAULT_BRANCH_NAME).unwrap(),
340 parent: None,
341 fork_position: None,
342 created_at_unix_nanos: 0,
343 metadata: Metadata::new(),
344 status: BranchStatus::Active,
345 }
346 }
347
348 #[test]
349 fn ancestor_vs_descendant_diverges_at_the_fork() {
350 let main = vec![root()];
351 let fork = vec![root(), node(1, Some(0), Some(8))];
352 let (ancestor, d) = divergence_of(&main, 12, &fork, 17);
353 assert_eq!(ancestor.id, BranchId::ZERO);
354 assert_eq!(d, 8);
355 }
356
357 #[test]
358 fn siblings_diverge_at_the_earlier_fork() {
359 let left = vec![root(), node(1, Some(0), Some(5))];
360 let right = vec![root(), node(2, Some(0), Some(9))];
361 let (ancestor, d) = divergence_of(&left, 20, &right, 20);
362 assert_eq!(ancestor.id, BranchId::ZERO);
363 assert_eq!(d, 5);
364 }
365
366 #[test]
367 fn same_branch_diverges_at_the_smaller_until() {
368 let path = vec![root(), node(1, Some(0), Some(3))];
369 let (ancestor, d) = divergence_of(&path, 4, &path, 9);
370 assert_eq!(ancestor.id, path[1].id);
371 assert_eq!(d, 4);
372 let (_, d) = divergence_of(&path, 9, &path, 9);
373 assert_eq!(d, 9);
374 }
375
376 #[test]
377 fn an_until_below_the_fork_caps_the_divergence() {
378 let main = vec![root()];
379 let fork = vec![root(), node(1, Some(0), Some(8))];
380 let (_, d) = divergence_of(&main, 3, &fork, 17);
381 assert_eq!(d, 3);
382 }
383
384 #[test]
385 fn grandchildren_share_the_deepest_common_node() {
386 let child = node(1, Some(0), Some(4));
387 let left = vec![root(), child.clone(), node(2, Some(1), Some(7))];
388 let right = vec![root(), child.clone(), node(3, Some(1), Some(10))];
389 let (ancestor, d) = divergence_of(&left, 20, &right, 20);
390 assert_eq!(ancestor.id, child.id);
391 assert_eq!(d, 7);
392 }
393
394 #[test]
395 fn fork_at_zero_diverges_at_zero() {
396 let main = vec![root()];
397 let fork = vec![root(), node(1, Some(0), Some(0))];
398 let (_, d) = divergence_of(&main, 6, &fork, 6);
399 assert_eq!(d, 0);
400 }
401
402 #[test]
403 fn a_fork_below_its_parents_fork_caps_the_divergence() {
404 let a = node(1, Some(0), Some(1));
409 let a_path = vec![root(), a.clone()];
410 let b_path = vec![root(), a, node(2, Some(1), Some(0))];
411 let (ancestor, d) = divergence_of(&b_path, 2, &a_path, 2);
412 assert_eq!(ancestor.id, b_path[1].id);
413 assert_eq!(d, 0);
414 }
415
416 #[test]
417 fn replay_scopes_cascade_downstream_fork_caps() {
418 let mut catalog = BranchCatalog {
419 by_id: HashMap::from([(BranchId::ZERO, root())]),
420 by_name: HashMap::from([(DEFAULT_BRANCH_NAME.to_string(), BranchId::ZERO)]),
421 };
422 catalog.insert(node(1, Some(0), Some(3))).unwrap();
423 catalog.insert(node(2, Some(1), Some(1))).unwrap();
424 assert_eq!(
428 catalog
429 .replay_scopes(BranchId::from_bytes([2; 16]), 10)
430 .unwrap(),
431 vec![
432 (BranchId::ZERO, 1),
433 (BranchId::from_bytes([1; 16]), 1),
434 (BranchId::from_bytes([2; 16]), 10),
435 ]
436 );
437 }
438}