branch_party_core 0.1.1

Core library for branch-party CLI tool
Documentation
use crate::config::{ConflictPolicy, MergeOrderStrategy};
use serde::{Deserialize, Serialize};

#[derive(Debug, Clone, PartialEq)]
pub struct BranchSpec {
    pub name: String,
    pub is_party: bool,
}

impl BranchSpec {
    pub fn branch(name: impl Into<String>) -> Self {
        Self {
            name: name.into(),
            is_party: false,
        }
    }

    pub fn party(name: impl Into<String>) -> Self {
        Self {
            name: name.into(),
            is_party: true,
        }
    }

    /// Parse a branch spec from a string
    /// Party references start with '@'
    pub fn parse(spec: impl AsRef<str>) -> Self {
        let spec = spec.as_ref();
        if spec.starts_with('@') {
            Self::party(&spec[1..])
        } else {
            Self::branch(spec)
        }
    }
}

impl std::fmt::Display for BranchSpec {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        if self.is_party {
            write!(f, "@{}", self.name)
        } else {
            write!(f, "{}", self.name)
        }
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MergePlan {
    pub party_name: String,
    pub destination: String,
    pub base_branch: String,
    pub sources: Vec<String>,
    pub strategy: MergeOrderStrategy,
    pub conflict_policy: ConflictPolicy,
    pub steps: Vec<MergeStep>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MergeStep {
    pub source: String,
    pub target: String,
    pub expected_conflicts: Vec<String>,
    pub is_fast_forward: bool,
}

impl MergePlan {
    pub fn new(
        party_name: String,
        destination: String,
        base_branch: String,
        sources: Vec<String>,
        strategy: MergeOrderStrategy,
        conflict_policy: ConflictPolicy,
    ) -> Self {
        Self {
            party_name,
            destination,
            base_branch,
            sources,
            strategy,
            conflict_policy,
            steps: Vec::new(),
        }
    }

    pub fn add_step(&mut self, step: MergeStep) {
        self.steps.push(step);
    }

    pub fn total_sources(&self) -> usize {
        self.sources.len()
    }

    pub fn estimated_conflicts(&self) -> usize {
        self.steps.iter().map(|s| s.expected_conflicts.len()).sum()
    }

    pub fn fast_forward_count(&self) -> usize {
        self.steps.iter().filter(|s| s.is_fast_forward).count()
    }
}

impl MergeStep {
    pub fn new(source: String, target: String) -> Self {
        Self {
            source,
            target,
            expected_conflicts: Vec::new(),
            is_fast_forward: false,
        }
    }

    pub fn with_conflicts(mut self, conflicts: Vec<String>) -> Self {
        self.expected_conflicts = conflicts;
        self
    }

    pub fn as_fast_forward(mut self) -> Self {
        self.is_fast_forward = true;
        self
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RepoState {
    pub current_branch: String,
    pub is_clean: bool,
    pub uncommitted_changes: Vec<String>,
    pub branch_exists: bool,
    pub commit_hash: String,
}

impl RepoState {
    pub fn new(
        current_branch: String,
        is_clean: bool,
        uncommitted_changes: Vec<String>,
        branch_exists: bool,
        commit_hash: String,
    ) -> Self {
        Self {
            current_branch,
            is_clean,
            uncommitted_changes,
            branch_exists,
            commit_hash,
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_branch_spec_parsing() {
        let branch = BranchSpec::parse("feature/test");
        assert!(!branch.is_party);
        assert_eq!(branch.name, "feature/test");

        let party = BranchSpec::parse("@payments");
        assert!(party.is_party);
        assert_eq!(party.name, "payments");
    }

    #[test]
    fn test_branch_spec_display() {
        let branch = BranchSpec::branch("main");
        assert_eq!(branch.to_string(), "main");

        let party = BranchSpec::party("qa");
        assert_eq!(party.to_string(), "@qa");
    }

    #[test]
    fn test_merge_plan_creation() {
        let plan = MergePlan::new(
            "qa".to_string(),
            "party/qa".to_string(),
            "main".to_string(),
            vec!["feature/a".to_string(), "feature/b".to_string()],
            MergeOrderStrategy::Listed,
            ConflictPolicy::default(),
        );

        assert_eq!(plan.party_name, "qa");
        assert_eq!(plan.total_sources(), 2);
        assert_eq!(plan.estimated_conflicts(), 0);
        assert_eq!(plan.fast_forward_count(), 0);
    }
}