just playing with tangled
at globpattern 160 lines 5.5 kB view raw
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#![allow(missing_docs)] 16 17use std::collections::BTreeMap; 18use std::sync::Arc; 19 20use pollster::FutureExt as _; 21 22use crate::backend; 23use crate::backend::BackendResult; 24use crate::backend::TreeId; 25use crate::backend::TreeValue; 26use crate::repo_path::RepoPath; 27use crate::repo_path::RepoPathBuf; 28use crate::store::Store; 29use crate::tree::Tree; 30 31#[derive(Debug)] 32enum Override { 33 Tombstone, 34 Replace(TreeValue), 35} 36 37#[derive(Debug)] 38pub struct TreeBuilder { 39 store: Arc<Store>, 40 base_tree_id: TreeId, 41 overrides: BTreeMap<RepoPathBuf, Override>, 42} 43 44impl TreeBuilder { 45 pub fn new(store: Arc<Store>, base_tree_id: TreeId) -> TreeBuilder { 46 let overrides = BTreeMap::new(); 47 TreeBuilder { 48 store, 49 base_tree_id, 50 overrides, 51 } 52 } 53 54 pub fn store(&self) -> &Store { 55 self.store.as_ref() 56 } 57 58 pub fn set(&mut self, path: RepoPathBuf, value: TreeValue) { 59 assert!(!path.is_root()); 60 self.overrides.insert(path, Override::Replace(value)); 61 } 62 63 pub fn remove(&mut self, path: RepoPathBuf) { 64 assert!(!path.is_root()); 65 self.overrides.insert(path, Override::Tombstone); 66 } 67 68 pub fn set_or_remove(&mut self, path: RepoPathBuf, value: Option<TreeValue>) { 69 assert!(!path.is_root()); 70 if let Some(value) = value { 71 self.overrides.insert(path, Override::Replace(value)); 72 } else { 73 self.overrides.insert(path, Override::Tombstone); 74 } 75 } 76 77 pub fn write_tree(self) -> BackendResult<TreeId> { 78 if self.overrides.is_empty() { 79 return Ok(self.base_tree_id); 80 } 81 82 let mut trees_to_write = self.get_base_trees()?; 83 84 // Update entries in parent trees for file overrides 85 for (path, file_override) in self.overrides { 86 let (dir, basename) = path.split().unwrap(); 87 let tree = trees_to_write.get_mut(dir).unwrap(); 88 match file_override { 89 Override::Replace(value) => { 90 tree.set(basename.to_owned(), value); 91 } 92 Override::Tombstone => { 93 tree.remove(basename); 94 } 95 } 96 } 97 98 // Write trees in reverse lexicographical order, starting with trees without 99 // children. 100 // TODO: Writing trees concurrently should help on high-latency backends 101 let store = &self.store; 102 while let Some((dir, tree)) = trees_to_write.pop_last() { 103 if let Some((parent, basename)) = dir.split() { 104 let parent_tree = trees_to_write.get_mut(parent).unwrap(); 105 if tree.is_empty() { 106 if let Some(TreeValue::Tree(_)) = parent_tree.value(basename) { 107 parent_tree.remove(basename); 108 } else { 109 // Entry would have been replaced with file (see above) 110 } 111 } else { 112 let tree = store.write_tree(&dir, tree).block_on()?; 113 parent_tree.set(basename.to_owned(), TreeValue::Tree(tree.id().clone())); 114 } 115 } else { 116 // We're writing the root tree. Write it even if empty. Return its id. 117 assert!(trees_to_write.is_empty()); 118 let written_tree = store.write_tree(&dir, tree).block_on()?; 119 return Ok(written_tree.id().clone()); 120 } 121 } 122 123 unreachable!("trees_to_write must contain the root tree"); 124 } 125 126 fn get_base_trees(&self) -> BackendResult<BTreeMap<RepoPathBuf, backend::Tree>> { 127 let store = &self.store; 128 let mut tree_cache = { 129 let dir = RepoPathBuf::root(); 130 let tree = store.get_tree(dir.clone(), &self.base_tree_id)?; 131 BTreeMap::from([(dir, tree)]) 132 }; 133 134 fn populate_trees<'a>( 135 tree_cache: &'a mut BTreeMap<RepoPathBuf, Tree>, 136 store: &Arc<Store>, 137 dir: &RepoPath, 138 ) -> BackendResult<&'a Tree> { 139 // `if let Some(tree) = ...` doesn't pass lifetime check as of Rust 1.84.0 140 if tree_cache.contains_key(dir) { 141 return Ok(tree_cache.get(dir).unwrap()); 142 } 143 let (parent, basename) = dir.split().expect("root must be populated"); 144 let tree = populate_trees(tree_cache, store, parent)? 145 .sub_tree(basename)? 146 .unwrap_or_else(|| Tree::empty(store.clone(), dir.to_owned())); 147 Ok(tree_cache.entry(dir.to_owned()).or_insert(tree)) 148 } 149 150 for path in self.overrides.keys() { 151 let parent = path.parent().unwrap(); 152 populate_trees(&mut tree_cache, store, parent)?; 153 } 154 155 Ok(tree_cache 156 .into_iter() 157 .map(|(dir, tree)| (dir, tree.data().clone())) 158 .collect()) 159 } 160}