##// END OF EJS Templates
rust: don't use a reference to a `Cow`...
Raphaël Gomès -
r50817:b6dc4802 default
parent child Browse files
Show More
@@ -1,1907 +1,1913 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 fn count_dropped_path(unreachable_bytes: &mut u32, path: &Cow<HgPath>) {
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 942 impl OwningDirstateMap {
943 943 pub fn clear(&mut self) {
944 944 self.with_dmap_mut(|map| {
945 945 map.root = Default::default();
946 946 map.nodes_with_entry_count = 0;
947 947 map.nodes_with_copy_source_count = 0;
948 948 });
949 949 }
950 950
951 951 pub fn set_tracked(
952 952 &mut self,
953 953 filename: &HgPath,
954 954 ) -> Result<bool, DirstateV2ParseError> {
955 955 let old_entry_opt = self.get(filename)?;
956 956 self.with_dmap_mut(|map| map.set_tracked(filename, old_entry_opt))
957 957 }
958 958
959 959 pub fn set_untracked(
960 960 &mut self,
961 961 filename: &HgPath,
962 962 ) -> Result<bool, DirstateError> {
963 963 let old_entry_opt = self.get(filename)?;
964 964 match old_entry_opt {
965 965 None => Ok(false),
966 966 Some(old_entry) => {
967 967 if !old_entry.tracked() {
968 968 // `DirstateMap::set_untracked` is not a noop if
969 969 // already not tracked as it will decrement the
970 970 // tracked counters while going down.
971 971 return Ok(true);
972 972 }
973 973 if old_entry.added() {
974 974 // Untracking an "added" entry will just result in a
975 975 // worthless entry (and other parts of the code will
976 976 // complain about it), just drop it entirely.
977 977 self.drop_entry_and_copy_source(filename)?;
978 978 return Ok(true);
979 979 }
980 980 if !old_entry.p2_info() {
981 981 self.copy_map_remove(filename)?;
982 982 }
983 983
984 984 self.with_dmap_mut(|map| {
985 985 map.set_untracked(filename, old_entry)?;
986 986 Ok(true)
987 987 })
988 988 }
989 989 }
990 990 }
991 991
992 992 pub fn set_clean(
993 993 &mut self,
994 994 filename: &HgPath,
995 995 mode: u32,
996 996 size: u32,
997 997 mtime: TruncatedTimestamp,
998 998 ) -> Result<(), DirstateError> {
999 999 let old_entry = match self.get(filename)? {
1000 1000 None => {
1001 1001 return Err(
1002 1002 DirstateMapError::PathNotFound(filename.into()).into()
1003 1003 )
1004 1004 }
1005 1005 Some(e) => e,
1006 1006 };
1007 1007 self.copy_map_remove(filename)?;
1008 1008 self.with_dmap_mut(|map| {
1009 1009 map.set_clean(filename, old_entry, mode, size, mtime)
1010 1010 })
1011 1011 }
1012 1012
1013 1013 pub fn set_possibly_dirty(
1014 1014 &mut self,
1015 1015 filename: &HgPath,
1016 1016 ) -> Result<(), DirstateError> {
1017 1017 if self.get(filename)?.is_none() {
1018 1018 return Err(DirstateMapError::PathNotFound(filename.into()).into());
1019 1019 }
1020 1020 self.with_dmap_mut(|map| map.set_possibly_dirty(filename))
1021 1021 }
1022 1022
1023 1023 pub fn reset_state(
1024 1024 &mut self,
1025 1025 filename: &HgPath,
1026 1026 wc_tracked: bool,
1027 1027 p1_tracked: bool,
1028 1028 p2_info: bool,
1029 1029 has_meaningful_mtime: bool,
1030 1030 parent_file_data_opt: Option<ParentFileData>,
1031 1031 ) -> Result<(), DirstateError> {
1032 1032 if !(p1_tracked || p2_info || wc_tracked) {
1033 1033 self.drop_entry_and_copy_source(filename)?;
1034 1034 return Ok(());
1035 1035 }
1036 1036 self.copy_map_remove(filename)?;
1037 1037 let old_entry_opt = self.get(filename)?;
1038 1038 self.with_dmap_mut(|map| {
1039 1039 map.reset_state(
1040 1040 filename,
1041 1041 old_entry_opt,
1042 1042 wc_tracked,
1043 1043 p1_tracked,
1044 1044 p2_info,
1045 1045 has_meaningful_mtime,
1046 1046 parent_file_data_opt,
1047 1047 )
1048 1048 })
1049 1049 }
1050 1050
1051 1051 pub fn drop_entry_and_copy_source(
1052 1052 &mut self,
1053 1053 filename: &HgPath,
1054 1054 ) -> Result<(), DirstateError> {
1055 1055 let was_tracked = self.get(filename)?.map_or(false, |e| e.tracked());
1056 1056 struct Dropped {
1057 1057 was_tracked: bool,
1058 1058 had_entry: bool,
1059 1059 had_copy_source: bool,
1060 1060 }
1061 1061
1062 1062 /// If this returns `Ok(Some((dropped, removed)))`, then
1063 1063 ///
1064 1064 /// * `dropped` is about the leaf node that was at `filename`
1065 1065 /// * `removed` is whether this particular level of recursion just
1066 1066 /// removed a node in `nodes`.
1067 1067 fn recur<'on_disk>(
1068 1068 on_disk: &'on_disk [u8],
1069 1069 unreachable_bytes: &mut u32,
1070 1070 nodes: &mut ChildNodes<'on_disk>,
1071 1071 path: &HgPath,
1072 1072 ) -> Result<Option<(Dropped, bool)>, DirstateV2ParseError> {
1073 1073 let (first_path_component, rest_of_path) =
1074 1074 path.split_first_component();
1075 1075 let nodes = nodes.make_mut(on_disk, unreachable_bytes)?;
1076 1076 let node = if let Some(node) = nodes.get_mut(first_path_component)
1077 1077 {
1078 1078 node
1079 1079 } else {
1080 1080 return Ok(None);
1081 1081 };
1082 1082 let dropped;
1083 1083 if let Some(rest) = rest_of_path {
1084 1084 if let Some((d, removed)) = recur(
1085 1085 on_disk,
1086 1086 unreachable_bytes,
1087 1087 &mut node.children,
1088 1088 rest,
1089 1089 )? {
1090 1090 dropped = d;
1091 1091 if dropped.had_entry {
1092 1092 node.descendants_with_entry_count = node
1093 1093 .descendants_with_entry_count
1094 1094 .checked_sub(1)
1095 1095 .expect(
1096 1096 "descendants_with_entry_count should be >= 0",
1097 1097 );
1098 1098 }
1099 1099 if dropped.was_tracked {
1100 1100 node.tracked_descendants_count = node
1101 1101 .tracked_descendants_count
1102 1102 .checked_sub(1)
1103 1103 .expect(
1104 1104 "tracked_descendants_count should be >= 0",
1105 1105 );
1106 1106 }
1107 1107
1108 1108 // Directory caches must be invalidated when removing a
1109 1109 // child node
1110 1110 if removed {
1111 1111 if let NodeData::CachedDirectory { .. } = &node.data {
1112 1112 node.data = NodeData::None
1113 1113 }
1114 1114 }
1115 1115 } else {
1116 1116 return Ok(None);
1117 1117 }
1118 1118 } else {
1119 1119 let entry = node.data.as_entry();
1120 1120 let was_tracked = entry.map_or(false, |entry| entry.tracked());
1121 1121 let had_entry = entry.is_some();
1122 1122 if had_entry {
1123 1123 node.data = NodeData::None
1124 1124 }
1125 1125 let mut had_copy_source = false;
1126 1126 if let Some(source) = &node.copy_source {
1127 DirstateMap::count_dropped_path(unreachable_bytes, source);
1127 DirstateMap::count_dropped_path(
1128 unreachable_bytes,
1129 Cow::Borrowed(source),
1130 );
1128 1131 had_copy_source = true;
1129 1132 node.copy_source = None
1130 1133 }
1131 1134 dropped = Dropped {
1132 1135 was_tracked,
1133 1136 had_entry,
1134 1137 had_copy_source,
1135 1138 };
1136 1139 }
1137 1140 // After recursion, for both leaf (rest_of_path is None) nodes and
1138 1141 // parent nodes, remove a node if it just became empty.
1139 1142 let remove = !node.data.has_entry()
1140 1143 && node.copy_source.is_none()
1141 1144 && node.children.is_empty();
1142 1145 if remove {
1143 1146 let (key, _) =
1144 1147 nodes.remove_entry(first_path_component).unwrap();
1145 1148 DirstateMap::count_dropped_path(
1146 1149 unreachable_bytes,
1147 key.full_path(),
1150 Cow::Borrowed(key.full_path()),
1148 1151 )
1149 1152 }
1150 1153 Ok(Some((dropped, remove)))
1151 1154 }
1152 1155
1153 1156 self.with_dmap_mut(|map| {
1154 1157 if let Some((dropped, _removed)) = recur(
1155 1158 map.on_disk,
1156 1159 &mut map.unreachable_bytes,
1157 1160 &mut map.root,
1158 1161 filename,
1159 1162 )? {
1160 1163 if dropped.had_entry {
1161 1164 map.nodes_with_entry_count = map
1162 1165 .nodes_with_entry_count
1163 1166 .checked_sub(1)
1164 1167 .expect("nodes_with_entry_count should be >= 0");
1165 1168 }
1166 1169 if dropped.had_copy_source {
1167 1170 map.nodes_with_copy_source_count = map
1168 1171 .nodes_with_copy_source_count
1169 1172 .checked_sub(1)
1170 1173 .expect("nodes_with_copy_source_count should be >= 0");
1171 1174 }
1172 1175 } else {
1173 1176 debug_assert!(!was_tracked);
1174 1177 }
1175 1178 Ok(())
1176 1179 })
1177 1180 }
1178 1181
1179 1182 pub fn has_tracked_dir(
1180 1183 &mut self,
1181 1184 directory: &HgPath,
1182 1185 ) -> Result<bool, DirstateError> {
1183 1186 self.with_dmap_mut(|map| {
1184 1187 if let Some(node) = map.get_node(directory)? {
1185 1188 // A node without a `DirstateEntry` was created to hold child
1186 1189 // nodes, and is therefore a directory.
1187 1190 let is_dir = node.entry()?.is_none();
1188 1191 Ok(is_dir && node.tracked_descendants_count() > 0)
1189 1192 } else {
1190 1193 Ok(false)
1191 1194 }
1192 1195 })
1193 1196 }
1194 1197
1195 1198 pub fn has_dir(
1196 1199 &mut self,
1197 1200 directory: &HgPath,
1198 1201 ) -> Result<bool, DirstateError> {
1199 1202 self.with_dmap_mut(|map| {
1200 1203 if let Some(node) = map.get_node(directory)? {
1201 1204 // A node without a `DirstateEntry` was created to hold child
1202 1205 // nodes, and is therefore a directory.
1203 1206 let is_dir = node.entry()?.is_none();
1204 1207 Ok(is_dir && node.descendants_with_entry_count() > 0)
1205 1208 } else {
1206 1209 Ok(false)
1207 1210 }
1208 1211 })
1209 1212 }
1210 1213
1211 1214 #[logging_timer::time("trace")]
1212 1215 pub fn pack_v1(
1213 1216 &self,
1214 1217 parents: DirstateParents,
1215 1218 ) -> Result<Vec<u8>, DirstateError> {
1216 1219 let map = self.get_map();
1217 1220 // Optizimation (to be measured?): pre-compute size to avoid `Vec`
1218 1221 // reallocations
1219 1222 let mut size = parents.as_bytes().len();
1220 1223 for node in map.iter_nodes() {
1221 1224 let node = node?;
1222 1225 if node.entry()?.is_some() {
1223 1226 size += packed_entry_size(
1224 1227 node.full_path(map.on_disk)?,
1225 1228 node.copy_source(map.on_disk)?,
1226 1229 );
1227 1230 }
1228 1231 }
1229 1232
1230 1233 let mut packed = Vec::with_capacity(size);
1231 1234 packed.extend(parents.as_bytes());
1232 1235
1233 1236 for node in map.iter_nodes() {
1234 1237 let node = node?;
1235 1238 if let Some(entry) = node.entry()? {
1236 1239 pack_entry(
1237 1240 node.full_path(map.on_disk)?,
1238 1241 &entry,
1239 1242 node.copy_source(map.on_disk)?,
1240 1243 &mut packed,
1241 1244 );
1242 1245 }
1243 1246 }
1244 1247 Ok(packed)
1245 1248 }
1246 1249
1247 1250 /// Returns new data and metadata together with whether that data should be
1248 1251 /// appended to the existing data file whose content is at
1249 1252 /// `map.on_disk` (true), instead of written to a new data file
1250 1253 /// (false), and the previous size of data on disk.
1251 1254 #[logging_timer::time("trace")]
1252 1255 pub fn pack_v2(
1253 1256 &self,
1254 1257 can_append: bool,
1255 1258 ) -> Result<(Vec<u8>, on_disk::TreeMetadata, bool, usize), DirstateError>
1256 1259 {
1257 1260 let map = self.get_map();
1258 1261 on_disk::write(map, can_append)
1259 1262 }
1260 1263
1261 1264 /// `callback` allows the caller to process and do something with the
1262 1265 /// results of the status. This is needed to do so efficiently (i.e.
1263 1266 /// without cloning the `DirstateStatus` object with its paths) because
1264 1267 /// we need to borrow from `Self`.
1265 1268 pub fn with_status<R>(
1266 1269 &mut self,
1267 1270 matcher: &(dyn Matcher + Sync),
1268 1271 root_dir: PathBuf,
1269 1272 ignore_files: Vec<PathBuf>,
1270 1273 options: StatusOptions,
1271 1274 callback: impl for<'r> FnOnce(
1272 1275 Result<(DirstateStatus<'r>, Vec<PatternFileWarning>), StatusError>,
1273 1276 ) -> R,
1274 1277 ) -> R {
1275 1278 self.with_dmap_mut(|map| {
1276 1279 callback(super::status::status(
1277 1280 map,
1278 1281 matcher,
1279 1282 root_dir,
1280 1283 ignore_files,
1281 1284 options,
1282 1285 ))
1283 1286 })
1284 1287 }
1285 1288
1286 1289 pub fn copy_map_len(&self) -> usize {
1287 1290 let map = self.get_map();
1288 1291 map.nodes_with_copy_source_count as usize
1289 1292 }
1290 1293
1291 1294 pub fn copy_map_iter(&self) -> CopyMapIter<'_> {
1292 1295 let map = self.get_map();
1293 1296 Box::new(filter_map_results(map.iter_nodes(), move |node| {
1294 1297 Ok(if let Some(source) = node.copy_source(map.on_disk)? {
1295 1298 Some((node.full_path(map.on_disk)?, source))
1296 1299 } else {
1297 1300 None
1298 1301 })
1299 1302 }))
1300 1303 }
1301 1304
1302 1305 pub fn copy_map_contains_key(
1303 1306 &self,
1304 1307 key: &HgPath,
1305 1308 ) -> Result<bool, DirstateV2ParseError> {
1306 1309 let map = self.get_map();
1307 1310 Ok(if let Some(node) = map.get_node(key)? {
1308 1311 node.has_copy_source()
1309 1312 } else {
1310 1313 false
1311 1314 })
1312 1315 }
1313 1316
1314 1317 pub fn copy_map_get(
1315 1318 &self,
1316 1319 key: &HgPath,
1317 1320 ) -> Result<Option<&HgPath>, DirstateV2ParseError> {
1318 1321 let map = self.get_map();
1319 1322 if let Some(node) = map.get_node(key)? {
1320 1323 if let Some(source) = node.copy_source(map.on_disk)? {
1321 1324 return Ok(Some(source));
1322 1325 }
1323 1326 }
1324 1327 Ok(None)
1325 1328 }
1326 1329
1327 1330 pub fn copy_map_remove(
1328 1331 &mut self,
1329 1332 key: &HgPath,
1330 1333 ) -> Result<Option<HgPathBuf>, DirstateV2ParseError> {
1331 1334 self.with_dmap_mut(|map| {
1332 1335 let count = &mut map.nodes_with_copy_source_count;
1333 1336 let unreachable_bytes = &mut map.unreachable_bytes;
1334 1337 Ok(DirstateMap::get_node_mut_inner(
1335 1338 map.on_disk,
1336 1339 unreachable_bytes,
1337 1340 &mut map.root,
1338 1341 key,
1339 1342 |_ancestor| {},
1340 1343 )?
1341 1344 .and_then(|node| {
1342 1345 if let Some(source) = &node.copy_source {
1343 1346 *count = count
1344 1347 .checked_sub(1)
1345 1348 .expect("nodes_with_copy_source_count should be >= 0");
1346 DirstateMap::count_dropped_path(unreachable_bytes, source);
1349 DirstateMap::count_dropped_path(
1350 unreachable_bytes,
1351 Cow::Borrowed(source),
1352 );
1347 1353 }
1348 1354 node.copy_source.take().map(Cow::into_owned)
1349 1355 }))
1350 1356 })
1351 1357 }
1352 1358
1353 1359 pub fn copy_map_insert(
1354 1360 &mut self,
1355 1361 key: &HgPath,
1356 1362 value: &HgPath,
1357 1363 ) -> Result<Option<HgPathBuf>, DirstateV2ParseError> {
1358 1364 self.with_dmap_mut(|map| {
1359 1365 let node = map.get_or_insert_node(&key, |_ancestor| {})?;
1360 1366 let had_copy_source = node.copy_source.is_none();
1361 1367 let old = node
1362 1368 .copy_source
1363 1369 .replace(value.to_owned().into())
1364 1370 .map(Cow::into_owned);
1365 1371 if had_copy_source {
1366 1372 map.nodes_with_copy_source_count += 1
1367 1373 }
1368 1374 Ok(old)
1369 1375 })
1370 1376 }
1371 1377
1372 1378 pub fn len(&self) -> usize {
1373 1379 let map = self.get_map();
1374 1380 map.nodes_with_entry_count as usize
1375 1381 }
1376 1382
1377 1383 pub fn contains_key(
1378 1384 &self,
1379 1385 key: &HgPath,
1380 1386 ) -> Result<bool, DirstateV2ParseError> {
1381 1387 Ok(self.get(key)?.is_some())
1382 1388 }
1383 1389
1384 1390 pub fn get(
1385 1391 &self,
1386 1392 key: &HgPath,
1387 1393 ) -> Result<Option<DirstateEntry>, DirstateV2ParseError> {
1388 1394 let map = self.get_map();
1389 1395 Ok(if let Some(node) = map.get_node(key)? {
1390 1396 node.entry()?
1391 1397 } else {
1392 1398 None
1393 1399 })
1394 1400 }
1395 1401
1396 1402 pub fn iter(&self) -> StateMapIter<'_> {
1397 1403 let map = self.get_map();
1398 1404 Box::new(filter_map_results(map.iter_nodes(), move |node| {
1399 1405 Ok(if let Some(entry) = node.entry()? {
1400 1406 Some((node.full_path(map.on_disk)?, entry))
1401 1407 } else {
1402 1408 None
1403 1409 })
1404 1410 }))
1405 1411 }
1406 1412
1407 1413 pub fn iter_tracked_dirs(
1408 1414 &mut self,
1409 1415 ) -> Result<
1410 1416 Box<
1411 1417 dyn Iterator<Item = Result<&HgPath, DirstateV2ParseError>>
1412 1418 + Send
1413 1419 + '_,
1414 1420 >,
1415 1421 DirstateError,
1416 1422 > {
1417 1423 let map = self.get_map();
1418 1424 let on_disk = map.on_disk;
1419 1425 Ok(Box::new(filter_map_results(
1420 1426 map.iter_nodes(),
1421 1427 move |node| {
1422 1428 Ok(if node.tracked_descendants_count() > 0 {
1423 1429 Some(node.full_path(on_disk)?)
1424 1430 } else {
1425 1431 None
1426 1432 })
1427 1433 },
1428 1434 )))
1429 1435 }
1430 1436
1431 1437 /// Only public because it needs to be exposed to the Python layer.
1432 1438 /// It is not the full `setparents` logic, only the parts that mutate the
1433 1439 /// entries.
1434 1440 pub fn setparents_fixup(
1435 1441 &mut self,
1436 1442 ) -> Result<Vec<(HgPathBuf, HgPathBuf)>, DirstateV2ParseError> {
1437 1443 // XXX
1438 1444 // All the copying and re-querying is quite inefficient, but this is
1439 1445 // still a lot better than doing it from Python.
1440 1446 //
1441 1447 // The better solution is to develop a mechanism for `iter_mut`,
1442 1448 // which will be a lot more involved: we're dealing with a lazy,
1443 1449 // append-mostly, tree-like data structure. This will do for now.
1444 1450 let mut copies = vec![];
1445 1451 let mut files_with_p2_info = vec![];
1446 1452 for res in self.iter() {
1447 1453 let (path, entry) = res?;
1448 1454 if entry.p2_info() {
1449 1455 files_with_p2_info.push(path.to_owned())
1450 1456 }
1451 1457 }
1452 1458 self.with_dmap_mut(|map| {
1453 1459 for path in files_with_p2_info.iter() {
1454 1460 let node = map.get_or_insert_node(path, |_| {})?;
1455 1461 let entry =
1456 1462 node.data.as_entry_mut().expect("entry should exist");
1457 1463 entry.drop_merge_data();
1458 1464 if let Some(source) = node.copy_source.take().as_deref() {
1459 1465 copies.push((path.to_owned(), source.to_owned()));
1460 1466 }
1461 1467 }
1462 1468 Ok(copies)
1463 1469 })
1464 1470 }
1465 1471
1466 1472 pub fn debug_iter(
1467 1473 &self,
1468 1474 all: bool,
1469 1475 ) -> Box<
1470 1476 dyn Iterator<
1471 1477 Item = Result<
1472 1478 (&HgPath, (u8, i32, i32, i32)),
1473 1479 DirstateV2ParseError,
1474 1480 >,
1475 1481 > + Send
1476 1482 + '_,
1477 1483 > {
1478 1484 let map = self.get_map();
1479 1485 Box::new(filter_map_results(map.iter_nodes(), move |node| {
1480 1486 let debug_tuple = if let Some(entry) = node.entry()? {
1481 1487 entry.debug_tuple()
1482 1488 } else if !all {
1483 1489 return Ok(None);
1484 1490 } else if let Some(mtime) = node.cached_directory_mtime()? {
1485 1491 (b' ', 0, -1, mtime.truncated_seconds() as i32)
1486 1492 } else {
1487 1493 (b' ', 0, -1, -1)
1488 1494 };
1489 1495 Ok(Some((node.full_path(map.on_disk)?, debug_tuple)))
1490 1496 }))
1491 1497 }
1492 1498 }
1493 1499 #[cfg(test)]
1494 1500 mod tests {
1495 1501 use super::*;
1496 1502
1497 1503 /// Shortcut to return tracked descendants of a path.
1498 1504 /// Panics if the path does not exist.
1499 1505 fn tracked_descendants(map: &OwningDirstateMap, path: &[u8]) -> u32 {
1500 1506 let path = dbg!(HgPath::new(path));
1501 1507 let node = map.get_map().get_node(path);
1502 1508 node.unwrap().unwrap().tracked_descendants_count()
1503 1509 }
1504 1510
1505 1511 /// Shortcut to return descendants with an entry.
1506 1512 /// Panics if the path does not exist.
1507 1513 fn descendants_with_an_entry(map: &OwningDirstateMap, path: &[u8]) -> u32 {
1508 1514 let path = dbg!(HgPath::new(path));
1509 1515 let node = map.get_map().get_node(path);
1510 1516 node.unwrap().unwrap().descendants_with_entry_count()
1511 1517 }
1512 1518
1513 1519 fn assert_does_not_exist(map: &OwningDirstateMap, path: &[u8]) {
1514 1520 let path = dbg!(HgPath::new(path));
1515 1521 let node = map.get_map().get_node(path);
1516 1522 assert!(node.unwrap().is_none());
1517 1523 }
1518 1524
1519 1525 /// Shortcut for path creation in tests
1520 1526 fn p(b: &[u8]) -> &HgPath {
1521 1527 HgPath::new(b)
1522 1528 }
1523 1529
1524 1530 /// Test the very simple case a single tracked file
1525 1531 #[test]
1526 1532 fn test_tracked_descendants_simple() -> Result<(), DirstateError> {
1527 1533 let mut map = OwningDirstateMap::new_empty(vec![]);
1528 1534 assert_eq!(map.len(), 0);
1529 1535
1530 1536 map.set_tracked(p(b"some/nested/path"))?;
1531 1537
1532 1538 assert_eq!(map.len(), 1);
1533 1539 assert_eq!(tracked_descendants(&map, b"some"), 1);
1534 1540 assert_eq!(tracked_descendants(&map, b"some/nested"), 1);
1535 1541 assert_eq!(tracked_descendants(&map, b"some/nested/path"), 0);
1536 1542
1537 1543 map.set_untracked(p(b"some/nested/path"))?;
1538 1544 assert_eq!(map.len(), 0);
1539 1545 assert!(map.get_map().get_node(p(b"some"))?.is_none());
1540 1546
1541 1547 Ok(())
1542 1548 }
1543 1549
1544 1550 /// Test the simple case of all tracked, but multiple files
1545 1551 #[test]
1546 1552 fn test_tracked_descendants_multiple() -> Result<(), DirstateError> {
1547 1553 let mut map = OwningDirstateMap::new_empty(vec![]);
1548 1554
1549 1555 map.set_tracked(p(b"some/nested/path"))?;
1550 1556 map.set_tracked(p(b"some/nested/file"))?;
1551 1557 // one layer without any files to test deletion cascade
1552 1558 map.set_tracked(p(b"some/other/nested/path"))?;
1553 1559 map.set_tracked(p(b"root_file"))?;
1554 1560 map.set_tracked(p(b"some/file"))?;
1555 1561 map.set_tracked(p(b"some/file2"))?;
1556 1562 map.set_tracked(p(b"some/file3"))?;
1557 1563
1558 1564 assert_eq!(map.len(), 7);
1559 1565 assert_eq!(tracked_descendants(&map, b"some"), 6);
1560 1566 assert_eq!(tracked_descendants(&map, b"some/nested"), 2);
1561 1567 assert_eq!(tracked_descendants(&map, b"some/other"), 1);
1562 1568 assert_eq!(tracked_descendants(&map, b"some/other/nested"), 1);
1563 1569 assert_eq!(tracked_descendants(&map, b"some/nested/path"), 0);
1564 1570
1565 1571 map.set_untracked(p(b"some/nested/path"))?;
1566 1572 assert_eq!(map.len(), 6);
1567 1573 assert_eq!(tracked_descendants(&map, b"some"), 5);
1568 1574 assert_eq!(tracked_descendants(&map, b"some/nested"), 1);
1569 1575 assert_eq!(tracked_descendants(&map, b"some/other"), 1);
1570 1576 assert_eq!(tracked_descendants(&map, b"some/other/nested"), 1);
1571 1577
1572 1578 map.set_untracked(p(b"some/nested/file"))?;
1573 1579 assert_eq!(map.len(), 5);
1574 1580 assert_eq!(tracked_descendants(&map, b"some"), 4);
1575 1581 assert_eq!(tracked_descendants(&map, b"some/other"), 1);
1576 1582 assert_eq!(tracked_descendants(&map, b"some/other/nested"), 1);
1577 1583 assert_does_not_exist(&map, b"some_nested");
1578 1584
1579 1585 map.set_untracked(p(b"some/other/nested/path"))?;
1580 1586 assert_eq!(map.len(), 4);
1581 1587 assert_eq!(tracked_descendants(&map, b"some"), 3);
1582 1588 assert_does_not_exist(&map, b"some/other");
1583 1589
1584 1590 map.set_untracked(p(b"root_file"))?;
1585 1591 assert_eq!(map.len(), 3);
1586 1592 assert_eq!(tracked_descendants(&map, b"some"), 3);
1587 1593 assert_does_not_exist(&map, b"root_file");
1588 1594
1589 1595 map.set_untracked(p(b"some/file"))?;
1590 1596 assert_eq!(map.len(), 2);
1591 1597 assert_eq!(tracked_descendants(&map, b"some"), 2);
1592 1598 assert_does_not_exist(&map, b"some/file");
1593 1599
1594 1600 map.set_untracked(p(b"some/file2"))?;
1595 1601 assert_eq!(map.len(), 1);
1596 1602 assert_eq!(tracked_descendants(&map, b"some"), 1);
1597 1603 assert_does_not_exist(&map, b"some/file2");
1598 1604
1599 1605 map.set_untracked(p(b"some/file3"))?;
1600 1606 assert_eq!(map.len(), 0);
1601 1607 assert_does_not_exist(&map, b"some/file3");
1602 1608
1603 1609 Ok(())
1604 1610 }
1605 1611
1606 1612 /// Check with a mix of tracked and non-tracked items
1607 1613 #[test]
1608 1614 fn test_tracked_descendants_different() -> Result<(), DirstateError> {
1609 1615 let mut map = OwningDirstateMap::new_empty(vec![]);
1610 1616
1611 1617 // A file that was just added
1612 1618 map.set_tracked(p(b"some/nested/path"))?;
1613 1619 // This has no information, the dirstate should ignore it
1614 1620 map.reset_state(p(b"some/file"), false, false, false, false, None)?;
1615 1621 assert_does_not_exist(&map, b"some/file");
1616 1622
1617 1623 // A file that was removed
1618 1624 map.reset_state(
1619 1625 p(b"some/nested/file"),
1620 1626 false,
1621 1627 true,
1622 1628 false,
1623 1629 false,
1624 1630 None,
1625 1631 )?;
1626 1632 assert!(!map.get(p(b"some/nested/file"))?.unwrap().tracked());
1627 1633 // Only present in p2
1628 1634 map.reset_state(p(b"some/file3"), false, false, true, false, None)?;
1629 1635 assert!(!map.get(p(b"some/file3"))?.unwrap().tracked());
1630 1636 // A file that was merged
1631 1637 map.reset_state(p(b"root_file"), true, true, true, false, None)?;
1632 1638 assert!(map.get(p(b"root_file"))?.unwrap().tracked());
1633 1639 // A file that is added, with info from p2
1634 1640 // XXX is that actually possible?
1635 1641 map.reset_state(p(b"some/file2"), true, false, true, false, None)?;
1636 1642 assert!(map.get(p(b"some/file2"))?.unwrap().tracked());
1637 1643 // A clean file
1638 1644 // One layer without any files to test deletion cascade
1639 1645 map.reset_state(
1640 1646 p(b"some/other/nested/path"),
1641 1647 true,
1642 1648 true,
1643 1649 false,
1644 1650 false,
1645 1651 None,
1646 1652 )?;
1647 1653 assert!(map.get(p(b"some/other/nested/path"))?.unwrap().tracked());
1648 1654
1649 1655 assert_eq!(map.len(), 6);
1650 1656 assert_eq!(tracked_descendants(&map, b"some"), 3);
1651 1657 assert_eq!(descendants_with_an_entry(&map, b"some"), 5);
1652 1658 assert_eq!(tracked_descendants(&map, b"some/other/nested"), 1);
1653 1659 assert_eq!(descendants_with_an_entry(&map, b"some/other/nested"), 1);
1654 1660 assert_eq!(tracked_descendants(&map, b"some/other/nested/path"), 0);
1655 1661 assert_eq!(
1656 1662 descendants_with_an_entry(&map, b"some/other/nested/path"),
1657 1663 0
1658 1664 );
1659 1665 assert_eq!(tracked_descendants(&map, b"some/nested"), 1);
1660 1666 assert_eq!(descendants_with_an_entry(&map, b"some/nested"), 2);
1661 1667
1662 1668 // might as well check this
1663 1669 map.set_untracked(p(b"path/does/not/exist"))?;
1664 1670 assert_eq!(map.len(), 6);
1665 1671
1666 1672 map.set_untracked(p(b"some/other/nested/path"))?;
1667 1673 // It is set untracked but not deleted since it held other information
1668 1674 assert_eq!(map.len(), 6);
1669 1675 assert_eq!(tracked_descendants(&map, b"some"), 2);
1670 1676 assert_eq!(descendants_with_an_entry(&map, b"some"), 5);
1671 1677 assert_eq!(descendants_with_an_entry(&map, b"some/other"), 1);
1672 1678 assert_eq!(descendants_with_an_entry(&map, b"some/other/nested"), 1);
1673 1679 assert_eq!(tracked_descendants(&map, b"some/nested"), 1);
1674 1680 assert_eq!(descendants_with_an_entry(&map, b"some/nested"), 2);
1675 1681
1676 1682 map.set_untracked(p(b"some/nested/path"))?;
1677 1683 // It is set untracked *and* deleted since it was only added
1678 1684 assert_eq!(map.len(), 5);
1679 1685 assert_eq!(tracked_descendants(&map, b"some"), 1);
1680 1686 assert_eq!(descendants_with_an_entry(&map, b"some"), 4);
1681 1687 assert_eq!(tracked_descendants(&map, b"some/nested"), 0);
1682 1688 assert_eq!(descendants_with_an_entry(&map, b"some/nested"), 1);
1683 1689 assert_does_not_exist(&map, b"some/nested/path");
1684 1690
1685 1691 map.set_untracked(p(b"root_file"))?;
1686 1692 // Untracked but not deleted
1687 1693 assert_eq!(map.len(), 5);
1688 1694 assert!(map.get(p(b"root_file"))?.is_some());
1689 1695
1690 1696 map.set_untracked(p(b"some/file2"))?;
1691 1697 assert_eq!(map.len(), 5);
1692 1698 assert_eq!(tracked_descendants(&map, b"some"), 0);
1693 1699 assert!(map.get(p(b"some/file2"))?.is_some());
1694 1700
1695 1701 map.set_untracked(p(b"some/file3"))?;
1696 1702 assert_eq!(map.len(), 5);
1697 1703 assert_eq!(tracked_descendants(&map, b"some"), 0);
1698 1704 assert!(map.get(p(b"some/file3"))?.is_some());
1699 1705
1700 1706 Ok(())
1701 1707 }
1702 1708
1703 1709 /// Check that copies counter is correctly updated
1704 1710 #[test]
1705 1711 fn test_copy_source() -> Result<(), DirstateError> {
1706 1712 let mut map = OwningDirstateMap::new_empty(vec![]);
1707 1713
1708 1714 // Clean file
1709 1715 map.reset_state(p(b"files/clean"), true, true, false, false, None)?;
1710 1716 // Merged file
1711 1717 map.reset_state(p(b"files/from_p2"), true, true, true, false, None)?;
1712 1718 // Removed file
1713 1719 map.reset_state(p(b"removed"), false, true, false, false, None)?;
1714 1720 // Added file
1715 1721 map.reset_state(p(b"files/added"), true, false, false, false, None)?;
1716 1722 // Add copy
1717 1723 map.copy_map_insert(p(b"files/clean"), p(b"clean_copy_source"))?;
1718 1724 assert_eq!(map.copy_map_len(), 1);
1719 1725
1720 1726 // Copy override
1721 1727 map.copy_map_insert(p(b"files/clean"), p(b"other_clean_copy_source"))?;
1722 1728 assert_eq!(map.copy_map_len(), 1);
1723 1729
1724 1730 // Multiple copies
1725 1731 map.copy_map_insert(p(b"removed"), p(b"removed_copy_source"))?;
1726 1732 assert_eq!(map.copy_map_len(), 2);
1727 1733
1728 1734 map.copy_map_insert(p(b"files/added"), p(b"added_copy_source"))?;
1729 1735 assert_eq!(map.copy_map_len(), 3);
1730 1736
1731 1737 // Added, so the entry is completely removed
1732 1738 map.set_untracked(p(b"files/added"))?;
1733 1739 assert_does_not_exist(&map, b"files/added");
1734 1740 assert_eq!(map.copy_map_len(), 2);
1735 1741
1736 1742 // Removed, so the entry is kept around, so is its copy
1737 1743 map.set_untracked(p(b"removed"))?;
1738 1744 assert!(map.get(p(b"removed"))?.is_some());
1739 1745 assert_eq!(map.copy_map_len(), 2);
1740 1746
1741 1747 // Clean, so the entry is kept around, but not its copy
1742 1748 map.set_untracked(p(b"files/clean"))?;
1743 1749 assert!(map.get(p(b"files/clean"))?.is_some());
1744 1750 assert_eq!(map.copy_map_len(), 1);
1745 1751
1746 1752 map.copy_map_insert(p(b"files/from_p2"), p(b"from_p2_copy_source"))?;
1747 1753 assert_eq!(map.copy_map_len(), 2);
1748 1754
1749 1755 // Info from p2, so its copy source info is kept around
1750 1756 map.set_untracked(p(b"files/from_p2"))?;
1751 1757 assert!(map.get(p(b"files/from_p2"))?.is_some());
1752 1758 assert_eq!(map.copy_map_len(), 2);
1753 1759
1754 1760 Ok(())
1755 1761 }
1756 1762
1757 1763 /// Test with "on disk" data. For the sake of this test, the "on disk" data
1758 1764 /// does not actually come from the disk, but it's opaque to the code being
1759 1765 /// tested.
1760 1766 #[test]
1761 1767 fn test_on_disk() -> Result<(), DirstateError> {
1762 1768 // First let's create some data to put "on disk"
1763 1769 let mut map = OwningDirstateMap::new_empty(vec![]);
1764 1770
1765 1771 // A file that was just added
1766 1772 map.set_tracked(p(b"some/nested/added"))?;
1767 1773 map.copy_map_insert(p(b"some/nested/added"), p(b"added_copy_source"))?;
1768 1774
1769 1775 // A file that was removed
1770 1776 map.reset_state(
1771 1777 p(b"some/nested/removed"),
1772 1778 false,
1773 1779 true,
1774 1780 false,
1775 1781 false,
1776 1782 None,
1777 1783 )?;
1778 1784 // Only present in p2
1779 1785 map.reset_state(
1780 1786 p(b"other/p2_info_only"),
1781 1787 false,
1782 1788 false,
1783 1789 true,
1784 1790 false,
1785 1791 None,
1786 1792 )?;
1787 1793 map.copy_map_insert(
1788 1794 p(b"other/p2_info_only"),
1789 1795 p(b"other/p2_info_copy_source"),
1790 1796 )?;
1791 1797 // A file that was merged
1792 1798 map.reset_state(p(b"merged"), true, true, true, false, None)?;
1793 1799 // A file that is added, with info from p2
1794 1800 // XXX is that actually possible?
1795 1801 map.reset_state(
1796 1802 p(b"other/added_with_p2"),
1797 1803 true,
1798 1804 false,
1799 1805 true,
1800 1806 false,
1801 1807 None,
1802 1808 )?;
1803 1809 // One layer without any files to test deletion cascade
1804 1810 // A clean file
1805 1811 map.reset_state(
1806 1812 p(b"some/other/nested/clean"),
1807 1813 true,
1808 1814 true,
1809 1815 false,
1810 1816 false,
1811 1817 None,
1812 1818 )?;
1813 1819
1814 1820 let (packed, metadata, _should_append, _old_data_size) =
1815 1821 map.pack_v2(false)?;
1816 1822 let packed_len = packed.len();
1817 1823 assert!(packed_len > 0);
1818 1824
1819 1825 // Recreate "from disk"
1820 1826 let mut map = OwningDirstateMap::new_v2(
1821 1827 packed,
1822 1828 packed_len,
1823 1829 metadata.as_bytes(),
1824 1830 )?;
1825 1831
1826 1832 // Check that everything is accounted for
1827 1833 assert!(map.contains_key(p(b"some/nested/added"))?);
1828 1834 assert!(map.contains_key(p(b"some/nested/removed"))?);
1829 1835 assert!(map.contains_key(p(b"merged"))?);
1830 1836 assert!(map.contains_key(p(b"other/p2_info_only"))?);
1831 1837 assert!(map.contains_key(p(b"other/added_with_p2"))?);
1832 1838 assert!(map.contains_key(p(b"some/other/nested/clean"))?);
1833 1839 assert_eq!(
1834 1840 map.copy_map_get(p(b"some/nested/added"))?,
1835 1841 Some(p(b"added_copy_source"))
1836 1842 );
1837 1843 assert_eq!(
1838 1844 map.copy_map_get(p(b"other/p2_info_only"))?,
1839 1845 Some(p(b"other/p2_info_copy_source"))
1840 1846 );
1841 1847 assert_eq!(tracked_descendants(&map, b"some"), 2);
1842 1848 assert_eq!(descendants_with_an_entry(&map, b"some"), 3);
1843 1849 assert_eq!(tracked_descendants(&map, b"other"), 1);
1844 1850 assert_eq!(descendants_with_an_entry(&map, b"other"), 2);
1845 1851 assert_eq!(tracked_descendants(&map, b"some/other"), 1);
1846 1852 assert_eq!(descendants_with_an_entry(&map, b"some/other"), 1);
1847 1853 assert_eq!(tracked_descendants(&map, b"some/other/nested"), 1);
1848 1854 assert_eq!(descendants_with_an_entry(&map, b"some/other/nested"), 1);
1849 1855 assert_eq!(tracked_descendants(&map, b"some/nested"), 1);
1850 1856 assert_eq!(descendants_with_an_entry(&map, b"some/nested"), 2);
1851 1857 assert_eq!(map.len(), 6);
1852 1858 assert_eq!(map.get_map().unreachable_bytes, 0);
1853 1859 assert_eq!(map.copy_map_len(), 2);
1854 1860
1855 1861 // Shouldn't change anything since it's already not tracked
1856 1862 map.set_untracked(p(b"some/nested/removed"))?;
1857 1863 assert_eq!(map.get_map().unreachable_bytes, 0);
1858 1864
1859 1865 match map.get_map().root {
1860 1866 ChildNodes::InMemory(_) => {
1861 1867 panic!("root should not have been mutated")
1862 1868 }
1863 1869 _ => (),
1864 1870 }
1865 1871 // We haven't mutated enough (nothing, actually), we should still be in
1866 1872 // the append strategy
1867 1873 assert!(map.get_map().write_should_append());
1868 1874
1869 1875 // But this mutates the structure, so there should be unreachable_bytes
1870 1876 assert!(map.set_untracked(p(b"some/nested/added"))?);
1871 1877 let unreachable_bytes = map.get_map().unreachable_bytes;
1872 1878 assert!(unreachable_bytes > 0);
1873 1879
1874 1880 match map.get_map().root {
1875 1881 ChildNodes::OnDisk(_) => panic!("root should have been mutated"),
1876 1882 _ => (),
1877 1883 }
1878 1884
1879 1885 // This should not mutate the structure either, since `root` has
1880 1886 // already been mutated along with its direct children.
1881 1887 map.set_untracked(p(b"merged"))?;
1882 1888 assert_eq!(map.get_map().unreachable_bytes, unreachable_bytes);
1883 1889
1884 1890 match map.get_map().get_node(p(b"other/added_with_p2"))?.unwrap() {
1885 1891 NodeRef::InMemory(_, _) => {
1886 1892 panic!("'other/added_with_p2' should not have been mutated")
1887 1893 }
1888 1894 _ => (),
1889 1895 }
1890 1896 // But this should, since it's in a different path
1891 1897 // than `<root>some/nested/add`
1892 1898 map.set_untracked(p(b"other/added_with_p2"))?;
1893 1899 assert!(map.get_map().unreachable_bytes > unreachable_bytes);
1894 1900
1895 1901 match map.get_map().get_node(p(b"other/added_with_p2"))?.unwrap() {
1896 1902 NodeRef::OnDisk(_) => {
1897 1903 panic!("'other/added_with_p2' should have been mutated")
1898 1904 }
1899 1905 _ => (),
1900 1906 }
1901 1907
1902 1908 // We have rewritten most of the tree, we should create a new file
1903 1909 assert!(!map.get_map().write_should_append());
1904 1910
1905 1911 Ok(())
1906 1912 }
1907 1913 }
General Comments 0
You need to be logged in to leave comments. Login now