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