##// END OF EJS Templates
rust: cleanup some white space in a dock...
marmoute -
r46301:91fafafd default
parent child Browse files
Show More
@@ -1,682 +1,682 b''
1 1 // tree.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 super::node::{Directory, Node, NodeKind};
10 10 use crate::dirstate::dirstate_tree::iter::FsIter;
11 11 use crate::dirstate::dirstate_tree::node::{InsertResult, RemoveResult};
12 12 use crate::utils::hg_path::{HgPath, HgPathBuf};
13 13 use crate::DirstateEntry;
14 14 use std::path::PathBuf;
15 15
16 16 /// A specialized tree to represent the Mercurial dirstate.
17 17 ///
18 18 /// # Advantages over a flat structure
19 19 ///
20 20 /// The dirstate is inherently hierarchical, since it's a representation of the
21 21 /// file structure of the project. The current dirstate format is flat, and
22 22 /// while that affords us potentially great (unordered) iteration speeds, the
23 23 /// need to retrieve a given path is great enough that you need some kind of
24 24 /// hashmap or tree in a lot of cases anyway.
25 25 ///
26 26 /// Going with a tree allows us to be smarter:
27 27 /// - Skipping an ignored directory means we don't visit its entire subtree
28 28 /// - Security auditing does not need to reconstruct paths backwards to check
29 29 /// for symlinked directories, this can be done during the iteration in a
30 30 /// very efficient fashion
31 31 /// - We don't need to build the directory information in another struct,
32 32 /// simplifying the code a lot, reducing the memory footprint and
33 33 /// potentially going faster depending on the implementation.
34 34 /// - We can use it to store a (platform-dependent) caching mechanism [1]
35 35 /// - And probably other types of optimizations.
36 36 ///
37 37 /// Only the first two items in this list are implemented as of this commit.
38 38 ///
39 39 /// [1]: https://www.mercurial-scm.org/wiki/DirsCachePlan
40 ///
40 ///
41 41 ///
42 42 /// # Structure
43 43 ///
44 44 /// It's a prefix (radix) tree with no fixed arity, with a granularity of a
45 45 /// folder, allowing it to mimic a filesystem hierarchy:
46 46 ///
47 47 /// ```text
48 48 /// foo/bar
49 49 /// foo/baz
50 50 /// test
51 51 /// ```
52 52 /// Will be represented (simplified) by:
53 53 ///
54 54 /// ```text
55 55 /// Directory(root):
56 56 /// - File("test")
57 57 /// - Directory("foo"):
58 58 /// - File("bar")
59 59 /// - File("baz")
60 60 /// ```
61 61 ///
62 62 /// Moreover, it is special-cased for storing the dirstate and as such handles
63 63 /// cases that a simple `HashMap` would handle, but while preserving the
64 64 /// hierarchy.
65 65 /// For example:
66 66 ///
67 67 /// ```shell
68 68 /// $ touch foo
69 69 /// $ hg add foo
70 70 /// $ hg commit -m "foo"
71 71 /// $ hg remove foo
72 72 /// $ rm foo
73 73 /// $ mkdir foo
74 74 /// $ touch foo/a
75 75 /// $ hg add foo/a
76 76 /// $ hg status
77 77 /// R foo
78 78 /// A foo/a
79 79 /// ```
80 80 /// To represent this in a tree, one needs to keep track of whether any given
81 81 /// file was a directory and whether any given directory was a file at the last
82 82 /// dirstate update. This tree stores that information, but only in the right
83 83 /// circumstances by respecting the high-level rules that prevent nonsensical
84 84 /// structures to exist:
85 85 /// - a file can only be added as a child of another file if the latter is
86 86 /// marked as `Removed`
87 87 /// - a file cannot replace a folder unless all its descendents are removed
88 88 ///
89 89 /// This second rule is not checked by the tree for performance reasons, and
90 90 /// because high-level logic already prevents that state from happening.
91 91 ///
92 92 /// # Ordering
93 93 ///
94 94 /// It makes no guarantee of ordering for now.
95 95 #[derive(Debug, Default, Clone, PartialEq)]
96 96 pub struct Tree {
97 97 pub root: Node,
98 98 files_count: usize,
99 99 }
100 100
101 101 impl Tree {
102 102 pub fn new() -> Self {
103 103 Self {
104 104 root: Node {
105 105 kind: NodeKind::Directory(Directory {
106 106 was_file: None,
107 107 children: Default::default(),
108 108 }),
109 109 },
110 110 files_count: 0,
111 111 }
112 112 }
113 113
114 114 /// How many files (not directories) are stored in the tree, including ones
115 115 /// marked as `Removed`.
116 116 pub fn len(&self) -> usize {
117 117 self.files_count
118 118 }
119 119
120 120 pub fn is_empty(&self) -> bool {
121 121 self.len() == 0
122 122 }
123 123
124 124 /// Inserts a file in the tree and returns the previous entry if any.
125 125 pub fn insert(
126 126 &mut self,
127 127 path: impl AsRef<HgPath>,
128 128 kind: DirstateEntry,
129 129 ) -> Option<DirstateEntry> {
130 130 let old = self.insert_node(path, kind);
131 131 match old?.kind {
132 132 NodeKind::Directory(_) => None,
133 133 NodeKind::File(f) => Some(f.entry),
134 134 }
135 135 }
136 136
137 137 /// Low-level insertion method that returns the previous node (directories
138 138 /// included).
139 139 fn insert_node(
140 140 &mut self,
141 141 path: impl AsRef<HgPath>,
142 142 kind: DirstateEntry,
143 143 ) -> Option<Node> {
144 144 let InsertResult {
145 145 did_insert,
146 146 old_entry,
147 147 } = self.root.insert(path.as_ref().as_bytes(), kind);
148 148 self.files_count += if did_insert { 1 } else { 0 };
149 149 old_entry
150 150 }
151 151
152 152 /// Returns a reference to a node if it exists.
153 153 pub fn get_node(&self, path: impl AsRef<HgPath>) -> Option<&Node> {
154 154 self.root.get(path.as_ref().as_bytes())
155 155 }
156 156
157 157 /// Returns a reference to the entry corresponding to `path` if it exists.
158 158 pub fn get(&self, path: impl AsRef<HgPath>) -> Option<&DirstateEntry> {
159 159 if let Some(node) = self.get_node(&path) {
160 160 return match &node.kind {
161 161 NodeKind::Directory(d) => {
162 162 d.was_file.as_ref().map(|f| &f.entry)
163 163 }
164 164 NodeKind::File(f) => Some(&f.entry),
165 165 };
166 166 }
167 167 None
168 168 }
169 169
170 170 /// Returns `true` if an entry is found for the given `path`.
171 171 pub fn contains_key(&self, path: impl AsRef<HgPath>) -> bool {
172 172 self.get(path).is_some()
173 173 }
174 174
175 175 /// Returns a mutable reference to the entry corresponding to `path` if it
176 176 /// exists.
177 177 pub fn get_mut(
178 178 &mut self,
179 179 path: impl AsRef<HgPath>,
180 180 ) -> Option<&mut DirstateEntry> {
181 181 if let Some(kind) = self.root.get_mut(path.as_ref().as_bytes()) {
182 182 return match kind {
183 183 NodeKind::Directory(d) => {
184 184 d.was_file.as_mut().map(|f| &mut f.entry)
185 185 }
186 186 NodeKind::File(f) => Some(&mut f.entry),
187 187 };
188 188 }
189 189 None
190 190 }
191 191
192 192 /// Returns an iterator over the paths and corresponding entries in the
193 193 /// tree.
194 194 pub fn iter(&self) -> Iter {
195 195 Iter::new(&self.root)
196 196 }
197 197
198 198 /// Returns an iterator of all entries in the tree, with a special
199 199 /// filesystem handling for the directories containing said entries. See
200 200 /// the documentation of `FsIter` for more.
201 201 pub fn fs_iter(&self, root_dir: PathBuf) -> FsIter {
202 202 FsIter::new(&self.root, root_dir)
203 203 }
204 204
205 205 /// Remove the entry at `path` and returns it, if it exists.
206 206 pub fn remove(
207 207 &mut self,
208 208 path: impl AsRef<HgPath>,
209 209 ) -> Option<DirstateEntry> {
210 210 let RemoveResult { old_entry, .. } =
211 211 self.root.remove(path.as_ref().as_bytes());
212 212 self.files_count = self
213 213 .files_count
214 214 .checked_sub(if old_entry.is_some() { 1 } else { 0 })
215 215 .expect("removed too many files");
216 216 old_entry
217 217 }
218 218 }
219 219
220 220 impl<P: AsRef<HgPath>> Extend<(P, DirstateEntry)> for Tree {
221 221 fn extend<T: IntoIterator<Item = (P, DirstateEntry)>>(&mut self, iter: T) {
222 222 for (path, entry) in iter {
223 223 self.insert(path, entry);
224 224 }
225 225 }
226 226 }
227 227
228 228 impl<'a> IntoIterator for &'a Tree {
229 229 type Item = (HgPathBuf, DirstateEntry);
230 230 type IntoIter = Iter<'a>;
231 231
232 232 fn into_iter(self) -> Self::IntoIter {
233 233 self.iter()
234 234 }
235 235 }
236 236
237 237 #[cfg(test)]
238 238 mod tests {
239 239 use super::*;
240 240 use crate::dirstate::dirstate_tree::node::File;
241 241 use crate::{EntryState, FastHashMap};
242 242 use pretty_assertions::assert_eq;
243 243
244 244 impl Node {
245 245 /// Shortcut for getting children of a node in tests.
246 246 fn children(&self) -> Option<&FastHashMap<Vec<u8>, Node>> {
247 247 match &self.kind {
248 248 NodeKind::Directory(d) => Some(&d.children),
249 249 NodeKind::File(_) => None,
250 250 }
251 251 }
252 252 }
253 253
254 254 #[test]
255 255 fn test_dirstate_tree() {
256 256 let mut tree = Tree::new();
257 257
258 258 assert_eq!(
259 259 tree.insert_node(
260 260 HgPath::new(b"we/p"),
261 261 DirstateEntry {
262 262 state: EntryState::Normal,
263 263 mode: 0,
264 264 mtime: 0,
265 265 size: 0
266 266 }
267 267 ),
268 268 None
269 269 );
270 270 dbg!(&tree);
271 271 assert!(tree.get_node(HgPath::new(b"we")).is_some());
272 272 let entry = DirstateEntry {
273 273 state: EntryState::Merged,
274 274 mode: 41,
275 275 mtime: 42,
276 276 size: 43,
277 277 };
278 278 assert_eq!(tree.insert_node(HgPath::new(b"foo/bar"), entry), None);
279 279 assert_eq!(
280 280 tree.get_node(HgPath::new(b"foo/bar")),
281 281 Some(&Node {
282 282 kind: NodeKind::File(File {
283 283 was_directory: None,
284 284 entry
285 285 })
286 286 })
287 287 );
288 288 // We didn't override the first entry we made
289 289 assert!(tree.get_node(HgPath::new(b"we")).is_some(),);
290 290 // Inserting the same key again
291 291 assert_eq!(
292 292 tree.insert_node(HgPath::new(b"foo/bar"), entry),
293 293 Some(Node {
294 294 kind: NodeKind::File(File {
295 295 was_directory: None,
296 296 entry
297 297 }),
298 298 })
299 299 );
300 300 // Inserting the two levels deep
301 301 assert_eq!(tree.insert_node(HgPath::new(b"foo/bar/baz"), entry), None);
302 302 // Getting a file "inside a file" should return `None`
303 303 assert_eq!(tree.get_node(HgPath::new(b"foo/bar/baz/bap"),), None);
304 304
305 305 assert_eq!(
306 306 tree.insert_node(HgPath::new(b"wasdir/subfile"), entry),
307 307 None,
308 308 );
309 309 let removed_entry = DirstateEntry {
310 310 state: EntryState::Removed,
311 311 mode: 0,
312 312 mtime: 0,
313 313 size: 0,
314 314 };
315 315 assert!(tree
316 316 .insert_node(HgPath::new(b"wasdir"), removed_entry)
317 317 .is_some());
318 318
319 319 assert_eq!(
320 320 tree.get_node(HgPath::new(b"wasdir")),
321 321 Some(&Node {
322 322 kind: NodeKind::File(File {
323 323 was_directory: Some(Box::new(Directory {
324 324 was_file: None,
325 325 children: [(
326 326 b"subfile".to_vec(),
327 327 Node {
328 328 kind: NodeKind::File(File {
329 329 was_directory: None,
330 330 entry,
331 331 })
332 332 }
333 333 )]
334 334 .to_vec()
335 335 .into_iter()
336 336 .collect()
337 337 })),
338 338 entry: removed_entry
339 339 })
340 340 })
341 341 );
342 342
343 343 assert!(tree.get(HgPath::new(b"wasdir/subfile")).is_some())
344 344 }
345 345
346 346 #[test]
347 347 fn test_insert_removed() {
348 348 let mut tree = Tree::new();
349 349 let entry = DirstateEntry {
350 350 state: EntryState::Merged,
351 351 mode: 1,
352 352 mtime: 2,
353 353 size: 3,
354 354 };
355 355 let removed_entry = DirstateEntry {
356 356 state: EntryState::Removed,
357 357 mode: 10,
358 358 mtime: 20,
359 359 size: 30,
360 360 };
361 361 assert_eq!(tree.insert_node(HgPath::new(b"foo"), entry), None);
362 362 assert_eq!(
363 363 tree.insert_node(HgPath::new(b"foo/a"), removed_entry),
364 364 None
365 365 );
366 366 // The insert should not turn `foo` into a directory as `foo` is not
367 367 // `Removed`.
368 368 match tree.get_node(HgPath::new(b"foo")).unwrap().kind {
369 369 NodeKind::Directory(_) => panic!("should be a file"),
370 370 NodeKind::File(_) => {}
371 371 }
372 372
373 373 let mut tree = Tree::new();
374 374 let entry = DirstateEntry {
375 375 state: EntryState::Merged,
376 376 mode: 1,
377 377 mtime: 2,
378 378 size: 3,
379 379 };
380 380 let removed_entry = DirstateEntry {
381 381 state: EntryState::Removed,
382 382 mode: 10,
383 383 mtime: 20,
384 384 size: 30,
385 385 };
386 386 // The insert *should* turn `foo` into a directory as it is `Removed`.
387 387 assert_eq!(tree.insert_node(HgPath::new(b"foo"), removed_entry), None);
388 388 assert_eq!(tree.insert_node(HgPath::new(b"foo/a"), entry), None);
389 389 match tree.get_node(HgPath::new(b"foo")).unwrap().kind {
390 390 NodeKind::Directory(_) => {}
391 391 NodeKind::File(_) => panic!("should be a directory"),
392 392 }
393 393 }
394 394
395 395 #[test]
396 396 fn test_get() {
397 397 let mut tree = Tree::new();
398 398 let entry = DirstateEntry {
399 399 state: EntryState::Merged,
400 400 mode: 1,
401 401 mtime: 2,
402 402 size: 3,
403 403 };
404 404 assert_eq!(tree.insert_node(HgPath::new(b"a/b/c"), entry), None);
405 405 assert_eq!(tree.files_count, 1);
406 406 assert_eq!(tree.get(HgPath::new(b"a/b/c")), Some(&entry));
407 407 assert_eq!(tree.get(HgPath::new(b"a/b")), None);
408 408 assert_eq!(tree.get(HgPath::new(b"a")), None);
409 409 assert_eq!(tree.get(HgPath::new(b"a/b/c/d")), None);
410 410 let entry2 = DirstateEntry {
411 411 state: EntryState::Removed,
412 412 mode: 0,
413 413 mtime: 5,
414 414 size: 1,
415 415 };
416 416 // was_directory
417 417 assert_eq!(tree.insert(HgPath::new(b"a/b"), entry2), None);
418 418 assert_eq!(tree.files_count, 2);
419 419 assert_eq!(tree.get(HgPath::new(b"a/b")), Some(&entry2));
420 420 assert_eq!(tree.get(HgPath::new(b"a/b/c")), Some(&entry));
421 421
422 422 let mut tree = Tree::new();
423 423
424 424 // was_file
425 425 assert_eq!(tree.insert_node(HgPath::new(b"a"), entry), None);
426 426 assert_eq!(tree.files_count, 1);
427 427 assert_eq!(tree.insert_node(HgPath::new(b"a/b"), entry2), None);
428 428 assert_eq!(tree.files_count, 2);
429 429 assert_eq!(tree.get(HgPath::new(b"a/b")), Some(&entry2));
430 430 }
431 431
432 432 #[test]
433 433 fn test_get_mut() {
434 434 let mut tree = Tree::new();
435 435 let mut entry = DirstateEntry {
436 436 state: EntryState::Merged,
437 437 mode: 1,
438 438 mtime: 2,
439 439 size: 3,
440 440 };
441 441 assert_eq!(tree.insert_node(HgPath::new(b"a/b/c"), entry), None);
442 442 assert_eq!(tree.files_count, 1);
443 443 assert_eq!(tree.get_mut(HgPath::new(b"a/b/c")), Some(&mut entry));
444 444 assert_eq!(tree.get_mut(HgPath::new(b"a/b")), None);
445 445 assert_eq!(tree.get_mut(HgPath::new(b"a")), None);
446 446 assert_eq!(tree.get_mut(HgPath::new(b"a/b/c/d")), None);
447 447 let mut entry2 = DirstateEntry {
448 448 state: EntryState::Removed,
449 449 mode: 0,
450 450 mtime: 5,
451 451 size: 1,
452 452 };
453 453 // was_directory
454 454 assert_eq!(tree.insert(HgPath::new(b"a/b"), entry2), None);
455 455 assert_eq!(tree.files_count, 2);
456 456 assert_eq!(tree.get_mut(HgPath::new(b"a/b")), Some(&mut entry2));
457 457 assert_eq!(tree.get_mut(HgPath::new(b"a/b/c")), Some(&mut entry));
458 458
459 459 let mut tree = Tree::new();
460 460
461 461 // was_file
462 462 assert_eq!(tree.insert_node(HgPath::new(b"a"), entry), None);
463 463 assert_eq!(tree.files_count, 1);
464 464 assert_eq!(tree.insert_node(HgPath::new(b"a/b"), entry2), None);
465 465 assert_eq!(tree.files_count, 2);
466 466 assert_eq!(tree.get_mut(HgPath::new(b"a/b")), Some(&mut entry2));
467 467 }
468 468
469 469 #[test]
470 470 fn test_remove() {
471 471 let mut tree = Tree::new();
472 472 assert_eq!(tree.files_count, 0);
473 473 assert_eq!(tree.remove(HgPath::new(b"foo")), None);
474 474 assert_eq!(tree.files_count, 0);
475 475
476 476 let entry = DirstateEntry {
477 477 state: EntryState::Normal,
478 478 mode: 0,
479 479 mtime: 0,
480 480 size: 0,
481 481 };
482 482 assert_eq!(tree.insert_node(HgPath::new(b"a/b/c"), entry), None);
483 483 assert_eq!(tree.files_count, 1);
484 484
485 485 assert_eq!(tree.remove(HgPath::new(b"a/b/c")), Some(entry));
486 486 assert_eq!(tree.files_count, 0);
487 487
488 488 assert_eq!(tree.insert_node(HgPath::new(b"a/b/x"), entry), None);
489 489 assert_eq!(tree.insert_node(HgPath::new(b"a/b/y"), entry), None);
490 490 assert_eq!(tree.insert_node(HgPath::new(b"a/b/z"), entry), None);
491 491 assert_eq!(tree.insert_node(HgPath::new(b"x"), entry), None);
492 492 assert_eq!(tree.insert_node(HgPath::new(b"y"), entry), None);
493 493 assert_eq!(tree.files_count, 5);
494 494
495 495 assert_eq!(tree.remove(HgPath::new(b"a/b/x")), Some(entry));
496 496 assert_eq!(tree.files_count, 4);
497 497 assert_eq!(tree.remove(HgPath::new(b"a/b/x")), None);
498 498 assert_eq!(tree.files_count, 4);
499 499 assert_eq!(tree.remove(HgPath::new(b"a/b/y")), Some(entry));
500 500 assert_eq!(tree.files_count, 3);
501 501 assert_eq!(tree.remove(HgPath::new(b"a/b/z")), Some(entry));
502 502 assert_eq!(tree.files_count, 2);
503 503
504 504 assert_eq!(tree.remove(HgPath::new(b"x")), Some(entry));
505 505 assert_eq!(tree.files_count, 1);
506 506 assert_eq!(tree.remove(HgPath::new(b"y")), Some(entry));
507 507 assert_eq!(tree.files_count, 0);
508 508
509 509 // `a` should have been cleaned up, no more files anywhere in its
510 510 // descendents
511 511 assert_eq!(tree.get_node(HgPath::new(b"a")), None);
512 512 assert_eq!(tree.root.children().unwrap().len(), 0);
513 513
514 514 let removed_entry = DirstateEntry {
515 515 state: EntryState::Removed,
516 516 ..entry
517 517 };
518 518 assert_eq!(tree.insert(HgPath::new(b"a"), removed_entry), None);
519 519 assert_eq!(tree.insert_node(HgPath::new(b"a/b/x"), entry), None);
520 520 assert_eq!(tree.files_count, 2);
521 521 dbg!(&tree);
522 522 assert_eq!(tree.remove(HgPath::new(b"a")), Some(removed_entry));
523 523 assert_eq!(tree.files_count, 1);
524 524 dbg!(&tree);
525 525 assert_eq!(tree.remove(HgPath::new(b"a/b/x")), Some(entry));
526 526 assert_eq!(tree.files_count, 0);
527 527
528 528 // The entire tree should have been cleaned up, no more files anywhere
529 529 // in its descendents
530 530 assert_eq!(tree.root.children().unwrap().len(), 0);
531 531
532 532 let removed_entry = DirstateEntry {
533 533 state: EntryState::Removed,
534 534 ..entry
535 535 };
536 536 assert_eq!(tree.insert(HgPath::new(b"a"), entry), None);
537 537 assert_eq!(
538 538 tree.insert_node(HgPath::new(b"a/b/x"), removed_entry),
539 539 None
540 540 );
541 541 assert_eq!(tree.files_count, 2);
542 542 dbg!(&tree);
543 543 assert_eq!(tree.remove(HgPath::new(b"a")), Some(entry));
544 544 assert_eq!(tree.files_count, 1);
545 545 dbg!(&tree);
546 546 assert_eq!(tree.remove(HgPath::new(b"a/b/x")), Some(removed_entry));
547 547 assert_eq!(tree.files_count, 0);
548 548
549 549 dbg!(&tree);
550 550 // The entire tree should have been cleaned up, no more files anywhere
551 551 // in its descendents
552 552 assert_eq!(tree.root.children().unwrap().len(), 0);
553 553
554 554 assert_eq!(tree.insert(HgPath::new(b"d"), entry), None);
555 555 assert_eq!(tree.insert(HgPath::new(b"d/d/d"), entry), None);
556 556 assert_eq!(tree.files_count, 2);
557 557
558 558 // Deleting the nested file should not delete the top directory as it
559 559 // used to be a file
560 560 assert_eq!(tree.remove(HgPath::new(b"d/d/d")), Some(entry));
561 561 assert_eq!(tree.files_count, 1);
562 562 assert!(tree.get_node(HgPath::new(b"d")).is_some());
563 563 assert!(tree.remove(HgPath::new(b"d")).is_some());
564 564 assert_eq!(tree.files_count, 0);
565 565
566 566 // Deleting the nested file should not delete the top file (other way
567 567 // around from the last case)
568 568 assert_eq!(tree.insert(HgPath::new(b"a/a"), entry), None);
569 569 assert_eq!(tree.files_count, 1);
570 570 assert_eq!(tree.insert(HgPath::new(b"a"), entry), None);
571 571 assert_eq!(tree.files_count, 2);
572 572 dbg!(&tree);
573 573 assert_eq!(tree.remove(HgPath::new(b"a/a")), Some(entry));
574 574 assert_eq!(tree.files_count, 1);
575 575 dbg!(&tree);
576 576 assert!(tree.get_node(HgPath::new(b"a")).is_some());
577 577 assert!(tree.get_node(HgPath::new(b"a/a")).is_none());
578 578 }
579 579
580 580 #[test]
581 581 fn test_was_directory() {
582 582 let mut tree = Tree::new();
583 583
584 584 let entry = DirstateEntry {
585 585 state: EntryState::Removed,
586 586 mode: 0,
587 587 mtime: 0,
588 588 size: 0,
589 589 };
590 590 assert_eq!(tree.insert_node(HgPath::new(b"a/b/c"), entry), None);
591 591 assert_eq!(tree.files_count, 1);
592 592
593 593 assert!(tree.insert_node(HgPath::new(b"a"), entry).is_some());
594 594 let new_a = tree.root.children().unwrap().get(&b"a".to_vec()).unwrap();
595 595
596 596 match &new_a.kind {
597 597 NodeKind::Directory(_) => panic!(),
598 598 NodeKind::File(f) => {
599 599 let dir = f.was_directory.clone().unwrap();
600 600 let c = dir
601 601 .children
602 602 .get(&b"b".to_vec())
603 603 .unwrap()
604 604 .children()
605 605 .unwrap()
606 606 .get(&b"c".to_vec())
607 607 .unwrap();
608 608
609 609 assert_eq!(
610 610 match &c.kind {
611 611 NodeKind::Directory(_) => panic!(),
612 612 NodeKind::File(f) => f.entry,
613 613 },
614 614 entry
615 615 );
616 616 }
617 617 }
618 618 assert_eq!(tree.files_count, 2);
619 619 dbg!(&tree);
620 620 assert_eq!(tree.remove(HgPath::new(b"a/b/c")), Some(entry));
621 621 assert_eq!(tree.files_count, 1);
622 622 dbg!(&tree);
623 623 let a = tree.get_node(HgPath::new(b"a")).unwrap();
624 624 match &a.kind {
625 625 NodeKind::Directory(_) => panic!(),
626 626 NodeKind::File(f) => {
627 627 // Directory in `was_directory` was emptied, should be removed
628 628 assert_eq!(f.was_directory, None);
629 629 }
630 630 }
631 631 }
632 632 #[test]
633 633 fn test_extend() {
634 634 let insertions = [
635 635 (
636 636 HgPathBuf::from_bytes(b"d"),
637 637 DirstateEntry {
638 638 state: EntryState::Added,
639 639 mode: 0,
640 640 mtime: -1,
641 641 size: -1,
642 642 },
643 643 ),
644 644 (
645 645 HgPathBuf::from_bytes(b"b"),
646 646 DirstateEntry {
647 647 state: EntryState::Normal,
648 648 mode: 33188,
649 649 mtime: 1599647984,
650 650 size: 2,
651 651 },
652 652 ),
653 653 (
654 654 HgPathBuf::from_bytes(b"a/a"),
655 655 DirstateEntry {
656 656 state: EntryState::Normal,
657 657 mode: 33188,
658 658 mtime: 1599647984,
659 659 size: 2,
660 660 },
661 661 ),
662 662 (
663 663 HgPathBuf::from_bytes(b"d/d/d"),
664 664 DirstateEntry {
665 665 state: EntryState::Removed,
666 666 mode: 0,
667 667 mtime: 0,
668 668 size: 0,
669 669 },
670 670 ),
671 671 ]
672 672 .to_vec();
673 673 let mut tree = Tree::new();
674 674
675 675 tree.extend(insertions.clone().into_iter());
676 676
677 677 for (path, _) in &insertions {
678 678 assert!(tree.contains_key(path), true);
679 679 }
680 680 assert_eq!(tree.files_count, 4);
681 681 }
682 682 }
General Comments 0
You need to be logged in to leave comments. Login now