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