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