##// END OF EJS Templates
dirstate-tree: simplify the control flow in the Node.insert method...
marmoute -
r46365:ae2873e9 default
parent child Browse files
Show More
@@ -1,395 +1,398 b''
1 1 // node.rs
2 2 //
3 3 // Copyright 2020, Raphaël Gomès <rgomes@octobus.net>
4 4 //
5 5 // This software may be used and distributed according to the terms of the
6 6 // GNU General Public License version 2 or any later version.
7 7
8 8 use super::iter::Iter;
9 9 use crate::utils::hg_path::HgPathBuf;
10 10 use crate::{DirstateEntry, EntryState, FastHashMap};
11 11
12 12 /// Represents a filesystem directory in the dirstate tree
13 13 #[derive(Debug, Default, Clone, PartialEq)]
14 14 pub struct Directory {
15 15 /// Contains the old file information if it existed between changesets.
16 16 /// Happens if a file `foo` is marked as removed, removed from the
17 17 /// filesystem then a directory `foo` is created and at least one of its
18 18 /// descendents is added to Mercurial.
19 19 pub(super) was_file: Option<Box<File>>,
20 20 pub(super) children: FastHashMap<Vec<u8>, Node>,
21 21 }
22 22
23 23 /// Represents a filesystem file (or symlink) in the dirstate tree
24 24 #[derive(Debug, Clone, PartialEq)]
25 25 pub struct File {
26 26 /// Contains the old structure if it existed between changesets.
27 27 /// Happens all descendents of `foo` marked as removed and removed from
28 28 /// the filesystem, then a file `foo` is created and added to Mercurial.
29 29 pub(super) was_directory: Option<Box<Directory>>,
30 30 pub(super) entry: DirstateEntry,
31 31 }
32 32
33 33 #[derive(Debug, Clone, PartialEq)]
34 34 pub enum NodeKind {
35 35 Directory(Directory),
36 36 File(File),
37 37 }
38 38
39 39 #[derive(Debug, Default, Clone, PartialEq)]
40 40 pub struct Node {
41 41 pub kind: NodeKind,
42 42 }
43 43
44 44 impl Default for NodeKind {
45 45 fn default() -> Self {
46 46 NodeKind::Directory(Default::default())
47 47 }
48 48 }
49 49
50 50 impl Node {
51 51 pub fn insert(
52 52 &mut self,
53 53 path: &[u8],
54 54 new_entry: DirstateEntry,
55 55 ) -> InsertResult {
56 56 let mut split = path.splitn(2, |&c| c == b'/');
57 57 let head = split.next().unwrap_or(b"");
58 58 let tail = split.next().unwrap_or(b"");
59 59
60 60 // Are we're modifying the current file ? Is the the end of the path ?
61 61 let is_current_file = tail.is_empty() && head.is_empty();
62 62
63 // Potentially Replace the current file with a directory if it's marked
64 // as `Removed`
65 if !is_current_file {
63 66 if let NodeKind::File(file) = &mut self.kind {
67 if file.entry.state == EntryState::Removed {
68 self.kind = NodeKind::Directory(Directory {
69 was_file: Some(Box::from(file.clone())),
70 children: Default::default(),
71 })
72 }
73 }
74 }
75 match &mut self.kind {
76 NodeKind::Directory(directory) => {
77 Node::insert_in_directory(directory, new_entry, head, tail)
78 }
79 NodeKind::File(file) => {
64 80 if is_current_file {
65 81 let new = Self {
66 82 kind: NodeKind::File(File {
67 83 entry: new_entry,
68 84 ..file.clone()
69 85 }),
70 86 };
71 return InsertResult {
87 InsertResult {
72 88 did_insert: false,
73 89 old_entry: Some(std::mem::replace(self, new)),
74 };
90 }
75 91 } else {
76 92 match file.entry.state {
77 // Only replace the current file with a directory if it's
78 // marked as `Removed`
79 93 EntryState::Removed => {
80 self.kind = NodeKind::Directory(Directory {
81 was_file: Some(Box::from(file.clone())),
82 children: Default::default(),
83 })
94 unreachable!("Removed file turning into a directory was dealt with earlier")
84 95 }
85 96 _ => {
86 return Node::insert_in_file(
97 Node::insert_in_file(
87 98 file, new_entry, head, tail,
88 99 )
89 100 }
90 101 }
91 102 }
92 103 }
93
94 match &mut self.kind {
95 NodeKind::Directory(directory) => {
96 Node::insert_in_directory(directory, new_entry, head, tail)
97 }
98 NodeKind::File(_) => {
99 unreachable!("The file case has already been handled")
100 }
101 104 }
102 105 }
103 106
104 107 /// The current file still exists and is not marked as `Removed`.
105 108 /// Insert the entry in its `was_directory`.
106 109 fn insert_in_file(
107 110 file: &mut File,
108 111 new_entry: DirstateEntry,
109 112 head: &[u8],
110 113 tail: &[u8],
111 114 ) -> InsertResult {
112 115 if let Some(d) = &mut file.was_directory {
113 116 Node::insert_in_directory(d, new_entry, head, tail)
114 117 } else {
115 118 let mut dir = Directory {
116 119 was_file: None,
117 120 children: FastHashMap::default(),
118 121 };
119 122 let res =
120 123 Node::insert_in_directory(&mut dir, new_entry, head, tail);
121 124 file.was_directory = Some(Box::new(dir));
122 125 res
123 126 }
124 127 }
125 128
126 129 /// Insert an entry in the subtree of `directory`
127 130 fn insert_in_directory(
128 131 directory: &mut Directory,
129 132 new_entry: DirstateEntry,
130 133 head: &[u8],
131 134 tail: &[u8],
132 135 ) -> InsertResult {
133 136 let mut res = InsertResult::default();
134 137
135 138 if let Some(node) = directory.children.get_mut(head) {
136 139 // Node exists
137 140 match &mut node.kind {
138 141 NodeKind::Directory(subdir) => {
139 142 if tail.is_empty() {
140 143 let becomes_file = Self {
141 144 kind: NodeKind::File(File {
142 145 was_directory: Some(Box::from(subdir.clone())),
143 146 entry: new_entry,
144 147 }),
145 148 };
146 149 let old_entry = directory
147 150 .children
148 151 .insert(head.to_owned(), becomes_file);
149 152 return InsertResult {
150 153 did_insert: true,
151 154 old_entry,
152 155 };
153 156 } else {
154 157 res = node.insert(tail, new_entry);
155 158 }
156 159 }
157 160 NodeKind::File(_) => {
158 161 res = node.insert(tail, new_entry);
159 162 }
160 163 }
161 164 } else if tail.is_empty() {
162 165 // File does not already exist
163 166 directory.children.insert(
164 167 head.to_owned(),
165 168 Self {
166 169 kind: NodeKind::File(File {
167 170 was_directory: None,
168 171 entry: new_entry,
169 172 }),
170 173 },
171 174 );
172 175 res.did_insert = true;
173 176 } else {
174 177 // Directory does not already exist
175 178 let mut nested = Self {
176 179 kind: NodeKind::Directory(Directory {
177 180 was_file: None,
178 181 children: Default::default(),
179 182 }),
180 183 };
181 184 res = nested.insert(tail, new_entry);
182 185 directory.children.insert(head.to_owned(), nested);
183 186 }
184 187 res
185 188 }
186 189
187 190 /// Removes an entry from the tree, returns a `RemoveResult`.
188 191 pub fn remove(&mut self, path: &[u8]) -> RemoveResult {
189 192 let empty_result = RemoveResult::default();
190 193 if path.is_empty() {
191 194 return empty_result;
192 195 }
193 196 let mut split = path.splitn(2, |&c| c == b'/');
194 197 let head = split.next();
195 198 let tail = split.next().unwrap_or(b"");
196 199
197 200 let head = match head {
198 201 None => {
199 202 return empty_result;
200 203 }
201 204 Some(h) => h,
202 205 };
203 206 if head == path {
204 207 match &mut self.kind {
205 208 NodeKind::Directory(d) => {
206 209 return Node::remove_from_directory(head, d);
207 210 }
208 211 NodeKind::File(f) => {
209 212 if let Some(d) = &mut f.was_directory {
210 213 let RemoveResult { old_entry, .. } =
211 214 Node::remove_from_directory(head, d);
212 215 return RemoveResult {
213 216 cleanup: false,
214 217 old_entry,
215 218 };
216 219 }
217 220 }
218 221 }
219 222 empty_result
220 223 } else {
221 224 // Look into the dirs
222 225 match &mut self.kind {
223 226 NodeKind::Directory(d) => {
224 227 if let Some(child) = d.children.get_mut(head) {
225 228 let mut res = child.remove(tail);
226 229 if res.cleanup {
227 230 d.children.remove(head);
228 231 }
229 232 res.cleanup =
230 233 d.children.is_empty() && d.was_file.is_none();
231 234 res
232 235 } else {
233 236 empty_result
234 237 }
235 238 }
236 239 NodeKind::File(f) => {
237 240 if let Some(d) = &mut f.was_directory {
238 241 if let Some(child) = d.children.get_mut(head) {
239 242 let RemoveResult { cleanup, old_entry } =
240 243 child.remove(tail);
241 244 if cleanup {
242 245 d.children.remove(head);
243 246 }
244 247 if d.children.is_empty() && d.was_file.is_none() {
245 248 f.was_directory = None;
246 249 }
247 250
248 251 return RemoveResult {
249 252 cleanup: false,
250 253 old_entry,
251 254 };
252 255 }
253 256 }
254 257 empty_result
255 258 }
256 259 }
257 260 }
258 261 }
259 262
260 263 fn remove_from_directory(head: &[u8], d: &mut Directory) -> RemoveResult {
261 264 if let Some(node) = d.children.get_mut(head) {
262 265 return match &mut node.kind {
263 266 NodeKind::Directory(d) => {
264 267 if let Some(f) = &mut d.was_file {
265 268 let entry = f.entry;
266 269 d.was_file = None;
267 270 RemoveResult {
268 271 cleanup: false,
269 272 old_entry: Some(entry),
270 273 }
271 274 } else {
272 275 RemoveResult::default()
273 276 }
274 277 }
275 278 NodeKind::File(f) => {
276 279 let entry = f.entry;
277 280 let mut cleanup = false;
278 281 match &f.was_directory {
279 282 None => {
280 283 if d.children.len() == 1 {
281 284 cleanup = true;
282 285 }
283 286 d.children.remove(head);
284 287 }
285 288 Some(dir) => {
286 289 node.kind = NodeKind::Directory(*dir.clone());
287 290 }
288 291 }
289 292
290 293 RemoveResult {
291 294 cleanup,
292 295 old_entry: Some(entry),
293 296 }
294 297 }
295 298 };
296 299 }
297 300 RemoveResult::default()
298 301 }
299 302
300 303 pub fn get(&self, path: &[u8]) -> Option<&Node> {
301 304 if path.is_empty() {
302 305 return Some(&self);
303 306 }
304 307 let mut split = path.splitn(2, |&c| c == b'/');
305 308 let head = split.next();
306 309 let tail = split.next().unwrap_or(b"");
307 310
308 311 let head = match head {
309 312 None => {
310 313 return Some(&self);
311 314 }
312 315 Some(h) => h,
313 316 };
314 317 match &self.kind {
315 318 NodeKind::Directory(d) => {
316 319 if let Some(child) = d.children.get(head) {
317 320 return child.get(tail);
318 321 }
319 322 }
320 323 NodeKind::File(f) => {
321 324 if let Some(d) = &f.was_directory {
322 325 if let Some(child) = d.children.get(head) {
323 326 return child.get(tail);
324 327 }
325 328 }
326 329 }
327 330 }
328 331
329 332 None
330 333 }
331 334
332 335 pub fn get_mut(&mut self, path: &[u8]) -> Option<&mut NodeKind> {
333 336 if path.is_empty() {
334 337 return Some(&mut self.kind);
335 338 }
336 339 let mut split = path.splitn(2, |&c| c == b'/');
337 340 let head = split.next();
338 341 let tail = split.next().unwrap_or(b"");
339 342
340 343 let head = match head {
341 344 None => {
342 345 return Some(&mut self.kind);
343 346 }
344 347 Some(h) => h,
345 348 };
346 349 match &mut self.kind {
347 350 NodeKind::Directory(d) => {
348 351 if let Some(child) = d.children.get_mut(head) {
349 352 return child.get_mut(tail);
350 353 }
351 354 }
352 355 NodeKind::File(f) => {
353 356 if let Some(d) = &mut f.was_directory {
354 357 if let Some(child) = d.children.get_mut(head) {
355 358 return child.get_mut(tail);
356 359 }
357 360 }
358 361 }
359 362 }
360 363
361 364 None
362 365 }
363 366
364 367 pub fn iter(&self) -> Iter {
365 368 Iter::new(self)
366 369 }
367 370 }
368 371
369 372 /// Information returned to the caller of an `insert` operation for integrity.
370 373 #[derive(Debug, Default)]
371 374 pub struct InsertResult {
372 375 /// Whether the insertion resulted in an actual insertion and not an
373 376 /// update
374 377 pub(super) did_insert: bool,
375 378 /// The entry that was replaced, if it exists
376 379 pub(super) old_entry: Option<Node>,
377 380 }
378 381
379 382 /// Information returned to the caller of a `remove` operation integrity.
380 383 #[derive(Debug, Default)]
381 384 pub struct RemoveResult {
382 385 /// If the caller needs to remove the current node
383 386 pub(super) cleanup: bool,
384 387 /// The entry that was replaced, if it exists
385 388 pub(super) old_entry: Option<DirstateEntry>,
386 389 }
387 390
388 391 impl<'a> IntoIterator for &'a Node {
389 392 type Item = (HgPathBuf, DirstateEntry);
390 393 type IntoIter = Iter<'a>;
391 394
392 395 fn into_iter(self) -> Self::IntoIter {
393 396 self.iter()
394 397 }
395 398 }
General Comments 0
You need to be logged in to leave comments. Login now