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