##// END OF EJS Templates
rust-dirstatemap: add `each_ancestor` argument to `get_node_mut`...
Raphaël Gomès -
r50022:fcf6f943 default
parent child Browse files
Show More
@@ -1,1861 +1,1870 b''
1 1 use bytes_cast::BytesCast;
2 2 use micro_timer::timed;
3 3 use std::borrow::Cow;
4 4 use std::path::PathBuf;
5 5
6 6 use super::on_disk;
7 7 use super::on_disk::DirstateV2ParseError;
8 8 use super::owning::OwningDirstateMap;
9 9 use super::path_with_basename::WithBasename;
10 10 use crate::dirstate::parsers::pack_entry;
11 11 use crate::dirstate::parsers::packed_entry_size;
12 12 use crate::dirstate::parsers::parse_dirstate_entries;
13 13 use crate::dirstate::CopyMapIter;
14 14 use crate::dirstate::DirstateV2Data;
15 15 use crate::dirstate::ParentFileData;
16 16 use crate::dirstate::StateMapIter;
17 17 use crate::dirstate::TruncatedTimestamp;
18 18 use crate::matchers::Matcher;
19 19 use crate::utils::hg_path::{HgPath, HgPathBuf};
20 20 use crate::DirstateEntry;
21 21 use crate::DirstateError;
22 22 use crate::DirstateMapError;
23 23 use crate::DirstateParents;
24 24 use crate::DirstateStatus;
25 25 use crate::EntryState;
26 26 use crate::FastHashbrownMap as FastHashMap;
27 27 use crate::PatternFileWarning;
28 28 use crate::StatusError;
29 29 use crate::StatusOptions;
30 30
31 31 /// Append to an existing data file if the amount of unreachable data (not used
32 32 /// anymore) is less than this fraction of the total amount of existing data.
33 33 const ACCEPTABLE_UNREACHABLE_BYTES_RATIO: f32 = 0.5;
34 34
35 35 #[derive(Debug)]
36 36 pub struct DirstateMap<'on_disk> {
37 37 /// Contents of the `.hg/dirstate` file
38 38 pub(super) on_disk: &'on_disk [u8],
39 39
40 40 pub(super) root: ChildNodes<'on_disk>,
41 41
42 42 /// Number of nodes anywhere in the tree that have `.entry.is_some()`.
43 43 pub(super) nodes_with_entry_count: u32,
44 44
45 45 /// Number of nodes anywhere in the tree that have
46 46 /// `.copy_source.is_some()`.
47 47 pub(super) nodes_with_copy_source_count: u32,
48 48
49 49 /// See on_disk::Header
50 50 pub(super) ignore_patterns_hash: on_disk::IgnorePatternsHash,
51 51
52 52 /// How many bytes of `on_disk` are not used anymore
53 53 pub(super) unreachable_bytes: u32,
54 54 }
55 55
56 56 /// Using a plain `HgPathBuf` of the full path from the repository root as a
57 57 /// map key would also work: all paths in a given map have the same parent
58 58 /// path, so comparing full paths gives the same result as comparing base
59 59 /// names. However `HashMap` would waste time always re-hashing the same
60 60 /// string prefix.
61 61 pub(super) type NodeKey<'on_disk> = WithBasename<Cow<'on_disk, HgPath>>;
62 62
63 63 /// Similar to `&'tree Cow<'on_disk, HgPath>`, but can also be returned
64 64 /// for on-disk nodes that don’t actually have a `Cow` to borrow.
65 65 #[derive(Debug)]
66 66 pub(super) enum BorrowedPath<'tree, 'on_disk> {
67 67 InMemory(&'tree HgPathBuf),
68 68 OnDisk(&'on_disk HgPath),
69 69 }
70 70
71 71 #[derive(Debug)]
72 72 pub(super) enum ChildNodes<'on_disk> {
73 73 InMemory(FastHashMap<NodeKey<'on_disk>, Node<'on_disk>>),
74 74 OnDisk(&'on_disk [on_disk::Node]),
75 75 }
76 76
77 77 #[derive(Debug)]
78 78 pub(super) enum ChildNodesRef<'tree, 'on_disk> {
79 79 InMemory(&'tree FastHashMap<NodeKey<'on_disk>, Node<'on_disk>>),
80 80 OnDisk(&'on_disk [on_disk::Node]),
81 81 }
82 82
83 83 #[derive(Debug)]
84 84 pub(super) enum NodeRef<'tree, 'on_disk> {
85 85 InMemory(&'tree NodeKey<'on_disk>, &'tree Node<'on_disk>),
86 86 OnDisk(&'on_disk on_disk::Node),
87 87 }
88 88
89 89 impl<'tree, 'on_disk> BorrowedPath<'tree, 'on_disk> {
90 90 pub fn detach_from_tree(&self) -> Cow<'on_disk, HgPath> {
91 91 match *self {
92 92 BorrowedPath::InMemory(in_memory) => Cow::Owned(in_memory.clone()),
93 93 BorrowedPath::OnDisk(on_disk) => Cow::Borrowed(on_disk),
94 94 }
95 95 }
96 96 }
97 97
98 98 impl<'tree, 'on_disk> std::ops::Deref for BorrowedPath<'tree, 'on_disk> {
99 99 type Target = HgPath;
100 100
101 101 fn deref(&self) -> &HgPath {
102 102 match *self {
103 103 BorrowedPath::InMemory(in_memory) => in_memory,
104 104 BorrowedPath::OnDisk(on_disk) => on_disk,
105 105 }
106 106 }
107 107 }
108 108
109 109 impl Default for ChildNodes<'_> {
110 110 fn default() -> Self {
111 111 ChildNodes::InMemory(Default::default())
112 112 }
113 113 }
114 114
115 115 impl<'on_disk> ChildNodes<'on_disk> {
116 116 pub(super) fn as_ref<'tree>(
117 117 &'tree self,
118 118 ) -> ChildNodesRef<'tree, 'on_disk> {
119 119 match self {
120 120 ChildNodes::InMemory(nodes) => ChildNodesRef::InMemory(nodes),
121 121 ChildNodes::OnDisk(nodes) => ChildNodesRef::OnDisk(nodes),
122 122 }
123 123 }
124 124
125 125 pub(super) fn is_empty(&self) -> bool {
126 126 match self {
127 127 ChildNodes::InMemory(nodes) => nodes.is_empty(),
128 128 ChildNodes::OnDisk(nodes) => nodes.is_empty(),
129 129 }
130 130 }
131 131
132 132 fn make_mut(
133 133 &mut self,
134 134 on_disk: &'on_disk [u8],
135 135 unreachable_bytes: &mut u32,
136 136 ) -> Result<
137 137 &mut FastHashMap<NodeKey<'on_disk>, Node<'on_disk>>,
138 138 DirstateV2ParseError,
139 139 > {
140 140 match self {
141 141 ChildNodes::InMemory(nodes) => Ok(nodes),
142 142 ChildNodes::OnDisk(nodes) => {
143 143 *unreachable_bytes +=
144 144 std::mem::size_of_val::<[on_disk::Node]>(nodes) as u32;
145 145 let nodes = nodes
146 146 .iter()
147 147 .map(|node| {
148 148 Ok((
149 149 node.path(on_disk)?,
150 150 node.to_in_memory_node(on_disk)?,
151 151 ))
152 152 })
153 153 .collect::<Result<_, _>>()?;
154 154 *self = ChildNodes::InMemory(nodes);
155 155 match self {
156 156 ChildNodes::InMemory(nodes) => Ok(nodes),
157 157 ChildNodes::OnDisk(_) => unreachable!(),
158 158 }
159 159 }
160 160 }
161 161 }
162 162 }
163 163
164 164 impl<'tree, 'on_disk> ChildNodesRef<'tree, 'on_disk> {
165 165 pub(super) fn get(
166 166 &self,
167 167 base_name: &HgPath,
168 168 on_disk: &'on_disk [u8],
169 169 ) -> Result<Option<NodeRef<'tree, 'on_disk>>, DirstateV2ParseError> {
170 170 match self {
171 171 ChildNodesRef::InMemory(nodes) => Ok(nodes
172 172 .get_key_value(base_name)
173 173 .map(|(k, v)| NodeRef::InMemory(k, v))),
174 174 ChildNodesRef::OnDisk(nodes) => {
175 175 let mut parse_result = Ok(());
176 176 let search_result = nodes.binary_search_by(|node| {
177 177 match node.base_name(on_disk) {
178 178 Ok(node_base_name) => node_base_name.cmp(base_name),
179 179 Err(e) => {
180 180 parse_result = Err(e);
181 181 // Dummy comparison result, `search_result` won’t
182 182 // be used since `parse_result` is an error
183 183 std::cmp::Ordering::Equal
184 184 }
185 185 }
186 186 });
187 187 parse_result.map(|()| {
188 188 search_result.ok().map(|i| NodeRef::OnDisk(&nodes[i]))
189 189 })
190 190 }
191 191 }
192 192 }
193 193
194 194 /// Iterate in undefined order
195 195 pub(super) fn iter(
196 196 &self,
197 197 ) -> impl Iterator<Item = NodeRef<'tree, 'on_disk>> {
198 198 match self {
199 199 ChildNodesRef::InMemory(nodes) => itertools::Either::Left(
200 200 nodes.iter().map(|(k, v)| NodeRef::InMemory(k, v)),
201 201 ),
202 202 ChildNodesRef::OnDisk(nodes) => {
203 203 itertools::Either::Right(nodes.iter().map(NodeRef::OnDisk))
204 204 }
205 205 }
206 206 }
207 207
208 208 /// Iterate in parallel in undefined order
209 209 pub(super) fn par_iter(
210 210 &self,
211 211 ) -> impl rayon::iter::ParallelIterator<Item = NodeRef<'tree, 'on_disk>>
212 212 {
213 213 use rayon::prelude::*;
214 214 match self {
215 215 ChildNodesRef::InMemory(nodes) => rayon::iter::Either::Left(
216 216 nodes.par_iter().map(|(k, v)| NodeRef::InMemory(k, v)),
217 217 ),
218 218 ChildNodesRef::OnDisk(nodes) => rayon::iter::Either::Right(
219 219 nodes.par_iter().map(NodeRef::OnDisk),
220 220 ),
221 221 }
222 222 }
223 223
224 224 pub(super) fn sorted(&self) -> Vec<NodeRef<'tree, 'on_disk>> {
225 225 match self {
226 226 ChildNodesRef::InMemory(nodes) => {
227 227 let mut vec: Vec<_> = nodes
228 228 .iter()
229 229 .map(|(k, v)| NodeRef::InMemory(k, v))
230 230 .collect();
231 231 fn sort_key<'a>(node: &'a NodeRef) -> &'a HgPath {
232 232 match node {
233 233 NodeRef::InMemory(path, _node) => path.base_name(),
234 234 NodeRef::OnDisk(_) => unreachable!(),
235 235 }
236 236 }
237 237 // `sort_unstable_by_key` doesn’t allow keys borrowing from the
238 238 // value: https://github.com/rust-lang/rust/issues/34162
239 239 vec.sort_unstable_by(|a, b| sort_key(a).cmp(sort_key(b)));
240 240 vec
241 241 }
242 242 ChildNodesRef::OnDisk(nodes) => {
243 243 // Nodes on disk are already sorted
244 244 nodes.iter().map(NodeRef::OnDisk).collect()
245 245 }
246 246 }
247 247 }
248 248 }
249 249
250 250 impl<'tree, 'on_disk> NodeRef<'tree, 'on_disk> {
251 251 pub(super) fn full_path(
252 252 &self,
253 253 on_disk: &'on_disk [u8],
254 254 ) -> Result<&'tree HgPath, DirstateV2ParseError> {
255 255 match self {
256 256 NodeRef::InMemory(path, _node) => Ok(path.full_path()),
257 257 NodeRef::OnDisk(node) => node.full_path(on_disk),
258 258 }
259 259 }
260 260
261 261 /// Returns a `BorrowedPath`, which can be turned into a `Cow<'on_disk,
262 262 /// HgPath>` detached from `'tree`
263 263 pub(super) fn full_path_borrowed(
264 264 &self,
265 265 on_disk: &'on_disk [u8],
266 266 ) -> Result<BorrowedPath<'tree, 'on_disk>, DirstateV2ParseError> {
267 267 match self {
268 268 NodeRef::InMemory(path, _node) => match path.full_path() {
269 269 Cow::Borrowed(on_disk) => Ok(BorrowedPath::OnDisk(on_disk)),
270 270 Cow::Owned(in_memory) => Ok(BorrowedPath::InMemory(in_memory)),
271 271 },
272 272 NodeRef::OnDisk(node) => {
273 273 Ok(BorrowedPath::OnDisk(node.full_path(on_disk)?))
274 274 }
275 275 }
276 276 }
277 277
278 278 pub(super) fn base_name(
279 279 &self,
280 280 on_disk: &'on_disk [u8],
281 281 ) -> Result<&'tree HgPath, DirstateV2ParseError> {
282 282 match self {
283 283 NodeRef::InMemory(path, _node) => Ok(path.base_name()),
284 284 NodeRef::OnDisk(node) => node.base_name(on_disk),
285 285 }
286 286 }
287 287
288 288 pub(super) fn children(
289 289 &self,
290 290 on_disk: &'on_disk [u8],
291 291 ) -> Result<ChildNodesRef<'tree, 'on_disk>, DirstateV2ParseError> {
292 292 match self {
293 293 NodeRef::InMemory(_path, node) => Ok(node.children.as_ref()),
294 294 NodeRef::OnDisk(node) => {
295 295 Ok(ChildNodesRef::OnDisk(node.children(on_disk)?))
296 296 }
297 297 }
298 298 }
299 299
300 300 pub(super) fn has_copy_source(&self) -> bool {
301 301 match self {
302 302 NodeRef::InMemory(_path, node) => node.copy_source.is_some(),
303 303 NodeRef::OnDisk(node) => node.has_copy_source(),
304 304 }
305 305 }
306 306
307 307 pub(super) fn copy_source(
308 308 &self,
309 309 on_disk: &'on_disk [u8],
310 310 ) -> Result<Option<&'tree HgPath>, DirstateV2ParseError> {
311 311 match self {
312 312 NodeRef::InMemory(_path, node) => {
313 313 Ok(node.copy_source.as_ref().map(|s| &**s))
314 314 }
315 315 NodeRef::OnDisk(node) => node.copy_source(on_disk),
316 316 }
317 317 }
318 318 /// Returns a `BorrowedPath`, which can be turned into a `Cow<'on_disk,
319 319 /// HgPath>` detached from `'tree`
320 320 pub(super) fn copy_source_borrowed(
321 321 &self,
322 322 on_disk: &'on_disk [u8],
323 323 ) -> Result<Option<BorrowedPath<'tree, 'on_disk>>, DirstateV2ParseError>
324 324 {
325 325 Ok(match self {
326 326 NodeRef::InMemory(_path, node) => {
327 327 node.copy_source.as_ref().map(|source| match source {
328 328 Cow::Borrowed(on_disk) => BorrowedPath::OnDisk(on_disk),
329 329 Cow::Owned(in_memory) => BorrowedPath::InMemory(in_memory),
330 330 })
331 331 }
332 332 NodeRef::OnDisk(node) => node
333 333 .copy_source(on_disk)?
334 334 .map(|source| BorrowedPath::OnDisk(source)),
335 335 })
336 336 }
337 337
338 338 pub(super) fn entry(
339 339 &self,
340 340 ) -> Result<Option<DirstateEntry>, DirstateV2ParseError> {
341 341 match self {
342 342 NodeRef::InMemory(_path, node) => {
343 343 Ok(node.data.as_entry().copied())
344 344 }
345 345 NodeRef::OnDisk(node) => node.entry(),
346 346 }
347 347 }
348 348
349 349 pub(super) fn state(
350 350 &self,
351 351 ) -> Result<Option<EntryState>, DirstateV2ParseError> {
352 352 Ok(self.entry()?.and_then(|e| {
353 353 if e.any_tracked() {
354 354 Some(e.state())
355 355 } else {
356 356 None
357 357 }
358 358 }))
359 359 }
360 360
361 361 pub(super) fn cached_directory_mtime(
362 362 &self,
363 363 ) -> Result<Option<TruncatedTimestamp>, DirstateV2ParseError> {
364 364 match self {
365 365 NodeRef::InMemory(_path, node) => Ok(match node.data {
366 366 NodeData::CachedDirectory { mtime } => Some(mtime),
367 367 _ => None,
368 368 }),
369 369 NodeRef::OnDisk(node) => node.cached_directory_mtime(),
370 370 }
371 371 }
372 372
373 373 pub(super) fn descendants_with_entry_count(&self) -> u32 {
374 374 match self {
375 375 NodeRef::InMemory(_path, node) => {
376 376 node.descendants_with_entry_count
377 377 }
378 378 NodeRef::OnDisk(node) => node.descendants_with_entry_count.get(),
379 379 }
380 380 }
381 381
382 382 pub(super) fn tracked_descendants_count(&self) -> u32 {
383 383 match self {
384 384 NodeRef::InMemory(_path, node) => node.tracked_descendants_count,
385 385 NodeRef::OnDisk(node) => node.tracked_descendants_count.get(),
386 386 }
387 387 }
388 388 }
389 389
390 390 /// Represents a file or a directory
391 391 #[derive(Default, Debug)]
392 392 pub(super) struct Node<'on_disk> {
393 393 pub(super) data: NodeData,
394 394
395 395 pub(super) copy_source: Option<Cow<'on_disk, HgPath>>,
396 396
397 397 pub(super) children: ChildNodes<'on_disk>,
398 398
399 399 /// How many (non-inclusive) descendants of this node have an entry.
400 400 pub(super) descendants_with_entry_count: u32,
401 401
402 402 /// How many (non-inclusive) descendants of this node have an entry whose
403 403 /// state is "tracked".
404 404 pub(super) tracked_descendants_count: u32,
405 405 }
406 406
407 407 #[derive(Debug)]
408 408 pub(super) enum NodeData {
409 409 Entry(DirstateEntry),
410 410 CachedDirectory { mtime: TruncatedTimestamp },
411 411 None,
412 412 }
413 413
414 414 impl Default for NodeData {
415 415 fn default() -> Self {
416 416 NodeData::None
417 417 }
418 418 }
419 419
420 420 impl NodeData {
421 421 fn has_entry(&self) -> bool {
422 422 match self {
423 423 NodeData::Entry(_) => true,
424 424 _ => false,
425 425 }
426 426 }
427 427
428 428 fn as_entry(&self) -> Option<&DirstateEntry> {
429 429 match self {
430 430 NodeData::Entry(entry) => Some(entry),
431 431 _ => None,
432 432 }
433 433 }
434 434
435 435 fn as_entry_mut(&mut self) -> Option<&mut DirstateEntry> {
436 436 match self {
437 437 NodeData::Entry(entry) => Some(entry),
438 438 _ => None,
439 439 }
440 440 }
441 441 }
442 442
443 443 impl<'on_disk> DirstateMap<'on_disk> {
444 444 pub(super) fn empty(on_disk: &'on_disk [u8]) -> Self {
445 445 Self {
446 446 on_disk,
447 447 root: ChildNodes::default(),
448 448 nodes_with_entry_count: 0,
449 449 nodes_with_copy_source_count: 0,
450 450 ignore_patterns_hash: [0; on_disk::IGNORE_PATTERNS_HASH_LEN],
451 451 unreachable_bytes: 0,
452 452 }
453 453 }
454 454
455 455 #[timed]
456 456 pub fn new_v2(
457 457 on_disk: &'on_disk [u8],
458 458 data_size: usize,
459 459 metadata: &[u8],
460 460 ) -> Result<Self, DirstateError> {
461 461 if let Some(data) = on_disk.get(..data_size) {
462 462 Ok(on_disk::read(data, metadata)?)
463 463 } else {
464 464 Err(DirstateV2ParseError.into())
465 465 }
466 466 }
467 467
468 468 #[timed]
469 469 pub fn new_v1(
470 470 on_disk: &'on_disk [u8],
471 471 ) -> Result<(Self, Option<DirstateParents>), DirstateError> {
472 472 let mut map = Self::empty(on_disk);
473 473 if map.on_disk.is_empty() {
474 474 return Ok((map, None));
475 475 }
476 476
477 477 let parents = parse_dirstate_entries(
478 478 map.on_disk,
479 479 |path, entry, copy_source| {
480 480 let tracked = entry.state().is_tracked();
481 481 let node = Self::get_or_insert_node_inner(
482 482 map.on_disk,
483 483 &mut map.unreachable_bytes,
484 484 &mut map.root,
485 485 path,
486 486 WithBasename::to_cow_borrowed,
487 487 |ancestor| {
488 488 if tracked {
489 489 ancestor.tracked_descendants_count += 1
490 490 }
491 491 ancestor.descendants_with_entry_count += 1
492 492 },
493 493 )?;
494 494 assert!(
495 495 !node.data.has_entry(),
496 496 "duplicate dirstate entry in read"
497 497 );
498 498 assert!(
499 499 node.copy_source.is_none(),
500 500 "duplicate dirstate entry in read"
501 501 );
502 502 node.data = NodeData::Entry(*entry);
503 503 node.copy_source = copy_source.map(Cow::Borrowed);
504 504 map.nodes_with_entry_count += 1;
505 505 if copy_source.is_some() {
506 506 map.nodes_with_copy_source_count += 1
507 507 }
508 508 Ok(())
509 509 },
510 510 )?;
511 511 let parents = Some(parents.clone());
512 512
513 513 Ok((map, parents))
514 514 }
515 515
516 516 /// Assuming dirstate-v2 format, returns whether the next write should
517 517 /// append to the existing data file that contains `self.on_disk` (true),
518 518 /// or create a new data file from scratch (false).
519 519 pub(super) fn write_should_append(&self) -> bool {
520 520 let ratio = self.unreachable_bytes as f32 / self.on_disk.len() as f32;
521 521 ratio < ACCEPTABLE_UNREACHABLE_BYTES_RATIO
522 522 }
523 523
524 524 fn get_node<'tree>(
525 525 &'tree self,
526 526 path: &HgPath,
527 527 ) -> Result<Option<NodeRef<'tree, 'on_disk>>, DirstateV2ParseError> {
528 528 let mut children = self.root.as_ref();
529 529 let mut components = path.components();
530 530 let mut component =
531 531 components.next().expect("expected at least one components");
532 532 loop {
533 533 if let Some(child) = children.get(component, self.on_disk)? {
534 534 if let Some(next_component) = components.next() {
535 535 component = next_component;
536 536 children = child.children(self.on_disk)?;
537 537 } else {
538 538 return Ok(Some(child));
539 539 }
540 540 } else {
541 541 return Ok(None);
542 542 }
543 543 }
544 544 }
545 545
546 546 /// Returns a mutable reference to the node at `path` if it exists
547 547 ///
548 548 /// This takes `root` instead of `&mut self` so that callers can mutate
549 /// other fields while the returned borrow is still valid
549 /// other fields while the returned borrow is still valid.
550 ///
551 /// `each_ancestor` is a callback that is called for each ancestor node
552 /// when descending the tree. It is used to keep the different counters
553 /// of the `DirstateMap` up-to-date.
550 554 fn get_node_mut<'tree>(
551 555 on_disk: &'on_disk [u8],
552 556 unreachable_bytes: &mut u32,
553 557 root: &'tree mut ChildNodes<'on_disk>,
554 558 path: &HgPath,
559 mut each_ancestor: impl FnMut(&mut Node),
555 560 ) -> Result<Option<&'tree mut Node<'on_disk>>, DirstateV2ParseError> {
556 561 let mut children = root;
557 562 let mut components = path.components();
558 563 let mut component =
559 564 components.next().expect("expected at least one components");
560 565 loop {
561 566 if let Some(child) = children
562 567 .make_mut(on_disk, unreachable_bytes)?
563 568 .get_mut(component)
564 569 {
565 570 if let Some(next_component) = components.next() {
571 each_ancestor(child);
566 572 component = next_component;
567 573 children = &mut child.children;
568 574 } else {
569 575 return Ok(Some(child));
570 576 }
571 577 } else {
572 578 return Ok(None);
573 579 }
574 580 }
575 581 }
576 582
577 583 /// Get a mutable reference to the node at `path`, creating it if it does
578 584 /// not exist.
579 585 ///
580 586 /// `each_ancestor` is a callback that is called for each ancestor node
581 587 /// when descending the tree. It is used to keep the different counters
582 588 /// of the `DirstateMap` up-to-date.
583 589 fn get_or_insert_node<'tree, 'path>(
584 590 &'tree mut self,
585 591 path: &'path HgPath,
586 592 each_ancestor: impl FnMut(&mut Node),
587 593 ) -> Result<&'tree mut Node<'on_disk>, DirstateV2ParseError> {
588 594 Self::get_or_insert_node_inner(
589 595 self.on_disk,
590 596 &mut self.unreachable_bytes,
591 597 &mut self.root,
592 598 path,
593 599 WithBasename::to_cow_owned,
594 600 each_ancestor,
595 601 )
596 602 }
597 603
598 604 /// Lower-level version of `get_or_insert_node_inner`, which is used when
599 605 /// parsing disk data to remove allocations for new nodes.
600 606 fn get_or_insert_node_inner<'tree, 'path>(
601 607 on_disk: &'on_disk [u8],
602 608 unreachable_bytes: &mut u32,
603 609 root: &'tree mut ChildNodes<'on_disk>,
604 610 path: &'path HgPath,
605 611 to_cow: impl Fn(
606 612 WithBasename<&'path HgPath>,
607 613 ) -> WithBasename<Cow<'on_disk, HgPath>>,
608 614 mut each_ancestor: impl FnMut(&mut Node),
609 615 ) -> Result<&'tree mut Node<'on_disk>, DirstateV2ParseError> {
610 616 let mut child_nodes = root;
611 617 let mut inclusive_ancestor_paths =
612 618 WithBasename::inclusive_ancestors_of(path);
613 619 let mut ancestor_path = inclusive_ancestor_paths
614 620 .next()
615 621 .expect("expected at least one inclusive ancestor");
616 622 loop {
617 623 let (_, child_node) = child_nodes
618 624 .make_mut(on_disk, unreachable_bytes)?
619 625 .raw_entry_mut()
620 626 .from_key(ancestor_path.base_name())
621 627 .or_insert_with(|| (to_cow(ancestor_path), Node::default()));
622 628 if let Some(next) = inclusive_ancestor_paths.next() {
623 629 each_ancestor(child_node);
624 630 ancestor_path = next;
625 631 child_nodes = &mut child_node.children;
626 632 } else {
627 633 return Ok(child_node);
628 634 }
629 635 }
630 636 }
631 637
632 638 fn reset_state(
633 639 &mut self,
634 640 filename: &HgPath,
635 641 old_entry_opt: Option<DirstateEntry>,
636 642 wc_tracked: bool,
637 643 p1_tracked: bool,
638 644 p2_info: bool,
639 645 has_meaningful_mtime: bool,
640 646 parent_file_data_opt: Option<ParentFileData>,
641 647 ) -> Result<(), DirstateError> {
642 648 let (had_entry, was_tracked) = match old_entry_opt {
643 649 Some(old_entry) => (true, old_entry.tracked()),
644 650 None => (false, false),
645 651 };
646 652 let node = self.get_or_insert_node(filename, |ancestor| {
647 653 if !had_entry {
648 654 ancestor.descendants_with_entry_count += 1;
649 655 }
650 656 if was_tracked {
651 657 if !wc_tracked {
652 658 ancestor.tracked_descendants_count = ancestor
653 659 .tracked_descendants_count
654 660 .checked_sub(1)
655 661 .expect("tracked count to be >= 0");
656 662 }
657 663 } else {
658 664 if wc_tracked {
659 665 ancestor.tracked_descendants_count += 1;
660 666 }
661 667 }
662 668 })?;
663 669
664 670 let v2_data = if let Some(parent_file_data) = parent_file_data_opt {
665 671 DirstateV2Data {
666 672 wc_tracked,
667 673 p1_tracked,
668 674 p2_info,
669 675 mode_size: parent_file_data.mode_size,
670 676 mtime: if has_meaningful_mtime {
671 677 parent_file_data.mtime
672 678 } else {
673 679 None
674 680 },
675 681 ..Default::default()
676 682 }
677 683 } else {
678 684 DirstateV2Data {
679 685 wc_tracked,
680 686 p1_tracked,
681 687 p2_info,
682 688 ..Default::default()
683 689 }
684 690 };
685 691 node.data = NodeData::Entry(DirstateEntry::from_v2_data(v2_data));
686 692 if !had_entry {
687 693 self.nodes_with_entry_count += 1;
688 694 }
689 695 Ok(())
690 696 }
691 697
692 698 fn set_tracked(
693 699 &mut self,
694 700 filename: &HgPath,
695 701 old_entry_opt: Option<DirstateEntry>,
696 702 ) -> Result<bool, DirstateV2ParseError> {
697 703 let was_tracked = old_entry_opt.map_or(false, |e| e.tracked());
698 704 let had_entry = old_entry_opt.is_some();
699 705 let tracked_count_increment = if was_tracked { 0 } else { 1 };
700 706 let mut new = false;
701 707
702 708 let node = self.get_or_insert_node(filename, |ancestor| {
703 709 if !had_entry {
704 710 ancestor.descendants_with_entry_count += 1;
705 711 }
706 712
707 713 ancestor.tracked_descendants_count += tracked_count_increment;
708 714 })?;
709 715 if let Some(old_entry) = old_entry_opt {
710 716 let mut e = old_entry.clone();
711 717 if e.tracked() {
712 718 // XXX
713 719 // This is probably overkill for more case, but we need this to
714 720 // fully replace the `normallookup` call with `set_tracked`
715 721 // one. Consider smoothing this in the future.
716 722 e.set_possibly_dirty();
717 723 } else {
718 724 new = true;
719 725 e.set_tracked();
720 726 }
721 727 node.data = NodeData::Entry(e)
722 728 } else {
723 729 node.data = NodeData::Entry(DirstateEntry::new_tracked());
724 730 self.nodes_with_entry_count += 1;
725 731 new = true;
726 732 };
727 733 Ok(new)
728 734 }
729 735
730 736 /// It is the responsibility of the caller to know that there was an entry
731 737 /// there before. Does not handle the removal of copy source
732 738 fn set_untracked(
733 739 &mut self,
734 740 filename: &HgPath,
735 741 old_entry: DirstateEntry,
736 742 ) -> Result<(), DirstateV2ParseError> {
737 743 let node = self.get_or_insert_node(filename, |ancestor| {
738 744 ancestor.tracked_descendants_count = ancestor
739 745 .tracked_descendants_count
740 746 .checked_sub(1)
741 747 .expect("tracked_descendants_count should be >= 0");
742 748 })?;
743 749 let mut new_entry = old_entry.clone();
744 750 new_entry.set_untracked();
745 751 node.data = NodeData::Entry(new_entry);
746 752 Ok(())
747 753 }
748 754
749 755 fn set_clean(
750 756 &mut self,
751 757 filename: &HgPath,
752 758 old_entry: DirstateEntry,
753 759 mode: u32,
754 760 size: u32,
755 761 mtime: TruncatedTimestamp,
756 762 ) -> Result<(), DirstateError> {
757 763 let node = self.get_or_insert_node(filename, |ancestor| {
758 764 if !old_entry.tracked() {
759 765 ancestor.tracked_descendants_count += 1;
760 766 }
761 767 })?;
762 768 let mut new_entry = old_entry.clone();
763 769 new_entry.set_clean(mode, size, mtime);
764 770 node.data = NodeData::Entry(new_entry);
765 771 Ok(())
766 772 }
767 773
768 774 fn set_possibly_dirty(
769 775 &mut self,
770 776 filename: &HgPath,
771 777 ) -> Result<(), DirstateError> {
772 778 let node = self.get_or_insert_node(filename, |_ancestor| {})?;
773 779 let entry = node.data.as_entry_mut().expect("entry should exist");
774 780 entry.set_possibly_dirty();
775 781 node.data = NodeData::Entry(*entry);
776 782 Ok(())
777 783 }
778 784
779 785 /// Clears the cached mtime for the (potential) folder at `path`.
780 786 pub(super) fn clear_cached_mtime(
781 787 &mut self,
782 788 path: &HgPath,
783 789 ) -> Result<(), DirstateV2ParseError> {
784 790 let node = match DirstateMap::get_node_mut(
785 791 self.on_disk,
786 792 &mut self.unreachable_bytes,
787 793 &mut self.root,
788 794 path,
795 |_ancestor| {},
789 796 )? {
790 797 Some(node) => node,
791 798 None => return Ok(()),
792 799 };
793 800 if let NodeData::CachedDirectory { .. } = &node.data {
794 801 node.data = NodeData::None
795 802 }
796 803 Ok(())
797 804 }
798 805
799 806 /// Sets the cached mtime for the (potential) folder at `path`.
800 807 pub(super) fn set_cached_mtime(
801 808 &mut self,
802 809 path: &HgPath,
803 810 mtime: TruncatedTimestamp,
804 811 ) -> Result<(), DirstateV2ParseError> {
805 812 let node = match DirstateMap::get_node_mut(
806 813 self.on_disk,
807 814 &mut self.unreachable_bytes,
808 815 &mut self.root,
809 816 path,
817 |_ancestor| {},
810 818 )? {
811 819 Some(node) => node,
812 820 None => return Ok(()),
813 821 };
814 822 match &node.data {
815 823 NodeData::Entry(_) => {} // Don’t overwrite an entry
816 824 NodeData::CachedDirectory { .. } | NodeData::None => {
817 825 node.data = NodeData::CachedDirectory { mtime }
818 826 }
819 827 }
820 828 Ok(())
821 829 }
822 830
823 831 fn iter_nodes<'tree>(
824 832 &'tree self,
825 833 ) -> impl Iterator<
826 834 Item = Result<NodeRef<'tree, 'on_disk>, DirstateV2ParseError>,
827 835 > + 'tree {
828 836 // Depth first tree traversal.
829 837 //
830 838 // If we could afford internal iteration and recursion,
831 839 // this would look like:
832 840 //
833 841 // ```
834 842 // fn traverse_children(
835 843 // children: &ChildNodes,
836 844 // each: &mut impl FnMut(&Node),
837 845 // ) {
838 846 // for child in children.values() {
839 847 // traverse_children(&child.children, each);
840 848 // each(child);
841 849 // }
842 850 // }
843 851 // ```
844 852 //
845 853 // However we want an external iterator and therefore can’t use the
846 854 // call stack. Use an explicit stack instead:
847 855 let mut stack = Vec::new();
848 856 let mut iter = self.root.as_ref().iter();
849 857 std::iter::from_fn(move || {
850 858 while let Some(child_node) = iter.next() {
851 859 let children = match child_node.children(self.on_disk) {
852 860 Ok(children) => children,
853 861 Err(error) => return Some(Err(error)),
854 862 };
855 863 // Pseudo-recursion
856 864 let new_iter = children.iter();
857 865 let old_iter = std::mem::replace(&mut iter, new_iter);
858 866 stack.push((child_node, old_iter));
859 867 }
860 868 // Found the end of a `children.iter()` iterator.
861 869 if let Some((child_node, next_iter)) = stack.pop() {
862 870 // "Return" from pseudo-recursion by restoring state from the
863 871 // explicit stack
864 872 iter = next_iter;
865 873
866 874 Some(Ok(child_node))
867 875 } else {
868 876 // Reached the bottom of the stack, we’re done
869 877 None
870 878 }
871 879 })
872 880 }
873 881
874 882 fn count_dropped_path(unreachable_bytes: &mut u32, path: &Cow<HgPath>) {
875 883 if let Cow::Borrowed(path) = path {
876 884 *unreachable_bytes += path.len() as u32
877 885 }
878 886 }
879 887 }
880 888
881 889 /// Like `Iterator::filter_map`, but over a fallible iterator of `Result`s.
882 890 ///
883 891 /// The callback is only called for incoming `Ok` values. Errors are passed
884 892 /// through as-is. In order to let it use the `?` operator the callback is
885 893 /// expected to return a `Result` of `Option`, instead of an `Option` of
886 894 /// `Result`.
887 895 fn filter_map_results<'a, I, F, A, B, E>(
888 896 iter: I,
889 897 f: F,
890 898 ) -> impl Iterator<Item = Result<B, E>> + 'a
891 899 where
892 900 I: Iterator<Item = Result<A, E>> + 'a,
893 901 F: Fn(A) -> Result<Option<B>, E> + 'a,
894 902 {
895 903 iter.filter_map(move |result| match result {
896 904 Ok(node) => f(node).transpose(),
897 905 Err(e) => Some(Err(e)),
898 906 })
899 907 }
900 908
901 909 impl OwningDirstateMap {
902 910 pub fn clear(&mut self) {
903 911 self.with_dmap_mut(|map| {
904 912 map.root = Default::default();
905 913 map.nodes_with_entry_count = 0;
906 914 map.nodes_with_copy_source_count = 0;
907 915 });
908 916 }
909 917
910 918 pub fn set_tracked(
911 919 &mut self,
912 920 filename: &HgPath,
913 921 ) -> Result<bool, DirstateV2ParseError> {
914 922 let old_entry_opt = self.get(filename)?;
915 923 self.with_dmap_mut(|map| map.set_tracked(filename, old_entry_opt))
916 924 }
917 925
918 926 pub fn set_untracked(
919 927 &mut self,
920 928 filename: &HgPath,
921 929 ) -> Result<bool, DirstateError> {
922 930 let old_entry_opt = self.get(filename)?;
923 931 match old_entry_opt {
924 932 None => Ok(false),
925 933 Some(old_entry) => {
926 934 if !old_entry.tracked() {
927 935 // `DirstateMap::set_untracked` is not a noop if
928 936 // already not tracked as it will decrement the
929 937 // tracked counters while going down.
930 938 return Ok(true);
931 939 }
932 940 if old_entry.added() {
933 941 // Untracking an "added" entry will just result in a
934 942 // worthless entry (and other parts of the code will
935 943 // complain about it), just drop it entirely.
936 944 self.drop_entry_and_copy_source(filename)?;
937 945 return Ok(true);
938 946 }
939 947 if !old_entry.p2_info() {
940 948 self.copy_map_remove(filename)?;
941 949 }
942 950
943 951 self.with_dmap_mut(|map| {
944 952 map.set_untracked(filename, old_entry)?;
945 953 Ok(true)
946 954 })
947 955 }
948 956 }
949 957 }
950 958
951 959 pub fn set_clean(
952 960 &mut self,
953 961 filename: &HgPath,
954 962 mode: u32,
955 963 size: u32,
956 964 mtime: TruncatedTimestamp,
957 965 ) -> Result<(), DirstateError> {
958 966 let old_entry = match self.get(filename)? {
959 967 None => {
960 968 return Err(
961 969 DirstateMapError::PathNotFound(filename.into()).into()
962 970 )
963 971 }
964 972 Some(e) => e,
965 973 };
966 974 self.copy_map_remove(filename)?;
967 975 self.with_dmap_mut(|map| {
968 976 map.set_clean(filename, old_entry, mode, size, mtime)
969 977 })
970 978 }
971 979
972 980 pub fn set_possibly_dirty(
973 981 &mut self,
974 982 filename: &HgPath,
975 983 ) -> Result<(), DirstateError> {
976 984 if self.get(filename)?.is_none() {
977 985 return Err(DirstateMapError::PathNotFound(filename.into()).into());
978 986 }
979 987 self.with_dmap_mut(|map| map.set_possibly_dirty(filename))
980 988 }
981 989
982 990 pub fn reset_state(
983 991 &mut self,
984 992 filename: &HgPath,
985 993 wc_tracked: bool,
986 994 p1_tracked: bool,
987 995 p2_info: bool,
988 996 has_meaningful_mtime: bool,
989 997 parent_file_data_opt: Option<ParentFileData>,
990 998 ) -> Result<(), DirstateError> {
991 999 if !(p1_tracked || p2_info || wc_tracked) {
992 1000 self.drop_entry_and_copy_source(filename)?;
993 1001 return Ok(());
994 1002 }
995 1003 self.copy_map_remove(filename)?;
996 1004 let old_entry_opt = self.get(filename)?;
997 1005 self.with_dmap_mut(|map| {
998 1006 map.reset_state(
999 1007 filename,
1000 1008 old_entry_opt,
1001 1009 wc_tracked,
1002 1010 p1_tracked,
1003 1011 p2_info,
1004 1012 has_meaningful_mtime,
1005 1013 parent_file_data_opt,
1006 1014 )
1007 1015 })
1008 1016 }
1009 1017
1010 1018 pub fn drop_entry_and_copy_source(
1011 1019 &mut self,
1012 1020 filename: &HgPath,
1013 1021 ) -> Result<(), DirstateError> {
1014 1022 let was_tracked = self.get(filename)?.map_or(false, |e| e.tracked());
1015 1023 struct Dropped {
1016 1024 was_tracked: bool,
1017 1025 had_entry: bool,
1018 1026 had_copy_source: bool,
1019 1027 }
1020 1028
1021 1029 /// If this returns `Ok(Some((dropped, removed)))`, then
1022 1030 ///
1023 1031 /// * `dropped` is about the leaf node that was at `filename`
1024 1032 /// * `removed` is whether this particular level of recursion just
1025 1033 /// removed a node in `nodes`.
1026 1034 fn recur<'on_disk>(
1027 1035 on_disk: &'on_disk [u8],
1028 1036 unreachable_bytes: &mut u32,
1029 1037 nodes: &mut ChildNodes<'on_disk>,
1030 1038 path: &HgPath,
1031 1039 ) -> Result<Option<(Dropped, bool)>, DirstateV2ParseError> {
1032 1040 let (first_path_component, rest_of_path) =
1033 1041 path.split_first_component();
1034 1042 let nodes = nodes.make_mut(on_disk, unreachable_bytes)?;
1035 1043 let node = if let Some(node) = nodes.get_mut(first_path_component)
1036 1044 {
1037 1045 node
1038 1046 } else {
1039 1047 return Ok(None);
1040 1048 };
1041 1049 let dropped;
1042 1050 if let Some(rest) = rest_of_path {
1043 1051 if let Some((d, removed)) = recur(
1044 1052 on_disk,
1045 1053 unreachable_bytes,
1046 1054 &mut node.children,
1047 1055 rest,
1048 1056 )? {
1049 1057 dropped = d;
1050 1058 if dropped.had_entry {
1051 1059 node.descendants_with_entry_count = node
1052 1060 .descendants_with_entry_count
1053 1061 .checked_sub(1)
1054 1062 .expect(
1055 1063 "descendants_with_entry_count should be >= 0",
1056 1064 );
1057 1065 }
1058 1066 if dropped.was_tracked {
1059 1067 node.tracked_descendants_count = node
1060 1068 .tracked_descendants_count
1061 1069 .checked_sub(1)
1062 1070 .expect(
1063 1071 "tracked_descendants_count should be >= 0",
1064 1072 );
1065 1073 }
1066 1074
1067 1075 // Directory caches must be invalidated when removing a
1068 1076 // child node
1069 1077 if removed {
1070 1078 if let NodeData::CachedDirectory { .. } = &node.data {
1071 1079 node.data = NodeData::None
1072 1080 }
1073 1081 }
1074 1082 } else {
1075 1083 return Ok(None);
1076 1084 }
1077 1085 } else {
1078 1086 let entry = node.data.as_entry();
1079 1087 let was_tracked = entry.map_or(false, |entry| entry.tracked());
1080 1088 let had_entry = entry.is_some();
1081 1089 if had_entry {
1082 1090 node.data = NodeData::None
1083 1091 }
1084 1092 let mut had_copy_source = false;
1085 1093 if let Some(source) = &node.copy_source {
1086 1094 DirstateMap::count_dropped_path(unreachable_bytes, source);
1087 1095 had_copy_source = true;
1088 1096 node.copy_source = None
1089 1097 }
1090 1098 dropped = Dropped {
1091 1099 was_tracked,
1092 1100 had_entry,
1093 1101 had_copy_source,
1094 1102 };
1095 1103 }
1096 1104 // After recursion, for both leaf (rest_of_path is None) nodes and
1097 1105 // parent nodes, remove a node if it just became empty.
1098 1106 let remove = !node.data.has_entry()
1099 1107 && node.copy_source.is_none()
1100 1108 && node.children.is_empty();
1101 1109 if remove {
1102 1110 let (key, _) =
1103 1111 nodes.remove_entry(first_path_component).unwrap();
1104 1112 DirstateMap::count_dropped_path(
1105 1113 unreachable_bytes,
1106 1114 key.full_path(),
1107 1115 )
1108 1116 }
1109 1117 Ok(Some((dropped, remove)))
1110 1118 }
1111 1119
1112 1120 self.with_dmap_mut(|map| {
1113 1121 if let Some((dropped, _removed)) = recur(
1114 1122 map.on_disk,
1115 1123 &mut map.unreachable_bytes,
1116 1124 &mut map.root,
1117 1125 filename,
1118 1126 )? {
1119 1127 if dropped.had_entry {
1120 1128 map.nodes_with_entry_count = map
1121 1129 .nodes_with_entry_count
1122 1130 .checked_sub(1)
1123 1131 .expect("nodes_with_entry_count should be >= 0");
1124 1132 }
1125 1133 if dropped.had_copy_source {
1126 1134 map.nodes_with_copy_source_count = map
1127 1135 .nodes_with_copy_source_count
1128 1136 .checked_sub(1)
1129 1137 .expect("nodes_with_copy_source_count should be >= 0");
1130 1138 }
1131 1139 } else {
1132 1140 debug_assert!(!was_tracked);
1133 1141 }
1134 1142 Ok(())
1135 1143 })
1136 1144 }
1137 1145
1138 1146 pub fn has_tracked_dir(
1139 1147 &mut self,
1140 1148 directory: &HgPath,
1141 1149 ) -> Result<bool, DirstateError> {
1142 1150 self.with_dmap_mut(|map| {
1143 1151 if let Some(node) = map.get_node(directory)? {
1144 1152 // A node without a `DirstateEntry` was created to hold child
1145 1153 // nodes, and is therefore a directory.
1146 1154 let state = node.state()?;
1147 1155 Ok(state.is_none() && node.tracked_descendants_count() > 0)
1148 1156 } else {
1149 1157 Ok(false)
1150 1158 }
1151 1159 })
1152 1160 }
1153 1161
1154 1162 pub fn has_dir(
1155 1163 &mut self,
1156 1164 directory: &HgPath,
1157 1165 ) -> Result<bool, DirstateError> {
1158 1166 self.with_dmap_mut(|map| {
1159 1167 if let Some(node) = map.get_node(directory)? {
1160 1168 // A node without a `DirstateEntry` was created to hold child
1161 1169 // nodes, and is therefore a directory.
1162 1170 let state = node.state()?;
1163 1171 Ok(state.is_none() && node.descendants_with_entry_count() > 0)
1164 1172 } else {
1165 1173 Ok(false)
1166 1174 }
1167 1175 })
1168 1176 }
1169 1177
1170 1178 #[timed]
1171 1179 pub fn pack_v1(
1172 1180 &self,
1173 1181 parents: DirstateParents,
1174 1182 ) -> Result<Vec<u8>, DirstateError> {
1175 1183 let map = self.get_map();
1176 1184 // Optizimation (to be measured?): pre-compute size to avoid `Vec`
1177 1185 // reallocations
1178 1186 let mut size = parents.as_bytes().len();
1179 1187 for node in map.iter_nodes() {
1180 1188 let node = node?;
1181 1189 if node.entry()?.is_some() {
1182 1190 size += packed_entry_size(
1183 1191 node.full_path(map.on_disk)?,
1184 1192 node.copy_source(map.on_disk)?,
1185 1193 );
1186 1194 }
1187 1195 }
1188 1196
1189 1197 let mut packed = Vec::with_capacity(size);
1190 1198 packed.extend(parents.as_bytes());
1191 1199
1192 1200 for node in map.iter_nodes() {
1193 1201 let node = node?;
1194 1202 if let Some(entry) = node.entry()? {
1195 1203 pack_entry(
1196 1204 node.full_path(map.on_disk)?,
1197 1205 &entry,
1198 1206 node.copy_source(map.on_disk)?,
1199 1207 &mut packed,
1200 1208 );
1201 1209 }
1202 1210 }
1203 1211 Ok(packed)
1204 1212 }
1205 1213
1206 1214 /// Returns new data and metadata together with whether that data should be
1207 1215 /// appended to the existing data file whose content is at
1208 1216 /// `map.on_disk` (true), instead of written to a new data file
1209 1217 /// (false).
1210 1218 #[timed]
1211 1219 pub fn pack_v2(
1212 1220 &self,
1213 1221 can_append: bool,
1214 1222 ) -> Result<(Vec<u8>, on_disk::TreeMetadata, bool), DirstateError> {
1215 1223 let map = self.get_map();
1216 1224 on_disk::write(map, can_append)
1217 1225 }
1218 1226
1219 1227 /// `callback` allows the caller to process and do something with the
1220 1228 /// results of the status. This is needed to do so efficiently (i.e.
1221 1229 /// without cloning the `DirstateStatus` object with its paths) because
1222 1230 /// we need to borrow from `Self`.
1223 1231 pub fn with_status<R>(
1224 1232 &mut self,
1225 1233 matcher: &(dyn Matcher + Sync),
1226 1234 root_dir: PathBuf,
1227 1235 ignore_files: Vec<PathBuf>,
1228 1236 options: StatusOptions,
1229 1237 callback: impl for<'r> FnOnce(
1230 1238 Result<(DirstateStatus<'r>, Vec<PatternFileWarning>), StatusError>,
1231 1239 ) -> R,
1232 1240 ) -> R {
1233 1241 self.with_dmap_mut(|map| {
1234 1242 callback(super::status::status(
1235 1243 map,
1236 1244 matcher,
1237 1245 root_dir,
1238 1246 ignore_files,
1239 1247 options,
1240 1248 ))
1241 1249 })
1242 1250 }
1243 1251
1244 1252 pub fn copy_map_len(&self) -> usize {
1245 1253 let map = self.get_map();
1246 1254 map.nodes_with_copy_source_count as usize
1247 1255 }
1248 1256
1249 1257 pub fn copy_map_iter(&self) -> CopyMapIter<'_> {
1250 1258 let map = self.get_map();
1251 1259 Box::new(filter_map_results(map.iter_nodes(), move |node| {
1252 1260 Ok(if let Some(source) = node.copy_source(map.on_disk)? {
1253 1261 Some((node.full_path(map.on_disk)?, source))
1254 1262 } else {
1255 1263 None
1256 1264 })
1257 1265 }))
1258 1266 }
1259 1267
1260 1268 pub fn copy_map_contains_key(
1261 1269 &self,
1262 1270 key: &HgPath,
1263 1271 ) -> Result<bool, DirstateV2ParseError> {
1264 1272 let map = self.get_map();
1265 1273 Ok(if let Some(node) = map.get_node(key)? {
1266 1274 node.has_copy_source()
1267 1275 } else {
1268 1276 false
1269 1277 })
1270 1278 }
1271 1279
1272 1280 pub fn copy_map_get(
1273 1281 &self,
1274 1282 key: &HgPath,
1275 1283 ) -> Result<Option<&HgPath>, DirstateV2ParseError> {
1276 1284 let map = self.get_map();
1277 1285 if let Some(node) = map.get_node(key)? {
1278 1286 if let Some(source) = node.copy_source(map.on_disk)? {
1279 1287 return Ok(Some(source));
1280 1288 }
1281 1289 }
1282 1290 Ok(None)
1283 1291 }
1284 1292
1285 1293 pub fn copy_map_remove(
1286 1294 &mut self,
1287 1295 key: &HgPath,
1288 1296 ) -> Result<Option<HgPathBuf>, DirstateV2ParseError> {
1289 1297 self.with_dmap_mut(|map| {
1290 1298 let count = &mut map.nodes_with_copy_source_count;
1291 1299 let unreachable_bytes = &mut map.unreachable_bytes;
1292 1300 Ok(DirstateMap::get_node_mut(
1293 1301 map.on_disk,
1294 1302 unreachable_bytes,
1295 1303 &mut map.root,
1296 1304 key,
1305 |_ancestor| {},
1297 1306 )?
1298 1307 .and_then(|node| {
1299 1308 if let Some(source) = &node.copy_source {
1300 1309 *count -= 1;
1301 1310 DirstateMap::count_dropped_path(unreachable_bytes, source);
1302 1311 }
1303 1312 node.copy_source.take().map(Cow::into_owned)
1304 1313 }))
1305 1314 })
1306 1315 }
1307 1316
1308 1317 pub fn copy_map_insert(
1309 1318 &mut self,
1310 1319 key: &HgPath,
1311 1320 value: &HgPath,
1312 1321 ) -> Result<Option<HgPathBuf>, DirstateV2ParseError> {
1313 1322 self.with_dmap_mut(|map| {
1314 1323 let node = map.get_or_insert_node(&key, |_ancestor| {})?;
1315 1324 let had_copy_source = node.copy_source.is_none();
1316 1325 let old = node
1317 1326 .copy_source
1318 1327 .replace(value.to_owned().into())
1319 1328 .map(Cow::into_owned);
1320 1329 if had_copy_source {
1321 1330 map.nodes_with_copy_source_count += 1
1322 1331 }
1323 1332 Ok(old)
1324 1333 })
1325 1334 }
1326 1335
1327 1336 pub fn len(&self) -> usize {
1328 1337 let map = self.get_map();
1329 1338 map.nodes_with_entry_count as usize
1330 1339 }
1331 1340
1332 1341 pub fn contains_key(
1333 1342 &self,
1334 1343 key: &HgPath,
1335 1344 ) -> Result<bool, DirstateV2ParseError> {
1336 1345 Ok(self.get(key)?.is_some())
1337 1346 }
1338 1347
1339 1348 pub fn get(
1340 1349 &self,
1341 1350 key: &HgPath,
1342 1351 ) -> Result<Option<DirstateEntry>, DirstateV2ParseError> {
1343 1352 let map = self.get_map();
1344 1353 Ok(if let Some(node) = map.get_node(key)? {
1345 1354 node.entry()?
1346 1355 } else {
1347 1356 None
1348 1357 })
1349 1358 }
1350 1359
1351 1360 pub fn iter(&self) -> StateMapIter<'_> {
1352 1361 let map = self.get_map();
1353 1362 Box::new(filter_map_results(map.iter_nodes(), move |node| {
1354 1363 Ok(if let Some(entry) = node.entry()? {
1355 1364 Some((node.full_path(map.on_disk)?, entry))
1356 1365 } else {
1357 1366 None
1358 1367 })
1359 1368 }))
1360 1369 }
1361 1370
1362 1371 pub fn iter_tracked_dirs(
1363 1372 &mut self,
1364 1373 ) -> Result<
1365 1374 Box<
1366 1375 dyn Iterator<Item = Result<&HgPath, DirstateV2ParseError>>
1367 1376 + Send
1368 1377 + '_,
1369 1378 >,
1370 1379 DirstateError,
1371 1380 > {
1372 1381 let map = self.get_map();
1373 1382 let on_disk = map.on_disk;
1374 1383 Ok(Box::new(filter_map_results(
1375 1384 map.iter_nodes(),
1376 1385 move |node| {
1377 1386 Ok(if node.tracked_descendants_count() > 0 {
1378 1387 Some(node.full_path(on_disk)?)
1379 1388 } else {
1380 1389 None
1381 1390 })
1382 1391 },
1383 1392 )))
1384 1393 }
1385 1394
1386 1395 /// Only public because it needs to be exposed to the Python layer.
1387 1396 /// It is not the full `setparents` logic, only the parts that mutate the
1388 1397 /// entries.
1389 1398 pub fn setparents_fixup(
1390 1399 &mut self,
1391 1400 ) -> Result<Vec<(HgPathBuf, HgPathBuf)>, DirstateV2ParseError> {
1392 1401 // XXX
1393 1402 // All the copying and re-querying is quite inefficient, but this is
1394 1403 // still a lot better than doing it from Python.
1395 1404 //
1396 1405 // The better solution is to develop a mechanism for `iter_mut`,
1397 1406 // which will be a lot more involved: we're dealing with a lazy,
1398 1407 // append-mostly, tree-like data structure. This will do for now.
1399 1408 let mut copies = vec![];
1400 1409 let mut files_with_p2_info = vec![];
1401 1410 for res in self.iter() {
1402 1411 let (path, entry) = res?;
1403 1412 if entry.p2_info() {
1404 1413 files_with_p2_info.push(path.to_owned())
1405 1414 }
1406 1415 }
1407 1416 self.with_dmap_mut(|map| {
1408 1417 for path in files_with_p2_info.iter() {
1409 1418 let node = map.get_or_insert_node(path, |_| {})?;
1410 1419 let entry =
1411 1420 node.data.as_entry_mut().expect("entry should exist");
1412 1421 entry.drop_merge_data();
1413 1422 if let Some(source) = node.copy_source.take().as_deref() {
1414 1423 copies.push((path.to_owned(), source.to_owned()));
1415 1424 }
1416 1425 }
1417 1426 Ok(copies)
1418 1427 })
1419 1428 }
1420 1429
1421 1430 pub fn debug_iter(
1422 1431 &self,
1423 1432 all: bool,
1424 1433 ) -> Box<
1425 1434 dyn Iterator<
1426 1435 Item = Result<
1427 1436 (&HgPath, (u8, i32, i32, i32)),
1428 1437 DirstateV2ParseError,
1429 1438 >,
1430 1439 > + Send
1431 1440 + '_,
1432 1441 > {
1433 1442 let map = self.get_map();
1434 1443 Box::new(filter_map_results(map.iter_nodes(), move |node| {
1435 1444 let debug_tuple = if let Some(entry) = node.entry()? {
1436 1445 entry.debug_tuple()
1437 1446 } else if !all {
1438 1447 return Ok(None);
1439 1448 } else if let Some(mtime) = node.cached_directory_mtime()? {
1440 1449 (b' ', 0, -1, mtime.truncated_seconds() as i32)
1441 1450 } else {
1442 1451 (b' ', 0, -1, -1)
1443 1452 };
1444 1453 Ok(Some((node.full_path(map.on_disk)?, debug_tuple)))
1445 1454 }))
1446 1455 }
1447 1456 }
1448 1457 #[cfg(test)]
1449 1458 mod tests {
1450 1459 use super::*;
1451 1460
1452 1461 /// Shortcut to return tracked descendants of a path.
1453 1462 /// Panics if the path does not exist.
1454 1463 fn tracked_descendants(map: &OwningDirstateMap, path: &[u8]) -> u32 {
1455 1464 let path = dbg!(HgPath::new(path));
1456 1465 let node = map.get_map().get_node(path);
1457 1466 node.unwrap().unwrap().tracked_descendants_count()
1458 1467 }
1459 1468
1460 1469 /// Shortcut to return descendants with an entry.
1461 1470 /// Panics if the path does not exist.
1462 1471 fn descendants_with_an_entry(map: &OwningDirstateMap, path: &[u8]) -> u32 {
1463 1472 let path = dbg!(HgPath::new(path));
1464 1473 let node = map.get_map().get_node(path);
1465 1474 node.unwrap().unwrap().descendants_with_entry_count()
1466 1475 }
1467 1476
1468 1477 fn assert_does_not_exist(map: &OwningDirstateMap, path: &[u8]) {
1469 1478 let path = dbg!(HgPath::new(path));
1470 1479 let node = map.get_map().get_node(path);
1471 1480 assert!(node.unwrap().is_none());
1472 1481 }
1473 1482
1474 1483 /// Shortcut for path creation in tests
1475 1484 fn p(b: &[u8]) -> &HgPath {
1476 1485 HgPath::new(b)
1477 1486 }
1478 1487
1479 1488 /// Test the very simple case a single tracked file
1480 1489 #[test]
1481 1490 fn test_tracked_descendants_simple() -> Result<(), DirstateError> {
1482 1491 let mut map = OwningDirstateMap::new_empty(vec![]);
1483 1492 assert_eq!(map.len(), 0);
1484 1493
1485 1494 map.set_tracked(p(b"some/nested/path"))?;
1486 1495
1487 1496 assert_eq!(map.len(), 1);
1488 1497 assert_eq!(tracked_descendants(&map, b"some"), 1);
1489 1498 assert_eq!(tracked_descendants(&map, b"some/nested"), 1);
1490 1499 assert_eq!(tracked_descendants(&map, b"some/nested/path"), 0);
1491 1500
1492 1501 map.set_untracked(p(b"some/nested/path"))?;
1493 1502 assert_eq!(map.len(), 0);
1494 1503 assert!(map.get_map().get_node(p(b"some"))?.is_none());
1495 1504
1496 1505 Ok(())
1497 1506 }
1498 1507
1499 1508 /// Test the simple case of all tracked, but multiple files
1500 1509 #[test]
1501 1510 fn test_tracked_descendants_multiple() -> Result<(), DirstateError> {
1502 1511 let mut map = OwningDirstateMap::new_empty(vec![]);
1503 1512
1504 1513 map.set_tracked(p(b"some/nested/path"))?;
1505 1514 map.set_tracked(p(b"some/nested/file"))?;
1506 1515 // one layer without any files to test deletion cascade
1507 1516 map.set_tracked(p(b"some/other/nested/path"))?;
1508 1517 map.set_tracked(p(b"root_file"))?;
1509 1518 map.set_tracked(p(b"some/file"))?;
1510 1519 map.set_tracked(p(b"some/file2"))?;
1511 1520 map.set_tracked(p(b"some/file3"))?;
1512 1521
1513 1522 assert_eq!(map.len(), 7);
1514 1523 assert_eq!(tracked_descendants(&map, b"some"), 6);
1515 1524 assert_eq!(tracked_descendants(&map, b"some/nested"), 2);
1516 1525 assert_eq!(tracked_descendants(&map, b"some/other"), 1);
1517 1526 assert_eq!(tracked_descendants(&map, b"some/other/nested"), 1);
1518 1527 assert_eq!(tracked_descendants(&map, b"some/nested/path"), 0);
1519 1528
1520 1529 map.set_untracked(p(b"some/nested/path"))?;
1521 1530 assert_eq!(map.len(), 6);
1522 1531 assert_eq!(tracked_descendants(&map, b"some"), 5);
1523 1532 assert_eq!(tracked_descendants(&map, b"some/nested"), 1);
1524 1533 assert_eq!(tracked_descendants(&map, b"some/other"), 1);
1525 1534 assert_eq!(tracked_descendants(&map, b"some/other/nested"), 1);
1526 1535
1527 1536 map.set_untracked(p(b"some/nested/file"))?;
1528 1537 assert_eq!(map.len(), 5);
1529 1538 assert_eq!(tracked_descendants(&map, b"some"), 4);
1530 1539 assert_eq!(tracked_descendants(&map, b"some/other"), 1);
1531 1540 assert_eq!(tracked_descendants(&map, b"some/other/nested"), 1);
1532 1541 assert_does_not_exist(&map, b"some_nested");
1533 1542
1534 1543 map.set_untracked(p(b"some/other/nested/path"))?;
1535 1544 assert_eq!(map.len(), 4);
1536 1545 assert_eq!(tracked_descendants(&map, b"some"), 3);
1537 1546 assert_does_not_exist(&map, b"some/other");
1538 1547
1539 1548 map.set_untracked(p(b"root_file"))?;
1540 1549 assert_eq!(map.len(), 3);
1541 1550 assert_eq!(tracked_descendants(&map, b"some"), 3);
1542 1551 assert_does_not_exist(&map, b"root_file");
1543 1552
1544 1553 map.set_untracked(p(b"some/file"))?;
1545 1554 assert_eq!(map.len(), 2);
1546 1555 assert_eq!(tracked_descendants(&map, b"some"), 2);
1547 1556 assert_does_not_exist(&map, b"some/file");
1548 1557
1549 1558 map.set_untracked(p(b"some/file2"))?;
1550 1559 assert_eq!(map.len(), 1);
1551 1560 assert_eq!(tracked_descendants(&map, b"some"), 1);
1552 1561 assert_does_not_exist(&map, b"some/file2");
1553 1562
1554 1563 map.set_untracked(p(b"some/file3"))?;
1555 1564 assert_eq!(map.len(), 0);
1556 1565 assert_does_not_exist(&map, b"some/file3");
1557 1566
1558 1567 Ok(())
1559 1568 }
1560 1569
1561 1570 /// Check with a mix of tracked and non-tracked items
1562 1571 #[test]
1563 1572 fn test_tracked_descendants_different() -> Result<(), DirstateError> {
1564 1573 let mut map = OwningDirstateMap::new_empty(vec![]);
1565 1574
1566 1575 // A file that was just added
1567 1576 map.set_tracked(p(b"some/nested/path"))?;
1568 1577 // This has no information, the dirstate should ignore it
1569 1578 map.reset_state(p(b"some/file"), false, false, false, false, None)?;
1570 1579 assert_does_not_exist(&map, b"some/file");
1571 1580
1572 1581 // A file that was removed
1573 1582 map.reset_state(
1574 1583 p(b"some/nested/file"),
1575 1584 false,
1576 1585 true,
1577 1586 false,
1578 1587 false,
1579 1588 None,
1580 1589 )?;
1581 1590 assert!(!map.get(p(b"some/nested/file"))?.unwrap().tracked());
1582 1591 // Only present in p2
1583 1592 map.reset_state(p(b"some/file3"), false, false, true, false, None)?;
1584 1593 assert!(!map.get(p(b"some/file3"))?.unwrap().tracked());
1585 1594 // A file that was merged
1586 1595 map.reset_state(p(b"root_file"), true, true, true, false, None)?;
1587 1596 assert!(map.get(p(b"root_file"))?.unwrap().tracked());
1588 1597 // A file that is added, with info from p2
1589 1598 // XXX is that actually possible?
1590 1599 map.reset_state(p(b"some/file2"), true, false, true, false, None)?;
1591 1600 assert!(map.get(p(b"some/file2"))?.unwrap().tracked());
1592 1601 // A clean file
1593 1602 // One layer without any files to test deletion cascade
1594 1603 map.reset_state(
1595 1604 p(b"some/other/nested/path"),
1596 1605 true,
1597 1606 true,
1598 1607 false,
1599 1608 false,
1600 1609 None,
1601 1610 )?;
1602 1611 assert!(map.get(p(b"some/other/nested/path"))?.unwrap().tracked());
1603 1612
1604 1613 assert_eq!(map.len(), 6);
1605 1614 assert_eq!(tracked_descendants(&map, b"some"), 3);
1606 1615 assert_eq!(descendants_with_an_entry(&map, b"some"), 5);
1607 1616 assert_eq!(tracked_descendants(&map, b"some/other/nested"), 1);
1608 1617 assert_eq!(descendants_with_an_entry(&map, b"some/other/nested"), 1);
1609 1618 assert_eq!(tracked_descendants(&map, b"some/other/nested/path"), 0);
1610 1619 assert_eq!(
1611 1620 descendants_with_an_entry(&map, b"some/other/nested/path"),
1612 1621 0
1613 1622 );
1614 1623 assert_eq!(tracked_descendants(&map, b"some/nested"), 1);
1615 1624 assert_eq!(descendants_with_an_entry(&map, b"some/nested"), 2);
1616 1625
1617 1626 // might as well check this
1618 1627 map.set_untracked(p(b"path/does/not/exist"))?;
1619 1628 assert_eq!(map.len(), 6);
1620 1629
1621 1630 map.set_untracked(p(b"some/other/nested/path"))?;
1622 1631 // It is set untracked but not deleted since it held other information
1623 1632 assert_eq!(map.len(), 6);
1624 1633 assert_eq!(tracked_descendants(&map, b"some"), 2);
1625 1634 assert_eq!(descendants_with_an_entry(&map, b"some"), 5);
1626 1635 assert_eq!(descendants_with_an_entry(&map, b"some/other"), 1);
1627 1636 assert_eq!(descendants_with_an_entry(&map, b"some/other/nested"), 1);
1628 1637 assert_eq!(tracked_descendants(&map, b"some/nested"), 1);
1629 1638 assert_eq!(descendants_with_an_entry(&map, b"some/nested"), 2);
1630 1639
1631 1640 map.set_untracked(p(b"some/nested/path"))?;
1632 1641 // It is set untracked *and* deleted since it was only added
1633 1642 assert_eq!(map.len(), 5);
1634 1643 assert_eq!(tracked_descendants(&map, b"some"), 1);
1635 1644 assert_eq!(descendants_with_an_entry(&map, b"some"), 4);
1636 1645 assert_eq!(tracked_descendants(&map, b"some/nested"), 0);
1637 1646 assert_eq!(descendants_with_an_entry(&map, b"some/nested"), 1);
1638 1647 assert_does_not_exist(&map, b"some/nested/path");
1639 1648
1640 1649 map.set_untracked(p(b"root_file"))?;
1641 1650 // Untracked but not deleted
1642 1651 assert_eq!(map.len(), 5);
1643 1652 assert!(map.get(p(b"root_file"))?.is_some());
1644 1653
1645 1654 map.set_untracked(p(b"some/file2"))?;
1646 1655 assert_eq!(map.len(), 5);
1647 1656 assert_eq!(tracked_descendants(&map, b"some"), 0);
1648 1657 assert!(map.get(p(b"some/file2"))?.is_some());
1649 1658
1650 1659 map.set_untracked(p(b"some/file3"))?;
1651 1660 assert_eq!(map.len(), 5);
1652 1661 assert_eq!(tracked_descendants(&map, b"some"), 0);
1653 1662 assert!(map.get(p(b"some/file3"))?.is_some());
1654 1663
1655 1664 Ok(())
1656 1665 }
1657 1666
1658 1667 /// Check that copies counter is correctly updated
1659 1668 #[test]
1660 1669 fn test_copy_source() -> Result<(), DirstateError> {
1661 1670 let mut map = OwningDirstateMap::new_empty(vec![]);
1662 1671
1663 1672 // Clean file
1664 1673 map.reset_state(p(b"files/clean"), true, true, false, false, None)?;
1665 1674 // Merged file
1666 1675 map.reset_state(p(b"files/from_p2"), true, true, true, false, None)?;
1667 1676 // Removed file
1668 1677 map.reset_state(p(b"removed"), false, true, false, false, None)?;
1669 1678 // Added file
1670 1679 map.reset_state(p(b"files/added"), true, false, false, false, None)?;
1671 1680 // Add copy
1672 1681 map.copy_map_insert(p(b"files/clean"), p(b"clean_copy_source"))?;
1673 1682 assert_eq!(map.copy_map_len(), 1);
1674 1683
1675 1684 // Copy override
1676 1685 map.copy_map_insert(p(b"files/clean"), p(b"other_clean_copy_source"))?;
1677 1686 assert_eq!(map.copy_map_len(), 1);
1678 1687
1679 1688 // Multiple copies
1680 1689 map.copy_map_insert(p(b"removed"), p(b"removed_copy_source"))?;
1681 1690 assert_eq!(map.copy_map_len(), 2);
1682 1691
1683 1692 map.copy_map_insert(p(b"files/added"), p(b"added_copy_source"))?;
1684 1693 assert_eq!(map.copy_map_len(), 3);
1685 1694
1686 1695 // Added, so the entry is completely removed
1687 1696 map.set_untracked(p(b"files/added"))?;
1688 1697 assert_does_not_exist(&map, b"files/added");
1689 1698 assert_eq!(map.copy_map_len(), 2);
1690 1699
1691 1700 // Removed, so the entry is kept around, so is its copy
1692 1701 map.set_untracked(p(b"removed"))?;
1693 1702 assert!(map.get(p(b"removed"))?.is_some());
1694 1703 assert_eq!(map.copy_map_len(), 2);
1695 1704
1696 1705 // Clean, so the entry is kept around, but not its copy
1697 1706 map.set_untracked(p(b"files/clean"))?;
1698 1707 assert!(map.get(p(b"files/clean"))?.is_some());
1699 1708 assert_eq!(map.copy_map_len(), 1);
1700 1709
1701 1710 map.copy_map_insert(p(b"files/from_p2"), p(b"from_p2_copy_source"))?;
1702 1711 assert_eq!(map.copy_map_len(), 2);
1703 1712
1704 1713 // Info from p2, so its copy source info is kept around
1705 1714 map.set_untracked(p(b"files/from_p2"))?;
1706 1715 assert!(map.get(p(b"files/from_p2"))?.is_some());
1707 1716 assert_eq!(map.copy_map_len(), 2);
1708 1717
1709 1718 Ok(())
1710 1719 }
1711 1720
1712 1721 /// Test with "on disk" data. For the sake of this test, the "on disk" data
1713 1722 /// does not actually come from the disk, but it's opaque to the code being
1714 1723 /// tested.
1715 1724 #[test]
1716 1725 fn test_on_disk() -> Result<(), DirstateError> {
1717 1726 // First let's create some data to put "on disk"
1718 1727 let mut map = OwningDirstateMap::new_empty(vec![]);
1719 1728
1720 1729 // A file that was just added
1721 1730 map.set_tracked(p(b"some/nested/added"))?;
1722 1731 map.copy_map_insert(p(b"some/nested/added"), p(b"added_copy_source"))?;
1723 1732
1724 1733 // A file that was removed
1725 1734 map.reset_state(
1726 1735 p(b"some/nested/removed"),
1727 1736 false,
1728 1737 true,
1729 1738 false,
1730 1739 false,
1731 1740 None,
1732 1741 )?;
1733 1742 // Only present in p2
1734 1743 map.reset_state(
1735 1744 p(b"other/p2_info_only"),
1736 1745 false,
1737 1746 false,
1738 1747 true,
1739 1748 false,
1740 1749 None,
1741 1750 )?;
1742 1751 map.copy_map_insert(
1743 1752 p(b"other/p2_info_only"),
1744 1753 p(b"other/p2_info_copy_source"),
1745 1754 )?;
1746 1755 // A file that was merged
1747 1756 map.reset_state(p(b"merged"), true, true, true, false, None)?;
1748 1757 // A file that is added, with info from p2
1749 1758 // XXX is that actually possible?
1750 1759 map.reset_state(
1751 1760 p(b"other/added_with_p2"),
1752 1761 true,
1753 1762 false,
1754 1763 true,
1755 1764 false,
1756 1765 None,
1757 1766 )?;
1758 1767 // One layer without any files to test deletion cascade
1759 1768 // A clean file
1760 1769 map.reset_state(
1761 1770 p(b"some/other/nested/clean"),
1762 1771 true,
1763 1772 true,
1764 1773 false,
1765 1774 false,
1766 1775 None,
1767 1776 )?;
1768 1777
1769 1778 let (packed, metadata, _should_append) = map.pack_v2(false)?;
1770 1779 let packed_len = packed.len();
1771 1780 assert!(packed_len > 0);
1772 1781
1773 1782 // Recreate "from disk"
1774 1783 let mut map = OwningDirstateMap::new_v2(
1775 1784 packed,
1776 1785 packed_len,
1777 1786 metadata.as_bytes(),
1778 1787 )?;
1779 1788
1780 1789 // Check that everything is accounted for
1781 1790 assert!(map.contains_key(p(b"some/nested/added"))?);
1782 1791 assert!(map.contains_key(p(b"some/nested/removed"))?);
1783 1792 assert!(map.contains_key(p(b"merged"))?);
1784 1793 assert!(map.contains_key(p(b"other/p2_info_only"))?);
1785 1794 assert!(map.contains_key(p(b"other/added_with_p2"))?);
1786 1795 assert!(map.contains_key(p(b"some/other/nested/clean"))?);
1787 1796 assert_eq!(
1788 1797 map.copy_map_get(p(b"some/nested/added"))?,
1789 1798 Some(p(b"added_copy_source"))
1790 1799 );
1791 1800 assert_eq!(
1792 1801 map.copy_map_get(p(b"other/p2_info_only"))?,
1793 1802 Some(p(b"other/p2_info_copy_source"))
1794 1803 );
1795 1804 assert_eq!(tracked_descendants(&map, b"some"), 2);
1796 1805 assert_eq!(descendants_with_an_entry(&map, b"some"), 3);
1797 1806 assert_eq!(tracked_descendants(&map, b"other"), 1);
1798 1807 assert_eq!(descendants_with_an_entry(&map, b"other"), 2);
1799 1808 assert_eq!(tracked_descendants(&map, b"some/other"), 1);
1800 1809 assert_eq!(descendants_with_an_entry(&map, b"some/other"), 1);
1801 1810 assert_eq!(tracked_descendants(&map, b"some/other/nested"), 1);
1802 1811 assert_eq!(descendants_with_an_entry(&map, b"some/other/nested"), 1);
1803 1812 assert_eq!(tracked_descendants(&map, b"some/nested"), 1);
1804 1813 assert_eq!(descendants_with_an_entry(&map, b"some/nested"), 2);
1805 1814 assert_eq!(map.len(), 6);
1806 1815 assert_eq!(map.get_map().unreachable_bytes, 0);
1807 1816 assert_eq!(map.copy_map_len(), 2);
1808 1817
1809 1818 // Shouldn't change anything since it's already not tracked
1810 1819 map.set_untracked(p(b"some/nested/removed"))?;
1811 1820 assert_eq!(map.get_map().unreachable_bytes, 0);
1812 1821
1813 1822 match map.get_map().root {
1814 1823 ChildNodes::InMemory(_) => {
1815 1824 panic!("root should not have been mutated")
1816 1825 }
1817 1826 _ => (),
1818 1827 }
1819 1828 // We haven't mutated enough (nothing, actually), we should still be in
1820 1829 // the append strategy
1821 1830 assert!(map.get_map().write_should_append());
1822 1831
1823 1832 // But this mutates the structure, so there should be unreachable_bytes
1824 1833 assert!(map.set_untracked(p(b"some/nested/added"))?);
1825 1834 let unreachable_bytes = map.get_map().unreachable_bytes;
1826 1835 assert!(unreachable_bytes > 0);
1827 1836
1828 1837 match map.get_map().root {
1829 1838 ChildNodes::OnDisk(_) => panic!("root should have been mutated"),
1830 1839 _ => (),
1831 1840 }
1832 1841
1833 1842 // This should not mutate the structure either, since `root` has
1834 1843 // already been mutated along with its direct children.
1835 1844 map.set_untracked(p(b"merged"))?;
1836 1845 assert_eq!(map.get_map().unreachable_bytes, unreachable_bytes);
1837 1846
1838 1847 match map.get_map().get_node(p(b"other/added_with_p2"))?.unwrap() {
1839 1848 NodeRef::InMemory(_, _) => {
1840 1849 panic!("'other/added_with_p2' should not have been mutated")
1841 1850 }
1842 1851 _ => (),
1843 1852 }
1844 1853 // But this should, since it's in a different path
1845 1854 // than `<root>some/nested/add`
1846 1855 map.set_untracked(p(b"other/added_with_p2"))?;
1847 1856 assert!(map.get_map().unreachable_bytes > unreachable_bytes);
1848 1857
1849 1858 match map.get_map().get_node(p(b"other/added_with_p2"))?.unwrap() {
1850 1859 NodeRef::OnDisk(_) => {
1851 1860 panic!("'other/added_with_p2' should have been mutated")
1852 1861 }
1853 1862 _ => (),
1854 1863 }
1855 1864
1856 1865 // We have rewritten most of the tree, we should create a new file
1857 1866 assert!(!map.get_map().write_should_append());
1858 1867
1859 1868 Ok(())
1860 1869 }
1861 1870 }
General Comments 0
You need to be logged in to leave comments. Login now