1use chrono::{DateTime, Utc};
11use serde::{Deserialize, Serialize};
12use std::collections::HashMap;
13use uuid::Uuid;
14
15use super::task_tree_manager::TaskTreeManager;
16use super::types::*;
17
18#[derive(Debug, Clone, Serialize, Deserialize)]
24pub struct CheckpointInfo {
25 pub id: String,
26 #[serde(rename = "type")]
27 pub checkpoint_type: CheckpointType,
28 pub name: String,
29 pub description: Option<String>,
30 pub timestamp: DateTime<Utc>,
31 pub task_id: Option<String>,
32 pub task_name: Option<String>,
33 pub task_path: Option<Vec<String>>,
34 pub status: String,
35 pub can_restore: bool,
36 pub has_code_changes: bool,
37 pub code_changes_count: usize,
38}
39
40#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
42#[serde(rename_all = "lowercase")]
43pub enum CheckpointType {
44 Task,
45 Global,
46}
47
48#[derive(Debug, Clone, Serialize, Deserialize)]
50pub struct TimelineView {
51 pub checkpoints: Vec<CheckpointInfo>,
52 pub current_position: Option<String>,
53 pub branches: Vec<BranchInfo>,
54}
55
56#[derive(Debug, Clone, Serialize, Deserialize)]
58pub struct BranchInfo {
59 pub id: String,
60 pub name: String,
61 pub from_checkpoint: String,
62 pub created_at: DateTime<Utc>,
63 pub status: BranchStatus,
64}
65
66#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
68#[serde(rename_all = "lowercase")]
69pub enum BranchStatus {
70 Active,
71 Merged,
72 Abandoned,
73}
74
75#[derive(Debug, Clone, Serialize, Deserialize)]
77pub struct DiffInfo {
78 pub file_path: String,
79 #[serde(rename = "type")]
80 pub diff_type: DiffType,
81 pub before_content: Option<String>,
82 pub after_content: Option<String>,
83 pub additions: usize,
84 pub deletions: usize,
85}
86
87#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
89#[serde(rename_all = "lowercase")]
90pub enum DiffType {
91 Added,
92 Modified,
93 Deleted,
94}
95
96#[derive(Debug, Clone, Serialize, Deserialize)]
98pub struct CompareResult {
99 pub from_checkpoint: String,
100 pub to_checkpoint: String,
101 pub task_changes: Vec<TaskChange>,
102 pub code_changes: Vec<DiffInfo>,
103 pub time_elapsed: i64,
105}
106
107#[derive(Debug, Clone, Serialize, Deserialize)]
109pub struct TaskChange {
110 pub task_id: String,
111 pub task_name: String,
112 pub from_status: String,
113 pub to_status: String,
114 pub iterations: Option<u32>,
115}
116
117#[derive(Debug, Clone, Serialize, Deserialize)]
119pub struct CheckpointDetails {
120 pub checkpoint: CheckpointInfo,
121 pub code_snapshots: Vec<CodeSnapshot>,
122 pub test_result: Option<TestResult>,
123}
124
125pub struct TimeTravelManager {
131 branches: HashMap<String, BranchInfo>,
132 current_branch: String,
133}
134
135impl Default for TimeTravelManager {
136 fn default() -> Self {
137 Self::new()
138 }
139}
140
141impl TimeTravelManager {
142 pub fn new() -> Self {
144 Self {
145 branches: HashMap::new(),
146 current_branch: "main".to_string(),
147 }
148 }
149
150 pub fn get_all_checkpoints(&self, tree: &TaskTree) -> Vec<CheckpointInfo> {
156 let mut checkpoints = Vec::new();
157
158 for gc in &tree.global_checkpoints {
160 checkpoints.push(CheckpointInfo {
161 id: gc.id.clone(),
162 checkpoint_type: CheckpointType::Global,
163 name: gc.name.clone(),
164 description: gc.description.clone(),
165 timestamp: gc.timestamp,
166 task_id: None,
167 task_name: None,
168 task_path: None,
169 status: "全局快照".to_string(),
170 can_restore: gc.can_restore,
171 has_code_changes: !gc.file_changes.is_empty(),
172 code_changes_count: gc.file_changes.len(),
173 });
174 }
175
176 self.collect_task_checkpoints(&tree.root, &mut checkpoints, Vec::new());
178
179 checkpoints.sort_by(|a, b| b.timestamp.cmp(&a.timestamp));
181
182 checkpoints
183 }
184
185 fn collect_task_checkpoints(
187 &self,
188 node: &TaskNode,
189 result: &mut Vec<CheckpointInfo>,
190 path: Vec<String>,
191 ) {
192 let mut current_path = path;
193 current_path.push(node.name.clone());
194
195 for cp in &node.checkpoints {
196 result.push(CheckpointInfo {
197 id: cp.id.clone(),
198 checkpoint_type: CheckpointType::Task,
199 name: cp.name.clone(),
200 description: cp.description.clone(),
201 timestamp: cp.timestamp,
202 task_id: Some(node.id.clone()),
203 task_name: Some(node.name.clone()),
204 task_path: Some(current_path.clone()),
205 status: format!("{:?}", cp.task_status),
206 can_restore: cp.can_restore,
207 has_code_changes: !cp.code_snapshot.is_empty(),
208 code_changes_count: cp.code_snapshot.len(),
209 });
210 }
211
212 for child in &node.children {
213 self.collect_task_checkpoints(child, result, current_path.clone());
214 }
215 }
216
217 pub fn get_timeline_view(&self, tree: &TaskTree) -> TimelineView {
219 let checkpoints = self.get_all_checkpoints(tree);
220 let branches: Vec<BranchInfo> = self
221 .branches
222 .values()
223 .filter(|b| b.status == BranchStatus::Active)
224 .cloned()
225 .collect();
226
227 TimelineView {
228 current_position: checkpoints.first().map(|c| c.id.clone()),
229 checkpoints,
230 branches,
231 }
232 }
233
234 pub async fn create_manual_checkpoint(
240 &self,
241 tree_manager: &mut TaskTreeManager,
242 tree_id: &str,
243 name: String,
244 description: Option<String>,
245 task_id: Option<&str>,
246 ) -> Result<CheckpointInfo, String> {
247 if let Some(tid) = task_id {
248 let checkpoint = tree_manager
250 .create_task_checkpoint(tree_id, tid, name.clone(), description.clone())
251 .await
252 .map_err(|e| e.to_string())?;
253
254 let tree = tree_manager
255 .get_task_tree(tree_id)
256 .await
257 .ok_or_else(|| format!("任务树 {} 不存在", tree_id))?;
258 let task = TaskTreeManager::find_task(&tree.root, tid);
259
260 Ok(CheckpointInfo {
261 id: checkpoint.id,
262 checkpoint_type: CheckpointType::Task,
263 name: checkpoint.name,
264 description: checkpoint.description,
265 timestamp: checkpoint.timestamp,
266 task_id: Some(tid.to_string()),
267 task_name: task.map(|t| t.name.clone()),
268 task_path: None,
269 status: format!("{:?}", checkpoint.task_status),
270 can_restore: checkpoint.can_restore,
271 has_code_changes: !checkpoint.code_snapshot.is_empty(),
272 code_changes_count: checkpoint.code_snapshot.len(),
273 })
274 } else {
275 let checkpoint = tree_manager
277 .create_global_checkpoint(tree_id, name.clone(), description.clone())
278 .await
279 .map_err(|e| e.to_string())?;
280
281 Ok(CheckpointInfo {
282 id: checkpoint.id,
283 checkpoint_type: CheckpointType::Global,
284 name: checkpoint.name,
285 description: checkpoint.description,
286 timestamp: checkpoint.timestamp,
287 task_id: None,
288 task_name: None,
289 task_path: None,
290 status: "全局快照".to_string(),
291 can_restore: checkpoint.can_restore,
292 has_code_changes: !checkpoint.file_changes.is_empty(),
293 code_changes_count: checkpoint.file_changes.len(),
294 })
295 }
296 }
297
298 pub async fn rollback(
300 &self,
301 tree_manager: &mut TaskTreeManager,
302 tree_id: &str,
303 checkpoint_id: &str,
304 ) -> Result<(), String> {
305 let tree = tree_manager
306 .get_task_tree(tree_id)
307 .await
308 .ok_or_else(|| format!("任务树 {} 不存在", tree_id))?;
309
310 let checkpoints = self.get_all_checkpoints(&tree);
311 let checkpoint = checkpoints
312 .iter()
313 .find(|c| c.id == checkpoint_id)
314 .ok_or_else(|| format!("检查点 {} 不存在", checkpoint_id))?;
315
316 if !checkpoint.can_restore {
317 return Err(format!("检查点 {} 无法恢复", checkpoint_id));
318 }
319
320 match checkpoint.checkpoint_type {
321 CheckpointType::Global => {
322 tree_manager
323 .rollback_to_global_checkpoint(tree_id, checkpoint_id)
324 .await
325 .map_err(|e| e.to_string())?;
326 Ok(())
327 }
328 CheckpointType::Task => {
329 let task_id = checkpoint
330 .task_id
331 .as_ref()
332 .ok_or_else(|| "任务检查点缺少 task_id".to_string())?;
333 tree_manager
334 .rollback_to_checkpoint(tree_id, task_id, checkpoint_id)
335 .await
336 .map_err(|e| e.to_string())?;
337 Ok(())
338 }
339 }
340 }
341
342 pub fn preview_rollback(
344 &self,
345 tree: &TaskTree,
346 checkpoint_id: &str,
347 ) -> Result<CompareResult, String> {
348 let checkpoints = self.get_all_checkpoints(tree);
349 let _target = checkpoints
350 .iter()
351 .find(|c| c.id == checkpoint_id)
352 .ok_or_else(|| format!("检查点 {} 不存在", checkpoint_id))?;
353
354 let current = checkpoints
355 .first()
356 .ok_or_else(|| "没有当前检查点".to_string())?;
357
358 self.compare_checkpoints(tree, checkpoint_id, ¤t.id)
359 }
360
361 pub async fn create_branch(
367 &mut self,
368 tree_manager: &mut TaskTreeManager,
369 tree_id: &str,
370 checkpoint_id: &str,
371 branch_name: String,
372 ) -> Result<BranchInfo, String> {
373 let tree = tree_manager
374 .get_task_tree(tree_id)
375 .await
376 .ok_or_else(|| format!("任务树 {} 不存在", tree_id))?;
377
378 let checkpoints = self.get_all_checkpoints(&tree);
379 let _checkpoint = checkpoints
380 .iter()
381 .find(|c| c.id == checkpoint_id)
382 .ok_or_else(|| format!("检查点 {} 不存在", checkpoint_id))?;
383
384 let branch = BranchInfo {
385 id: Uuid::new_v4().to_string(),
386 name: branch_name,
387 from_checkpoint: checkpoint_id.to_string(),
388 created_at: Utc::now(),
389 status: BranchStatus::Active,
390 };
391
392 self.rollback(tree_manager, tree_id, checkpoint_id).await?;
394
395 self.branches.insert(branch.id.clone(), branch.clone());
396
397 Ok(branch)
398 }
399
400 pub fn switch_branch(&mut self, branch_id: &str) -> Result<(), String> {
402 if !self.branches.contains_key(branch_id) {
403 return Err(format!("分支 {} 不存在", branch_id));
404 }
405
406 self.current_branch = branch_id.to_string();
407 Ok(())
408 }
409
410 pub fn get_current_branch(&self) -> &str {
412 &self.current_branch
413 }
414
415 pub fn get_branches(&self) -> Vec<&BranchInfo> {
417 self.branches.values().collect()
418 }
419
420 pub fn compare_checkpoints(
426 &self,
427 tree: &TaskTree,
428 from_checkpoint_id: &str,
429 to_checkpoint_id: &str,
430 ) -> Result<CompareResult, String> {
431 let checkpoints = self.get_all_checkpoints(tree);
432
433 let from = checkpoints
434 .iter()
435 .find(|c| c.id == from_checkpoint_id)
436 .ok_or_else(|| format!("检查点 {} 不存在", from_checkpoint_id))?;
437
438 let to = checkpoints
439 .iter()
440 .find(|c| c.id == to_checkpoint_id)
441 .ok_or_else(|| format!("检查点 {} 不存在", to_checkpoint_id))?;
442
443 let time_elapsed = to.timestamp.timestamp_millis() - from.timestamp.timestamp_millis();
444
445 Ok(CompareResult {
447 from_checkpoint: from_checkpoint_id.to_string(),
448 to_checkpoint: to_checkpoint_id.to_string(),
449 task_changes: Vec::new(),
450 code_changes: Vec::new(),
451 time_elapsed,
452 })
453 }
454
455 pub fn get_checkpoint_details(
457 &self,
458 tree: &TaskTree,
459 checkpoint_id: &str,
460 ) -> Option<CheckpointDetails> {
461 if let Some(gc) = tree
463 .global_checkpoints
464 .iter()
465 .find(|c| c.id == checkpoint_id)
466 {
467 return Some(CheckpointDetails {
468 checkpoint: CheckpointInfo {
469 id: gc.id.clone(),
470 checkpoint_type: CheckpointType::Global,
471 name: gc.name.clone(),
472 description: gc.description.clone(),
473 timestamp: gc.timestamp,
474 task_id: None,
475 task_name: None,
476 task_path: None,
477 status: "全局快照".to_string(),
478 can_restore: gc.can_restore,
479 has_code_changes: !gc.file_changes.is_empty(),
480 code_changes_count: gc.file_changes.len(),
481 },
482 code_snapshots: gc
483 .file_changes
484 .iter()
485 .map(|fc| CodeSnapshot {
486 file_path: fc.file_path.clone(),
487 content: fc.new_content.clone().unwrap_or_default(),
488 hash: String::new(),
489 })
490 .collect(),
491 test_result: None,
492 });
493 }
494
495 self.find_task_checkpoint(&tree.root, checkpoint_id)
497 }
498
499 fn find_task_checkpoint(
501 &self,
502 node: &TaskNode,
503 checkpoint_id: &str,
504 ) -> Option<CheckpointDetails> {
505 for cp in &node.checkpoints {
506 if cp.id == checkpoint_id {
507 return Some(CheckpointDetails {
508 checkpoint: CheckpointInfo {
509 id: cp.id.clone(),
510 checkpoint_type: CheckpointType::Task,
511 name: cp.name.clone(),
512 description: cp.description.clone(),
513 timestamp: cp.timestamp,
514 task_id: Some(node.id.clone()),
515 task_name: Some(node.name.clone()),
516 task_path: None,
517 status: format!("{:?}", cp.task_status),
518 can_restore: cp.can_restore,
519 has_code_changes: !cp.code_snapshot.is_empty(),
520 code_changes_count: cp.code_snapshot.len(),
521 },
522 code_snapshots: cp.code_snapshot.clone(),
523 test_result: cp.test_result.clone(),
524 });
525 }
526 }
527
528 for child in &node.children {
529 if let Some(details) = self.find_task_checkpoint(child, checkpoint_id) {
530 return Some(details);
531 }
532 }
533
534 None
535 }
536
537 pub fn generate_checkpoint_tree(&self, tree: &TaskTree) -> String {
543 let checkpoints = self.get_all_checkpoints(tree);
544 let mut lines = Vec::new();
545
546 lines.push("检查点时间线".to_string());
547 lines.push("============".to_string());
548 lines.push(String::new());
549
550 for (i, cp) in checkpoints.iter().enumerate() {
551 let is_last = i == checkpoints.len() - 1;
552 let prefix = if is_last { "└── " } else { "├── " };
553 let type_icon = if cp.checkpoint_type == CheckpointType::Global {
554 "🌍"
555 } else {
556 "📌"
557 };
558 let status_icon = if cp.can_restore { "✅" } else { "⚠️" };
559
560 lines.push(format!(
561 "{}{} {} {}",
562 prefix, type_icon, cp.name, status_icon
563 ));
564
565 let indent = if is_last { " " } else { "│ " };
566 lines.push(format!(
567 "{}📅 {}",
568 indent,
569 cp.timestamp.format("%Y-%m-%d %H:%M:%S")
570 ));
571
572 if let Some(ref task_name) = cp.task_name {
573 lines.push(format!("{}📁 {}", indent, task_name));
574 }
575
576 lines.push(format!("{}💾 {} 个文件变更", indent, cp.code_changes_count));
577 lines.push(indent.to_string());
578 }
579
580 lines.join("\n")
581 }
582
583 pub fn generate_timeline_ascii(&self, tree: &TaskTree) -> String {
585 let checkpoints = self.get_all_checkpoints(tree);
586 let mut lines = Vec::new();
587
588 lines.push(String::new());
589 lines.push("时间线 →".to_string());
590 lines.push(String::new());
591
592 let mut timeline = "○".to_string();
594 for _ in 0..checkpoints.len().saturating_sub(1) {
595 timeline.push_str("───●");
596 }
597 timeline.push_str("───◉ (当前)");
598 lines.push(timeline);
599
600 let mut labels = String::new();
602 for cp in checkpoints.iter().rev() {
603 let short_name: String = cp.name.chars().take(10).collect();
604 let display_name = if cp.name.chars().count() > 10 {
605 format!("{}..", short_name)
606 } else {
607 short_name
608 };
609 labels.push_str(&format!("{:<15}", display_name));
610 }
611 lines.push(labels);
612
613 lines.join("\n")
614 }
615}
616
617#[cfg(test)]
618mod tests {
619 use super::*;
620
621 #[test]
622 fn test_time_travel_manager_creation() {
623 let manager = TimeTravelManager::new();
624 assert_eq!(manager.get_current_branch(), "main");
625 assert!(manager.get_branches().is_empty());
626 }
627
628 #[test]
629 fn test_checkpoint_type_serialization() {
630 let task_type = CheckpointType::Task;
631 let global_type = CheckpointType::Global;
632
633 let task_json = serde_json::to_string(&task_type).unwrap();
634 let global_json = serde_json::to_string(&global_type).unwrap();
635
636 assert_eq!(task_json, "\"task\"");
637 assert_eq!(global_json, "\"global\"");
638 }
639}