jujube_lib/
local_store.rs1use std::fmt::Debug;
16use std::fs;
17use std::fs::File;
18use std::io::Write;
19use std::io::{ErrorKind, Read};
20use std::path::PathBuf;
21
22use blake2::{Blake2b, Digest};
23use protobuf::{Message, ProtobufError};
24use tempfile::{NamedTempFile, PersistError};
25
26use crate::repo_path::{DirRepoPath, FileRepoPath};
27use crate::store::{
28 ChangeId, Commit, CommitId, Conflict, ConflictId, ConflictPart, FileId, MillisSinceEpoch,
29 Signature, Store, StoreError, StoreResult, SymlinkId, Timestamp, Tree, TreeId, TreeValue,
30};
31
32impl From<std::io::Error> for StoreError {
33 fn from(err: std::io::Error) -> Self {
34 StoreError::Other(err.to_string())
35 }
36}
37
38impl From<PersistError> for StoreError {
39 fn from(err: PersistError) -> Self {
40 StoreError::Other(err.to_string())
41 }
42}
43
44impl From<ProtobufError> for StoreError {
45 fn from(err: ProtobufError) -> Self {
46 StoreError::Other(err.to_string())
47 }
48}
49
50#[derive(Debug)]
51pub struct LocalStore {
52 path: PathBuf,
53 empty_tree_id: TreeId,
54}
55
56impl LocalStore {
57 pub fn init(store_path: PathBuf) -> Self {
58 fs::create_dir(store_path.join("commits")).unwrap();
59 fs::create_dir(store_path.join("trees")).unwrap();
60 fs::create_dir(store_path.join("files")).unwrap();
61 fs::create_dir(store_path.join("symlinks")).unwrap();
62 fs::create_dir(store_path.join("conflicts")).unwrap();
63 let store = Self::load(store_path);
64 let empty_tree_id = store
65 .write_tree(&DirRepoPath::root(), &Tree::default())
66 .unwrap();
67 assert_eq!(empty_tree_id, store.empty_tree_id);
68 store
69 }
70
71 pub fn load(store_path: PathBuf) -> Self {
72 let empty_tree_id = TreeId(hex::decode("786a02f742015903c6c6fd852552d272912f4740e15847618a86e217f71f5419d25e1031afee585313896444934eb04b903a685b1448b755d56f701afe9be2ce").unwrap());
73 LocalStore {
74 path: store_path,
75 empty_tree_id,
76 }
77 }
78
79 fn file_path(&self, id: &FileId) -> PathBuf {
80 self.path.join("files").join(id.hex())
81 }
82
83 fn symlink_path(&self, id: &SymlinkId) -> PathBuf {
84 self.path.join("symlinks").join(id.hex())
85 }
86
87 fn tree_path(&self, id: &TreeId) -> PathBuf {
88 self.path.join("trees").join(id.hex())
89 }
90
91 fn commit_path(&self, id: &CommitId) -> PathBuf {
92 self.path.join("commits").join(id.hex())
93 }
94
95 fn conflict_path(&self, id: &ConflictId) -> PathBuf {
96 self.path.join("conflicts").join(id.hex())
97 }
98}
99
100fn not_found_to_store_error(err: std::io::Error) -> StoreError {
101 if err.kind() == ErrorKind::NotFound {
102 StoreError::NotFound
103 } else {
104 StoreError::from(err)
105 }
106}
107
108impl Store for LocalStore {
109 fn hash_length(&self) -> usize {
110 64
111 }
112
113 fn git_repo(&self) -> Option<git2::Repository> {
114 None
115 }
116
117 fn read_file(&self, _path: &FileRepoPath, id: &FileId) -> StoreResult<Box<dyn Read>> {
118 let path = self.file_path(&id);
119 let file = File::open(path).map_err(not_found_to_store_error)?;
120 Ok(Box::new(zstd::Decoder::new(file)?))
121 }
122
123 fn write_file(&self, _path: &FileRepoPath, contents: &mut dyn Read) -> StoreResult<FileId> {
124 let temp_file = NamedTempFile::new_in(&self.path)?;
125 let mut encoder = zstd::Encoder::new(temp_file.as_file(), 0)?;
126 let mut hasher = Blake2b::new();
127 loop {
128 let mut buff: Vec<u8> = Vec::with_capacity(1 << 14);
129 let bytes_read;
130 unsafe {
131 buff.set_len(1 << 14);
132 bytes_read = contents.read(&mut buff)?;
133 buff.set_len(bytes_read);
134 }
135 if bytes_read == 0 {
136 break;
137 }
138 encoder.write_all(&buff)?;
139 hasher.update(&buff);
140 }
141 encoder.finish()?;
142 let id = FileId(hasher.finalize().to_vec());
143
144 temp_file.persist(self.file_path(&id))?;
145 Ok(id)
146 }
147
148 fn read_symlink(&self, _path: &FileRepoPath, id: &SymlinkId) -> Result<String, StoreError> {
149 let path = self.symlink_path(&id);
150 let mut file = File::open(path).map_err(not_found_to_store_error)?;
151 let mut target = String::new();
152 file.read_to_string(&mut target).unwrap();
153 Ok(target)
154 }
155
156 fn write_symlink(&self, _path: &FileRepoPath, target: &str) -> Result<SymlinkId, StoreError> {
157 let mut temp_file = NamedTempFile::new_in(&self.path)?;
158 temp_file.write_all(target.as_bytes()).unwrap();
159 let mut hasher = Blake2b::new();
160 hasher.update(&target.as_bytes());
161 let id = SymlinkId(hasher.finalize().to_vec());
162
163 temp_file.persist(self.symlink_path(&id))?;
164 Ok(id)
165 }
166
167 fn empty_tree_id(&self) -> &TreeId {
168 &self.empty_tree_id
169 }
170
171 fn read_tree(&self, _path: &DirRepoPath, id: &TreeId) -> StoreResult<Tree> {
172 let path = self.tree_path(&id);
173 let mut file = File::open(path).map_err(not_found_to_store_error)?;
174
175 let proto: crate::protos::store::Tree = protobuf::parse_from_reader(&mut file)?;
176 Ok(tree_from_proto(&proto))
177 }
178
179 fn write_tree(&self, _path: &DirRepoPath, tree: &Tree) -> StoreResult<TreeId> {
180 let temp_file = NamedTempFile::new_in(&self.path)?;
181
182 let proto = tree_to_proto(tree);
183 let mut proto_bytes: Vec<u8> = Vec::new();
184 proto.write_to_writer(&mut proto_bytes)?;
185
186 temp_file.as_file().write_all(&proto_bytes)?;
187
188 let id = TreeId(Blake2b::digest(&proto_bytes).to_vec());
189
190 temp_file.persist(self.tree_path(&id))?;
191 Ok(id)
192 }
193
194 fn read_commit(&self, id: &CommitId) -> StoreResult<Commit> {
195 let path = self.commit_path(&id);
196 let mut file = File::open(path).map_err(not_found_to_store_error)?;
197
198 let proto: crate::protos::store::Commit = protobuf::parse_from_reader(&mut file)?;
199 Ok(commit_from_proto(&proto))
200 }
201
202 fn write_commit(&self, commit: &Commit) -> StoreResult<CommitId> {
203 let temp_file = NamedTempFile::new_in(&self.path)?;
204
205 let proto = commit_to_proto(commit);
206 let mut proto_bytes: Vec<u8> = Vec::new();
207 proto.write_to_writer(&mut proto_bytes)?;
208
209 temp_file.as_file().write_all(&proto_bytes)?;
210
211 let id = CommitId(Blake2b::digest(&proto_bytes).to_vec());
212
213 temp_file.persist(self.commit_path(&id))?;
214 Ok(id)
215 }
216
217 fn read_conflict(&self, id: &ConflictId) -> StoreResult<Conflict> {
218 let path = self.conflict_path(&id);
219 let mut file = File::open(path).map_err(not_found_to_store_error)?;
220
221 let proto: crate::protos::store::Conflict = protobuf::parse_from_reader(&mut file)?;
222 Ok(conflict_from_proto(&proto))
223 }
224
225 fn write_conflict(&self, conflict: &Conflict) -> StoreResult<ConflictId> {
226 let temp_file = NamedTempFile::new_in(&self.path)?;
227
228 let proto = conflict_to_proto(conflict);
229 let mut proto_bytes: Vec<u8> = Vec::new();
230 proto.write_to_writer(&mut proto_bytes)?;
231
232 temp_file.as_file().write_all(&proto_bytes)?;
233
234 let id = ConflictId(Blake2b::digest(&proto_bytes).to_vec());
235
236 temp_file.persist(self.conflict_path(&id))?;
237 Ok(id)
238 }
239}
240
241pub fn commit_to_proto(commit: &Commit) -> crate::protos::store::Commit {
242 let mut proto = crate::protos::store::Commit::new();
243 for parent in &commit.parents {
244 proto.parents.push(parent.0.clone());
245 }
246 for predecessor in &commit.predecessors {
247 proto.predecessors.push(predecessor.0.clone());
248 }
249 proto.set_root_tree(commit.root_tree.0.clone());
250 proto.set_change_id(commit.change_id.0.clone());
251 proto.set_description(commit.description.clone());
252 proto.set_author(signature_to_proto(&commit.author));
253 proto.set_committer(signature_to_proto(&commit.committer));
254 proto.set_is_open(commit.is_open);
255 proto.set_is_pruned(commit.is_pruned);
256 proto
257}
258
259fn commit_from_proto(proto: &crate::protos::store::Commit) -> Commit {
260 let commit_id_from_proto = |parent: &Vec<u8>| CommitId(parent.clone());
261 let parents = proto.parents.iter().map(commit_id_from_proto).collect();
262 let predecessors = proto
263 .predecessors
264 .iter()
265 .map(commit_id_from_proto)
266 .collect();
267 let root_tree = TreeId(proto.root_tree.to_vec());
268 let change_id = ChangeId(proto.change_id.to_vec());
269 Commit {
270 parents,
271 predecessors,
272 root_tree,
273 change_id,
274 description: proto.description.clone(),
275 author: signature_from_proto(proto.author.get_ref()),
276 committer: signature_from_proto(proto.committer.get_ref()),
277 is_open: proto.is_open,
278 is_pruned: proto.is_pruned,
279 }
280}
281
282fn tree_to_proto(tree: &Tree) -> crate::protos::store::Tree {
283 let mut proto = crate::protos::store::Tree::new();
284 for entry in tree.entries() {
285 let mut proto_entry = crate::protos::store::Tree_Entry::new();
286 proto_entry.set_name(entry.name().to_owned());
287 proto_entry.set_value(tree_value_to_proto(entry.value()));
288 proto.entries.push(proto_entry);
289 }
290 proto
291}
292
293fn tree_from_proto(proto: &crate::protos::store::Tree) -> Tree {
294 let mut tree = Tree::default();
295 for proto_entry in proto.entries.iter() {
296 let value = tree_value_from_proto(proto_entry.value.as_ref().unwrap());
297 tree.set(proto_entry.name.to_string(), value);
298 }
299 tree
300}
301
302fn tree_value_to_proto(value: &TreeValue) -> crate::protos::store::TreeValue {
303 let mut proto = crate::protos::store::TreeValue::new();
304 match value {
305 TreeValue::Normal { id, executable } => {
306 let mut file = crate::protos::store::TreeValue_NormalFile::new();
307 file.set_id(id.0.clone());
308 file.set_executable(*executable);
309 proto.set_normal_file(file);
310 }
311 TreeValue::Symlink(id) => {
312 proto.set_symlink_id(id.0.clone());
313 }
314 TreeValue::GitSubmodule(_id) => {
315 panic!("cannot store git submodules");
316 }
317 TreeValue::Tree(id) => {
318 proto.set_tree_id(id.0.clone());
319 }
320 TreeValue::Conflict(id) => {
321 proto.set_conflict_id(id.0.clone());
322 }
323 };
324 proto
325}
326
327fn tree_value_from_proto(proto: &crate::protos::store::TreeValue) -> TreeValue {
328 match proto.value.as_ref().unwrap() {
329 crate::protos::store::TreeValue_oneof_value::tree_id(id) => {
330 TreeValue::Tree(TreeId(id.clone()))
331 }
332 crate::protos::store::TreeValue_oneof_value::normal_file(
333 crate::protos::store::TreeValue_NormalFile { id, executable, .. },
334 ) => TreeValue::Normal {
335 id: FileId(id.clone()),
336 executable: *executable,
337 },
338 crate::protos::store::TreeValue_oneof_value::symlink_id(id) => {
339 TreeValue::Symlink(SymlinkId(id.clone()))
340 }
341 crate::protos::store::TreeValue_oneof_value::conflict_id(id) => {
342 TreeValue::Conflict(ConflictId(id.clone()))
343 }
344 }
345}
346
347fn signature_to_proto(signature: &Signature) -> crate::protos::store::Commit_Signature {
348 let mut proto = crate::protos::store::Commit_Signature::new();
349 proto.set_name(signature.name.clone());
350 proto.set_email(signature.email.clone());
351 let mut timestamp_proto = crate::protos::store::Commit_Timestamp::new();
352 timestamp_proto.set_millis_since_epoch(signature.timestamp.timestamp.0);
353 timestamp_proto.set_tz_offset(signature.timestamp.tz_offset);
354 proto.set_timestamp(timestamp_proto);
355 proto
356}
357
358fn signature_from_proto(proto: &crate::protos::store::Commit_Signature) -> Signature {
359 let timestamp = proto.get_timestamp();
360 Signature {
361 name: proto.name.clone(),
362 email: proto.email.clone(),
363 timestamp: Timestamp {
364 timestamp: MillisSinceEpoch(timestamp.millis_since_epoch),
365 tz_offset: timestamp.tz_offset,
366 },
367 }
368}
369
370fn conflict_to_proto(conflict: &Conflict) -> crate::protos::store::Conflict {
371 let mut proto = crate::protos::store::Conflict::new();
372 for part in &conflict.adds {
373 proto.adds.push(conflict_part_to_proto(part));
374 }
375 for part in &conflict.removes {
376 proto.removes.push(conflict_part_to_proto(part));
377 }
378 proto
379}
380
381fn conflict_from_proto(proto: &crate::protos::store::Conflict) -> Conflict {
382 let mut conflict = Conflict::default();
383 for part in &proto.removes {
384 conflict.removes.push(conflict_part_from_proto(part))
385 }
386 for part in &proto.adds {
387 conflict.adds.push(conflict_part_from_proto(part))
388 }
389 conflict
390}
391
392fn conflict_part_from_proto(proto: &crate::protos::store::Conflict_Part) -> ConflictPart {
393 ConflictPart {
394 value: tree_value_from_proto(proto.content.as_ref().unwrap()),
395 }
396}
397
398fn conflict_part_to_proto(part: &ConflictPart) -> crate::protos::store::Conflict_Part {
399 let mut proto = crate::protos::store::Conflict_Part::new();
400 proto.set_content(tree_value_to_proto(&part.value));
401 proto
402}