gitstack 5.3.0

Git history viewer with insights - Author stats, file heatmap, code ownership
Documentation
//! Branch comparison feature
//!
//! Lists diff commits between two branches

use chrono::{DateTime, Local};

/// Commit information for comparison
#[derive(Debug, Clone)]
pub struct CompareCommit {
    /// Commit hash (short form)
    pub hash: String,
    /// Commit message (first line)
    pub message: String,
    /// Author name
    pub author: String,
    /// Commit date/time
    pub date: DateTime<Local>,
}

/// Branch comparison result
#[derive(Debug, Clone)]
pub struct BranchCompare {
    /// Base branch name
    pub base_branch: String,
    /// Target branch name
    pub target_branch: String,
    /// Commits where target is ahead of base
    pub ahead_commits: Vec<CompareCommit>,
    /// Commits where target is behind base
    pub behind_commits: Vec<CompareCommit>,
    /// Merge base commit hash
    pub merge_base: String,
}

/// Tab for comparison view
#[derive(Debug, Clone, Copy, PartialEq, Default)]
pub enum CompareTab {
    #[default]
    Ahead,
    Behind,
}

impl CompareTab {
    /// Toggle the tab
    pub fn toggle(&self) -> Self {
        match self {
            CompareTab::Ahead => CompareTab::Behind,
            CompareTab::Behind => CompareTab::Ahead,
        }
    }
}

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

    #[test]
    fn compare_tab_default_is_ahead() {
        let tab = CompareTab::default();
        assert_eq!(tab, CompareTab::Ahead);
    }

    #[test]
    fn compare_tab_toggle_ahead_to_behind() {
        let tab = CompareTab::Ahead;
        assert_eq!(tab.toggle(), CompareTab::Behind);
    }

    #[test]
    fn compare_tab_toggle_behind_to_ahead() {
        let tab = CompareTab::Behind;
        assert_eq!(tab.toggle(), CompareTab::Ahead);
    }

    #[test]
    fn compare_tab_toggle_roundtrip() {
        let tab = CompareTab::Ahead;
        assert_eq!(tab.toggle().toggle(), CompareTab::Ahead);
    }

    #[test]
    fn compare_tab_clone() {
        let tab = CompareTab::Ahead;
        let cloned = tab;
        assert_eq!(tab, cloned);
    }

    #[test]
    fn compare_tab_debug() {
        let ahead = format!("{:?}", CompareTab::Ahead);
        let behind = format!("{:?}", CompareTab::Behind);
        assert_eq!(ahead, "Ahead");
        assert_eq!(behind, "Behind");
    }

    #[test]
    fn compare_commit_fields() {
        let commit = CompareCommit {
            hash: "abc1234".to_string(),
            message: "fix: some bug".to_string(),
            author: "Test User".to_string(),
            date: Local::now(),
        };
        assert_eq!(commit.hash, "abc1234");
        assert_eq!(commit.message, "fix: some bug");
        assert_eq!(commit.author, "Test User");
    }

    #[test]
    fn branch_compare_empty_commits() {
        let compare = BranchCompare {
            base_branch: "main".to_string(),
            target_branch: "feature".to_string(),
            ahead_commits: vec![],
            behind_commits: vec![],
            merge_base: "deadbeef".to_string(),
        };
        assert_eq!(compare.base_branch, "main");
        assert_eq!(compare.target_branch, "feature");
        assert!(compare.ahead_commits.is_empty());
        assert!(compare.behind_commits.is_empty());
        assert_eq!(compare.merge_base, "deadbeef");
    }

    #[test]
    fn branch_compare_with_commits() {
        let now = Local::now();
        let compare = BranchCompare {
            base_branch: "main".to_string(),
            target_branch: "feature/test".to_string(),
            ahead_commits: vec![
                CompareCommit {
                    hash: "aaa1111".to_string(),
                    message: "feat: add feature".to_string(),
                    author: "Alice".to_string(),
                    date: now,
                },
                CompareCommit {
                    hash: "bbb2222".to_string(),
                    message: "test: add tests".to_string(),
                    author: "Bob".to_string(),
                    date: now,
                },
            ],
            behind_commits: vec![CompareCommit {
                hash: "ccc3333".to_string(),
                message: "fix: hotfix".to_string(),
                author: "Charlie".to_string(),
                date: now,
            }],
            merge_base: "000abcd".to_string(),
        };
        assert_eq!(compare.ahead_commits.len(), 2);
        assert_eq!(compare.behind_commits.len(), 1);
        assert_eq!(compare.ahead_commits[0].author, "Alice");
        assert_eq!(compare.behind_commits[0].hash, "ccc3333");
    }

    #[test]
    fn compare_commit_clone() {
        let commit = CompareCommit {
            hash: "abc".to_string(),
            message: "msg".to_string(),
            author: "author".to_string(),
            date: Local::now(),
        };
        let cloned = commit.clone();
        assert_eq!(commit.hash, cloned.hash);
        assert_eq!(commit.message, cloned.message);
        assert_eq!(commit.author, cloned.author);
    }
}