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