Skip to main content

jj_lib/
tree_builder.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::collections::BTreeMap;
18use std::sync::Arc;
19
20use futures::TryStreamExt as _;
21use futures::future::BoxFuture;
22use futures::stream::FuturesUnordered;
23
24use crate::backend;
25use crate::backend::BackendResult;
26use crate::backend::TreeId;
27use crate::backend::TreeValue;
28use crate::repo_path::RepoPathBuf;
29use crate::repo_path::RepoPathComponentBuf;
30use crate::repo_path::RepoPathTree;
31use crate::store::Store;
32use crate::tree::Tree;
33
34#[derive(Debug)]
35enum Override {
36    Tombstone,
37    Replace(TreeValue),
38}
39
40#[derive(Debug)]
41pub struct TreeBuilder {
42    store: Arc<Store>,
43    base_tree_id: TreeId,
44    overrides: BTreeMap<RepoPathBuf, Override>,
45}
46
47impl TreeBuilder {
48    pub fn new(store: Arc<Store>, base_tree_id: TreeId) -> Self {
49        let overrides = BTreeMap::new();
50        Self {
51            store,
52            base_tree_id,
53            overrides,
54        }
55    }
56
57    pub fn store(&self) -> &Store {
58        self.store.as_ref()
59    }
60
61    pub fn set(&mut self, path: RepoPathBuf, value: TreeValue) {
62        assert!(!path.is_root());
63        self.overrides.insert(path, Override::Replace(value));
64    }
65
66    pub fn remove(&mut self, path: RepoPathBuf) {
67        assert!(!path.is_root());
68        self.overrides.insert(path, Override::Tombstone);
69    }
70
71    pub fn set_or_remove(&mut self, path: RepoPathBuf, value: Option<TreeValue>) {
72        assert!(!path.is_root());
73        if let Some(value) = value {
74            self.overrides.insert(path, Override::Replace(value));
75        } else {
76            self.overrides.insert(path, Override::Tombstone);
77        }
78    }
79
80    pub async fn write_tree(self) -> BackendResult<TreeId> {
81        if self.overrides.is_empty() {
82            return Ok(self.base_tree_id);
83        }
84
85        let mut trees_to_write = self.get_base_trees().await?;
86
87        // Update entries in parent trees for file overrides
88        for (path, file_override) in self.overrides {
89            let (dir, basename) = path.split().unwrap();
90            let tree_entries = trees_to_write.get_mut(dir).unwrap();
91            match file_override {
92                Override::Replace(value) => {
93                    tree_entries.insert(basename.to_owned(), value);
94                }
95                Override::Tombstone => {
96                    tree_entries.remove(basename);
97                }
98            }
99        }
100
101        // Write trees in reverse lexicographical order, starting with trees without
102        // children.
103        // TODO: Writing trees concurrently should help on high-latency backends
104        let store = &self.store;
105        while let Some((dir, cur_entries)) = trees_to_write.pop_last() {
106            if let Some((parent, basename)) = dir.split() {
107                let parent_entries = trees_to_write.get_mut(parent).unwrap();
108                if cur_entries.is_empty() {
109                    if let Some(TreeValue::Tree(_)) = parent_entries.get(basename) {
110                        parent_entries.remove(basename);
111                    } else {
112                        // Entry would have been replaced with file (see above)
113                    }
114                } else {
115                    let data =
116                        backend::Tree::from_sorted_entries(cur_entries.into_iter().collect());
117                    let tree = store.write_tree(&dir, data).await?;
118                    parent_entries.insert(basename.to_owned(), TreeValue::Tree(tree.id().clone()));
119                }
120            } else {
121                // We're writing the root tree. Write it even if empty. Return its id.
122                assert!(trees_to_write.is_empty());
123                let data = backend::Tree::from_sorted_entries(cur_entries.into_iter().collect());
124                let written_tree = store.write_tree(&dir, data).await?;
125                return Ok(written_tree.id().clone());
126            }
127        }
128
129        unreachable!("trees_to_write must contain the root tree");
130    }
131
132    async fn get_base_trees(
133        &self,
134    ) -> BackendResult<BTreeMap<RepoPathBuf, BTreeMap<RepoPathComponentBuf, TreeValue>>> {
135        // All base trees we need
136        let mut needed_dirs: RepoPathTree<()> = RepoPathTree::default();
137        for path in self.overrides.keys() {
138            if let Some(dir) = path.parent() {
139                needed_dirs.add(dir);
140            }
141        }
142
143        let mut tree_reads: FuturesUnordered<BoxFuture<'_, BackendResult<(RepoPathBuf, Tree)>>> =
144            FuturesUnordered::new();
145
146        // Schedule reading the root tree
147        tree_reads.push(Box::pin(async move {
148            let root_dir = RepoPathBuf::root();
149            let tree = self
150                .store
151                .get_tree(root_dir.clone(), &self.base_tree_id)
152                .await?;
153            Ok((root_dir, tree))
154        }));
155
156        let mut base_trees = BTreeMap::new();
157        while let Some((dir, tree)) = tree_reads.try_next().await? {
158            if let Some(node) = needed_dirs.get(&dir) {
159                for (basename, _child) in node.children() {
160                    let basename = basename.to_owned();
161                    let sub_dir = dir.join(&basename);
162                    let tree = tree.clone();
163                    tree_reads.push(Box::pin(async move {
164                        let sub_tree = tree
165                            .sub_tree(&basename)
166                            .await?
167                            .unwrap_or_else(|| Tree::empty(self.store.clone(), sub_dir.clone()));
168                        Ok((sub_dir, sub_tree))
169                    }));
170                }
171            }
172            base_trees.insert(dir, tree);
173        }
174
175        Ok(base_trees
176            .into_iter()
177            .map(|(dir, tree)| {
178                let entries = tree
179                    .data()
180                    .entries()
181                    .map(|entry| (entry.name().to_owned(), entry.value().clone()))
182                    .collect();
183                (dir, entries)
184            })
185            .collect())
186    }
187}