1#![allow(missing_docs)]
16
17use std::any::Any;
18use std::fmt::Debug;
19use std::fs;
20use std::fs::File;
21use std::io::Read;
22use std::io::Write as _;
23use std::path::Path;
24use std::path::PathBuf;
25use std::time::SystemTime;
26
27use async_trait::async_trait;
28use blake2::Blake2b512;
29use blake2::Digest as _;
30use futures::stream;
31use futures::stream::BoxStream;
32use pollster::FutureExt as _;
33use prost::Message as _;
34use tempfile::NamedTempFile;
35
36use crate::backend::make_root_commit;
37use crate::backend::Backend;
38use crate::backend::BackendError;
39use crate::backend::BackendResult;
40use crate::backend::ChangeId;
41use crate::backend::Commit;
42use crate::backend::CommitId;
43use crate::backend::Conflict;
44use crate::backend::ConflictId;
45use crate::backend::ConflictTerm;
46use crate::backend::CopyRecord;
47use crate::backend::FileId;
48use crate::backend::MergedTreeId;
49use crate::backend::MillisSinceEpoch;
50use crate::backend::SecureSig;
51use crate::backend::Signature;
52use crate::backend::SigningFn;
53use crate::backend::SymlinkId;
54use crate::backend::Timestamp;
55use crate::backend::Tree;
56use crate::backend::TreeId;
57use crate::backend::TreeValue;
58use crate::content_hash::blake2b_hash;
59use crate::file_util::persist_content_addressed_temp_file;
60use crate::index::Index;
61use crate::merge::MergeBuilder;
62use crate::object_id::ObjectId;
63use crate::repo_path::RepoPath;
64use crate::repo_path::RepoPathBuf;
65use crate::repo_path::RepoPathComponentBuf;
66
67const COMMIT_ID_LENGTH: usize = 64;
68const CHANGE_ID_LENGTH: usize = 16;
69
70fn map_not_found_err(err: std::io::Error, id: &impl ObjectId) -> BackendError {
71 if err.kind() == std::io::ErrorKind::NotFound {
72 BackendError::ObjectNotFound {
73 object_type: id.object_type(),
74 hash: id.hex(),
75 source: Box::new(err),
76 }
77 } else {
78 BackendError::ReadObject {
79 object_type: id.object_type(),
80 hash: id.hex(),
81 source: Box::new(err),
82 }
83 }
84}
85
86fn to_other_err(err: impl Into<Box<dyn std::error::Error + Send + Sync>>) -> BackendError {
87 BackendError::Other(err.into())
88}
89
90#[derive(Debug)]
91pub struct SimpleBackend {
92 path: PathBuf,
93 root_commit_id: CommitId,
94 root_change_id: ChangeId,
95 empty_tree_id: TreeId,
96}
97
98impl SimpleBackend {
99 pub fn name() -> &'static str {
100 "Simple"
101 }
102
103 pub fn init(store_path: &Path) -> Self {
104 fs::create_dir(store_path.join("commits")).unwrap();
105 fs::create_dir(store_path.join("trees")).unwrap();
106 fs::create_dir(store_path.join("files")).unwrap();
107 fs::create_dir(store_path.join("symlinks")).unwrap();
108 fs::create_dir(store_path.join("conflicts")).unwrap();
109 let backend = Self::load(store_path);
110 let empty_tree_id = backend
111 .write_tree(RepoPath::root(), &Tree::default())
112 .block_on()
113 .unwrap();
114 assert_eq!(empty_tree_id, backend.empty_tree_id);
115 backend
116 }
117
118 pub fn load(store_path: &Path) -> Self {
119 let root_commit_id = CommitId::from_bytes(&[0; COMMIT_ID_LENGTH]);
120 let root_change_id = ChangeId::from_bytes(&[0; CHANGE_ID_LENGTH]);
121 let empty_tree_id = TreeId::from_hex(
122 "482ae5a29fbe856c7272f2071b8b0f0359ee2d89ff392b8a900643fbd0836eccd067b8bf41909e206c90d45d6e7d8b6686b93ecaee5fe1a9060d87b672101310",
123 );
124 SimpleBackend {
125 path: store_path.to_path_buf(),
126 root_commit_id,
127 root_change_id,
128 empty_tree_id,
129 }
130 }
131
132 fn file_path(&self, id: &FileId) -> PathBuf {
133 self.path.join("files").join(id.hex())
134 }
135
136 fn symlink_path(&self, id: &SymlinkId) -> PathBuf {
137 self.path.join("symlinks").join(id.hex())
138 }
139
140 fn tree_path(&self, id: &TreeId) -> PathBuf {
141 self.path.join("trees").join(id.hex())
142 }
143
144 fn commit_path(&self, id: &CommitId) -> PathBuf {
145 self.path.join("commits").join(id.hex())
146 }
147
148 fn conflict_path(&self, id: &ConflictId) -> PathBuf {
149 self.path.join("conflicts").join(id.hex())
150 }
151}
152
153#[async_trait]
154impl Backend for SimpleBackend {
155 fn as_any(&self) -> &dyn Any {
156 self
157 }
158
159 fn name(&self) -> &str {
160 Self::name()
161 }
162
163 fn commit_id_length(&self) -> usize {
164 COMMIT_ID_LENGTH
165 }
166
167 fn change_id_length(&self) -> usize {
168 CHANGE_ID_LENGTH
169 }
170
171 fn root_commit_id(&self) -> &CommitId {
172 &self.root_commit_id
173 }
174
175 fn root_change_id(&self) -> &ChangeId {
176 &self.root_change_id
177 }
178
179 fn empty_tree_id(&self) -> &TreeId {
180 &self.empty_tree_id
181 }
182
183 fn concurrency(&self) -> usize {
184 1
185 }
186
187 async fn read_file(&self, _path: &RepoPath, id: &FileId) -> BackendResult<Box<dyn Read>> {
188 let path = self.file_path(id);
189 let file = File::open(path).map_err(|err| map_not_found_err(err, id))?;
190 Ok(Box::new(file))
191 }
192
193 async fn write_file(
194 &self,
195 _path: &RepoPath,
196 contents: &mut (dyn Read + Send),
197 ) -> BackendResult<FileId> {
198 let temp_file = NamedTempFile::new_in(&self.path).map_err(to_other_err)?;
200 let mut file = temp_file.as_file();
201 let mut hasher = Blake2b512::new();
202 let mut buff: Vec<u8> = vec![0; 1 << 14];
203 loop {
204 let bytes_read = contents.read(&mut buff).map_err(to_other_err)?;
205 if bytes_read == 0 {
206 break;
207 }
208 let bytes = &buff[..bytes_read];
209 file.write_all(bytes).map_err(to_other_err)?;
210 hasher.update(bytes);
211 }
212 file.flush().map_err(to_other_err)?;
213 let id = FileId::new(hasher.finalize().to_vec());
214
215 persist_content_addressed_temp_file(temp_file, self.file_path(&id))
216 .map_err(to_other_err)?;
217 Ok(id)
218 }
219
220 async fn read_symlink(&self, _path: &RepoPath, id: &SymlinkId) -> BackendResult<String> {
221 let path = self.symlink_path(id);
222 let target = fs::read_to_string(path).map_err(|err| map_not_found_err(err, id))?;
223 Ok(target)
224 }
225
226 async fn write_symlink(&self, _path: &RepoPath, target: &str) -> BackendResult<SymlinkId> {
227 let mut temp_file = NamedTempFile::new_in(&self.path).map_err(to_other_err)?;
229 temp_file
230 .write_all(target.as_bytes())
231 .map_err(to_other_err)?;
232 let mut hasher = Blake2b512::new();
233 hasher.update(target.as_bytes());
234 let id = SymlinkId::new(hasher.finalize().to_vec());
235
236 persist_content_addressed_temp_file(temp_file, self.symlink_path(&id))
237 .map_err(to_other_err)?;
238 Ok(id)
239 }
240
241 async fn read_tree(&self, _path: &RepoPath, id: &TreeId) -> BackendResult<Tree> {
242 let path = self.tree_path(id);
243 let buf = fs::read(path).map_err(|err| map_not_found_err(err, id))?;
244
245 let proto = crate::protos::simple_store::Tree::decode(&*buf).map_err(to_other_err)?;
246 Ok(tree_from_proto(proto))
247 }
248
249 async fn write_tree(&self, _path: &RepoPath, tree: &Tree) -> BackendResult<TreeId> {
250 let temp_file = NamedTempFile::new_in(&self.path).map_err(to_other_err)?;
252
253 let proto = tree_to_proto(tree);
254 temp_file
255 .as_file()
256 .write_all(&proto.encode_to_vec())
257 .map_err(to_other_err)?;
258
259 let id = TreeId::new(blake2b_hash(tree).to_vec());
260
261 persist_content_addressed_temp_file(temp_file, self.tree_path(&id))
262 .map_err(to_other_err)?;
263 Ok(id)
264 }
265
266 fn read_conflict(&self, _path: &RepoPath, id: &ConflictId) -> BackendResult<Conflict> {
267 let path = self.conflict_path(id);
268 let buf = fs::read(path).map_err(|err| map_not_found_err(err, id))?;
269
270 let proto = crate::protos::simple_store::Conflict::decode(&*buf).map_err(to_other_err)?;
271 Ok(conflict_from_proto(proto))
272 }
273
274 fn write_conflict(&self, _path: &RepoPath, conflict: &Conflict) -> BackendResult<ConflictId> {
275 let temp_file = NamedTempFile::new_in(&self.path).map_err(to_other_err)?;
277
278 let proto = conflict_to_proto(conflict);
279 temp_file
280 .as_file()
281 .write_all(&proto.encode_to_vec())
282 .map_err(to_other_err)?;
283
284 let id = ConflictId::new(blake2b_hash(conflict).to_vec());
285
286 persist_content_addressed_temp_file(temp_file, self.conflict_path(&id))
287 .map_err(to_other_err)?;
288 Ok(id)
289 }
290
291 async fn read_commit(&self, id: &CommitId) -> BackendResult<Commit> {
292 if *id == self.root_commit_id {
293 return Ok(make_root_commit(
294 self.root_change_id().clone(),
295 self.empty_tree_id.clone(),
296 ));
297 }
298
299 let path = self.commit_path(id);
300 let buf = fs::read(path).map_err(|err| map_not_found_err(err, id))?;
301
302 let proto = crate::protos::simple_store::Commit::decode(&*buf).map_err(to_other_err)?;
303 Ok(commit_from_proto(proto))
304 }
305
306 async fn write_commit(
307 &self,
308 mut commit: Commit,
309 sign_with: Option<&mut SigningFn>,
310 ) -> BackendResult<(CommitId, Commit)> {
311 assert!(commit.secure_sig.is_none(), "commit.secure_sig was set");
312
313 if commit.parents.is_empty() {
314 return Err(BackendError::Other(
315 "Cannot write a commit with no parents".into(),
316 ));
317 }
318 let temp_file = NamedTempFile::new_in(&self.path).map_err(to_other_err)?;
320
321 let mut proto = commit_to_proto(&commit);
322 if let Some(sign) = sign_with {
323 let data = proto.encode_to_vec();
324 let sig = sign(&data).map_err(to_other_err)?;
325 proto.secure_sig = Some(sig.clone());
326 commit.secure_sig = Some(SecureSig { data, sig });
327 }
328
329 temp_file
330 .as_file()
331 .write_all(&proto.encode_to_vec())
332 .map_err(to_other_err)?;
333
334 let id = CommitId::new(blake2b_hash(&commit).to_vec());
335
336 persist_content_addressed_temp_file(temp_file, self.commit_path(&id))
337 .map_err(to_other_err)?;
338 Ok((id, commit))
339 }
340
341 fn get_copy_records(
342 &self,
343 _paths: Option<&[RepoPathBuf]>,
344 _root: &CommitId,
345 _head: &CommitId,
346 ) -> BackendResult<BoxStream<BackendResult<CopyRecord>>> {
347 Ok(Box::pin(stream::empty()))
348 }
349
350 fn gc(&self, _index: &dyn Index, _keep_newer: SystemTime) -> BackendResult<()> {
351 Ok(())
352 }
353}
354
355#[allow(clippy::assigning_clones)]
356pub fn commit_to_proto(commit: &Commit) -> crate::protos::simple_store::Commit {
357 let mut proto = crate::protos::simple_store::Commit::default();
358 for parent in &commit.parents {
359 proto.parents.push(parent.to_bytes());
360 }
361 for predecessor in &commit.predecessors {
362 proto.predecessors.push(predecessor.to_bytes());
363 }
364 match &commit.root_tree {
365 MergedTreeId::Legacy(tree_id) => {
366 proto.root_tree = vec![tree_id.to_bytes()];
367 }
368 MergedTreeId::Merge(tree_ids) => {
369 proto.uses_tree_conflict_format = true;
370 proto.root_tree = tree_ids.iter().map(|id| id.to_bytes()).collect();
371 }
372 }
373 proto.change_id = commit.change_id.to_bytes();
374 proto.description = commit.description.clone();
375 proto.author = Some(signature_to_proto(&commit.author));
376 proto.committer = Some(signature_to_proto(&commit.committer));
377 proto
378}
379
380fn commit_from_proto(mut proto: crate::protos::simple_store::Commit) -> Commit {
381 let secure_sig = proto.secure_sig.take().map(|sig| SecureSig {
384 data: proto.encode_to_vec(),
385 sig,
386 });
387
388 let parents = proto.parents.into_iter().map(CommitId::new).collect();
389 let predecessors = proto.predecessors.into_iter().map(CommitId::new).collect();
390 let root_tree = if proto.uses_tree_conflict_format {
391 let merge_builder: MergeBuilder<_> = proto.root_tree.into_iter().map(TreeId::new).collect();
392 MergedTreeId::Merge(merge_builder.build())
393 } else {
394 assert_eq!(proto.root_tree.len(), 1);
395 MergedTreeId::Legacy(TreeId::new(proto.root_tree[0].clone()))
396 };
397 let change_id = ChangeId::new(proto.change_id);
398 Commit {
399 parents,
400 predecessors,
401 root_tree,
402 change_id,
403 description: proto.description,
404 author: signature_from_proto(proto.author.unwrap_or_default()),
405 committer: signature_from_proto(proto.committer.unwrap_or_default()),
406 secure_sig,
407 }
408}
409
410fn tree_to_proto(tree: &Tree) -> crate::protos::simple_store::Tree {
411 let mut proto = crate::protos::simple_store::Tree::default();
412 for entry in tree.entries() {
413 proto
414 .entries
415 .push(crate::protos::simple_store::tree::Entry {
416 name: entry.name().as_internal_str().to_owned(),
417 value: Some(tree_value_to_proto(entry.value())),
418 });
419 }
420 proto
421}
422
423fn tree_from_proto(proto: crate::protos::simple_store::Tree) -> Tree {
424 let mut tree = Tree::default();
425 for proto_entry in proto.entries {
426 let value = tree_value_from_proto(proto_entry.value.unwrap());
427 tree.set(RepoPathComponentBuf::new(proto_entry.name).unwrap(), value);
428 }
429 tree
430}
431
432fn tree_value_to_proto(value: &TreeValue) -> crate::protos::simple_store::TreeValue {
433 let mut proto = crate::protos::simple_store::TreeValue::default();
434 match value {
435 TreeValue::File { id, executable } => {
436 proto.value = Some(crate::protos::simple_store::tree_value::Value::File(
437 crate::protos::simple_store::tree_value::File {
438 id: id.to_bytes(),
439 executable: *executable,
440 },
441 ));
442 }
443 TreeValue::Symlink(id) => {
444 proto.value = Some(crate::protos::simple_store::tree_value::Value::SymlinkId(
445 id.to_bytes(),
446 ));
447 }
448 TreeValue::GitSubmodule(_id) => {
449 panic!("cannot store git submodules");
450 }
451 TreeValue::Tree(id) => {
452 proto.value = Some(crate::protos::simple_store::tree_value::Value::TreeId(
453 id.to_bytes(),
454 ));
455 }
456 TreeValue::Conflict(id) => {
457 proto.value = Some(crate::protos::simple_store::tree_value::Value::ConflictId(
458 id.to_bytes(),
459 ));
460 }
461 }
462 proto
463}
464
465fn tree_value_from_proto(proto: crate::protos::simple_store::TreeValue) -> TreeValue {
466 match proto.value.unwrap() {
467 crate::protos::simple_store::tree_value::Value::TreeId(id) => {
468 TreeValue::Tree(TreeId::new(id))
469 }
470 crate::protos::simple_store::tree_value::Value::File(
471 crate::protos::simple_store::tree_value::File { id, executable, .. },
472 ) => TreeValue::File {
473 id: FileId::new(id),
474 executable,
475 },
476 crate::protos::simple_store::tree_value::Value::SymlinkId(id) => {
477 TreeValue::Symlink(SymlinkId::new(id))
478 }
479 crate::protos::simple_store::tree_value::Value::ConflictId(id) => {
480 TreeValue::Conflict(ConflictId::new(id))
481 }
482 }
483}
484
485fn signature_to_proto(signature: &Signature) -> crate::protos::simple_store::commit::Signature {
486 crate::protos::simple_store::commit::Signature {
487 name: signature.name.clone(),
488 email: signature.email.clone(),
489 timestamp: Some(crate::protos::simple_store::commit::Timestamp {
490 millis_since_epoch: signature.timestamp.timestamp.0,
491 tz_offset: signature.timestamp.tz_offset,
492 }),
493 }
494}
495
496fn signature_from_proto(proto: crate::protos::simple_store::commit::Signature) -> Signature {
497 let timestamp = proto.timestamp.unwrap_or_default();
498 Signature {
499 name: proto.name,
500 email: proto.email,
501 timestamp: Timestamp {
502 timestamp: MillisSinceEpoch(timestamp.millis_since_epoch),
503 tz_offset: timestamp.tz_offset,
504 },
505 }
506}
507
508fn conflict_to_proto(conflict: &Conflict) -> crate::protos::simple_store::Conflict {
509 let mut proto = crate::protos::simple_store::Conflict::default();
510 for term in &conflict.removes {
511 proto.removes.push(conflict_term_to_proto(term));
512 }
513 for term in &conflict.adds {
514 proto.adds.push(conflict_term_to_proto(term));
515 }
516 proto
517}
518
519fn conflict_from_proto(proto: crate::protos::simple_store::Conflict) -> Conflict {
520 let removes = proto
521 .removes
522 .into_iter()
523 .map(conflict_term_from_proto)
524 .collect();
525 let adds = proto
526 .adds
527 .into_iter()
528 .map(conflict_term_from_proto)
529 .collect();
530 Conflict { removes, adds }
531}
532
533fn conflict_term_from_proto(proto: crate::protos::simple_store::conflict::Term) -> ConflictTerm {
534 ConflictTerm {
535 value: tree_value_from_proto(proto.content.unwrap()),
536 }
537}
538
539fn conflict_term_to_proto(part: &ConflictTerm) -> crate::protos::simple_store::conflict::Term {
540 crate::protos::simple_store::conflict::Term {
541 content: Some(tree_value_to_proto(&part.value)),
542 }
543}
544
545#[cfg(test)]
546mod tests {
547 use assert_matches::assert_matches;
548 use pollster::FutureExt as _;
549
550 use super::*;
551 use crate::tests::new_temp_dir;
552
553 #[test]
555 fn write_commit_parents() {
556 let temp_dir = new_temp_dir();
557 let store_path = temp_dir.path();
558
559 let backend = SimpleBackend::init(store_path);
560 let mut commit = Commit {
561 parents: vec![],
562 predecessors: vec![],
563 root_tree: MergedTreeId::resolved(backend.empty_tree_id().clone()),
564 change_id: ChangeId::from_hex("abc123"),
565 description: "".to_string(),
566 author: create_signature(),
567 committer: create_signature(),
568 secure_sig: None,
569 };
570
571 let write_commit = |commit: Commit| -> BackendResult<(CommitId, Commit)> {
572 backend.write_commit(commit, None).block_on()
573 };
574
575 commit.parents = vec![];
577 assert_matches!(
578 write_commit(commit.clone()),
579 Err(BackendError::Other(err)) if err.to_string().contains("no parents")
580 );
581
582 commit.parents = vec![backend.root_commit_id().clone()];
584 let first_id = write_commit(commit.clone()).unwrap().0;
585 let first_commit = backend.read_commit(&first_id).block_on().unwrap();
586 assert_eq!(first_commit, commit);
587
588 commit.parents = vec![first_id.clone()];
590 let second_id = write_commit(commit.clone()).unwrap().0;
591 let second_commit = backend.read_commit(&second_id).block_on().unwrap();
592 assert_eq!(second_commit, commit);
593
594 commit.parents = vec![first_id.clone(), second_id.clone()];
596 let merge_id = write_commit(commit.clone()).unwrap().0;
597 let merge_commit = backend.read_commit(&merge_id).block_on().unwrap();
598 assert_eq!(merge_commit, commit);
599
600 commit.parents = vec![first_id, backend.root_commit_id().clone()];
602 let root_merge_id = write_commit(commit.clone()).unwrap().0;
603 let root_merge_commit = backend.read_commit(&root_merge_id).block_on().unwrap();
604 assert_eq!(root_merge_commit, commit);
605 }
606
607 fn create_signature() -> Signature {
608 Signature {
609 name: "Someone".to_string(),
610 email: "someone@example.com".to_string(),
611 timestamp: Timestamp {
612 timestamp: MillisSinceEpoch(0),
613 tz_offset: 0,
614 },
615 }
616 }
617}