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