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