jujutsu_lib/
local_backend.rs

1// Copyright 2020 The Jujutsu Authors
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// https://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use 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}