Skip to main content

jj_lib/
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
15#![expect(missing_docs)]
16
17use std::fmt::Debug;
18use std::fmt::Formatter;
19use std::num::NonZeroUsize;
20use std::pin::Pin;
21use std::sync::Arc;
22use std::sync::Mutex;
23
24use clru::CLruCache;
25use futures::AsyncRead;
26use futures::stream::BoxStream;
27use pollster::FutureExt as _;
28
29use crate::backend;
30use crate::backend::Backend;
31use crate::backend::BackendResult;
32use crate::backend::ChangeId;
33use crate::backend::CommitId;
34use crate::backend::CopyRecord;
35use crate::backend::FileId;
36use crate::backend::SigningFn;
37use crate::backend::SymlinkId;
38use crate::backend::TreeId;
39use crate::commit::Commit;
40use crate::merge::Merge;
41use crate::merged_tree::MergedTree;
42use crate::repo_path::RepoPath;
43use crate::repo_path::RepoPathBuf;
44use crate::signing::Signer;
45use crate::tree::Tree;
46use crate::tree_merge::MergeOptions;
47
48// There are more tree objects than commits, and trees are often shared across
49// commits.
50pub(crate) const COMMIT_CACHE_CAPACITY: NonZeroUsize = NonZeroUsize::new(100).unwrap();
51const TREE_CACHE_CAPACITY: NonZeroUsize = NonZeroUsize::new(1000).unwrap();
52
53/// Wraps the low-level backend and makes it return more convenient types. Also
54/// adds caching.
55pub struct Store {
56    backend: Box<dyn Backend>,
57    signer: Signer,
58    commit_cache: Mutex<CLruCache<CommitId, Arc<backend::Commit>>>,
59    tree_cache: Mutex<CLruCache<(RepoPathBuf, TreeId), Arc<backend::Tree>>>,
60    merge_options: MergeOptions,
61}
62
63impl Debug for Store {
64    fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> {
65        f.debug_struct("Store")
66            .field("backend", &self.backend)
67            .finish_non_exhaustive()
68    }
69}
70
71impl Store {
72    pub fn new(
73        backend: Box<dyn Backend>,
74        signer: Signer,
75        merge_options: MergeOptions,
76    ) -> Arc<Self> {
77        Arc::new(Self {
78            backend,
79            signer,
80            commit_cache: Mutex::new(CLruCache::new(COMMIT_CACHE_CAPACITY)),
81            tree_cache: Mutex::new(CLruCache::new(TREE_CACHE_CAPACITY)),
82            merge_options,
83        })
84    }
85
86    pub fn backend(&self) -> &dyn Backend {
87        self.backend.as_ref()
88    }
89
90    /// Returns backend as the implementation type.
91    pub fn backend_impl<T: Backend>(&self) -> Option<&T> {
92        self.backend.downcast_ref()
93    }
94
95    pub fn signer(&self) -> &Signer {
96        &self.signer
97    }
98
99    /// Default merge options to be used when resolving parent trees.
100    pub fn merge_options(&self) -> &MergeOptions {
101        &self.merge_options
102    }
103
104    pub fn get_copy_records(
105        &self,
106        paths: Option<&[RepoPathBuf]>,
107        root: &CommitId,
108        head: &CommitId,
109    ) -> BackendResult<BoxStream<'_, BackendResult<CopyRecord>>> {
110        self.backend.get_copy_records(paths, root, head)
111    }
112
113    pub fn commit_id_length(&self) -> usize {
114        self.backend.commit_id_length()
115    }
116
117    pub fn change_id_length(&self) -> usize {
118        self.backend.change_id_length()
119    }
120
121    pub fn root_commit_id(&self) -> &CommitId {
122        self.backend.root_commit_id()
123    }
124
125    pub fn root_change_id(&self) -> &ChangeId {
126        self.backend.root_change_id()
127    }
128
129    pub fn empty_tree_id(&self) -> &TreeId {
130        self.backend.empty_tree_id()
131    }
132
133    pub fn concurrency(&self) -> usize {
134        self.backend.concurrency()
135    }
136
137    pub fn empty_merged_tree(self: &Arc<Self>) -> MergedTree {
138        let empty_tree_id = self.backend.empty_tree_id().clone();
139        MergedTree::resolved(self.clone(), empty_tree_id)
140    }
141
142    pub fn empty_merged_tree_id(&self) -> Merge<TreeId> {
143        Merge::resolved(self.backend.empty_tree_id().clone())
144    }
145
146    pub fn root_commit(self: &Arc<Self>) -> Commit {
147        self.get_commit(self.backend.root_commit_id()).unwrap()
148    }
149
150    pub fn get_commit(self: &Arc<Self>, id: &CommitId) -> BackendResult<Commit> {
151        self.get_commit_async(id).block_on()
152    }
153
154    pub async fn get_commit_async(self: &Arc<Self>, id: &CommitId) -> BackendResult<Commit> {
155        let data = self.get_backend_commit(id).await?;
156        Ok(Commit::new(self.clone(), id.clone(), data))
157    }
158
159    async fn get_backend_commit(&self, id: &CommitId) -> BackendResult<Arc<backend::Commit>> {
160        {
161            let mut locked_cache = self.commit_cache.lock().unwrap();
162            if let Some(data) = locked_cache.get(id).cloned() {
163                return Ok(data);
164            }
165        }
166        let commit = self.backend.read_commit(id).await?;
167        let data = Arc::new(commit);
168        let mut locked_cache = self.commit_cache.lock().unwrap();
169        locked_cache.put(id.clone(), data.clone());
170        Ok(data)
171    }
172
173    pub async fn write_commit(
174        self: &Arc<Self>,
175        commit: backend::Commit,
176        sign_with: Option<&mut SigningFn<'_>>,
177    ) -> BackendResult<Commit> {
178        assert!(!commit.parents.is_empty());
179
180        let (commit_id, commit) = self.backend.write_commit(commit, sign_with).await?;
181        let data = Arc::new(commit);
182        {
183            let mut locked_cache = self.commit_cache.lock().unwrap();
184            locked_cache.put(commit_id.clone(), data.clone());
185        }
186
187        Ok(Commit::new(self.clone(), commit_id, data))
188    }
189
190    pub async fn get_tree(self: &Arc<Self>, dir: RepoPathBuf, id: &TreeId) -> BackendResult<Tree> {
191        let data = self.get_backend_tree(&dir, id).await?;
192        Ok(Tree::new(self.clone(), dir, id.clone(), data))
193    }
194
195    async fn get_backend_tree(
196        &self,
197        dir: &RepoPath,
198        id: &TreeId,
199    ) -> BackendResult<Arc<backend::Tree>> {
200        let key = (dir.to_owned(), id.clone());
201        {
202            let mut locked_cache = self.tree_cache.lock().unwrap();
203            if let Some(data) = locked_cache.get(&key).cloned() {
204                return Ok(data);
205            }
206        }
207        let data = self.backend.read_tree(dir, id).await?;
208        let data = Arc::new(data);
209        let mut locked_cache = self.tree_cache.lock().unwrap();
210        locked_cache.put(key, data.clone());
211        Ok(data)
212    }
213
214    pub async fn write_tree(
215        self: &Arc<Self>,
216        path: &RepoPath,
217        tree: backend::Tree,
218    ) -> BackendResult<Tree> {
219        let tree_id = self.backend.write_tree(path, &tree).await?;
220        let data = Arc::new(tree);
221        {
222            let mut locked_cache = self.tree_cache.lock().unwrap();
223            locked_cache.put((path.to_owned(), tree_id.clone()), data.clone());
224        }
225
226        Ok(Tree::new(self.clone(), path.to_owned(), tree_id, data))
227    }
228
229    pub async fn read_file(
230        &self,
231        path: &RepoPath,
232        id: &FileId,
233    ) -> BackendResult<Pin<Box<dyn AsyncRead + Send>>> {
234        self.backend.read_file(path, id).await
235    }
236
237    pub async fn write_file(
238        &self,
239        path: &RepoPath,
240        contents: &mut (dyn AsyncRead + Send + Unpin),
241    ) -> BackendResult<FileId> {
242        self.backend.write_file(path, contents).await
243    }
244
245    pub async fn read_symlink(&self, path: &RepoPath, id: &SymlinkId) -> BackendResult<String> {
246        self.backend.read_symlink(path, id).await
247    }
248
249    pub async fn write_symlink(&self, path: &RepoPath, contents: &str) -> BackendResult<SymlinkId> {
250        self.backend.write_symlink(path, contents).await
251    }
252
253    /// Clear cached objects. Mainly intended for testing.
254    pub fn clear_caches(&self) {
255        self.commit_cache.lock().unwrap().clear();
256        self.tree_cache.lock().unwrap().clear();
257    }
258}