use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct NeuralCommit {
pub git_hash: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub parent_hash: Option<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub parent_hashes: Vec<String>,
pub author: String,
pub roadmap_hash: String,
pub trace_hash: String,
pub summary: String,
#[serde(with = "chrono::serde::ts_seconds")]
pub created_at: DateTime<Utc>,
}
impl NeuralCommit {
#[allow(clippy::too_many_arguments)]
pub fn new(
git_hash: impl Into<String>,
parent_hash: Option<String>,
author: impl Into<String>,
roadmap_hash: impl Into<String>,
trace_hash: impl Into<String>,
summary: impl Into<String>,
) -> Self {
Self {
git_hash: git_hash.into(),
parent_hash,
parent_hashes: Vec::new(),
author: author.into(),
roadmap_hash: roadmap_hash.into(),
trace_hash: trace_hash.into(),
summary: summary.into(),
created_at: Utc::now(),
}
}
#[allow(clippy::too_many_arguments)]
pub fn new_with_parents(
git_hash: impl Into<String>,
parent_hashes: Vec<String>,
author: impl Into<String>,
roadmap_hash: impl Into<String>,
trace_hash: impl Into<String>,
summary: impl Into<String>,
) -> Self {
Self {
git_hash: git_hash.into(),
parent_hash: None,
parent_hashes,
author: author.into(),
roadmap_hash: roadmap_hash.into(),
trace_hash: trace_hash.into(),
summary: summary.into(),
created_at: Utc::now(),
}
}
pub fn is_root(&self) -> bool {
self.parent_hash.is_none() && self.parent_hashes.is_empty()
}
pub fn is_merge(&self) -> bool {
self.parent_hashes.len() > 1
}
pub fn parents(&self) -> Vec<&str> {
if !self.parent_hashes.is_empty() {
self.parent_hashes.iter().map(|s| s.as_str()).collect()
} else if let Some(ref p) = self.parent_hash {
vec![p.as_str()]
} else {
vec![]
}
}
pub fn first_parent(&self) -> Option<&str> {
if !self.parent_hashes.is_empty() {
Some(&self.parent_hashes[0])
} else {
self.parent_hash.as_deref()
}
}
pub fn short_hash(&self) -> &str {
if self.git_hash.len() >= 7 {
&self.git_hash[..7]
} else {
&self.git_hash
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_neural_commit_creation() {
let commit = NeuralCommit::new(
"abc123def456",
None,
"test@example.com",
"roadmap_hash",
"trace_hash",
"Intent: Fix bug. Plan: Add validation.",
);
assert_eq!(commit.git_hash, "abc123def456");
assert!(commit.is_root());
assert_eq!(commit.short_hash(), "abc123d");
}
#[test]
fn test_neural_commit_with_parent() {
let commit = NeuralCommit::new(
"abc123",
Some("parent123".to_string()),
"test@example.com",
"roadmap",
"trace",
"Summary",
);
assert!(!commit.is_root());
assert_eq!(commit.parent_hash, Some("parent123".to_string()));
}
#[test]
fn test_neural_commit_serialization() {
let commit = NeuralCommit::new(
"abc123",
None,
"test@example.com",
"roadmap",
"trace",
"Summary",
);
let json = serde_json::to_string(&commit).unwrap();
assert!(json.contains("\"git_hash\":\"abc123\""));
assert!(!json.contains("parent_hash")); }
}