use chrono::{DateTime, Local};
use crate::intent::ChangeIntent;
#[derive(Debug, Clone, PartialEq)]
pub enum GitEventKind {
Commit,
Merge,
BranchSwitch,
}
#[derive(Debug, Clone)]
pub struct GitEvent {
pub kind: GitEventKind,
pub short_hash: String,
pub message: String,
pub author: String,
pub timestamp: DateTime<Local>,
pub files_added: usize,
pub files_deleted: usize,
pub parent_hashes: Vec<String>,
pub branch_labels: Vec<String>,
pub session_id: Option<u32>,
pub inferred_intent: Option<ChangeIntent>,
}
impl GitEvent {
pub fn commit(
short_hash: String,
message: String,
author: String,
timestamp: DateTime<Local>,
files_added: usize,
files_deleted: usize,
) -> Self {
Self {
kind: GitEventKind::Commit,
short_hash,
message,
author,
timestamp,
files_added,
files_deleted,
parent_hashes: Vec::new(),
branch_labels: Vec::new(),
session_id: None,
inferred_intent: None,
}
}
pub fn merge(
short_hash: String,
message: String,
author: String,
timestamp: DateTime<Local>,
) -> Self {
Self {
kind: GitEventKind::Merge,
short_hash,
message,
author,
timestamp,
files_added: 0,
files_deleted: 0,
parent_hashes: Vec::new(),
branch_labels: Vec::new(),
session_id: None,
inferred_intent: None,
}
}
pub fn with_parents(mut self, parents: Vec<String>) -> Self {
self.parent_hashes = parents;
self
}
pub fn with_labels(mut self, labels: Vec<String>) -> Self {
self.branch_labels = labels;
self
}
pub fn has_labels(&self) -> bool {
!self.branch_labels.is_empty()
}
pub fn relative_time(&self) -> String {
let now = Local::now();
let duration = now.signed_duration_since(self.timestamp);
if duration.num_minutes() < 1 {
"just now".to_string()
} else if duration.num_minutes() < 60 {
format!("{}m ago", duration.num_minutes())
} else if duration.num_hours() < 24 {
format!("{}h ago", duration.num_hours())
} else if duration.num_days() < 30 {
format!("{}d ago", duration.num_days())
} else {
format!("{}mo ago", duration.num_days() / 30)
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use chrono::Duration;
fn create_test_timestamp() -> DateTime<Local> {
Local::now()
}
#[test]
fn test_git_event_kind_commit_is_distinct() {
assert_ne!(GitEventKind::Commit, GitEventKind::Merge);
assert_ne!(GitEventKind::Commit, GitEventKind::BranchSwitch);
}
#[test]
fn test_git_event_commit_creates_commit_kind() {
let event = GitEvent::commit(
"abc1234".to_string(),
"feat: add feature".to_string(),
"author".to_string(),
create_test_timestamp(),
2,
1,
);
assert_eq!(event.kind, GitEventKind::Commit);
}
#[test]
fn test_git_event_commit_stores_hash() {
let event = GitEvent::commit(
"abc1234".to_string(),
"message".to_string(),
"author".to_string(),
create_test_timestamp(),
0,
0,
);
assert_eq!(event.short_hash, "abc1234");
}
#[test]
fn test_git_event_commit_stores_message() {
let event = GitEvent::commit(
"abc1234".to_string(),
"feat: new feature".to_string(),
"author".to_string(),
create_test_timestamp(),
0,
0,
);
assert_eq!(event.message, "feat: new feature");
}
#[test]
fn test_git_event_commit_stores_author() {
let event = GitEvent::commit(
"abc1234".to_string(),
"message".to_string(),
"John Doe".to_string(),
create_test_timestamp(),
0,
0,
);
assert_eq!(event.author, "John Doe");
}
#[test]
fn test_git_event_commit_stores_file_counts() {
let event = GitEvent::commit(
"abc1234".to_string(),
"message".to_string(),
"author".to_string(),
create_test_timestamp(),
5,
3,
);
assert_eq!(event.files_added, 5);
assert_eq!(event.files_deleted, 3);
}
#[test]
fn test_git_event_merge_creates_merge_kind() {
let event = GitEvent::merge(
"abc1234".to_string(),
"Merge PR #1".to_string(),
"author".to_string(),
create_test_timestamp(),
);
assert_eq!(event.kind, GitEventKind::Merge);
}
#[test]
fn test_git_event_merge_has_zero_file_counts() {
let event = GitEvent::merge(
"abc1234".to_string(),
"Merge PR #1".to_string(),
"author".to_string(),
create_test_timestamp(),
);
assert_eq!(event.files_added, 0);
assert_eq!(event.files_deleted, 0);
}
#[test]
fn test_relative_time_just_now() {
let event = GitEvent::commit(
"abc1234".to_string(),
"message".to_string(),
"author".to_string(),
Local::now(),
0,
0,
);
assert_eq!(event.relative_time(), "just now");
}
#[test]
fn test_relative_time_minutes() {
let event = GitEvent::commit(
"abc1234".to_string(),
"message".to_string(),
"author".to_string(),
Local::now() - Duration::minutes(14),
0,
0,
);
assert_eq!(event.relative_time(), "14m ago");
}
#[test]
fn test_relative_time_hours() {
let event = GitEvent::commit(
"abc1234".to_string(),
"message".to_string(),
"author".to_string(),
Local::now() - Duration::hours(2),
0,
0,
);
assert_eq!(event.relative_time(), "2h ago");
}
#[test]
fn test_relative_time_days() {
let event = GitEvent::commit(
"abc1234".to_string(),
"message".to_string(),
"author".to_string(),
Local::now() - Duration::days(3),
0,
0,
);
assert_eq!(event.relative_time(), "3d ago");
}
}