1use chrono::{DateTime, Utc};
2use serde::{Deserialize, Serialize};
3use std::collections::HashMap;
4use std::path::PathBuf;
5use uuid::Uuid;
6
7#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
8pub enum ChangeType {
9 Create,
10 Modify,
11 Delete,
12 Rename,
13}
14
15impl ChangeType {
16 pub fn as_str(&self) -> &str {
17 match self {
18 ChangeType::Create => "create",
19 ChangeType::Modify => "modify",
20 ChangeType::Delete => "delete",
21 ChangeType::Rename => "rename",
22 }
23 }
24
25 pub fn parse(s: &str) -> Option<Self> {
26 match s {
27 "create" => Some(ChangeType::Create),
28 "modify" => Some(ChangeType::Modify),
29 "delete" => Some(ChangeType::Delete),
30 "rename" => Some(ChangeType::Rename),
31 _ => None,
32 }
33 }
34}
35
36#[derive(Debug, Clone, Serialize, Deserialize)]
37pub struct Change {
38 pub id: Uuid,
39 pub timestamp: DateTime<Utc>,
40 pub change_type: ChangeType,
41 pub path: PathBuf,
42 pub old_path: Option<PathBuf>,
43 pub content_before: Option<Vec<u8>>,
44 pub content_after: Option<Vec<u8>>,
45 pub content_hash_before: Option<String>,
46 pub content_hash_after: Option<String>,
47 pub agent_id: Option<String>,
48 pub metadata: HashMap<String, String>,
49 pub session_id: Uuid,
50}
51
52impl Change {
53 pub fn new(change_type: ChangeType, path: PathBuf, session_id: Uuid) -> Self {
54 Self {
55 id: Uuid::new_v4(),
56 timestamp: Utc::now(),
57 change_type,
58 path,
59 old_path: None,
60 content_before: None,
61 content_after: None,
62 content_hash_before: None,
63 content_hash_after: None,
64 agent_id: None,
65 metadata: HashMap::new(),
66 session_id,
67 }
68 }
69
70 pub fn with_content_before(mut self, content: Vec<u8>) -> Self {
71 self.content_hash_before = Some(Self::hash_content(&content));
72 self.content_before = Some(content);
73 self
74 }
75
76 pub fn with_content_after(mut self, content: Vec<u8>) -> Self {
77 self.content_hash_after = Some(Self::hash_content(&content));
78 self.content_after = Some(content);
79 self
80 }
81
82 pub fn with_agent_id(mut self, agent_id: String) -> Self {
83 self.agent_id = Some(agent_id);
84 self
85 }
86
87 pub fn with_metadata(mut self, key: String, value: String) -> Self {
88 self.metadata.insert(key, value);
89 self
90 }
91
92 pub fn with_old_path(mut self, old_path: PathBuf) -> Self {
93 self.old_path = Some(old_path);
94 self
95 }
96
97 fn hash_content(content: &[u8]) -> String {
98 use sha2::{Digest, Sha256};
99 let mut hasher = Sha256::new();
100 hasher.update(content);
101 hex::encode(hasher.finalize())
102 }
103}
104
105#[derive(Debug, Clone, Serialize, Deserialize)]
106pub struct Commit {
107 pub id: Uuid,
108 pub parent: Option<Uuid>,
109 pub timestamp: DateTime<Utc>,
110 pub message: String,
111 pub agent_id: String,
112 pub changes: Vec<Uuid>,
113 pub session_id: Uuid,
114 pub metadata: HashMap<String, String>,
115}
116
117impl Commit {
118 pub fn new(message: String, agent_id: String, changes: Vec<Uuid>, session_id: Uuid) -> Self {
119 Self {
120 id: Uuid::new_v4(),
121 parent: None,
122 timestamp: Utc::now(),
123 message,
124 agent_id,
125 changes,
126 session_id,
127 metadata: HashMap::new(),
128 }
129 }
130
131 pub fn with_parent(mut self, parent: Uuid) -> Self {
132 self.parent = Some(parent);
133 self
134 }
135
136 pub fn with_metadata(mut self, key: String, value: String) -> Self {
137 self.metadata.insert(key, value);
138 self
139 }
140}
141
142#[derive(Debug, Clone, Serialize, Deserialize)]
143pub struct Session {
144 pub id: Uuid,
145 pub root_path: PathBuf,
146 pub started: DateTime<Utc>,
147 pub ended: Option<DateTime<Utc>>,
148 pub active: bool,
149 pub ignore_patterns: Vec<String>,
150}
151
152impl Session {
153 pub fn new(root_path: PathBuf) -> Self {
154 Self {
155 id: Uuid::new_v4(),
156 root_path,
157 started: Utc::now(),
158 ended: None,
159 active: true,
160 ignore_patterns: vec![
161 ".git".to_string(),
162 "target".to_string(),
163 "node_modules".to_string(),
164 ".gitent".to_string(),
165 ],
166 }
167 }
168
169 pub fn with_ignore_patterns(mut self, patterns: Vec<String>) -> Self {
170 self.ignore_patterns = patterns;
171 self
172 }
173
174 pub fn end(&mut self) {
175 self.active = false;
176 self.ended = Some(Utc::now());
177 }
178}
179
180#[derive(Debug, Clone, Serialize, Deserialize)]
181pub struct CommitInfo {
182 pub commit: Commit,
183 pub change_count: usize,
184 pub files_affected: Vec<PathBuf>,
185}
186
187#[cfg(test)]
188mod tests {
189 use super::*;
190
191 #[test]
192 fn test_change_creation() {
193 let session_id = Uuid::new_v4();
194 let change = Change::new(ChangeType::Create, PathBuf::from("test.txt"), session_id);
195
196 assert_eq!(change.change_type, ChangeType::Create);
197 assert_eq!(change.path, PathBuf::from("test.txt"));
198 assert_eq!(change.session_id, session_id);
199 }
200
201 #[test]
202 fn test_change_with_content() {
203 let session_id = Uuid::new_v4();
204 let content = b"Hello, World!".to_vec();
205 let change = Change::new(ChangeType::Create, PathBuf::from("test.txt"), session_id)
206 .with_content_after(content.clone());
207
208 assert!(change.content_after.is_some());
209 assert!(change.content_hash_after.is_some());
210 assert_eq!(change.content_after.unwrap(), content);
211 }
212
213 #[test]
214 fn test_commit_creation() {
215 let session_id = Uuid::new_v4();
216 let change_ids = vec![Uuid::new_v4(), Uuid::new_v4()];
217 let commit = Commit::new(
218 "Initial commit".to_string(),
219 "test-agent".to_string(),
220 change_ids.clone(),
221 session_id,
222 );
223
224 assert_eq!(commit.message, "Initial commit");
225 assert_eq!(commit.agent_id, "test-agent");
226 assert_eq!(commit.changes, change_ids);
227 }
228
229 #[test]
230 fn test_session_creation() {
231 let session = Session::new(PathBuf::from("/test/path"));
232
233 assert_eq!(session.root_path, PathBuf::from("/test/path"));
234 assert!(session.active);
235 assert!(session.ended.is_none());
236 assert!(!session.ignore_patterns.is_empty());
237 }
238}