use chrono::Local;
#[derive(Debug, Clone)]
pub struct BranchInfo {
pub name: String,
pub is_gone: bool,
}
impl BranchInfo {
pub fn new(name: String, is_gone: bool) -> Self {
Self { name, is_gone }
}
}
impl std::fmt::Display for BranchInfo {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.name)
}
}
impl PartialEq<str> for BranchInfo {
fn eq(&self, other: &str) -> bool {
self.name == other
}
}
impl PartialEq<String> for BranchInfo {
fn eq(&self, other: &String) -> bool {
self.name == *other
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum FileStatusKind {
StagedNew,
StagedModified,
StagedDeleted,
Modified,
Deleted,
Untracked,
}
impl FileStatusKind {
pub fn is_staged(&self) -> bool {
matches!(
self,
FileStatusKind::StagedNew
| FileStatusKind::StagedModified
| FileStatusKind::StagedDeleted
)
}
}
#[derive(Debug, Clone)]
pub struct FileStatus {
pub path: String,
pub kind: FileStatusKind,
}
#[derive(Debug, Clone, Default)]
pub struct DiffStats {
pub files_changed: usize,
pub insertions: usize,
pub deletions: usize,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum FileChangeStatus {
Added,
Modified,
Deleted,
Renamed,
}
impl FileChangeStatus {
pub fn as_char(&self) -> char {
match self {
Self::Added => 'A',
Self::Modified => 'M',
Self::Deleted => 'D',
Self::Renamed => 'R',
}
}
}
#[derive(Debug, Clone)]
pub struct FileChange {
pub path: String,
pub status: FileChangeStatus,
pub insertions: usize,
pub deletions: usize,
}
#[derive(Debug, Clone, Default)]
pub struct CommitDiff {
pub stats: DiffStats,
pub files: Vec<FileChange>,
pub patches: Vec<FilePatch>,
}
#[derive(Debug, Clone)]
pub struct RepoInfo {
pub name: String,
pub branch: String,
}
#[derive(Debug, Clone)]
pub struct DiffLine {
pub origin: char,
pub content: String,
pub old_lineno: Option<u32>,
pub new_lineno: Option<u32>,
}
#[derive(Debug, Clone)]
pub struct FilePatch {
pub path: String,
pub lines: Vec<DiffLine>,
}
#[derive(Debug, Clone)]
pub struct BlameLine {
pub hash: String,
pub author: String,
pub date: chrono::DateTime<Local>,
pub line_number: usize,
pub content: String,
}
#[derive(Debug, Clone)]
pub struct FileHistoryEntry {
pub hash: String,
pub author: String,
pub date: chrono::DateTime<Local>,
pub message: String,
pub insertions: usize,
pub deletions: usize,
}
#[derive(Debug, Clone)]
pub struct StashEntry {
pub index: usize,
pub message: String,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_branch_info_new() {
let b = BranchInfo::new("main".to_string(), false);
assert_eq!(b.name, "main");
assert!(!b.is_gone);
}
#[test]
fn test_branch_info_new_gone() {
let b = BranchInfo::new("stale-branch".to_string(), true);
assert_eq!(b.name, "stale-branch");
assert!(b.is_gone);
}
#[test]
fn test_branch_info_display() {
let b = BranchInfo::new("feature/test".to_string(), false);
assert_eq!(format!("{}", b), "feature/test");
}
#[test]
fn test_branch_info_partial_eq_str() {
let b = BranchInfo::new("main".to_string(), false);
assert!(b == *"main");
assert!(!(b == *"develop"));
}
#[test]
fn test_branch_info_partial_eq_string() {
let b = BranchInfo::new("main".to_string(), false);
let main_str = "main".to_string();
let develop_str = "develop".to_string();
assert!(b == main_str);
assert!(!(b == develop_str));
}
#[test]
fn test_file_status_kind_is_staged() {
assert!(FileStatusKind::StagedNew.is_staged());
assert!(FileStatusKind::StagedModified.is_staged());
assert!(FileStatusKind::StagedDeleted.is_staged());
assert!(!FileStatusKind::Modified.is_staged());
assert!(!FileStatusKind::Deleted.is_staged());
assert!(!FileStatusKind::Untracked.is_staged());
}
#[test]
fn test_file_change_status_as_char() {
assert_eq!(FileChangeStatus::Added.as_char(), 'A');
assert_eq!(FileChangeStatus::Modified.as_char(), 'M');
assert_eq!(FileChangeStatus::Deleted.as_char(), 'D');
assert_eq!(FileChangeStatus::Renamed.as_char(), 'R');
}
#[test]
fn test_diff_stats_default() {
let s = DiffStats::default();
assert_eq!(s.files_changed, 0);
assert_eq!(s.insertions, 0);
assert_eq!(s.deletions, 0);
}
#[test]
fn test_commit_diff_default() {
let d = CommitDiff::default();
assert_eq!(d.stats.files_changed, 0);
assert!(d.files.is_empty());
assert!(d.patches.is_empty());
}
#[test]
fn test_stash_entry_construction() {
let e = StashEntry {
index: 0,
message: "WIP: feature".to_string(),
};
assert_eq!(e.index, 0);
assert_eq!(e.message, "WIP: feature");
}
#[test]
fn test_file_patch_construction() {
let p = FilePatch {
path: "src/main.rs".to_string(),
lines: vec![DiffLine {
origin: '+',
content: "new line".to_string(),
old_lineno: None,
new_lineno: Some(10),
}],
};
assert_eq!(p.path, "src/main.rs");
assert_eq!(p.lines.len(), 1);
assert_eq!(p.lines[0].origin, '+');
assert_eq!(p.lines[0].new_lineno, Some(10));
assert_eq!(p.lines[0].old_lineno, None);
}
#[test]
fn test_file_status_construction() {
let s = FileStatus {
path: "README.md".to_string(),
kind: FileStatusKind::Modified,
};
assert_eq!(s.path, "README.md");
assert_eq!(s.kind, FileStatusKind::Modified);
}
#[test]
fn test_file_change_construction() {
let c = FileChange {
path: "lib.rs".to_string(),
status: FileChangeStatus::Added,
insertions: 50,
deletions: 0,
};
assert_eq!(c.path, "lib.rs");
assert_eq!(c.status, FileChangeStatus::Added);
assert_eq!(c.insertions, 50);
assert_eq!(c.deletions, 0);
}
#[test]
fn test_repo_info_construction() {
let r = RepoInfo {
name: "my-repo".to_string(),
branch: "main".to_string(),
};
assert_eq!(r.name, "my-repo");
assert_eq!(r.branch, "main");
}
}