jujutsu_lib/
proto_op_store.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::collections::BTreeMap;
16use std::fmt::Debug;
17use std::fs;
18use std::io::{ErrorKind, Write};
19use std::path::PathBuf;
20
21use itertools::Itertools;
22use prost::Message;
23use tempfile::NamedTempFile;
24
25use crate::backend::{CommitId, MillisSinceEpoch, ObjectId, Timestamp};
26use crate::content_hash::blake2b_hash;
27use crate::file_util::persist_content_addressed_temp_file;
28use crate::op_store::{
29    BranchTarget, OpStoreError, OpStoreResult, Operation, OperationId, OperationMetadata,
30    RefTarget, View, ViewId, WorkspaceId,
31};
32
33impl From<prost::DecodeError> for OpStoreError {
34    fn from(err: prost::DecodeError) -> Self {
35        OpStoreError::Other(err.to_string())
36    }
37}
38
39#[derive(Debug)]
40pub struct ProtoOpStore {
41    path: PathBuf,
42}
43
44impl ProtoOpStore {
45    pub fn init(store_path: PathBuf) -> Self {
46        fs::create_dir(store_path.join("views")).unwrap();
47        fs::create_dir(store_path.join("operations")).unwrap();
48        ProtoOpStore { path: store_path }
49    }
50
51    pub fn load(store_path: PathBuf) -> Self {
52        ProtoOpStore { path: store_path }
53    }
54
55    fn view_path(&self, id: &ViewId) -> PathBuf {
56        self.path.join("views").join(id.hex())
57    }
58
59    fn operation_path(&self, id: &OperationId) -> PathBuf {
60        self.path.join("operations").join(id.hex())
61    }
62
63    pub fn read_view(&self, id: &ViewId) -> OpStoreResult<View> {
64        let path = self.view_path(id);
65        let buf = fs::read(path)?;
66
67        let proto = crate::protos::op_store::View::decode(&*buf)?;
68        Ok(view_from_proto(proto))
69    }
70
71    pub fn write_view(&self, view: &View) -> OpStoreResult<ViewId> {
72        let temp_file = NamedTempFile::new_in(&self.path)?;
73
74        let proto = view_to_proto(view);
75        temp_file.as_file().write_all(&proto.encode_to_vec())?;
76
77        let id = ViewId::new(blake2b_hash(view).to_vec());
78
79        persist_content_addressed_temp_file(temp_file, self.view_path(&id))?;
80        Ok(id)
81    }
82
83    pub fn read_operation(&self, id: &OperationId) -> OpStoreResult<Operation> {
84        let path = self.operation_path(id);
85        let buf = fs::read(path).map_err(not_found_to_store_error)?;
86
87        let proto = crate::protos::op_store::Operation::decode(&*buf)?;
88        Ok(operation_from_proto(proto))
89    }
90
91    pub fn write_operation(&self, operation: &Operation) -> OpStoreResult<OperationId> {
92        let temp_file = NamedTempFile::new_in(&self.path)?;
93
94        let proto = operation_to_proto(operation);
95        temp_file.as_file().write_all(&proto.encode_to_vec())?;
96
97        let id = OperationId::new(blake2b_hash(operation).to_vec());
98
99        persist_content_addressed_temp_file(temp_file, self.operation_path(&id))?;
100        Ok(id)
101    }
102}
103
104fn not_found_to_store_error(err: std::io::Error) -> OpStoreError {
105    if err.kind() == ErrorKind::NotFound {
106        OpStoreError::NotFound
107    } else {
108        OpStoreError::from(err)
109    }
110}
111
112fn timestamp_to_proto(timestamp: &Timestamp) -> crate::protos::op_store::Timestamp {
113    crate::protos::op_store::Timestamp {
114        millis_since_epoch: timestamp.timestamp.0,
115        tz_offset: timestamp.tz_offset,
116    }
117}
118
119fn timestamp_from_proto(proto: crate::protos::op_store::Timestamp) -> Timestamp {
120    Timestamp {
121        timestamp: MillisSinceEpoch(proto.millis_since_epoch),
122        tz_offset: proto.tz_offset,
123    }
124}
125
126fn operation_metadata_to_proto(
127    metadata: &OperationMetadata,
128) -> crate::protos::op_store::OperationMetadata {
129    crate::protos::op_store::OperationMetadata {
130        start_time: Some(timestamp_to_proto(&metadata.start_time)),
131        end_time: Some(timestamp_to_proto(&metadata.end_time)),
132        description: metadata.description.clone(),
133        hostname: metadata.hostname.clone(),
134        username: metadata.username.clone(),
135        tags: metadata.tags.clone(),
136    }
137}
138
139fn operation_metadata_from_proto(
140    proto: crate::protos::op_store::OperationMetadata,
141) -> OperationMetadata {
142    let start_time = timestamp_from_proto(proto.start_time.unwrap_or_default());
143    let end_time = timestamp_from_proto(proto.end_time.unwrap_or_default());
144    OperationMetadata {
145        start_time,
146        end_time,
147        description: proto.description,
148        hostname: proto.hostname,
149        username: proto.username,
150        tags: proto.tags,
151    }
152}
153
154fn operation_to_proto(operation: &Operation) -> crate::protos::op_store::Operation {
155    let mut proto = crate::protos::op_store::Operation {
156        view_id: operation.view_id.as_bytes().to_vec(),
157        metadata: Some(operation_metadata_to_proto(&operation.metadata)),
158        ..Default::default()
159    };
160    for parent in &operation.parents {
161        proto.parents.push(parent.to_bytes());
162    }
163    proto
164}
165
166fn operation_from_proto(proto: crate::protos::op_store::Operation) -> Operation {
167    let parents = proto.parents.into_iter().map(OperationId::new).collect();
168    let view_id = ViewId::new(proto.view_id);
169    let metadata = operation_metadata_from_proto(proto.metadata.unwrap_or_default());
170    Operation {
171        view_id,
172        parents,
173        metadata,
174    }
175}
176
177fn view_to_proto(view: &View) -> crate::protos::op_store::View {
178    let mut proto = crate::protos::op_store::View::default();
179    for (workspace_id, commit_id) in &view.wc_commit_ids {
180        proto
181            .wc_commit_ids
182            .insert(workspace_id.as_str().to_string(), commit_id.to_bytes());
183    }
184    for head_id in &view.head_ids {
185        proto.head_ids.push(head_id.to_bytes());
186    }
187    for head_id in &view.public_head_ids {
188        proto.public_head_ids.push(head_id.to_bytes());
189    }
190
191    for (name, target) in &view.branches {
192        let mut branch_proto = crate::protos::op_store::Branch {
193            name: name.clone(),
194            ..Default::default()
195        };
196        branch_proto.name = name.clone();
197        if let Some(local_target) = &target.local_target {
198            branch_proto.local_target = Some(ref_target_to_proto(local_target));
199        }
200        for (remote_name, target) in &target.remote_targets {
201            branch_proto
202                .remote_branches
203                .push(crate::protos::op_store::RemoteBranch {
204                    remote_name: remote_name.clone(),
205                    target: Some(ref_target_to_proto(target)),
206                });
207        }
208        proto.branches.push(branch_proto);
209    }
210
211    for (name, target) in &view.tags {
212        proto.tags.push(crate::protos::op_store::Tag {
213            name: name.clone(),
214            target: Some(ref_target_to_proto(target)),
215        });
216    }
217
218    for (git_ref_name, target) in &view.git_refs {
219        proto.git_refs.push(crate::protos::op_store::GitRef {
220            name: git_ref_name.clone(),
221            target: Some(ref_target_to_proto(target)),
222            ..Default::default()
223        });
224    }
225
226    if let Some(git_head) = &view.git_head {
227        proto.git_head = Some(ref_target_to_proto(git_head));
228    }
229
230    proto
231}
232
233fn view_from_proto(proto: crate::protos::op_store::View) -> View {
234    let mut view = View::default();
235    // For compatibility with old repos before we had support for multiple working
236    // copies
237    #[allow(deprecated)]
238    if !proto.wc_commit_id.is_empty() {
239        view.wc_commit_ids
240            .insert(WorkspaceId::default(), CommitId::new(proto.wc_commit_id));
241    }
242    for (workspace_id, commit_id) in proto.wc_commit_ids {
243        view.wc_commit_ids
244            .insert(WorkspaceId::new(workspace_id), CommitId::new(commit_id));
245    }
246    for head_id_bytes in proto.head_ids {
247        view.head_ids.insert(CommitId::new(head_id_bytes));
248    }
249    for head_id_bytes in proto.public_head_ids {
250        view.public_head_ids.insert(CommitId::new(head_id_bytes));
251    }
252
253    for branch_proto in proto.branches {
254        let local_target = branch_proto.local_target.map(ref_target_from_proto);
255
256        let mut remote_targets = BTreeMap::new();
257        for remote_branch in branch_proto.remote_branches {
258            remote_targets.insert(
259                remote_branch.remote_name,
260                ref_target_from_proto(remote_branch.target.unwrap_or_default()),
261            );
262        }
263
264        view.branches.insert(
265            branch_proto.name.clone(),
266            BranchTarget {
267                local_target,
268                remote_targets,
269            },
270        );
271    }
272
273    for tag_proto in proto.tags {
274        view.tags.insert(
275            tag_proto.name,
276            ref_target_from_proto(tag_proto.target.unwrap_or_default()),
277        );
278    }
279
280    for git_ref in proto.git_refs {
281        if let Some(target) = git_ref.target {
282            view.git_refs
283                .insert(git_ref.name, ref_target_from_proto(target));
284        } else {
285            // Legacy format
286            view.git_refs.insert(
287                git_ref.name,
288                RefTarget::Normal(CommitId::new(git_ref.commit_id)),
289            );
290        }
291    }
292
293    #[allow(deprecated)]
294    if let Some(git_head) = proto.git_head.as_ref() {
295        view.git_head = Some(ref_target_from_proto(git_head.clone()));
296    } else if !proto.git_head_legacy.is_empty() {
297        view.git_head = Some(RefTarget::Normal(CommitId::new(proto.git_head_legacy)));
298    }
299
300    view
301}
302
303fn ref_target_to_proto(value: &RefTarget) -> crate::protos::op_store::RefTarget {
304    let mut proto = crate::protos::op_store::RefTarget::default();
305    match value {
306        RefTarget::Normal(id) => {
307            proto.value = Some(crate::protos::op_store::ref_target::Value::CommitId(
308                id.to_bytes(),
309            ));
310        }
311        RefTarget::Conflict { removes, adds } => {
312            let mut ref_conflict_proto = crate::protos::op_store::RefConflict::default();
313            for id in removes {
314                ref_conflict_proto.removes.push(id.to_bytes());
315            }
316            for id in adds {
317                ref_conflict_proto.adds.push(id.to_bytes());
318            }
319            proto.value = Some(crate::protos::op_store::ref_target::Value::Conflict(
320                ref_conflict_proto,
321            ));
322        }
323    }
324    proto
325}
326
327fn ref_target_from_proto(proto: crate::protos::op_store::RefTarget) -> RefTarget {
328    match proto.value.unwrap() {
329        crate::protos::op_store::ref_target::Value::CommitId(id) => {
330            RefTarget::Normal(CommitId::new(id))
331        }
332        crate::protos::op_store::ref_target::Value::Conflict(conflict) => {
333            let removes = conflict
334                .removes
335                .into_iter()
336                .map(CommitId::new)
337                .collect_vec();
338            let adds = conflict.adds.into_iter().map(CommitId::new).collect_vec();
339            RefTarget::Conflict { removes, adds }
340        }
341    }
342}