use std::collections::HashMap;
use std::time::{SystemTime, UNIX_EPOCH};
use crate::trajectory::graph::NodeId;
#[inline]
fn current_timestamp() -> i64 {
SystemTime::now()
.duration_since(UNIX_EPOCH)
.map(|d| d.as_secs() as i64)
.unwrap_or(0)
}
pub type BranchId = u64;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum BranchStatus {
Active,
Archived,
Merged,
Recovered,
Orphaned,
}
impl Default for BranchStatus {
fn default() -> Self {
Self::Active
}
}
#[derive(Debug, Clone)]
pub struct ForkPoint {
pub node_id: NodeId,
pub children: Vec<BranchId>,
pub selected_child: Option<BranchId>,
pub created_at: i64,
pub depth: u32,
}
impl ForkPoint {
pub fn new(node_id: NodeId, children: Vec<BranchId>, depth: u32) -> Self {
Self {
node_id,
children,
selected_child: None,
created_at: current_timestamp(),
depth,
}
}
#[inline]
pub fn is_branch_point(&self) -> bool {
self.children.len() > 1
}
#[inline]
pub fn child_count(&self) -> usize {
self.children.len()
}
pub fn select(&mut self, branch_id: BranchId) -> Result<(), BranchError> {
if self.children.contains(&branch_id) {
self.selected_child = Some(branch_id);
Ok(())
} else {
Err(BranchError::InvalidBranch(branch_id))
}
}
}
#[derive(Debug, Clone)]
pub struct Branch {
pub id: BranchId,
pub fork_point: NodeId,
pub head: NodeId,
pub status: BranchStatus,
pub parent_branch: Option<BranchId>,
pub child_branches: Vec<BranchId>,
pub nodes: Vec<NodeId>,
pub metadata: HashMap<String, String>,
pub created_at: i64,
pub updated_at: i64,
pub label: Option<String>,
}
impl Branch {
pub fn new(id: BranchId, fork_point: NodeId, head: NodeId) -> Self {
let now = current_timestamp();
Self {
id,
fork_point,
head,
status: BranchStatus::Active,
parent_branch: None,
child_branches: Vec::new(),
nodes: vec![head],
metadata: HashMap::new(),
created_at: now,
updated_at: now,
label: None,
}
}
pub fn root(id: BranchId, root_node: NodeId) -> Self {
let now = current_timestamp();
Self {
id,
fork_point: root_node,
head: root_node,
status: BranchStatus::Active,
parent_branch: None,
child_branches: Vec::new(),
nodes: vec![root_node],
metadata: HashMap::new(),
created_at: now,
updated_at: now,
label: Some("main".to_string()),
}
}
#[inline]
pub fn is_active(&self) -> bool {
self.status == BranchStatus::Active
}
#[inline]
pub fn is_root(&self) -> bool {
self.parent_branch.is_none()
}
#[inline]
pub fn is_merged(&self) -> bool {
self.status == BranchStatus::Merged
}
pub fn archive(&mut self) {
self.status = BranchStatus::Archived;
self.updated_at = current_timestamp();
}
pub fn mark_merged(&mut self) {
self.status = BranchStatus::Merged;
self.updated_at = current_timestamp();
}
pub fn recover(&mut self) {
self.status = BranchStatus::Recovered;
self.updated_at = current_timestamp();
}
pub fn add_node(&mut self, node_id: NodeId) {
self.nodes.push(node_id);
self.head = node_id;
self.updated_at = current_timestamp();
}
#[inline]
pub fn len(&self) -> usize {
self.nodes.len()
}
#[inline]
pub fn is_empty(&self) -> bool {
self.nodes.is_empty()
}
pub fn set_label(&mut self, label: impl Into<String>) {
self.label = Some(label.into());
self.updated_at = current_timestamp();
}
pub fn add_metadata(&mut self, key: impl Into<String>, value: impl Into<String>) {
self.metadata.insert(key.into(), value.into());
self.updated_at = current_timestamp();
}
}
#[derive(Debug, Clone)]
pub enum BranchOperation {
Split {
from: BranchId,
to: BranchId,
at: NodeId,
timestamp: i64,
},
Merge {
from: BranchId,
into: BranchId,
at: NodeId,
timestamp: i64,
},
Recover {
branch: BranchId,
strategy: String,
timestamp: i64,
},
Traverse {
from: BranchId,
to: BranchId,
timestamp: i64,
},
Archive {
branch: BranchId,
reason: Option<String>,
timestamp: i64,
},
Fork {
parent: BranchId,
child: BranchId,
at: NodeId,
timestamp: i64,
},
}
impl BranchOperation {
pub fn split(from: BranchId, to: BranchId, at: NodeId) -> Self {
Self::Split {
from,
to,
at,
timestamp: current_timestamp(),
}
}
pub fn merge(from: BranchId, into: BranchId, at: NodeId) -> Self {
Self::Merge {
from,
into,
at,
timestamp: current_timestamp(),
}
}
pub fn recover(branch: BranchId, strategy: impl Into<String>) -> Self {
Self::Recover {
branch,
strategy: strategy.into(),
timestamp: current_timestamp(),
}
}
pub fn traverse(from: BranchId, to: BranchId) -> Self {
Self::Traverse {
from,
to,
timestamp: current_timestamp(),
}
}
pub fn archive(branch: BranchId, reason: Option<String>) -> Self {
Self::Archive {
branch,
reason,
timestamp: current_timestamp(),
}
}
pub fn fork(parent: BranchId, child: BranchId, at: NodeId) -> Self {
Self::Fork {
parent,
child,
at,
timestamp: current_timestamp(),
}
}
pub fn timestamp(&self) -> i64 {
match self {
Self::Split { timestamp, .. } => *timestamp,
Self::Merge { timestamp, .. } => *timestamp,
Self::Recover { timestamp, .. } => *timestamp,
Self::Traverse { timestamp, .. } => *timestamp,
Self::Archive { timestamp, .. } => *timestamp,
Self::Fork { timestamp, .. } => *timestamp,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum BranchError {
BranchNotFound(BranchId),
NodeNotFound(NodeId),
CycleDetected,
CannotSplitRoot,
AlreadyMerged(BranchId),
InvalidBranch(BranchId),
SelfMerge,
NoParent(BranchId),
InvalidState(String),
}
impl std::fmt::Display for BranchError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::BranchNotFound(id) => write!(f, "Branch {} not found", id),
Self::NodeNotFound(id) => write!(f, "Node {} not found", id),
Self::CycleDetected => write!(f, "Operation would create a cycle"),
Self::CannotSplitRoot => write!(f, "Cannot split the root node"),
Self::AlreadyMerged(id) => write!(f, "Branch {} is already merged", id),
Self::InvalidBranch(id) => write!(f, "Invalid branch ID: {}", id),
Self::SelfMerge => write!(f, "Cannot merge branch with itself"),
Self::NoParent(id) => write!(f, "Branch {} has no parent", id),
Self::InvalidState(msg) => write!(f, "Invalid state: {}", msg),
}
}
}
impl std::error::Error for BranchError {}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_branch_creation() {
let branch = Branch::new(1, 100, 101);
assert_eq!(branch.id, 1);
assert_eq!(branch.fork_point, 100);
assert_eq!(branch.head, 101);
assert!(branch.is_active());
assert!(branch.parent_branch.is_none());
}
#[test]
fn test_root_branch() {
let branch = Branch::root(0, 1);
assert!(branch.is_root());
assert_eq!(branch.label, Some("main".to_string()));
}
#[test]
fn test_branch_archive() {
let mut branch = Branch::new(1, 100, 101);
assert!(branch.is_active());
branch.archive();
assert!(!branch.is_active());
assert_eq!(branch.status, BranchStatus::Archived);
}
#[test]
fn test_branch_recover() {
let mut branch = Branch::new(1, 100, 101);
branch.archive();
branch.recover();
assert_eq!(branch.status, BranchStatus::Recovered);
}
#[test]
fn test_fork_point() {
let mut fork = ForkPoint::new(100, vec![1, 2, 3], 5);
assert!(fork.is_branch_point());
assert_eq!(fork.child_count(), 3);
assert!(fork.select(2).is_ok());
assert_eq!(fork.selected_child, Some(2));
assert!(fork.select(99).is_err());
}
#[test]
fn test_branch_operations() {
let split_op = BranchOperation::split(1, 2, 100);
assert!(split_op.timestamp() > 0);
let merge_op = BranchOperation::merge(2, 1, 100);
assert!(merge_op.timestamp() > 0);
let recover_op = BranchOperation::recover(1, "manual");
assert!(recover_op.timestamp() > 0);
}
#[test]
fn test_branch_add_node() {
let mut branch = Branch::new(1, 100, 101);
assert_eq!(branch.len(), 1);
branch.add_node(102);
assert_eq!(branch.len(), 2);
assert_eq!(branch.head, 102);
assert!(branch.nodes.contains(&102));
}
#[test]
fn test_branch_metadata() {
let mut branch = Branch::new(1, 100, 101);
branch.add_metadata("source", "regeneration");
branch.set_label("experiment-1");
assert_eq!(branch.metadata.get("source"), Some(&"regeneration".to_string()));
assert_eq!(branch.label, Some("experiment-1".to_string()));
}
}