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 let before_seq = self.transactions.last_logged_sequence();
206 let result = self.transactions.update(id, source, target)?;
207 if self.active_transaction.is_none() {
208 let branch = self.current_branch.clone();
209 self.attribute_new_transitions_for_branch(before_seq, &branch)?;
210 }
211 Ok(result)
212 }
213
214 pub fn delete(&mut self, id: u32) -> Result<Link> {
215 self.delete_observed(id, &mut |_, _| {})
216 }
217
218 pub fn delete_observed(&mut self, id: u32, observer: ChangeObserver<'_>) -> Result<Link> {
221 let before_seq = self.transactions.last_logged_sequence();
222 let result = self.transactions.delete_observed(id, observer)?;
223 if self.active_transaction.is_none() {
224 let branch = self.current_branch.clone();
225 self.attribute_new_transitions_for_branch(before_seq, &branch)?;
226 }
227 Ok(result)
228 }
229
230 pub fn create_and_update(&mut self, source: u32, target: u32) -> Result<u32> {
231 let before_seq = self.transactions.last_logged_sequence();
232 let id = self.transactions.create_and_update(source, target)?;
233 if self.active_transaction.is_none() {
234 let branch = self.current_branch.clone();
235 self.attribute_new_transitions_for_branch(before_seq, &branch)?;
236 }
237 Ok(id)
238 }
239
240 pub fn exists(&self, id: u32) -> bool {
241 self.transactions.exists(id)
242 }
243
244 pub fn get(&self, id: u32) -> Option<&Link> {
245 self.transactions.get(id)
246 }
247
248 pub fn all(&self) -> Vec<&Link> {
249 self.transactions.all()
250 }
251
252 pub fn search(&self, source: u32, target: u32) -> Option<u32> {
253 self.transactions.search(source, target)
254 }
255
256 pub fn get_or_create(&mut self, source: u32, target: u32) -> Result<u32> {
257 if let Some(existing) = self.transactions.search(source, target) {
258 return Ok(existing);
259 }
260 self.create(source, target)
261 }
262
263 pub fn ensure_created(&mut self, id: u32) -> u32 {
264 self.transactions
265 .ensure_created(id)
266 .expect("TransactionsDecorator::ensure_created failed")
267 }
268
269 fn attribute_new_transitions_for_branch(
270 &mut self,
271 before_seq: i64,
272 branch_name: &str,
273 ) -> Result<()> {
274 let after_seq = self.transactions.last_logged_sequence();
275 if after_seq <= before_seq {
276 return Ok(());
277 }
278 for s in (before_seq + 1)..=after_seq {
279 self.transition_branches.insert(s, branch_name.to_string());
280 let marker = format!("{TRANSITION_PREFIX}{s}:branch={branch_name}");
281 self.write_immutable_marker(&marker)?;
282 }
283 if let Some(info) = self.branches.get(branch_name).cloned() {
284 let updated = BranchInfo {
285 head: after_seq,
286 ..info
287 };
288 self.branches
289 .insert(branch_name.to_string(), updated.clone());
290 self.update_branch_link(&updated)?;
291 }
292 if self.current_branch == branch_name {
293 self.current_applied = after_seq;
294 self.set_applied(after_seq)?;
295 }
296 Ok(())
297 }
298
299 pub fn branch(&mut self, name: &str, from: Option<i64>) -> Result<()> {
302 self.ensure_no_open_transaction("branch")?;
303 if name.trim().is_empty() {
304 bail!("Branch name must not be empty.");
305 }
306 if self.branches.contains_key(name) {
307 bail!("Branch '{name}' already exists.");
308 }
309 let parent = self.current_branch.clone();
310 let fork_seq = from.unwrap_or(self.current_applied);
311 if fork_seq < 0 {
312 bail!("Fork point cannot be negative.");
313 }
314 if fork_seq > 0 {
315 let path = self.build_branch_seqs(&parent);
316 if !path.contains(&fork_seq) {
317 bail!("Fork point {fork_seq} is not reachable on branch '{parent}'.",);
318 }
319 }
320 self.create_branch(name, Some(parent), fork_seq, fork_seq)?;
321 self.trace(&format!(
322 "Created branch '{name}' from '{}' at seq {fork_seq}.",
323 self.current_branch
324 ));
325 Ok(())
326 }
327
328 pub fn switch_branch(&mut self, name: &str) -> Result<()> {
329 self.ensure_no_open_transaction("switch_branch")?;
330 if !self.branches.contains_key(name) {
331 bail!("Unknown branch '{name}'.");
332 }
333 let target_path = self.build_branch_seqs(name);
334 self.apply_diff_to(target_path, name)?;
335 self.trace(&format!(
336 "Switched to branch '{name}' at seq {}.",
337 self.current_applied
338 ));
339 Ok(())
340 }
341
342 pub fn checkout(&mut self, sequence: i64) -> Result<()> {
343 self.ensure_no_open_transaction("checkout")?;
344 if sequence < 0 {
345 bail!("Sequence must be non-negative.");
346 }
347 let current = self.current_branch.clone();
348 let path = self.build_branch_seqs(¤t);
349 if sequence > 0 && !path.contains(&sequence) {
350 bail!("Sequence {sequence} is not reachable on branch '{current}'.",);
351 }
352 let target_path: Vec<i64> = path.iter().copied().filter(|s| *s <= sequence).collect();
353 self.apply_diff_to(target_path, ¤t)?;
354 self.trace(&format!(
355 "Checked out seq {sequence} on branch '{current}'.",
356 ));
357 Ok(())
358 }
359
360 pub fn tag(&mut self, name: &str, sequence: Option<i64>) -> Result<()> {
361 self.ensure_no_open_transaction("tag")?;
362 if name.trim().is_empty() {
363 bail!("Tag name must not be empty.");
364 }
365 let seq = sequence.unwrap_or(self.current_applied);
366 if seq < 0 {
367 bail!("Tag sequence must be non-negative.");
368 }
369 self.tags.insert(name.to_string(), seq);
370 self.update_tag_link(name, seq)?;
371 self.trace(&format!("Created tag '{name}' at seq {seq}.",));
372 Ok(())
373 }
374
375 fn apply_diff_to(&mut self, target_path: Vec<i64>, new_branch: &str) -> Result<()> {
378 let current_branch_name = self.current_branch.clone();
379 let current_path: Vec<i64> = self
380 .build_branch_seqs(¤t_branch_name)
381 .into_iter()
382 .filter(|s| *s <= self.current_applied)
383 .collect();
384
385 let mut common = 0usize;
386 let max_common = current_path.len().min(target_path.len());
387 while common < max_common && current_path[common] == target_path[common] {
388 common += 1;
389 }
390
391 let to_revert: Vec<i64> = current_path[common..].iter().rev().copied().collect();
393 for seq in to_revert {
394 if let Some(transition) = self.find_transition(seq) {
395 self.transactions.revert_transition(&transition);
396 }
397 }
398 let to_apply: Vec<i64> = target_path[common..].to_vec();
400 for seq in to_apply {
401 if let Some(transition) = self.find_transition(seq) {
402 self.transactions.apply_transition(&transition);
403 }
404 }
405
406 if new_branch != self.current_branch {
407 self.current_branch = new_branch.to_string();
408 self.set_current_branch(new_branch)?;
409 }
410 self.current_applied = target_path.last().copied().unwrap_or(0);
411 self.set_applied(self.current_applied)?;
412 Ok(())
413 }
414
415 fn ensure_no_open_transaction(&self, operation: &str) -> Result<()> {
416 if self.active_transaction.is_some() {
417 bail!("{operation} is not allowed while a version-control transaction is open.");
418 }
419 Ok(())
420 }
421
422 fn build_branch_seqs(&self, branch_name: &str) -> Vec<i64> {
423 let mut visited: HashSet<String> = HashSet::new();
424 self.build_branch_seqs_inner(branch_name, &mut visited)
425 }
426
427 fn build_branch_seqs_inner(
428 &self,
429 branch_name: &str,
430 visited: &mut HashSet<String>,
431 ) -> Vec<i64> {
432 let info = match self.branches.get(branch_name) {
433 Some(info) => info,
434 None => return Vec::new(),
435 };
436 if !visited.insert(branch_name.to_string()) {
437 return Vec::new();
438 }
439 let mut seqs = Vec::new();
440 if let Some(parent_name) = info.parent.as_deref() {
441 if self.branches.contains_key(parent_name) {
442 let mut parent_seqs = self.build_branch_seqs_inner(parent_name, visited);
443 parent_seqs.retain(|s| *s <= info.fork_seq);
444 seqs.extend(parent_seqs);
445 }
446 }
447 let mut own: Vec<i64> = self
448 .transition_branches
449 .iter()
450 .filter(|(s, b)| b.as_str() == branch_name && **s <= info.head)
451 .map(|(s, _)| *s)
452 .collect();
453 own.sort();
454 seqs.extend(own);
455 seqs
456 }
457
458 fn find_transition(&self, sequence: i64) -> Option<Transition> {
459 self.transactions
460 .log()
461 .into_iter()
462 .find(|t| t.sequence == sequence)
463 }
464
465 fn ensure_default_branch(&mut self) -> Result<()> {
468 let existing = self.transactions.last_logged_sequence();
469 if !self.branches.contains_key(DEFAULT_BRANCH_NAME) {
470 for s in 1..=existing {
472 if let std::collections::btree_map::Entry::Vacant(entry) =
473 self.transition_branches.entry(s)
474 {
475 entry.insert(DEFAULT_BRANCH_NAME.to_string());
476 let marker = format!("{TRANSITION_PREFIX}{s}:branch={DEFAULT_BRANCH_NAME}");
477 self.write_immutable_marker(&marker)?;
478 }
479 }
480 self.create_branch(DEFAULT_BRANCH_NAME, None, 0, existing)?;
481 self.current_branch = DEFAULT_BRANCH_NAME.to_string();
482 self.current_applied = existing;
483 self.set_current_branch(DEFAULT_BRANCH_NAME)?;
484 self.set_applied(existing)?;
485 } else if self.current_branch_link == 0 {
486 let branch = self.current_branch.clone();
487 self.set_current_branch(&branch)?;
488 }
489 Ok(())
490 }
491
492 fn create_branch(
493 &mut self,
494 name: &str,
495 parent: Option<String>,
496 fork_seq: i64,
497 head: i64,
498 ) -> Result<()> {
499 let info = BranchInfo::new(name.to_string(), parent, fork_seq, head);
500 self.branches.insert(name.to_string(), info.clone());
501 self.update_branch_link(&info)?;
502 Ok(())
503 }
504
505 fn update_branch_link(&mut self, info: &BranchInfo) -> Result<()> {
506 let marker = encode_branch_marker(info);
507 let link = match self.branch_links.get(&info.name).copied() {
508 Some(link) => link,
509 None => {
510 let new_link = self.branches_store.create(0, 0);
511 self.branch_links.insert(info.name.clone(), new_link);
512 new_link
513 }
514 };
515 self.branches_store.set_name(link, &marker)?;
516 Ok(())
517 }
518
519 fn update_tag_link(&mut self, name: &str, seq: i64) -> Result<()> {
520 let marker = format!("{TAG_PREFIX}{name}={seq}");
521 let link = match self.tag_links.get(name).copied() {
522 Some(link) => link,
523 None => {
524 let new_link = self.branches_store.create(0, 0);
525 self.tag_links.insert(name.to_string(), new_link);
526 new_link
527 }
528 };
529 self.branches_store.set_name(link, &marker)?;
530 Ok(())
531 }
532
533 fn set_current_branch(&mut self, name: &str) -> Result<()> {
534 self.current_branch = name.to_string();
535 if self.current_branch_link == 0 {
536 self.current_branch_link = self.branches_store.create(0, 0);
537 }
538 let link = self.current_branch_link;
539 let marker = format!("{CURRENT_PREFIX}{name}");
540 self.branches_store.set_name(link, &marker)?;
541 Ok(())
542 }
543
544 fn set_applied(&mut self, seq: i64) -> Result<()> {
545 if self.applied_link == 0 {
546 self.applied_link = self.branches_store.create(0, 0);
547 }
548 let link = self.applied_link;
549 let marker = format!("{APPLIED_PREFIX}{seq}");
550 self.branches_store.set_name(link, &marker)?;
551 Ok(())
552 }
553
554 fn write_immutable_marker(&mut self, name: &str) -> Result<()> {
555 let link = self.branches_store.create(0, 0);
556 self.branches_store.set_name(link, name)?;
557 Ok(())
558 }
559
560 pub fn recover(&mut self) -> Result<()> {
561 self.branches.clear();
562 self.tags.clear();
563 self.transition_branches.clear();
564 self.branch_links.clear();
565 self.tag_links.clear();
566 self.current_branch = DEFAULT_BRANCH_NAME.to_string();
567 self.current_branch_link = 0;
568 self.applied_link = 0;
569 self.current_applied = 0;
570
571 let links: Vec<Link> = self.branches_store.all().into_iter().copied().collect();
572 for link in &links {
573 let name = match self.branches_store.get_name(link.index)? {
574 Some(value) => value,
575 None => continue,
576 };
577 if name.starts_with(BRANCH_PREFIX) {
578 if let Some(info) = try_decode_branch_marker(&name) {
579 self.branches.insert(info.name.clone(), info.clone());
580 self.branch_links.insert(info.name.clone(), link.index);
581 }
582 } else if let Some(rest) = name.strip_prefix(CURRENT_PREFIX) {
583 self.current_branch = rest.to_string();
584 self.current_branch_link = link.index;
585 } else if let Some(rest) = name.strip_prefix(APPLIED_PREFIX) {
586 if let Ok(seq) = rest.parse::<i64>() {
587 self.current_applied = seq;
588 self.applied_link = link.index;
589 }
590 } else if let Some(rest) = name.strip_prefix(TAG_PREFIX) {
591 if let Some(eq) = rest.find('=') {
592 let tag_name = &rest[..eq];
593 if let Ok(tag_seq) = rest[eq + 1..].parse::<i64>() {
594 self.tags.insert(tag_name.to_string(), tag_seq);
595 self.tag_links.insert(tag_name.to_string(), link.index);
596 }
597 }
598 } else if let Some(rest) = name.strip_prefix(TRANSITION_PREFIX) {
599 if let Some(colon) = rest.find(":branch=") {
600 if let Ok(seq) = rest[..colon].parse::<i64>() {
601 let branch_name = &rest[colon + ":branch=".len()..];
602 self.transition_branches
603 .insert(seq, branch_name.to_string());
604 }
605 }
606 }
607 }
608 Ok(())
609 }
610
611 fn trace(&self, message: &str) {
612 if self.trace {
613 eprintln!("[VersionControl] {message}");
614 }
615 }
616}
617
618fn encode_branch_marker(info: &BranchInfo) -> String {
619 let parent = info.parent.as_deref().unwrap_or("");
620 format!(
621 "{BRANCH_PREFIX}{name}:parent={parent}:fork={fork}:head={head}",
622 name = info.name,
623 fork = info.fork_seq,
624 head = info.head,
625 )
626}
627
628fn try_decode_branch_marker(text: &str) -> Option<BranchInfo> {
629 let rest = text.strip_prefix(BRANCH_PREFIX)?;
630 let parent_idx = rest.find(":parent=")?;
631 let name = &rest[..parent_idx];
632 let rest = &rest[parent_idx + ":parent=".len()..];
633 let fork_idx = rest.find(":fork=")?;
634 let parent_text = &rest[..fork_idx];
635 let rest = &rest[fork_idx + ":fork=".len()..];
636 let head_idx = rest.find(":head=")?;
637 let fork_text = &rest[..head_idx];
638 let head_text = &rest[head_idx + ":head=".len()..];
639 let fork: i64 = fork_text.parse().ok()?;
640 let head: i64 = head_text.parse().ok()?;
641 let parent = if parent_text.is_empty() {
642 None
643 } else {
644 Some(parent_text.to_string())
645 };
646 Some(BranchInfo::new(name.to_string(), parent, fork, head))
647}
648
649#[cfg(test)]
650mod tests {
651 use super::*;
652
653 #[test]
654 fn encode_round_trips_through_decode() {
655 let info = BranchInfo::new("feature".into(), Some("main".into()), 5, 9);
656 let text = encode_branch_marker(&info);
657 let decoded = try_decode_branch_marker(&text).unwrap();
658 assert_eq!(info, decoded);
659 }
660
661 #[test]
662 fn make_version_control_database_filename_returns_sibling_path() {
663 let path =
664 VersionControlDecorator::make_version_control_database_filename("/var/data/db.links");
665 assert_eq!(path, PathBuf::from("/var/data/db.versioncontrol.links"));
666 }
667
668 #[test]
669 fn decode_branch_marker_rejects_invalid_input() {
670 assert!(try_decode_branch_marker("not a marker").is_none());
671 assert!(try_decode_branch_marker("__vc:branch:x:parent=:fork=z:head=1").is_none());
672 }
673}