1#![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 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 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 }
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 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 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 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}