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