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