1use std::collections::{BTreeMap, HashMap, HashSet};
15use std::path::{Path, PathBuf};
16
17use anyhow::{bail, Result};
18
19use crate::link::Link;
20use crate::link_storage::ChangeObserver;
21use crate::named_types::{NamedTypes, NamedTypesDecorator};
22use crate::transactions::{TransactionHandle, TransactionsDecorator, Transition};
23
24pub const DEFAULT_BRANCH_NAME: &str = "main";
26
27const BRANCH_PREFIX: &str = "__vc:branch:";
28const TAG_PREFIX: &str = "__vc:tag:";
29const CURRENT_PREFIX: &str = "__vc:current=";
30const APPLIED_PREFIX: &str = "__vc:applied=";
31const TRANSITION_PREFIX: &str = "__vc:trans:";
32
33#[derive(Debug, Clone, PartialEq, Eq)]
35pub struct BranchInfo {
36 pub name: String,
37 pub parent: Option<String>,
38 pub fork_seq: i64,
39 pub head: i64,
40}
41
42impl BranchInfo {
43 pub fn new(name: String, parent: Option<String>, fork_seq: i64, head: i64) -> Self {
44 Self {
45 name,
46 parent,
47 fork_seq,
48 head,
49 }
50 }
51}
52
53pub struct VersionControlDecorator {
56 transactions: TransactionsDecorator,
57 branches_store: NamedTypesDecorator,
58 branches: HashMap<String, BranchInfo>,
59 tags: BTreeMap<String, i64>,
60 transition_branches: BTreeMap<i64, String>,
61 branch_links: HashMap<String, u32>,
62 tag_links: HashMap<String, u32>,
63 current_branch_link: u32,
64 applied_link: u32,
65 current_branch: String,
66 current_applied: i64,
67 active_transaction: Option<VersionControlTransactionState>,
68 trace: bool,
69}
70
71#[derive(Debug, Clone)]
72struct VersionControlTransactionState {
73 branch_name: String,
74 before_sequence: i64,
75}
76
77impl VersionControlDecorator {
78 pub fn new(
79 transactions: TransactionsDecorator,
80 branches_store: NamedTypesDecorator,
81 trace: bool,
82 ) -> Result<Self> {
83 let mut decorator = Self {
84 transactions,
85 branches_store,
86 branches: HashMap::new(),
87 tags: BTreeMap::new(),
88 transition_branches: BTreeMap::new(),
89 branch_links: HashMap::new(),
90 tag_links: HashMap::new(),
91 current_branch_link: 0,
92 applied_link: 0,
93 current_branch: DEFAULT_BRANCH_NAME.to_string(),
94 current_applied: 0,
95 active_transaction: None,
96 trace,
97 };
98 decorator.recover()?;
99 decorator.ensure_default_branch()?;
100 Ok(decorator)
101 }
102
103 pub fn make_version_control_database_filename<P: AsRef<Path>>(p: P) -> PathBuf {
105 let path = p.as_ref();
106 let stem = path
107 .file_stem()
108 .and_then(|s| s.to_str())
109 .unwrap_or_default();
110 let name = format!("{stem}.versioncontrol.links");
111 match path.parent() {
112 Some(parent) if !parent.as_os_str().is_empty() => parent.join(name),
113 _ => PathBuf::from(name),
114 }
115 }
116
117 pub fn current_branch(&self) -> &str {
118 &self.current_branch
119 }
120
121 pub fn current_sequence(&self) -> i64 {
122 self.current_applied
123 }
124
125 pub fn list_branches(&self) -> Vec<BranchInfo> {
126 let mut branches: Vec<BranchInfo> = self.branches.values().cloned().collect();
127 branches.sort_by(|a, b| a.name.cmp(&b.name));
128 branches
129 }
130
131 pub fn list_tags(&self) -> BTreeMap<String, i64> {
132 self.tags.clone()
133 }
134
135 pub fn try_get_tag(&self, name: &str) -> Option<i64> {
136 self.tags.get(name).copied()
137 }
138
139 pub fn save(&mut self) -> Result<()> {
140 self.transactions.save()?;
141 self.branches_store.save()?;
142 Ok(())
143 }
144
145 pub fn transactions(&self) -> &TransactionsDecorator {
146 &self.transactions
147 }
148
149 pub fn transactions_mut(&mut self) -> &mut TransactionsDecorator {
150 &mut self.transactions
151 }
152
153 pub fn branches_store(&self) -> &NamedTypesDecorator {
154 &self.branches_store
155 }
156
157 pub fn begin_transaction(&mut self) -> Result<TransactionHandle> {
158 if self.active_transaction.is_some() {
159 bail!("Nested version-control transactions are not supported.");
160 }
161 let before_sequence = self.transactions.last_logged_sequence();
162 let branch_name = self.current_branch.clone();
163 let handle = self.transactions.begin_transaction()?;
164 self.active_transaction = Some(VersionControlTransactionState {
165 branch_name,
166 before_sequence,
167 });
168 Ok(handle)
169 }
170
171 pub fn commit(&mut self) -> Result<()> {
172 let state = self
173 .active_transaction
174 .as_ref()
175 .cloned()
176 .ok_or_else(|| anyhow::anyhow!("No version-control transaction is open."))?;
177 self.transactions.commit()?;
178 self.active_transaction = None;
179 self.attribute_new_transitions_for_branch(state.before_sequence, &state.branch_name)?;
180 Ok(())
181 }
182
183 pub fn rollback(&mut self) -> Result<()> {
184 self.active_transaction
185 .as_ref()
186 .ok_or_else(|| anyhow::anyhow!("No version-control transaction is open."))?;
187 self.transactions.rollback()?;
188 self.active_transaction = None;
189 Ok(())
190 }
191
192 pub fn create(&mut self, source: u32, target: u32) -> Result<u32> {
195 let before_seq = self.transactions.last_logged_sequence();
196 let id = self.transactions.create(source, target)?;
197 if self.active_transaction.is_none() {
198 let branch = self.current_branch.clone();
199 self.attribute_new_transitions_for_branch(before_seq, &branch)?;
200 }
201 Ok(id)
202 }
203
204 pub fn update(&mut self, id: u32, source: u32, target: u32) -> Result<Link> {
205 self.update_observed(id, source, target, &mut |_, _| {})
206 }
207
208 pub fn update_observed(
211 &mut self,
212 id: u32,
213 source: u32,
214 target: u32,
215 observer: ChangeObserver<'_>,
216 ) -> Result<Link> {
217 let before_seq = self.transactions.last_logged_sequence();
218 let result = self
219 .transactions
220 .update_observed(id, source, target, observer)?;
221 if self.active_transaction.is_none() {
222 let branch = self.current_branch.clone();
223 self.attribute_new_transitions_for_branch(before_seq, &branch)?;
224 }
225 Ok(result)
226 }
227
228 pub fn delete(&mut self, id: u32) -> Result<Link> {
229 self.delete_observed(id, &mut |_, _| {})
230 }
231
232 pub fn delete_observed(&mut self, id: u32, observer: ChangeObserver<'_>) -> Result<Link> {
235 let before_seq = self.transactions.last_logged_sequence();
236 let result = self.transactions.delete_observed(id, observer)?;
237 if self.active_transaction.is_none() {
238 let branch = self.current_branch.clone();
239 self.attribute_new_transitions_for_branch(before_seq, &branch)?;
240 }
241 Ok(result)
242 }
243
244 pub fn create_and_update(&mut self, source: u32, target: u32) -> Result<u32> {
245 let before_seq = self.transactions.last_logged_sequence();
246 let id = self.transactions.create_and_update(source, target)?;
247 if self.active_transaction.is_none() {
248 let branch = self.current_branch.clone();
249 self.attribute_new_transitions_for_branch(before_seq, &branch)?;
250 }
251 Ok(id)
252 }
253
254 pub fn exists(&self, id: u32) -> bool {
255 self.transactions.exists(id)
256 }
257
258 pub fn get(&self, id: u32) -> Option<&Link> {
259 self.transactions.get(id)
260 }
261
262 pub fn all(&self) -> Vec<&Link> {
263 self.transactions.all()
264 }
265
266 pub fn search(&self, source: u32, target: u32) -> Option<u32> {
267 self.transactions.search(source, target)
268 }
269
270 pub fn get_or_create(&mut self, source: u32, target: u32) -> Result<u32> {
271 if let Some(existing) = self.transactions.search(source, target) {
272 return Ok(existing);
273 }
274 self.create(source, target)
275 }
276
277 pub fn ensure_created(&mut self, id: u32) -> u32 {
278 self.transactions
279 .ensure_created(id)
280 .expect("TransactionsDecorator::ensure_created failed")
281 }
282
283 fn attribute_new_transitions_for_branch(
284 &mut self,
285 before_seq: i64,
286 branch_name: &str,
287 ) -> Result<()> {
288 let after_seq = self.transactions.last_logged_sequence();
289 if after_seq <= before_seq {
290 return Ok(());
291 }
292 for s in (before_seq + 1)..=after_seq {
293 self.transition_branches.insert(s, branch_name.to_string());
294 let marker = format!("{TRANSITION_PREFIX}{s}:branch={branch_name}");
295 self.write_immutable_marker(&marker)?;
296 }
297 if let Some(info) = self.branches.get(branch_name).cloned() {
298 let updated = BranchInfo {
299 head: after_seq,
300 ..info
301 };
302 self.branches
303 .insert(branch_name.to_string(), updated.clone());
304 self.update_branch_link(&updated)?;
305 }
306 if self.current_branch == branch_name {
307 self.current_applied = after_seq;
308 self.set_applied(after_seq)?;
309 }
310 Ok(())
311 }
312
313 pub fn branch(&mut self, name: &str, from: Option<i64>) -> Result<()> {
316 self.ensure_no_open_transaction("branch")?;
317 if name.trim().is_empty() {
318 bail!("Branch name must not be empty.");
319 }
320 if self.branches.contains_key(name) {
321 bail!("Branch '{name}' already exists.");
322 }
323 let parent = self.current_branch.clone();
324 let fork_seq = from.unwrap_or(self.current_applied);
325 if fork_seq < 0 {
326 bail!("Fork point cannot be negative.");
327 }
328 if fork_seq > 0 {
329 let path = self.build_branch_seqs(&parent);
330 if !path.contains(&fork_seq) {
331 bail!("Fork point {fork_seq} is not reachable on branch '{parent}'.",);
332 }
333 }
334 self.create_branch(name, Some(parent), fork_seq, fork_seq)?;
335 self.trace(&format!(
336 "Created branch '{name}' from '{}' at seq {fork_seq}.",
337 self.current_branch
338 ));
339 Ok(())
340 }
341
342 pub fn switch_branch(&mut self, name: &str) -> Result<()> {
343 self.ensure_no_open_transaction("switch_branch")?;
344 if !self.branches.contains_key(name) {
345 bail!("Unknown branch '{name}'.");
346 }
347 let target_path = self.build_branch_seqs(name);
348 self.apply_diff_to(target_path, name)?;
349 self.trace(&format!(
350 "Switched to branch '{name}' at seq {}.",
351 self.current_applied
352 ));
353 Ok(())
354 }
355
356 pub fn checkout(&mut self, sequence: i64) -> Result<()> {
357 self.ensure_no_open_transaction("checkout")?;
358 if sequence < 0 {
359 bail!("Sequence must be non-negative.");
360 }
361 let current = self.current_branch.clone();
362 let path = self.build_branch_seqs(¤t);
363 if sequence > 0 && !path.contains(&sequence) {
364 bail!("Sequence {sequence} is not reachable on branch '{current}'.",);
365 }
366 let target_path: Vec<i64> = path.iter().copied().filter(|s| *s <= sequence).collect();
367 self.apply_diff_to(target_path, ¤t)?;
368 self.trace(&format!(
369 "Checked out seq {sequence} on branch '{current}'.",
370 ));
371 Ok(())
372 }
373
374 pub fn tag(&mut self, name: &str, sequence: Option<i64>) -> Result<()> {
375 self.ensure_no_open_transaction("tag")?;
376 if name.trim().is_empty() {
377 bail!("Tag name must not be empty.");
378 }
379 let seq = sequence.unwrap_or(self.current_applied);
380 if seq < 0 {
381 bail!("Tag sequence must be non-negative.");
382 }
383 self.tags.insert(name.to_string(), seq);
384 self.update_tag_link(name, seq)?;
385 self.trace(&format!("Created tag '{name}' at seq {seq}.",));
386 Ok(())
387 }
388
389 fn apply_diff_to(&mut self, target_path: Vec<i64>, new_branch: &str) -> Result<()> {
392 let current_branch_name = self.current_branch.clone();
393 let current_path: Vec<i64> = self
394 .build_branch_seqs(¤t_branch_name)
395 .into_iter()
396 .filter(|s| *s <= self.current_applied)
397 .collect();
398
399 let mut common = 0usize;
400 let max_common = current_path.len().min(target_path.len());
401 while common < max_common && current_path[common] == target_path[common] {
402 common += 1;
403 }
404
405 let to_revert: Vec<i64> = current_path[common..].iter().rev().copied().collect();
407 for seq in to_revert {
408 if let Some(transition) = self.find_transition(seq) {
409 self.transactions.revert_transition(&transition);
410 }
411 }
412 let to_apply: Vec<i64> = target_path[common..].to_vec();
414 for seq in to_apply {
415 if let Some(transition) = self.find_transition(seq) {
416 self.transactions.apply_transition(&transition);
417 }
418 }
419
420 if new_branch != self.current_branch {
421 self.current_branch = new_branch.to_string();
422 self.set_current_branch(new_branch)?;
423 }
424 self.current_applied = target_path.last().copied().unwrap_or(0);
425 self.set_applied(self.current_applied)?;
426 Ok(())
427 }
428
429 fn ensure_no_open_transaction(&self, operation: &str) -> Result<()> {
430 if self.active_transaction.is_some() {
431 bail!("{operation} is not allowed while a version-control transaction is open.");
432 }
433 Ok(())
434 }
435
436 fn build_branch_seqs(&self, branch_name: &str) -> Vec<i64> {
437 let mut visited: HashSet<String> = HashSet::new();
438 self.build_branch_seqs_inner(branch_name, &mut visited)
439 }
440
441 fn build_branch_seqs_inner(
442 &self,
443 branch_name: &str,
444 visited: &mut HashSet<String>,
445 ) -> Vec<i64> {
446 let info = match self.branches.get(branch_name) {
447 Some(info) => info,
448 None => return Vec::new(),
449 };
450 if !visited.insert(branch_name.to_string()) {
451 return Vec::new();
452 }
453 let mut seqs = Vec::new();
454 if let Some(parent_name) = info.parent.as_deref() {
455 if self.branches.contains_key(parent_name) {
456 let mut parent_seqs = self.build_branch_seqs_inner(parent_name, visited);
457 parent_seqs.retain(|s| *s <= info.fork_seq);
458 seqs.extend(parent_seqs);
459 }
460 }
461 let mut own: Vec<i64> = self
462 .transition_branches
463 .iter()
464 .filter(|(s, b)| b.as_str() == branch_name && **s <= info.head)
465 .map(|(s, _)| *s)
466 .collect();
467 own.sort();
468 seqs.extend(own);
469 seqs
470 }
471
472 fn find_transition(&self, sequence: i64) -> Option<Transition> {
473 self.transactions
474 .log()
475 .into_iter()
476 .find(|t| t.sequence == sequence)
477 }
478
479 fn ensure_default_branch(&mut self) -> Result<()> {
482 let existing = self.transactions.last_logged_sequence();
483 if !self.branches.contains_key(DEFAULT_BRANCH_NAME) {
484 for s in 1..=existing {
486 if let std::collections::btree_map::Entry::Vacant(entry) =
487 self.transition_branches.entry(s)
488 {
489 entry.insert(DEFAULT_BRANCH_NAME.to_string());
490 let marker = format!("{TRANSITION_PREFIX}{s}:branch={DEFAULT_BRANCH_NAME}");
491 self.write_immutable_marker(&marker)?;
492 }
493 }
494 self.create_branch(DEFAULT_BRANCH_NAME, None, 0, existing)?;
495 self.current_branch = DEFAULT_BRANCH_NAME.to_string();
496 self.current_applied = existing;
497 self.set_current_branch(DEFAULT_BRANCH_NAME)?;
498 self.set_applied(existing)?;
499 } else if self.current_branch_link == 0 {
500 let branch = self.current_branch.clone();
501 self.set_current_branch(&branch)?;
502 }
503 Ok(())
504 }
505
506 fn create_branch(
507 &mut self,
508 name: &str,
509 parent: Option<String>,
510 fork_seq: i64,
511 head: i64,
512 ) -> Result<()> {
513 let info = BranchInfo::new(name.to_string(), parent, fork_seq, head);
514 self.branches.insert(name.to_string(), info.clone());
515 self.update_branch_link(&info)?;
516 Ok(())
517 }
518
519 fn update_branch_link(&mut self, info: &BranchInfo) -> Result<()> {
520 let marker = encode_branch_marker(info);
521 let link = match self.branch_links.get(&info.name).copied() {
522 Some(link) => link,
523 None => {
524 let new_link = self.branches_store.create(0, 0);
525 self.branch_links.insert(info.name.clone(), new_link);
526 new_link
527 }
528 };
529 self.branches_store.set_name(link, &marker)?;
530 Ok(())
531 }
532
533 fn update_tag_link(&mut self, name: &str, seq: i64) -> Result<()> {
534 let marker = format!("{TAG_PREFIX}{name}={seq}");
535 let link = match self.tag_links.get(name).copied() {
536 Some(link) => link,
537 None => {
538 let new_link = self.branches_store.create(0, 0);
539 self.tag_links.insert(name.to_string(), new_link);
540 new_link
541 }
542 };
543 self.branches_store.set_name(link, &marker)?;
544 Ok(())
545 }
546
547 fn set_current_branch(&mut self, name: &str) -> Result<()> {
548 self.current_branch = name.to_string();
549 if self.current_branch_link == 0 {
550 self.current_branch_link = self.branches_store.create(0, 0);
551 }
552 let link = self.current_branch_link;
553 let marker = format!("{CURRENT_PREFIX}{name}");
554 self.branches_store.set_name(link, &marker)?;
555 Ok(())
556 }
557
558 fn set_applied(&mut self, seq: i64) -> Result<()> {
559 if self.applied_link == 0 {
560 self.applied_link = self.branches_store.create(0, 0);
561 }
562 let link = self.applied_link;
563 let marker = format!("{APPLIED_PREFIX}{seq}");
564 self.branches_store.set_name(link, &marker)?;
565 Ok(())
566 }
567
568 fn write_immutable_marker(&mut self, name: &str) -> Result<()> {
569 let link = self.branches_store.create(0, 0);
570 self.branches_store.set_name(link, name)?;
571 Ok(())
572 }
573
574 pub fn recover(&mut self) -> Result<()> {
575 self.branches.clear();
576 self.tags.clear();
577 self.transition_branches.clear();
578 self.branch_links.clear();
579 self.tag_links.clear();
580 self.current_branch = DEFAULT_BRANCH_NAME.to_string();
581 self.current_branch_link = 0;
582 self.applied_link = 0;
583 self.current_applied = 0;
584
585 let links: Vec<Link> = self.branches_store.all().into_iter().copied().collect();
586 for link in &links {
587 let name = match self.branches_store.get_name(link.index)? {
588 Some(value) => value,
589 None => continue,
590 };
591 if name.starts_with(BRANCH_PREFIX) {
592 if let Some(info) = try_decode_branch_marker(&name) {
593 self.branches.insert(info.name.clone(), info.clone());
594 self.branch_links.insert(info.name.clone(), link.index);
595 }
596 } else if let Some(rest) = name.strip_prefix(CURRENT_PREFIX) {
597 self.current_branch = rest.to_string();
598 self.current_branch_link = link.index;
599 } else if let Some(rest) = name.strip_prefix(APPLIED_PREFIX) {
600 if let Ok(seq) = rest.parse::<i64>() {
601 self.current_applied = seq;
602 self.applied_link = link.index;
603 }
604 } else if let Some(rest) = name.strip_prefix(TAG_PREFIX) {
605 if let Some(eq) = rest.find('=') {
606 let tag_name = &rest[..eq];
607 if let Ok(tag_seq) = rest[eq + 1..].parse::<i64>() {
608 self.tags.insert(tag_name.to_string(), tag_seq);
609 self.tag_links.insert(tag_name.to_string(), link.index);
610 }
611 }
612 } else if let Some(rest) = name.strip_prefix(TRANSITION_PREFIX) {
613 if let Some(colon) = rest.find(":branch=") {
614 if let Ok(seq) = rest[..colon].parse::<i64>() {
615 let branch_name = &rest[colon + ":branch=".len()..];
616 self.transition_branches
617 .insert(seq, branch_name.to_string());
618 }
619 }
620 }
621 }
622 Ok(())
623 }
624
625 fn trace(&self, message: &str) {
626 if self.trace {
627 eprintln!("[VersionControl] {message}");
628 }
629 }
630}
631
632fn encode_branch_marker(info: &BranchInfo) -> String {
633 let parent = info.parent.as_deref().unwrap_or("");
634 format!(
635 "{BRANCH_PREFIX}{name}:parent={parent}:fork={fork}:head={head}",
636 name = info.name,
637 fork = info.fork_seq,
638 head = info.head,
639 )
640}
641
642fn try_decode_branch_marker(text: &str) -> Option<BranchInfo> {
643 let rest = text.strip_prefix(BRANCH_PREFIX)?;
644 let parent_idx = rest.find(":parent=")?;
645 let name = &rest[..parent_idx];
646 let rest = &rest[parent_idx + ":parent=".len()..];
647 let fork_idx = rest.find(":fork=")?;
648 let parent_text = &rest[..fork_idx];
649 let rest = &rest[fork_idx + ":fork=".len()..];
650 let head_idx = rest.find(":head=")?;
651 let fork_text = &rest[..head_idx];
652 let head_text = &rest[head_idx + ":head=".len()..];
653 let fork: i64 = fork_text.parse().ok()?;
654 let head: i64 = head_text.parse().ok()?;
655 let parent = if parent_text.is_empty() {
656 None
657 } else {
658 Some(parent_text.to_string())
659 };
660 Some(BranchInfo::new(name.to_string(), parent, fork, head))
661}
662
663#[cfg(test)]
664mod tests {
665 use super::*;
666
667 #[test]
668 fn encode_round_trips_through_decode() {
669 let info = BranchInfo::new("feature".into(), Some("main".into()), 5, 9);
670 let text = encode_branch_marker(&info);
671 let decoded = try_decode_branch_marker(&text).unwrap();
672 assert_eq!(info, decoded);
673 }
674
675 #[test]
676 fn make_version_control_database_filename_returns_sibling_path() {
677 let path =
678 VersionControlDecorator::make_version_control_database_filename("/var/data/db.links");
679 assert_eq!(path, PathBuf::from("/var/data/db.versioncontrol.links"));
680 }
681
682 #[test]
683 fn decode_branch_marker_rejects_invalid_input() {
684 assert!(try_decode_branch_marker("not a marker").is_none());
685 assert!(try_decode_branch_marker("__vc:branch:x:parent=:fork=z:head=1").is_none());
686 }
687}