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