1use std::path::{Path, PathBuf};
6use std::fs;
7use chrono::{DateTime, Utc};
8use serde::{Serialize, Deserialize};
9use anyhow::Result;
10use uuid::Uuid;
11use std::collections::HashMap;
12
13use crate::vcs::{ObjectId, Pile, FileChange, ChangeType, Repository, Tree};
14use crate::vcs::objects::EntryType;
15
16#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
18pub struct ShoveId(String);
19
20impl ShoveId {
21 pub fn new() -> Self {
23 Self(Uuid::new_v4().to_string())
24 }
25
26 pub fn from_str(s: &str) -> Result<Self> {
28 Ok(Self(s.to_string()))
29 }
30
31 pub fn as_str(&self) -> &str {
33 &self.0
34 }
35}
36
37#[derive(Debug, Clone, Serialize, Deserialize)]
39pub struct Author {
40 pub name: String,
41 pub email: String,
42 pub timestamp: DateTime<Utc>,
43}
44
45#[derive(Debug, Serialize, Deserialize)]
47pub struct Shove {
48 pub id: ShoveId,
50
51 pub parent_ids: Vec<ShoveId>,
53
54 pub author: Author,
56
57 pub timestamp: DateTime<Utc>,
59
60 pub message: String,
62
63 pub root_tree_id: ObjectId,
65}
66
67impl Shove {
68 pub fn new(
70 pile: &Pile,
71 parent_ids: Vec<ShoveId>,
72 author: Author,
73 message: &str,
74 root_tree_id: ObjectId,
75 ) -> Self {
76 Self {
77 id: ShoveId::new(),
78 parent_ids,
79 author,
80 timestamp: Utc::now(),
81 message: message.to_string(),
82 root_tree_id,
83 }
84 }
85
86 pub fn load(path: &Path) -> Result<Self> {
88 let content = fs::read_to_string(path)?;
89 let shove: Self = toml::from_str(&content)?;
90 Ok(shove)
91 }
92
93 pub fn save(&self, path: &Path) -> Result<()> {
95 let content = toml::to_string_pretty(self)?;
96 fs::write(path, content)?;
97 Ok(())
98 }
99
100 pub fn get_changes(&self, repo: &Repository) -> Result<Vec<FileChange>> {
102 let mut changes = Vec::new();
103
104 if self.parent_ids.is_empty() {
106 let tree_path = repo.path.join(".pocket").join("objects").join(self.root_tree_id.as_str());
108 let tree_content = fs::read_to_string(&tree_path)?;
109 let tree: Tree = toml::from_str(&tree_content)?;
110
111 for entry in tree.entries {
113 if entry.entry_type == EntryType::File {
114 changes.push(FileChange {
115 path: PathBuf::from(&entry.name),
116 change_type: ChangeType::Added,
117 old_id: None,
118 new_id: Some(entry.id),
119 });
120 }
121 }
122
123 return Ok(changes);
124 }
125
126 let parent_id = &self.parent_ids[0]; let parent_path = repo.path.join(".pocket").join("shoves").join(format!("{}.toml", parent_id.as_str()));
129 let parent_content = fs::read_to_string(&parent_path)?;
130 let parent: Shove = toml::from_str(&parent_content)?;
131
132 let parent_tree_path = repo.path.join(".pocket").join("objects").join(parent.root_tree_id.as_str());
134 let current_tree_path = repo.path.join(".pocket").join("objects").join(self.root_tree_id.as_str());
135
136 let parent_tree_content = fs::read_to_string(&parent_tree_path)?;
137 let current_tree_content = fs::read_to_string(¤t_tree_path)?;
138
139 let parent_tree: Tree = toml::from_str(&parent_tree_content)?;
140 let current_tree: Tree = toml::from_str(¤t_tree_content)?;
141
142 let mut parent_entries = HashMap::new();
144 for entry in parent_tree.entries {
145 parent_entries.insert(entry.name.clone(), entry);
146 }
147
148 for entry in ¤t_tree.entries {
150 if entry.entry_type == EntryType::File {
151 if let Some(parent_entry) = parent_entries.get(&entry.name) {
152 if parent_entry.id != entry.id {
154 changes.push(FileChange {
155 path: PathBuf::from(&entry.name),
156 change_type: ChangeType::Modified,
157 old_id: Some(parent_entry.id.clone()),
158 new_id: Some(entry.id.clone()),
159 });
160 }
161 } else {
162 changes.push(FileChange {
164 path: PathBuf::from(&entry.name),
165 change_type: ChangeType::Added,
166 old_id: None,
167 new_id: Some(entry.id.clone()),
168 });
169 }
170 }
171 }
172
173 for (name, entry) in parent_entries {
175 if entry.entry_type == EntryType::File {
176 let exists = current_tree.entries.iter().any(|e| e.name == name);
177 if !exists {
178 changes.push(FileChange {
179 path: PathBuf::from(&name),
180 change_type: ChangeType::Deleted,
181 old_id: Some(entry.id),
182 new_id: None,
183 });
184 }
185 }
186 }
187
188 Ok(changes)
189 }
190
191 }