##// END OF EJS Templates
rust-dirstate-v2: save proper data size if no new data on append...
Raphaël Gomès -
r50037:dd2503a6 stable
parent child Browse files
Show More
@@ -1,1197 +1,1203 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::StateMapIter;
15 15 use crate::dirstate::TruncatedTimestamp;
16 16 use crate::dirstate::SIZE_FROM_OTHER_PARENT;
17 17 use crate::dirstate::SIZE_NON_NORMAL;
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::DirstateParents;
23 23 use crate::DirstateStatus;
24 24 use crate::EntryState;
25 25 use crate::FastHashMap;
26 26 use crate::PatternFileWarning;
27 27 use crate::StatusError;
28 28 use crate::StatusOptions;
29 29
30 30 /// Append to an existing data file if the amount of unreachable data (not used
31 31 /// anymore) is less than this fraction of the total amount of existing data.
32 32 const ACCEPTABLE_UNREACHABLE_BYTES_RATIO: f32 = 0.5;
33 33
34 34 pub struct DirstateMap<'on_disk> {
35 35 /// Contents of the `.hg/dirstate` file
36 36 pub(super) on_disk: &'on_disk [u8],
37 37
38 38 pub(super) root: ChildNodes<'on_disk>,
39 39
40 40 /// Number of nodes anywhere in the tree that have `.entry.is_some()`.
41 41 pub(super) nodes_with_entry_count: u32,
42 42
43 43 /// Number of nodes anywhere in the tree that have
44 44 /// `.copy_source.is_some()`.
45 45 pub(super) nodes_with_copy_source_count: u32,
46 46
47 47 /// See on_disk::Header
48 48 pub(super) ignore_patterns_hash: on_disk::IgnorePatternsHash,
49 49
50 50 /// How many bytes of `on_disk` are not used anymore
51 51 pub(super) unreachable_bytes: u32,
52
53 /// Size of the data used to first load this `DirstateMap`. Used in case
54 /// we need to write some new metadata, but no new data on disk.
55 pub(super) old_data_size: usize,
52 56 }
53 57
54 58 /// Using a plain `HgPathBuf` of the full path from the repository root as a
55 59 /// map key would also work: all paths in a given map have the same parent
56 60 /// path, so comparing full paths gives the same result as comparing base
57 61 /// names. However `HashMap` would waste time always re-hashing the same
58 62 /// string prefix.
59 63 pub(super) type NodeKey<'on_disk> = WithBasename<Cow<'on_disk, HgPath>>;
60 64
61 65 /// Similar to `&'tree Cow<'on_disk, HgPath>`, but can also be returned
62 66 /// for on-disk nodes that don’t actually have a `Cow` to borrow.
63 67 pub(super) enum BorrowedPath<'tree, 'on_disk> {
64 68 InMemory(&'tree HgPathBuf),
65 69 OnDisk(&'on_disk HgPath),
66 70 }
67 71
68 72 pub(super) enum ChildNodes<'on_disk> {
69 73 InMemory(FastHashMap<NodeKey<'on_disk>, Node<'on_disk>>),
70 74 OnDisk(&'on_disk [on_disk::Node]),
71 75 }
72 76
73 77 pub(super) enum ChildNodesRef<'tree, 'on_disk> {
74 78 InMemory(&'tree FastHashMap<NodeKey<'on_disk>, Node<'on_disk>>),
75 79 OnDisk(&'on_disk [on_disk::Node]),
76 80 }
77 81
78 82 pub(super) enum NodeRef<'tree, 'on_disk> {
79 83 InMemory(&'tree NodeKey<'on_disk>, &'tree Node<'on_disk>),
80 84 OnDisk(&'on_disk on_disk::Node),
81 85 }
82 86
83 87 impl<'tree, 'on_disk> BorrowedPath<'tree, 'on_disk> {
84 88 pub fn detach_from_tree(&self) -> Cow<'on_disk, HgPath> {
85 89 match *self {
86 90 BorrowedPath::InMemory(in_memory) => Cow::Owned(in_memory.clone()),
87 91 BorrowedPath::OnDisk(on_disk) => Cow::Borrowed(on_disk),
88 92 }
89 93 }
90 94 }
91 95
92 96 impl<'tree, 'on_disk> std::ops::Deref for BorrowedPath<'tree, 'on_disk> {
93 97 type Target = HgPath;
94 98
95 99 fn deref(&self) -> &HgPath {
96 100 match *self {
97 101 BorrowedPath::InMemory(in_memory) => in_memory,
98 102 BorrowedPath::OnDisk(on_disk) => on_disk,
99 103 }
100 104 }
101 105 }
102 106
103 107 impl Default for ChildNodes<'_> {
104 108 fn default() -> Self {
105 109 ChildNodes::InMemory(Default::default())
106 110 }
107 111 }
108 112
109 113 impl<'on_disk> ChildNodes<'on_disk> {
110 114 pub(super) fn as_ref<'tree>(
111 115 &'tree self,
112 116 ) -> ChildNodesRef<'tree, 'on_disk> {
113 117 match self {
114 118 ChildNodes::InMemory(nodes) => ChildNodesRef::InMemory(nodes),
115 119 ChildNodes::OnDisk(nodes) => ChildNodesRef::OnDisk(nodes),
116 120 }
117 121 }
118 122
119 123 pub(super) fn is_empty(&self) -> bool {
120 124 match self {
121 125 ChildNodes::InMemory(nodes) => nodes.is_empty(),
122 126 ChildNodes::OnDisk(nodes) => nodes.is_empty(),
123 127 }
124 128 }
125 129
126 130 fn make_mut(
127 131 &mut self,
128 132 on_disk: &'on_disk [u8],
129 133 unreachable_bytes: &mut u32,
130 134 ) -> Result<
131 135 &mut FastHashMap<NodeKey<'on_disk>, Node<'on_disk>>,
132 136 DirstateV2ParseError,
133 137 > {
134 138 match self {
135 139 ChildNodes::InMemory(nodes) => Ok(nodes),
136 140 ChildNodes::OnDisk(nodes) => {
137 141 *unreachable_bytes +=
138 142 std::mem::size_of_val::<[on_disk::Node]>(nodes) as u32;
139 143 let nodes = nodes
140 144 .iter()
141 145 .map(|node| {
142 146 Ok((
143 147 node.path(on_disk)?,
144 148 node.to_in_memory_node(on_disk)?,
145 149 ))
146 150 })
147 151 .collect::<Result<_, _>>()?;
148 152 *self = ChildNodes::InMemory(nodes);
149 153 match self {
150 154 ChildNodes::InMemory(nodes) => Ok(nodes),
151 155 ChildNodes::OnDisk(_) => unreachable!(),
152 156 }
153 157 }
154 158 }
155 159 }
156 160 }
157 161
158 162 impl<'tree, 'on_disk> ChildNodesRef<'tree, 'on_disk> {
159 163 pub(super) fn get(
160 164 &self,
161 165 base_name: &HgPath,
162 166 on_disk: &'on_disk [u8],
163 167 ) -> Result<Option<NodeRef<'tree, 'on_disk>>, DirstateV2ParseError> {
164 168 match self {
165 169 ChildNodesRef::InMemory(nodes) => Ok(nodes
166 170 .get_key_value(base_name)
167 171 .map(|(k, v)| NodeRef::InMemory(k, v))),
168 172 ChildNodesRef::OnDisk(nodes) => {
169 173 let mut parse_result = Ok(());
170 174 let search_result = nodes.binary_search_by(|node| {
171 175 match node.base_name(on_disk) {
172 176 Ok(node_base_name) => node_base_name.cmp(base_name),
173 177 Err(e) => {
174 178 parse_result = Err(e);
175 179 // Dummy comparison result, `search_result` won’t
176 180 // be used since `parse_result` is an error
177 181 std::cmp::Ordering::Equal
178 182 }
179 183 }
180 184 });
181 185 parse_result.map(|()| {
182 186 search_result.ok().map(|i| NodeRef::OnDisk(&nodes[i]))
183 187 })
184 188 }
185 189 }
186 190 }
187 191
188 192 /// Iterate in undefined order
189 193 pub(super) fn iter(
190 194 &self,
191 195 ) -> impl Iterator<Item = NodeRef<'tree, 'on_disk>> {
192 196 match self {
193 197 ChildNodesRef::InMemory(nodes) => itertools::Either::Left(
194 198 nodes.iter().map(|(k, v)| NodeRef::InMemory(k, v)),
195 199 ),
196 200 ChildNodesRef::OnDisk(nodes) => {
197 201 itertools::Either::Right(nodes.iter().map(NodeRef::OnDisk))
198 202 }
199 203 }
200 204 }
201 205
202 206 /// Iterate in parallel in undefined order
203 207 pub(super) fn par_iter(
204 208 &self,
205 209 ) -> impl rayon::iter::ParallelIterator<Item = NodeRef<'tree, 'on_disk>>
206 210 {
207 211 use rayon::prelude::*;
208 212 match self {
209 213 ChildNodesRef::InMemory(nodes) => rayon::iter::Either::Left(
210 214 nodes.par_iter().map(|(k, v)| NodeRef::InMemory(k, v)),
211 215 ),
212 216 ChildNodesRef::OnDisk(nodes) => rayon::iter::Either::Right(
213 217 nodes.par_iter().map(NodeRef::OnDisk),
214 218 ),
215 219 }
216 220 }
217 221
218 222 pub(super) fn sorted(&self) -> Vec<NodeRef<'tree, 'on_disk>> {
219 223 match self {
220 224 ChildNodesRef::InMemory(nodes) => {
221 225 let mut vec: Vec<_> = nodes
222 226 .iter()
223 227 .map(|(k, v)| NodeRef::InMemory(k, v))
224 228 .collect();
225 229 fn sort_key<'a>(node: &'a NodeRef) -> &'a HgPath {
226 230 match node {
227 231 NodeRef::InMemory(path, _node) => path.base_name(),
228 232 NodeRef::OnDisk(_) => unreachable!(),
229 233 }
230 234 }
231 235 // `sort_unstable_by_key` doesn’t allow keys borrowing from the
232 236 // value: https://github.com/rust-lang/rust/issues/34162
233 237 vec.sort_unstable_by(|a, b| sort_key(a).cmp(sort_key(b)));
234 238 vec
235 239 }
236 240 ChildNodesRef::OnDisk(nodes) => {
237 241 // Nodes on disk are already sorted
238 242 nodes.iter().map(NodeRef::OnDisk).collect()
239 243 }
240 244 }
241 245 }
242 246 }
243 247
244 248 impl<'tree, 'on_disk> NodeRef<'tree, 'on_disk> {
245 249 pub(super) fn full_path(
246 250 &self,
247 251 on_disk: &'on_disk [u8],
248 252 ) -> Result<&'tree HgPath, DirstateV2ParseError> {
249 253 match self {
250 254 NodeRef::InMemory(path, _node) => Ok(path.full_path()),
251 255 NodeRef::OnDisk(node) => node.full_path(on_disk),
252 256 }
253 257 }
254 258
255 259 /// Returns a `BorrowedPath`, which can be turned into a `Cow<'on_disk,
256 260 /// HgPath>` detached from `'tree`
257 261 pub(super) fn full_path_borrowed(
258 262 &self,
259 263 on_disk: &'on_disk [u8],
260 264 ) -> Result<BorrowedPath<'tree, 'on_disk>, DirstateV2ParseError> {
261 265 match self {
262 266 NodeRef::InMemory(path, _node) => match path.full_path() {
263 267 Cow::Borrowed(on_disk) => Ok(BorrowedPath::OnDisk(on_disk)),
264 268 Cow::Owned(in_memory) => Ok(BorrowedPath::InMemory(in_memory)),
265 269 },
266 270 NodeRef::OnDisk(node) => {
267 271 Ok(BorrowedPath::OnDisk(node.full_path(on_disk)?))
268 272 }
269 273 }
270 274 }
271 275
272 276 pub(super) fn base_name(
273 277 &self,
274 278 on_disk: &'on_disk [u8],
275 279 ) -> Result<&'tree HgPath, DirstateV2ParseError> {
276 280 match self {
277 281 NodeRef::InMemory(path, _node) => Ok(path.base_name()),
278 282 NodeRef::OnDisk(node) => node.base_name(on_disk),
279 283 }
280 284 }
281 285
282 286 pub(super) fn children(
283 287 &self,
284 288 on_disk: &'on_disk [u8],
285 289 ) -> Result<ChildNodesRef<'tree, 'on_disk>, DirstateV2ParseError> {
286 290 match self {
287 291 NodeRef::InMemory(_path, node) => Ok(node.children.as_ref()),
288 292 NodeRef::OnDisk(node) => {
289 293 Ok(ChildNodesRef::OnDisk(node.children(on_disk)?))
290 294 }
291 295 }
292 296 }
293 297
294 298 pub(super) fn has_copy_source(&self) -> bool {
295 299 match self {
296 300 NodeRef::InMemory(_path, node) => node.copy_source.is_some(),
297 301 NodeRef::OnDisk(node) => node.has_copy_source(),
298 302 }
299 303 }
300 304
301 305 pub(super) fn copy_source(
302 306 &self,
303 307 on_disk: &'on_disk [u8],
304 308 ) -> Result<Option<&'tree HgPath>, DirstateV2ParseError> {
305 309 match self {
306 310 NodeRef::InMemory(_path, node) => {
307 311 Ok(node.copy_source.as_ref().map(|s| &**s))
308 312 }
309 313 NodeRef::OnDisk(node) => node.copy_source(on_disk),
310 314 }
311 315 }
312 316 /// Returns a `BorrowedPath`, which can be turned into a `Cow<'on_disk,
313 317 /// HgPath>` detached from `'tree`
314 318 pub(super) fn copy_source_borrowed(
315 319 &self,
316 320 on_disk: &'on_disk [u8],
317 321 ) -> Result<Option<BorrowedPath<'tree, 'on_disk>>, DirstateV2ParseError>
318 322 {
319 323 Ok(match self {
320 324 NodeRef::InMemory(_path, node) => {
321 325 node.copy_source.as_ref().map(|source| match source {
322 326 Cow::Borrowed(on_disk) => BorrowedPath::OnDisk(on_disk),
323 327 Cow::Owned(in_memory) => BorrowedPath::InMemory(in_memory),
324 328 })
325 329 }
326 330 NodeRef::OnDisk(node) => node
327 331 .copy_source(on_disk)?
328 332 .map(|source| BorrowedPath::OnDisk(source)),
329 333 })
330 334 }
331 335
332 336 pub(super) fn entry(
333 337 &self,
334 338 ) -> Result<Option<DirstateEntry>, DirstateV2ParseError> {
335 339 match self {
336 340 NodeRef::InMemory(_path, node) => {
337 341 Ok(node.data.as_entry().copied())
338 342 }
339 343 NodeRef::OnDisk(node) => node.entry(),
340 344 }
341 345 }
342 346
343 347 pub(super) fn state(
344 348 &self,
345 349 ) -> Result<Option<EntryState>, DirstateV2ParseError> {
346 350 Ok(self.entry()?.map(|e| e.state()))
347 351 }
348 352
349 353 pub(super) fn cached_directory_mtime(
350 354 &self,
351 355 ) -> Result<Option<TruncatedTimestamp>, DirstateV2ParseError> {
352 356 match self {
353 357 NodeRef::InMemory(_path, node) => Ok(match node.data {
354 358 NodeData::CachedDirectory { mtime } => Some(mtime),
355 359 _ => None,
356 360 }),
357 361 NodeRef::OnDisk(node) => node.cached_directory_mtime(),
358 362 }
359 363 }
360 364
361 365 pub(super) fn descendants_with_entry_count(&self) -> u32 {
362 366 match self {
363 367 NodeRef::InMemory(_path, node) => {
364 368 node.descendants_with_entry_count
365 369 }
366 370 NodeRef::OnDisk(node) => node.descendants_with_entry_count.get(),
367 371 }
368 372 }
369 373
370 374 pub(super) fn tracked_descendants_count(&self) -> u32 {
371 375 match self {
372 376 NodeRef::InMemory(_path, node) => node.tracked_descendants_count,
373 377 NodeRef::OnDisk(node) => node.tracked_descendants_count.get(),
374 378 }
375 379 }
376 380 }
377 381
378 382 /// Represents a file or a directory
379 383 #[derive(Default)]
380 384 pub(super) struct Node<'on_disk> {
381 385 pub(super) data: NodeData,
382 386
383 387 pub(super) copy_source: Option<Cow<'on_disk, HgPath>>,
384 388
385 389 pub(super) children: ChildNodes<'on_disk>,
386 390
387 391 /// How many (non-inclusive) descendants of this node have an entry.
388 392 pub(super) descendants_with_entry_count: u32,
389 393
390 394 /// How many (non-inclusive) descendants of this node have an entry whose
391 395 /// state is "tracked".
392 396 pub(super) tracked_descendants_count: u32,
393 397 }
394 398
395 399 pub(super) enum NodeData {
396 400 Entry(DirstateEntry),
397 401 CachedDirectory { mtime: TruncatedTimestamp },
398 402 None,
399 403 }
400 404
401 405 impl Default for NodeData {
402 406 fn default() -> Self {
403 407 NodeData::None
404 408 }
405 409 }
406 410
407 411 impl NodeData {
408 412 fn has_entry(&self) -> bool {
409 413 match self {
410 414 NodeData::Entry(_) => true,
411 415 _ => false,
412 416 }
413 417 }
414 418
415 419 fn as_entry(&self) -> Option<&DirstateEntry> {
416 420 match self {
417 421 NodeData::Entry(entry) => Some(entry),
418 422 _ => None,
419 423 }
420 424 }
421 425 }
422 426
423 427 impl<'on_disk> DirstateMap<'on_disk> {
424 428 pub(super) fn empty(on_disk: &'on_disk [u8]) -> Self {
425 429 Self {
426 430 on_disk,
427 431 root: ChildNodes::default(),
428 432 nodes_with_entry_count: 0,
429 433 nodes_with_copy_source_count: 0,
430 434 ignore_patterns_hash: [0; on_disk::IGNORE_PATTERNS_HASH_LEN],
431 435 unreachable_bytes: 0,
436 old_data_size: 0,
432 437 }
433 438 }
434 439
435 440 #[timed]
436 441 pub fn new_v2(
437 442 on_disk: &'on_disk [u8],
438 443 data_size: usize,
439 444 metadata: &[u8],
440 445 ) -> Result<Self, DirstateError> {
441 446 if let Some(data) = on_disk.get(..data_size) {
442 447 Ok(on_disk::read(data, metadata)?)
443 448 } else {
444 449 Err(DirstateV2ParseError.into())
445 450 }
446 451 }
447 452
448 453 #[timed]
449 454 pub fn new_v1(
450 455 on_disk: &'on_disk [u8],
451 456 ) -> Result<(Self, Option<DirstateParents>), DirstateError> {
452 457 let mut map = Self::empty(on_disk);
453 458 if map.on_disk.is_empty() {
454 459 return Ok((map, None));
455 460 }
456 461
457 462 let parents = parse_dirstate_entries(
458 463 map.on_disk,
459 464 |path, entry, copy_source| {
460 465 let tracked = entry.state().is_tracked();
461 466 let node = Self::get_or_insert_node(
462 467 map.on_disk,
463 468 &mut map.unreachable_bytes,
464 469 &mut map.root,
465 470 path,
466 471 WithBasename::to_cow_borrowed,
467 472 |ancestor| {
468 473 if tracked {
469 474 ancestor.tracked_descendants_count += 1
470 475 }
471 476 ancestor.descendants_with_entry_count += 1
472 477 },
473 478 )?;
474 479 assert!(
475 480 !node.data.has_entry(),
476 481 "duplicate dirstate entry in read"
477 482 );
478 483 assert!(
479 484 node.copy_source.is_none(),
480 485 "duplicate dirstate entry in read"
481 486 );
482 487 node.data = NodeData::Entry(*entry);
483 488 node.copy_source = copy_source.map(Cow::Borrowed);
484 489 map.nodes_with_entry_count += 1;
485 490 if copy_source.is_some() {
486 491 map.nodes_with_copy_source_count += 1
487 492 }
488 493 Ok(())
489 494 },
490 495 )?;
491 496 let parents = Some(parents.clone());
492 497
493 498 Ok((map, parents))
494 499 }
495 500
496 501 /// Assuming dirstate-v2 format, returns whether the next write should
497 502 /// append to the existing data file that contains `self.on_disk` (true),
498 503 /// or create a new data file from scratch (false).
499 504 pub(super) fn write_should_append(&self) -> bool {
500 505 let ratio = self.unreachable_bytes as f32 / self.on_disk.len() as f32;
501 506 ratio < ACCEPTABLE_UNREACHABLE_BYTES_RATIO
502 507 }
503 508
504 509 fn get_node<'tree>(
505 510 &'tree self,
506 511 path: &HgPath,
507 512 ) -> Result<Option<NodeRef<'tree, 'on_disk>>, DirstateV2ParseError> {
508 513 let mut children = self.root.as_ref();
509 514 let mut components = path.components();
510 515 let mut component =
511 516 components.next().expect("expected at least one components");
512 517 loop {
513 518 if let Some(child) = children.get(component, self.on_disk)? {
514 519 if let Some(next_component) = components.next() {
515 520 component = next_component;
516 521 children = child.children(self.on_disk)?;
517 522 } else {
518 523 return Ok(Some(child));
519 524 }
520 525 } else {
521 526 return Ok(None);
522 527 }
523 528 }
524 529 }
525 530
526 531 /// Returns a mutable reference to the node at `path` if it exists
527 532 ///
528 533 /// This takes `root` instead of `&mut self` so that callers can mutate
529 534 /// other fields while the returned borrow is still valid
530 535 fn get_node_mut<'tree>(
531 536 on_disk: &'on_disk [u8],
532 537 unreachable_bytes: &mut u32,
533 538 root: &'tree mut ChildNodes<'on_disk>,
534 539 path: &HgPath,
535 540 ) -> Result<Option<&'tree mut Node<'on_disk>>, DirstateV2ParseError> {
536 541 let mut children = root;
537 542 let mut components = path.components();
538 543 let mut component =
539 544 components.next().expect("expected at least one components");
540 545 loop {
541 546 if let Some(child) = children
542 547 .make_mut(on_disk, unreachable_bytes)?
543 548 .get_mut(component)
544 549 {
545 550 if let Some(next_component) = components.next() {
546 551 component = next_component;
547 552 children = &mut child.children;
548 553 } else {
549 554 return Ok(Some(child));
550 555 }
551 556 } else {
552 557 return Ok(None);
553 558 }
554 559 }
555 560 }
556 561
557 562 pub(super) fn get_or_insert<'tree, 'path>(
558 563 &'tree mut self,
559 564 path: &HgPath,
560 565 ) -> Result<&'tree mut Node<'on_disk>, DirstateV2ParseError> {
561 566 Self::get_or_insert_node(
562 567 self.on_disk,
563 568 &mut self.unreachable_bytes,
564 569 &mut self.root,
565 570 path,
566 571 WithBasename::to_cow_owned,
567 572 |_| {},
568 573 )
569 574 }
570 575
571 576 fn get_or_insert_node<'tree, 'path>(
572 577 on_disk: &'on_disk [u8],
573 578 unreachable_bytes: &mut u32,
574 579 root: &'tree mut ChildNodes<'on_disk>,
575 580 path: &'path HgPath,
576 581 to_cow: impl Fn(
577 582 WithBasename<&'path HgPath>,
578 583 ) -> WithBasename<Cow<'on_disk, HgPath>>,
579 584 mut each_ancestor: impl FnMut(&mut Node),
580 585 ) -> Result<&'tree mut Node<'on_disk>, DirstateV2ParseError> {
581 586 let mut child_nodes = root;
582 587 let mut inclusive_ancestor_paths =
583 588 WithBasename::inclusive_ancestors_of(path);
584 589 let mut ancestor_path = inclusive_ancestor_paths
585 590 .next()
586 591 .expect("expected at least one inclusive ancestor");
587 592 loop {
588 593 // TODO: can we avoid allocating an owned key in cases where the
589 594 // map already contains that key, without introducing double
590 595 // lookup?
591 596 let child_node = child_nodes
592 597 .make_mut(on_disk, unreachable_bytes)?
593 598 .entry(to_cow(ancestor_path))
594 599 .or_default();
595 600 if let Some(next) = inclusive_ancestor_paths.next() {
596 601 each_ancestor(child_node);
597 602 ancestor_path = next;
598 603 child_nodes = &mut child_node.children;
599 604 } else {
600 605 return Ok(child_node);
601 606 }
602 607 }
603 608 }
604 609
605 610 fn add_or_remove_file(
606 611 &mut self,
607 612 path: &HgPath,
608 613 old_state: Option<EntryState>,
609 614 new_entry: DirstateEntry,
610 615 ) -> Result<(), DirstateV2ParseError> {
611 616 let had_entry = old_state.is_some();
612 617 let was_tracked = old_state.map_or(false, |s| s.is_tracked());
613 618 let tracked_count_increment =
614 619 match (was_tracked, new_entry.state().is_tracked()) {
615 620 (false, true) => 1,
616 621 (true, false) => -1,
617 622 _ => 0,
618 623 };
619 624
620 625 let node = Self::get_or_insert_node(
621 626 self.on_disk,
622 627 &mut self.unreachable_bytes,
623 628 &mut self.root,
624 629 path,
625 630 WithBasename::to_cow_owned,
626 631 |ancestor| {
627 632 if !had_entry {
628 633 ancestor.descendants_with_entry_count += 1;
629 634 }
630 635
631 636 // We can’t use `+= increment` because the counter is unsigned,
632 637 // and we want debug builds to detect accidental underflow
633 638 // through zero
634 639 match tracked_count_increment {
635 640 1 => ancestor.tracked_descendants_count += 1,
636 641 -1 => ancestor.tracked_descendants_count -= 1,
637 642 _ => {}
638 643 }
639 644 },
640 645 )?;
641 646 if !had_entry {
642 647 self.nodes_with_entry_count += 1
643 648 }
644 649 node.data = NodeData::Entry(new_entry);
645 650 Ok(())
646 651 }
647 652
648 653 fn iter_nodes<'tree>(
649 654 &'tree self,
650 655 ) -> impl Iterator<
651 656 Item = Result<NodeRef<'tree, 'on_disk>, DirstateV2ParseError>,
652 657 > + 'tree {
653 658 // Depth first tree traversal.
654 659 //
655 660 // If we could afford internal iteration and recursion,
656 661 // this would look like:
657 662 //
658 663 // ```
659 664 // fn traverse_children(
660 665 // children: &ChildNodes,
661 666 // each: &mut impl FnMut(&Node),
662 667 // ) {
663 668 // for child in children.values() {
664 669 // traverse_children(&child.children, each);
665 670 // each(child);
666 671 // }
667 672 // }
668 673 // ```
669 674 //
670 675 // However we want an external iterator and therefore can’t use the
671 676 // call stack. Use an explicit stack instead:
672 677 let mut stack = Vec::new();
673 678 let mut iter = self.root.as_ref().iter();
674 679 std::iter::from_fn(move || {
675 680 while let Some(child_node) = iter.next() {
676 681 let children = match child_node.children(self.on_disk) {
677 682 Ok(children) => children,
678 683 Err(error) => return Some(Err(error)),
679 684 };
680 685 // Pseudo-recursion
681 686 let new_iter = children.iter();
682 687 let old_iter = std::mem::replace(&mut iter, new_iter);
683 688 stack.push((child_node, old_iter));
684 689 }
685 690 // Found the end of a `children.iter()` iterator.
686 691 if let Some((child_node, next_iter)) = stack.pop() {
687 692 // "Return" from pseudo-recursion by restoring state from the
688 693 // explicit stack
689 694 iter = next_iter;
690 695
691 696 Some(Ok(child_node))
692 697 } else {
693 698 // Reached the bottom of the stack, we’re done
694 699 None
695 700 }
696 701 })
697 702 }
698 703
699 704 fn count_dropped_path(unreachable_bytes: &mut u32, path: &Cow<HgPath>) {
700 705 if let Cow::Borrowed(path) = path {
701 706 *unreachable_bytes += path.len() as u32
702 707 }
703 708 }
704 709 }
705 710
706 711 /// Like `Iterator::filter_map`, but over a fallible iterator of `Result`s.
707 712 ///
708 713 /// The callback is only called for incoming `Ok` values. Errors are passed
709 714 /// through as-is. In order to let it use the `?` operator the callback is
710 715 /// expected to return a `Result` of `Option`, instead of an `Option` of
711 716 /// `Result`.
712 717 fn filter_map_results<'a, I, F, A, B, E>(
713 718 iter: I,
714 719 f: F,
715 720 ) -> impl Iterator<Item = Result<B, E>> + 'a
716 721 where
717 722 I: Iterator<Item = Result<A, E>> + 'a,
718 723 F: Fn(A) -> Result<Option<B>, E> + 'a,
719 724 {
720 725 iter.filter_map(move |result| match result {
721 726 Ok(node) => f(node).transpose(),
722 727 Err(e) => Some(Err(e)),
723 728 })
724 729 }
725 730
726 731 impl OwningDirstateMap {
727 732 pub fn clear(&mut self) {
728 733 self.with_dmap_mut(|map| {
729 734 map.root = Default::default();
730 735 map.nodes_with_entry_count = 0;
731 736 map.nodes_with_copy_source_count = 0;
732 737 });
733 738 }
734 739
735 740 pub fn set_entry(
736 741 &mut self,
737 742 filename: &HgPath,
738 743 entry: DirstateEntry,
739 744 ) -> Result<(), DirstateV2ParseError> {
740 745 self.with_dmap_mut(|map| {
741 746 map.get_or_insert(&filename)?.data = NodeData::Entry(entry);
742 747 Ok(())
743 748 })
744 749 }
745 750
746 751 pub fn add_file(
747 752 &mut self,
748 753 filename: &HgPath,
749 754 entry: DirstateEntry,
750 755 ) -> Result<(), DirstateError> {
751 756 let old_state = self.get(filename)?.map(|e| e.state());
752 757 self.with_dmap_mut(|map| {
753 758 Ok(map.add_or_remove_file(filename, old_state, entry)?)
754 759 })
755 760 }
756 761
757 762 pub fn remove_file(
758 763 &mut self,
759 764 filename: &HgPath,
760 765 in_merge: bool,
761 766 ) -> Result<(), DirstateError> {
762 767 let old_entry_opt = self.get(filename)?;
763 768 let old_state = old_entry_opt.map(|e| e.state());
764 769 let mut size = 0;
765 770 if in_merge {
766 771 // XXX we should not be able to have 'm' state and 'FROM_P2' if not
767 772 // during a merge. So I (marmoute) am not sure we need the
768 773 // conditionnal at all. Adding double checking this with assert
769 774 // would be nice.
770 775 if let Some(old_entry) = old_entry_opt {
771 776 // backup the previous state
772 777 if old_entry.state() == EntryState::Merged {
773 778 size = SIZE_NON_NORMAL;
774 779 } else if old_entry.state() == EntryState::Normal
775 780 && old_entry.size() == SIZE_FROM_OTHER_PARENT
776 781 {
777 782 // other parent
778 783 size = SIZE_FROM_OTHER_PARENT;
779 784 }
780 785 }
781 786 }
782 787 if size == 0 {
783 788 self.copy_map_remove(filename)?;
784 789 }
785 790 self.with_dmap_mut(|map| {
786 791 let entry = DirstateEntry::new_removed(size);
787 792 Ok(map.add_or_remove_file(filename, old_state, entry)?)
788 793 })
789 794 }
790 795
791 796 pub fn drop_entry_and_copy_source(
792 797 &mut self,
793 798 filename: &HgPath,
794 799 ) -> Result<(), DirstateError> {
795 800 let was_tracked = self
796 801 .get(filename)?
797 802 .map_or(false, |e| e.state().is_tracked());
798 803 struct Dropped {
799 804 was_tracked: bool,
800 805 had_entry: bool,
801 806 had_copy_source: bool,
802 807 }
803 808
804 809 /// If this returns `Ok(Some((dropped, removed)))`, then
805 810 ///
806 811 /// * `dropped` is about the leaf node that was at `filename`
807 812 /// * `removed` is whether this particular level of recursion just
808 813 /// removed a node in `nodes`.
809 814 fn recur<'on_disk>(
810 815 on_disk: &'on_disk [u8],
811 816 unreachable_bytes: &mut u32,
812 817 nodes: &mut ChildNodes<'on_disk>,
813 818 path: &HgPath,
814 819 ) -> Result<Option<(Dropped, bool)>, DirstateV2ParseError> {
815 820 let (first_path_component, rest_of_path) =
816 821 path.split_first_component();
817 822 let nodes = nodes.make_mut(on_disk, unreachable_bytes)?;
818 823 let node = if let Some(node) = nodes.get_mut(first_path_component)
819 824 {
820 825 node
821 826 } else {
822 827 return Ok(None);
823 828 };
824 829 let dropped;
825 830 if let Some(rest) = rest_of_path {
826 831 if let Some((d, removed)) = recur(
827 832 on_disk,
828 833 unreachable_bytes,
829 834 &mut node.children,
830 835 rest,
831 836 )? {
832 837 dropped = d;
833 838 if dropped.had_entry {
834 839 node.descendants_with_entry_count = node
835 840 .descendants_with_entry_count
836 841 .checked_sub(1)
837 842 .expect(
838 843 "descendants_with_entry_count should be >= 0",
839 844 );
840 845 }
841 846 if dropped.was_tracked {
842 847 node.tracked_descendants_count = node
843 848 .tracked_descendants_count
844 849 .checked_sub(1)
845 850 .expect(
846 851 "tracked_descendants_count should be >= 0",
847 852 );
848 853 }
849 854
850 855 // Directory caches must be invalidated when removing a
851 856 // child node
852 857 if removed {
853 858 if let NodeData::CachedDirectory { .. } = &node.data {
854 859 node.data = NodeData::None
855 860 }
856 861 }
857 862 } else {
858 863 return Ok(None);
859 864 }
860 865 } else {
861 866 let entry = node.data.as_entry();
862 867 let was_tracked = entry.map_or(false, |entry| entry.tracked());
863 868 let had_entry = entry.is_some();
864 869 if had_entry {
865 870 node.data = NodeData::None
866 871 }
867 872 let mut had_copy_source = false;
868 873 if let Some(source) = &node.copy_source {
869 874 DirstateMap::count_dropped_path(unreachable_bytes, source);
870 875 had_copy_source = true;
871 876 node.copy_source = None
872 877 }
873 878 dropped = Dropped {
874 879 was_tracked,
875 880 had_entry,
876 881 had_copy_source,
877 882 };
878 883 }
879 884 // After recursion, for both leaf (rest_of_path is None) nodes and
880 885 // parent nodes, remove a node if it just became empty.
881 886 let remove = !node.data.has_entry()
882 887 && node.copy_source.is_none()
883 888 && node.children.is_empty();
884 889 if remove {
885 890 let (key, _) =
886 891 nodes.remove_entry(first_path_component).unwrap();
887 892 DirstateMap::count_dropped_path(
888 893 unreachable_bytes,
889 894 key.full_path(),
890 895 )
891 896 }
892 897 Ok(Some((dropped, remove)))
893 898 }
894 899
895 900 self.with_dmap_mut(|map| {
896 901 if let Some((dropped, _removed)) = recur(
897 902 map.on_disk,
898 903 &mut map.unreachable_bytes,
899 904 &mut map.root,
900 905 filename,
901 906 )? {
902 907 if dropped.had_entry {
903 908 map.nodes_with_entry_count = map
904 909 .nodes_with_entry_count
905 910 .checked_sub(1)
906 911 .expect("nodes_with_entry_count should be >= 0");
907 912 }
908 913 if dropped.had_copy_source {
909 914 map.nodes_with_copy_source_count = map
910 915 .nodes_with_copy_source_count
911 916 .checked_sub(1)
912 917 .expect("nodes_with_copy_source_count should be >= 0");
913 918 }
914 919 } else {
915 920 debug_assert!(!was_tracked);
916 921 }
917 922 Ok(())
918 923 })
919 924 }
920 925
921 926 pub fn has_tracked_dir(
922 927 &mut self,
923 928 directory: &HgPath,
924 929 ) -> Result<bool, DirstateError> {
925 930 self.with_dmap_mut(|map| {
926 931 if let Some(node) = map.get_node(directory)? {
927 932 // A node without a `DirstateEntry` was created to hold child
928 933 // nodes, and is therefore a directory.
929 934 let state = node.state()?;
930 935 Ok(state.is_none() && node.tracked_descendants_count() > 0)
931 936 } else {
932 937 Ok(false)
933 938 }
934 939 })
935 940 }
936 941
937 942 pub fn has_dir(
938 943 &mut self,
939 944 directory: &HgPath,
940 945 ) -> Result<bool, DirstateError> {
941 946 self.with_dmap_mut(|map| {
942 947 if let Some(node) = map.get_node(directory)? {
943 948 // A node without a `DirstateEntry` was created to hold child
944 949 // nodes, and is therefore a directory.
945 950 let state = node.state()?;
946 951 Ok(state.is_none() && node.descendants_with_entry_count() > 0)
947 952 } else {
948 953 Ok(false)
949 954 }
950 955 })
951 956 }
952 957
953 958 #[timed]
954 959 pub fn pack_v1(
955 960 &self,
956 961 parents: DirstateParents,
957 962 ) -> Result<Vec<u8>, DirstateError> {
958 963 let map = self.get_map();
959 964 // Optizimation (to be measured?): pre-compute size to avoid `Vec`
960 965 // reallocations
961 966 let mut size = parents.as_bytes().len();
962 967 for node in map.iter_nodes() {
963 968 let node = node?;
964 969 if node.entry()?.is_some() {
965 970 size += packed_entry_size(
966 971 node.full_path(map.on_disk)?,
967 972 node.copy_source(map.on_disk)?,
968 973 );
969 974 }
970 975 }
971 976
972 977 let mut packed = Vec::with_capacity(size);
973 978 packed.extend(parents.as_bytes());
974 979
975 980 for node in map.iter_nodes() {
976 981 let node = node?;
977 982 if let Some(entry) = node.entry()? {
978 983 pack_entry(
979 984 node.full_path(map.on_disk)?,
980 985 &entry,
981 986 node.copy_source(map.on_disk)?,
982 987 &mut packed,
983 988 );
984 989 }
985 990 }
986 991 Ok(packed)
987 992 }
988 993
989 994 /// Returns new data and metadata together with whether that data should be
990 995 /// appended to the existing data file whose content is at
991 996 /// `map.on_disk` (true), instead of written to a new data file
992 /// (false).
997 /// (false), and the previous size of data on disk.
993 998 #[timed]
994 999 pub fn pack_v2(
995 1000 &self,
996 1001 can_append: bool,
997 ) -> Result<(Vec<u8>, on_disk::TreeMetadata, bool), DirstateError> {
1002 ) -> Result<(Vec<u8>, on_disk::TreeMetadata, bool, usize), DirstateError>
1003 {
998 1004 let map = self.get_map();
999 1005 on_disk::write(map, can_append)
1000 1006 }
1001 1007
1002 1008 /// `callback` allows the caller to process and do something with the
1003 1009 /// results of the status. This is needed to do so efficiently (i.e.
1004 1010 /// without cloning the `DirstateStatus` object with its paths) because
1005 1011 /// we need to borrow from `Self`.
1006 1012 pub fn with_status<R>(
1007 1013 &mut self,
1008 1014 matcher: &(dyn Matcher + Sync),
1009 1015 root_dir: PathBuf,
1010 1016 ignore_files: Vec<PathBuf>,
1011 1017 options: StatusOptions,
1012 1018 callback: impl for<'r> FnOnce(
1013 1019 Result<(DirstateStatus<'r>, Vec<PatternFileWarning>), StatusError>,
1014 1020 ) -> R,
1015 1021 ) -> R {
1016 1022 self.with_dmap_mut(|map| {
1017 1023 callback(super::status::status(
1018 1024 map,
1019 1025 matcher,
1020 1026 root_dir,
1021 1027 ignore_files,
1022 1028 options,
1023 1029 ))
1024 1030 })
1025 1031 }
1026 1032
1027 1033 pub fn copy_map_len(&self) -> usize {
1028 1034 let map = self.get_map();
1029 1035 map.nodes_with_copy_source_count as usize
1030 1036 }
1031 1037
1032 1038 pub fn copy_map_iter(&self) -> CopyMapIter<'_> {
1033 1039 let map = self.get_map();
1034 1040 Box::new(filter_map_results(map.iter_nodes(), move |node| {
1035 1041 Ok(if let Some(source) = node.copy_source(map.on_disk)? {
1036 1042 Some((node.full_path(map.on_disk)?, source))
1037 1043 } else {
1038 1044 None
1039 1045 })
1040 1046 }))
1041 1047 }
1042 1048
1043 1049 pub fn copy_map_contains_key(
1044 1050 &self,
1045 1051 key: &HgPath,
1046 1052 ) -> Result<bool, DirstateV2ParseError> {
1047 1053 let map = self.get_map();
1048 1054 Ok(if let Some(node) = map.get_node(key)? {
1049 1055 node.has_copy_source()
1050 1056 } else {
1051 1057 false
1052 1058 })
1053 1059 }
1054 1060
1055 1061 pub fn copy_map_get(
1056 1062 &self,
1057 1063 key: &HgPath,
1058 1064 ) -> Result<Option<&HgPath>, DirstateV2ParseError> {
1059 1065 let map = self.get_map();
1060 1066 if let Some(node) = map.get_node(key)? {
1061 1067 if let Some(source) = node.copy_source(map.on_disk)? {
1062 1068 return Ok(Some(source));
1063 1069 }
1064 1070 }
1065 1071 Ok(None)
1066 1072 }
1067 1073
1068 1074 pub fn copy_map_remove(
1069 1075 &mut self,
1070 1076 key: &HgPath,
1071 1077 ) -> Result<Option<HgPathBuf>, DirstateV2ParseError> {
1072 1078 self.with_dmap_mut(|map| {
1073 1079 let count = &mut map.nodes_with_copy_source_count;
1074 1080 let unreachable_bytes = &mut map.unreachable_bytes;
1075 1081 Ok(DirstateMap::get_node_mut(
1076 1082 map.on_disk,
1077 1083 unreachable_bytes,
1078 1084 &mut map.root,
1079 1085 key,
1080 1086 )?
1081 1087 .and_then(|node| {
1082 1088 if let Some(source) = &node.copy_source {
1083 1089 *count -= 1;
1084 1090 DirstateMap::count_dropped_path(unreachable_bytes, source);
1085 1091 }
1086 1092 node.copy_source.take().map(Cow::into_owned)
1087 1093 }))
1088 1094 })
1089 1095 }
1090 1096
1091 1097 pub fn copy_map_insert(
1092 1098 &mut self,
1093 1099 key: HgPathBuf,
1094 1100 value: HgPathBuf,
1095 1101 ) -> Result<Option<HgPathBuf>, DirstateV2ParseError> {
1096 1102 self.with_dmap_mut(|map| {
1097 1103 let node = DirstateMap::get_or_insert_node(
1098 1104 map.on_disk,
1099 1105 &mut map.unreachable_bytes,
1100 1106 &mut map.root,
1101 1107 &key,
1102 1108 WithBasename::to_cow_owned,
1103 1109 |_ancestor| {},
1104 1110 )?;
1105 1111 if node.copy_source.is_none() {
1106 1112 map.nodes_with_copy_source_count += 1
1107 1113 }
1108 1114 Ok(node.copy_source.replace(value.into()).map(Cow::into_owned))
1109 1115 })
1110 1116 }
1111 1117
1112 1118 pub fn len(&self) -> usize {
1113 1119 let map = self.get_map();
1114 1120 map.nodes_with_entry_count as usize
1115 1121 }
1116 1122
1117 1123 pub fn contains_key(
1118 1124 &self,
1119 1125 key: &HgPath,
1120 1126 ) -> Result<bool, DirstateV2ParseError> {
1121 1127 Ok(self.get(key)?.is_some())
1122 1128 }
1123 1129
1124 1130 pub fn get(
1125 1131 &self,
1126 1132 key: &HgPath,
1127 1133 ) -> Result<Option<DirstateEntry>, DirstateV2ParseError> {
1128 1134 let map = self.get_map();
1129 1135 Ok(if let Some(node) = map.get_node(key)? {
1130 1136 node.entry()?
1131 1137 } else {
1132 1138 None
1133 1139 })
1134 1140 }
1135 1141
1136 1142 pub fn iter(&self) -> StateMapIter<'_> {
1137 1143 let map = self.get_map();
1138 1144 Box::new(filter_map_results(map.iter_nodes(), move |node| {
1139 1145 Ok(if let Some(entry) = node.entry()? {
1140 1146 Some((node.full_path(map.on_disk)?, entry))
1141 1147 } else {
1142 1148 None
1143 1149 })
1144 1150 }))
1145 1151 }
1146 1152
1147 1153 pub fn iter_tracked_dirs(
1148 1154 &mut self,
1149 1155 ) -> Result<
1150 1156 Box<
1151 1157 dyn Iterator<Item = Result<&HgPath, DirstateV2ParseError>>
1152 1158 + Send
1153 1159 + '_,
1154 1160 >,
1155 1161 DirstateError,
1156 1162 > {
1157 1163 let map = self.get_map();
1158 1164 let on_disk = map.on_disk;
1159 1165 Ok(Box::new(filter_map_results(
1160 1166 map.iter_nodes(),
1161 1167 move |node| {
1162 1168 Ok(if node.tracked_descendants_count() > 0 {
1163 1169 Some(node.full_path(on_disk)?)
1164 1170 } else {
1165 1171 None
1166 1172 })
1167 1173 },
1168 1174 )))
1169 1175 }
1170 1176
1171 1177 pub fn debug_iter(
1172 1178 &self,
1173 1179 all: bool,
1174 1180 ) -> Box<
1175 1181 dyn Iterator<
1176 1182 Item = Result<
1177 1183 (&HgPath, (u8, i32, i32, i32)),
1178 1184 DirstateV2ParseError,
1179 1185 >,
1180 1186 > + Send
1181 1187 + '_,
1182 1188 > {
1183 1189 let map = self.get_map();
1184 1190 Box::new(filter_map_results(map.iter_nodes(), move |node| {
1185 1191 let debug_tuple = if let Some(entry) = node.entry()? {
1186 1192 entry.debug_tuple()
1187 1193 } else if !all {
1188 1194 return Ok(None);
1189 1195 } else if let Some(mtime) = node.cached_directory_mtime()? {
1190 1196 (b' ', 0, -1, mtime.truncated_seconds() as i32)
1191 1197 } else {
1192 1198 (b' ', 0, -1, -1)
1193 1199 };
1194 1200 Ok(Some((node.full_path(map.on_disk)?, debug_tuple)))
1195 1201 }))
1196 1202 }
1197 1203 }
@@ -1,843 +1,844 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::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)]
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)]
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)]
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)]
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 old_data_size: on_disk.len(),
293 294 };
294 295 Ok(dirstate_map)
295 296 }
296 297
297 298 impl Node {
298 299 pub(super) fn full_path<'on_disk>(
299 300 &self,
300 301 on_disk: &'on_disk [u8],
301 302 ) -> Result<&'on_disk HgPath, DirstateV2ParseError> {
302 303 read_hg_path(on_disk, self.full_path)
303 304 }
304 305
305 306 pub(super) fn base_name_start<'on_disk>(
306 307 &self,
307 308 ) -> Result<usize, DirstateV2ParseError> {
308 309 let start = self.base_name_start.get();
309 310 if start < self.full_path.len.get() {
310 311 let start = usize::try_from(start)
311 312 // u32 -> usize, could only panic on a 16-bit CPU
312 313 .expect("dirstate-v2 base_name_start out of bounds");
313 314 Ok(start)
314 315 } else {
315 316 Err(DirstateV2ParseError)
316 317 }
317 318 }
318 319
319 320 pub(super) fn base_name<'on_disk>(
320 321 &self,
321 322 on_disk: &'on_disk [u8],
322 323 ) -> Result<&'on_disk HgPath, DirstateV2ParseError> {
323 324 let full_path = self.full_path(on_disk)?;
324 325 let base_name_start = self.base_name_start()?;
325 326 Ok(HgPath::new(&full_path.as_bytes()[base_name_start..]))
326 327 }
327 328
328 329 pub(super) fn path<'on_disk>(
329 330 &self,
330 331 on_disk: &'on_disk [u8],
331 332 ) -> Result<dirstate_map::NodeKey<'on_disk>, DirstateV2ParseError> {
332 333 Ok(WithBasename::from_raw_parts(
333 334 Cow::Borrowed(self.full_path(on_disk)?),
334 335 self.base_name_start()?,
335 336 ))
336 337 }
337 338
338 339 pub(super) fn has_copy_source<'on_disk>(&self) -> bool {
339 340 self.copy_source.start.get() != 0
340 341 }
341 342
342 343 pub(super) fn copy_source<'on_disk>(
343 344 &self,
344 345 on_disk: &'on_disk [u8],
345 346 ) -> Result<Option<&'on_disk HgPath>, DirstateV2ParseError> {
346 347 Ok(if self.has_copy_source() {
347 348 Some(read_hg_path(on_disk, self.copy_source)?)
348 349 } else {
349 350 None
350 351 })
351 352 }
352 353
353 354 fn flags(&self) -> Flags {
354 355 Flags::from_bits_truncate(self.flags.get())
355 356 }
356 357
357 358 fn has_entry(&self) -> bool {
358 359 self.flags().intersects(
359 360 Flags::WDIR_TRACKED | Flags::P1_TRACKED | Flags::P2_INFO,
360 361 )
361 362 }
362 363
363 364 pub(super) fn node_data(
364 365 &self,
365 366 ) -> Result<dirstate_map::NodeData, DirstateV2ParseError> {
366 367 if self.has_entry() {
367 368 Ok(dirstate_map::NodeData::Entry(self.assume_entry()?))
368 369 } else if let Some(mtime) = self.cached_directory_mtime()? {
369 370 Ok(dirstate_map::NodeData::CachedDirectory { mtime })
370 371 } else {
371 372 Ok(dirstate_map::NodeData::None)
372 373 }
373 374 }
374 375
375 376 pub(super) fn cached_directory_mtime(
376 377 &self,
377 378 ) -> Result<Option<TruncatedTimestamp>, DirstateV2ParseError> {
378 379 // For now we do not have code to handle the absence of
379 380 // ALL_UNKNOWN_RECORDED, so we ignore the mtime if the flag is
380 381 // unset.
381 382 if self.flags().contains(Flags::DIRECTORY)
382 383 && self.flags().contains(Flags::HAS_MTIME)
383 384 && self.flags().contains(Flags::ALL_UNKNOWN_RECORDED)
384 385 {
385 386 Ok(Some(self.mtime()?))
386 387 } else {
387 388 Ok(None)
388 389 }
389 390 }
390 391
391 392 fn synthesize_unix_mode(&self) -> u32 {
392 393 let file_type = if self.flags().contains(Flags::MODE_IS_SYMLINK) {
393 394 libc::S_IFLNK
394 395 } else {
395 396 libc::S_IFREG
396 397 };
397 398 let permisions = if self.flags().contains(Flags::MODE_EXEC_PERM) {
398 399 0o755
399 400 } else {
400 401 0o644
401 402 };
402 403 (file_type | permisions).into()
403 404 }
404 405
405 406 fn mtime(&self) -> Result<TruncatedTimestamp, DirstateV2ParseError> {
406 407 let mut m: TruncatedTimestamp = self.mtime.try_into()?;
407 408 if self.flags().contains(Flags::MTIME_SECOND_AMBIGUOUS) {
408 409 m.second_ambiguous = true;
409 410 }
410 411 Ok(m)
411 412 }
412 413
413 414 fn assume_entry(&self) -> Result<DirstateEntry, DirstateV2ParseError> {
414 415 // TODO: convert through raw bits instead?
415 416 let wdir_tracked = self.flags().contains(Flags::WDIR_TRACKED);
416 417 let p1_tracked = self.flags().contains(Flags::P1_TRACKED);
417 418 let p2_info = self.flags().contains(Flags::P2_INFO);
418 419 let mode_size = if self.flags().contains(Flags::HAS_MODE_AND_SIZE)
419 420 && !self.flags().contains(Flags::EXPECTED_STATE_IS_MODIFIED)
420 421 {
421 422 Some((self.synthesize_unix_mode(), self.size.into()))
422 423 } else {
423 424 None
424 425 };
425 426 let mtime = if self.flags().contains(Flags::HAS_MTIME)
426 427 && !self.flags().contains(Flags::DIRECTORY)
427 428 && !self.flags().contains(Flags::EXPECTED_STATE_IS_MODIFIED)
428 429 {
429 430 Some(self.mtime()?)
430 431 } else {
431 432 None
432 433 };
433 434 let fallback_exec = if self.flags().contains(Flags::HAS_FALLBACK_EXEC)
434 435 {
435 436 Some(self.flags().contains(Flags::FALLBACK_EXEC))
436 437 } else {
437 438 None
438 439 };
439 440 let fallback_symlink =
440 441 if self.flags().contains(Flags::HAS_FALLBACK_SYMLINK) {
441 442 Some(self.flags().contains(Flags::FALLBACK_SYMLINK))
442 443 } else {
443 444 None
444 445 };
445 446 Ok(DirstateEntry::from_v2_data(
446 447 wdir_tracked,
447 448 p1_tracked,
448 449 p2_info,
449 450 mode_size,
450 451 mtime,
451 452 fallback_exec,
452 453 fallback_symlink,
453 454 ))
454 455 }
455 456
456 457 pub(super) fn entry(
457 458 &self,
458 459 ) -> Result<Option<DirstateEntry>, DirstateV2ParseError> {
459 460 if self.has_entry() {
460 461 Ok(Some(self.assume_entry()?))
461 462 } else {
462 463 Ok(None)
463 464 }
464 465 }
465 466
466 467 pub(super) fn children<'on_disk>(
467 468 &self,
468 469 on_disk: &'on_disk [u8],
469 470 ) -> Result<&'on_disk [Node], DirstateV2ParseError> {
470 471 read_nodes(on_disk, self.children)
471 472 }
472 473
473 474 pub(super) fn to_in_memory_node<'on_disk>(
474 475 &self,
475 476 on_disk: &'on_disk [u8],
476 477 ) -> Result<dirstate_map::Node<'on_disk>, DirstateV2ParseError> {
477 478 Ok(dirstate_map::Node {
478 479 children: dirstate_map::ChildNodes::OnDisk(
479 480 self.children(on_disk)?,
480 481 ),
481 482 copy_source: self.copy_source(on_disk)?.map(Cow::Borrowed),
482 483 data: self.node_data()?,
483 484 descendants_with_entry_count: self
484 485 .descendants_with_entry_count
485 486 .get(),
486 487 tracked_descendants_count: self.tracked_descendants_count.get(),
487 488 })
488 489 }
489 490
490 491 fn from_dirstate_entry(
491 492 entry: &DirstateEntry,
492 493 ) -> (Flags, U32Be, PackedTruncatedTimestamp) {
493 494 let (
494 495 wdir_tracked,
495 496 p1_tracked,
496 497 p2_info,
497 498 mode_size_opt,
498 499 mtime_opt,
499 500 fallback_exec,
500 501 fallback_symlink,
501 502 ) = entry.v2_data();
502 503 // TODO: convert throug raw flag bits instead?
503 504 let mut flags = Flags::empty();
504 505 flags.set(Flags::WDIR_TRACKED, wdir_tracked);
505 506 flags.set(Flags::P1_TRACKED, p1_tracked);
506 507 flags.set(Flags::P2_INFO, p2_info);
507 508 let size = if let Some((m, s)) = mode_size_opt {
508 509 let exec_perm = m & (libc::S_IXUSR as u32) != 0;
509 510 let is_symlink = m & (libc::S_IFMT as u32) == libc::S_IFLNK as u32;
510 511 flags.set(Flags::MODE_EXEC_PERM, exec_perm);
511 512 flags.set(Flags::MODE_IS_SYMLINK, is_symlink);
512 513 flags.insert(Flags::HAS_MODE_AND_SIZE);
513 514 s.into()
514 515 } else {
515 516 0.into()
516 517 };
517 518 let mtime = if let Some(m) = mtime_opt {
518 519 flags.insert(Flags::HAS_MTIME);
519 520 if m.second_ambiguous {
520 521 flags.insert(Flags::MTIME_SECOND_AMBIGUOUS);
521 522 };
522 523 m.into()
523 524 } else {
524 525 PackedTruncatedTimestamp::null()
525 526 };
526 527 if let Some(f_exec) = fallback_exec {
527 528 flags.insert(Flags::HAS_FALLBACK_EXEC);
528 529 if f_exec {
529 530 flags.insert(Flags::FALLBACK_EXEC);
530 531 }
531 532 }
532 533 if let Some(f_symlink) = fallback_symlink {
533 534 flags.insert(Flags::HAS_FALLBACK_SYMLINK);
534 535 if f_symlink {
535 536 flags.insert(Flags::FALLBACK_SYMLINK);
536 537 }
537 538 }
538 539 (flags, size, mtime)
539 540 }
540 541 }
541 542
542 543 fn read_hg_path(
543 544 on_disk: &[u8],
544 545 slice: PathSlice,
545 546 ) -> Result<&HgPath, DirstateV2ParseError> {
546 547 read_slice(on_disk, slice.start, slice.len.get()).map(HgPath::new)
547 548 }
548 549
549 550 fn read_nodes(
550 551 on_disk: &[u8],
551 552 slice: ChildNodes,
552 553 ) -> Result<&[Node], DirstateV2ParseError> {
553 554 read_slice(on_disk, slice.start, slice.len.get())
554 555 }
555 556
556 557 fn read_slice<T, Len>(
557 558 on_disk: &[u8],
558 559 start: Offset,
559 560 len: Len,
560 561 ) -> Result<&[T], DirstateV2ParseError>
561 562 where
562 563 T: BytesCast,
563 564 Len: TryInto<usize>,
564 565 {
565 566 // Either `usize::MAX` would result in "out of bounds" error since a single
566 567 // `&[u8]` cannot occupy the entire addess space.
567 568 let start = start.get().try_into().unwrap_or(std::usize::MAX);
568 569 let len = len.try_into().unwrap_or(std::usize::MAX);
569 570 on_disk
570 571 .get(start..)
571 572 .and_then(|bytes| T::slice_from_bytes(bytes, len).ok())
572 573 .map(|(slice, _rest)| slice)
573 574 .ok_or_else(|| DirstateV2ParseError)
574 575 }
575 576
576 577 pub(crate) fn for_each_tracked_path<'on_disk>(
577 578 on_disk: &'on_disk [u8],
578 579 metadata: &[u8],
579 580 mut f: impl FnMut(&'on_disk HgPath),
580 581 ) -> Result<(), DirstateV2ParseError> {
581 582 let (meta, _) = TreeMetadata::from_bytes(metadata)
582 583 .map_err(|_| DirstateV2ParseError)?;
583 584 fn recur<'on_disk>(
584 585 on_disk: &'on_disk [u8],
585 586 nodes: ChildNodes,
586 587 f: &mut impl FnMut(&'on_disk HgPath),
587 588 ) -> Result<(), DirstateV2ParseError> {
588 589 for node in read_nodes(on_disk, nodes)? {
589 590 if let Some(entry) = node.entry()? {
590 591 if entry.state().is_tracked() {
591 592 f(node.full_path(on_disk)?)
592 593 }
593 594 }
594 595 recur(on_disk, node.children, f)?
595 596 }
596 597 Ok(())
597 598 }
598 599 recur(on_disk, meta.root_nodes, &mut f)
599 600 }
600 601
601 602 /// Returns new data and metadata, together with whether that data should be
602 603 /// appended to the existing data file whose content is at
603 604 /// `dirstate_map.on_disk` (true), instead of written to a new data file
604 /// (false).
605 /// (false), and the previous size of data on disk.
605 606 pub(super) fn write(
606 607 dirstate_map: &DirstateMap,
607 608 can_append: bool,
608 ) -> Result<(Vec<u8>, TreeMetadata, bool), DirstateError> {
609 ) -> Result<(Vec<u8>, TreeMetadata, bool, usize), DirstateError> {
609 610 let append = can_append && dirstate_map.write_should_append();
610 611
611 612 // This ignores the space for paths, and for nodes without an entry.
612 613 // TODO: better estimate? Skip the `Vec` and write to a file directly?
613 614 let size_guess = std::mem::size_of::<Node>()
614 615 * dirstate_map.nodes_with_entry_count as usize;
615 616
616 617 let mut writer = Writer {
617 618 dirstate_map,
618 619 append,
619 620 out: Vec::with_capacity(size_guess),
620 621 };
621 622
622 623 let root_nodes = writer.write_nodes(dirstate_map.root.as_ref())?;
623 624
624 625 let meta = TreeMetadata {
625 626 root_nodes,
626 627 nodes_with_entry_count: dirstate_map.nodes_with_entry_count.into(),
627 628 nodes_with_copy_source_count: dirstate_map
628 629 .nodes_with_copy_source_count
629 630 .into(),
630 631 unreachable_bytes: dirstate_map.unreachable_bytes.into(),
631 632 unused: [0; 4],
632 633 ignore_patterns_hash: dirstate_map.ignore_patterns_hash,
633 634 };
634 Ok((writer.out, meta, append))
635 Ok((writer.out, meta, append, dirstate_map.old_data_size))
635 636 }
636 637
637 638 struct Writer<'dmap, 'on_disk> {
638 639 dirstate_map: &'dmap DirstateMap<'on_disk>,
639 640 append: bool,
640 641 out: Vec<u8>,
641 642 }
642 643
643 644 impl Writer<'_, '_> {
644 645 fn write_nodes(
645 646 &mut self,
646 647 nodes: dirstate_map::ChildNodesRef,
647 648 ) -> Result<ChildNodes, DirstateError> {
648 649 // Reuse already-written nodes if possible
649 650 if self.append {
650 651 if let dirstate_map::ChildNodesRef::OnDisk(nodes_slice) = nodes {
651 652 let start = self.on_disk_offset_of(nodes_slice).expect(
652 653 "dirstate-v2 OnDisk nodes not found within on_disk",
653 654 );
654 655 let len = child_nodes_len_from_usize(nodes_slice.len());
655 656 return Ok(ChildNodes { start, len });
656 657 }
657 658 }
658 659
659 660 // `dirstate_map::ChildNodes::InMemory` contains a `HashMap` which has
660 661 // undefined iteration order. Sort to enable binary search in the
661 662 // written file.
662 663 let nodes = nodes.sorted();
663 664 let nodes_len = nodes.len();
664 665
665 666 // First accumulate serialized nodes in a `Vec`
666 667 let mut on_disk_nodes = Vec::with_capacity(nodes_len);
667 668 for node in nodes {
668 669 let children =
669 670 self.write_nodes(node.children(self.dirstate_map.on_disk)?)?;
670 671 let full_path = node.full_path(self.dirstate_map.on_disk)?;
671 672 let full_path = self.write_path(full_path.as_bytes());
672 673 let copy_source = if let Some(source) =
673 674 node.copy_source(self.dirstate_map.on_disk)?
674 675 {
675 676 self.write_path(source.as_bytes())
676 677 } else {
677 678 PathSlice {
678 679 start: 0.into(),
679 680 len: 0.into(),
680 681 }
681 682 };
682 683 on_disk_nodes.push(match node {
683 684 NodeRef::InMemory(path, node) => {
684 685 let (flags, size, mtime) = match &node.data {
685 686 dirstate_map::NodeData::Entry(entry) => {
686 687 Node::from_dirstate_entry(entry)
687 688 }
688 689 dirstate_map::NodeData::CachedDirectory { mtime } => {
689 690 // we currently never set a mtime if unknown file
690 691 // are present.
691 692 // So if we have a mtime for a directory, we know
692 693 // they are no unknown
693 694 // files and we
694 695 // blindly set ALL_UNKNOWN_RECORDED.
695 696 //
696 697 // We never set ALL_IGNORED_RECORDED since we
697 698 // don't track that case
698 699 // currently.
699 700 let mut flags = Flags::DIRECTORY
700 701 | Flags::HAS_MTIME
701 702 | Flags::ALL_UNKNOWN_RECORDED;
702 703 if mtime.second_ambiguous {
703 704 flags.insert(Flags::MTIME_SECOND_AMBIGUOUS)
704 705 }
705 706 (flags, 0.into(), (*mtime).into())
706 707 }
707 708 dirstate_map::NodeData::None => (
708 709 Flags::DIRECTORY,
709 710 0.into(),
710 711 PackedTruncatedTimestamp::null(),
711 712 ),
712 713 };
713 714 Node {
714 715 children,
715 716 copy_source,
716 717 full_path,
717 718 base_name_start: u16::try_from(path.base_name_start())
718 719 // Could only panic for paths over 64 KiB
719 720 .expect("dirstate-v2 path length overflow")
720 721 .into(),
721 722 descendants_with_entry_count: node
722 723 .descendants_with_entry_count
723 724 .into(),
724 725 tracked_descendants_count: node
725 726 .tracked_descendants_count
726 727 .into(),
727 728 flags: flags.bits().into(),
728 729 size,
729 730 mtime,
730 731 }
731 732 }
732 733 NodeRef::OnDisk(node) => Node {
733 734 children,
734 735 copy_source,
735 736 full_path,
736 737 ..*node
737 738 },
738 739 })
739 740 }
740 741 // … so we can write them contiguously, after writing everything else
741 742 // they refer to.
742 743 let start = self.current_offset();
743 744 let len = child_nodes_len_from_usize(nodes_len);
744 745 self.out.extend(on_disk_nodes.as_bytes());
745 746 Ok(ChildNodes { start, len })
746 747 }
747 748
748 749 /// If the given slice of items is within `on_disk`, returns its offset
749 750 /// from the start of `on_disk`.
750 751 fn on_disk_offset_of<T>(&self, slice: &[T]) -> Option<Offset>
751 752 where
752 753 T: BytesCast,
753 754 {
754 755 fn address_range(slice: &[u8]) -> std::ops::RangeInclusive<usize> {
755 756 let start = slice.as_ptr() as usize;
756 757 let end = start + slice.len();
757 758 start..=end
758 759 }
759 760 let slice_addresses = address_range(slice.as_bytes());
760 761 let on_disk_addresses = address_range(self.dirstate_map.on_disk);
761 762 if on_disk_addresses.contains(slice_addresses.start())
762 763 && on_disk_addresses.contains(slice_addresses.end())
763 764 {
764 765 let offset = slice_addresses.start() - on_disk_addresses.start();
765 766 Some(offset_from_usize(offset))
766 767 } else {
767 768 None
768 769 }
769 770 }
770 771
771 772 fn current_offset(&mut self) -> Offset {
772 773 let mut offset = self.out.len();
773 774 if self.append {
774 775 offset += self.dirstate_map.on_disk.len()
775 776 }
776 777 offset_from_usize(offset)
777 778 }
778 779
779 780 fn write_path(&mut self, slice: &[u8]) -> PathSlice {
780 781 let len = path_len_from_usize(slice.len());
781 782 // Reuse an already-written path if possible
782 783 if self.append {
783 784 if let Some(start) = self.on_disk_offset_of(slice) {
784 785 return PathSlice { start, len };
785 786 }
786 787 }
787 788 let start = self.current_offset();
788 789 self.out.extend(slice.as_bytes());
789 790 PathSlice { start, len }
790 791 }
791 792 }
792 793
793 794 fn offset_from_usize(x: usize) -> Offset {
794 795 u32::try_from(x)
795 796 // Could only panic for a dirstate file larger than 4 GiB
796 797 .expect("dirstate-v2 offset overflow")
797 798 .into()
798 799 }
799 800
800 801 fn child_nodes_len_from_usize(x: usize) -> Size {
801 802 u32::try_from(x)
802 803 // Could only panic with over 4 billion nodes
803 804 .expect("dirstate-v2 slice length overflow")
804 805 .into()
805 806 }
806 807
807 808 fn path_len_from_usize(x: usize) -> PathSize {
808 809 u16::try_from(x)
809 810 // Could only panic for paths over 64 KiB
810 811 .expect("dirstate-v2 path length overflow")
811 812 .into()
812 813 }
813 814
814 815 impl From<TruncatedTimestamp> for PackedTruncatedTimestamp {
815 816 fn from(timestamp: TruncatedTimestamp) -> Self {
816 817 Self {
817 818 truncated_seconds: timestamp.truncated_seconds().into(),
818 819 nanoseconds: timestamp.nanoseconds().into(),
819 820 }
820 821 }
821 822 }
822 823
823 824 impl TryFrom<PackedTruncatedTimestamp> for TruncatedTimestamp {
824 825 type Error = DirstateV2ParseError;
825 826
826 827 fn try_from(
827 828 timestamp: PackedTruncatedTimestamp,
828 829 ) -> Result<Self, Self::Error> {
829 830 Self::from_already_truncated(
830 831 timestamp.truncated_seconds.get(),
831 832 timestamp.nanoseconds.get(),
832 833 false,
833 834 )
834 835 }
835 836 }
836 837 impl PackedTruncatedTimestamp {
837 838 fn null() -> Self {
838 839 Self {
839 840 truncated_seconds: 0.into(),
840 841 nanoseconds: 0.into(),
841 842 }
842 843 }
843 844 }
@@ -1,533 +1,543 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::exit_codes;
9 9 use crate::lock::{try_with_lock_no_wait, LockError};
10 10 use crate::manifest::{Manifest, Manifestlog};
11 11 use crate::revlog::filelog::Filelog;
12 12 use crate::revlog::revlog::RevlogError;
13 13 use crate::utils::files::get_path_from_bytes;
14 14 use crate::utils::hg_path::HgPath;
15 15 use crate::utils::SliceExt;
16 16 use crate::vfs::{is_dir, is_file, Vfs};
17 17 use crate::{requirements, NodePrefix};
18 18 use crate::{DirstateError, Revision};
19 19 use std::cell::{Ref, RefCell, RefMut};
20 20 use std::collections::HashSet;
21 21 use std::io::Seek;
22 22 use std::io::SeekFrom;
23 23 use std::io::Write as IoWrite;
24 24 use std::path::{Path, PathBuf};
25 25
26 26 /// A repository on disk
27 27 pub struct Repo {
28 28 working_directory: PathBuf,
29 29 dot_hg: PathBuf,
30 30 store: PathBuf,
31 31 requirements: HashSet<String>,
32 32 config: Config,
33 33 dirstate_parents: LazyCell<DirstateParents, HgError>,
34 34 dirstate_data_file_uuid: LazyCell<Option<Vec<u8>>, HgError>,
35 35 dirstate_map: LazyCell<OwningDirstateMap, DirstateError>,
36 36 changelog: LazyCell<Changelog, HgError>,
37 37 manifestlog: LazyCell<Manifestlog, HgError>,
38 38 }
39 39
40 40 #[derive(Debug, derive_more::From)]
41 41 pub enum RepoError {
42 42 NotFound {
43 43 at: PathBuf,
44 44 },
45 45 #[from]
46 46 ConfigParseError(ConfigParseError),
47 47 #[from]
48 48 Other(HgError),
49 49 }
50 50
51 51 impl From<ConfigError> for RepoError {
52 52 fn from(error: ConfigError) -> Self {
53 53 match error {
54 54 ConfigError::Parse(error) => error.into(),
55 55 ConfigError::Other(error) => error.into(),
56 56 }
57 57 }
58 58 }
59 59
60 60 impl Repo {
61 61 /// tries to find nearest repository root in current working directory or
62 62 /// its ancestors
63 63 pub fn find_repo_root() -> Result<PathBuf, RepoError> {
64 64 let current_directory = crate::utils::current_dir()?;
65 65 // ancestors() is inclusive: it first yields `current_directory`
66 66 // as-is.
67 67 for ancestor in current_directory.ancestors() {
68 68 if is_dir(ancestor.join(".hg"))? {
69 69 return Ok(ancestor.to_path_buf());
70 70 }
71 71 }
72 72 return Err(RepoError::NotFound {
73 73 at: current_directory,
74 74 });
75 75 }
76 76
77 77 /// Find a repository, either at the given path (which must contain a `.hg`
78 78 /// sub-directory) or by searching the current directory and its
79 79 /// ancestors.
80 80 ///
81 81 /// A method with two very different "modes" like this usually a code smell
82 82 /// to make two methods instead, but in this case an `Option` is what rhg
83 83 /// sub-commands get from Clap for the `-R` / `--repository` CLI argument.
84 84 /// Having two methods would just move that `if` to almost all callers.
85 85 pub fn find(
86 86 config: &Config,
87 87 explicit_path: Option<PathBuf>,
88 88 ) -> Result<Self, RepoError> {
89 89 if let Some(root) = explicit_path {
90 90 if is_dir(root.join(".hg"))? {
91 91 Self::new_at_path(root.to_owned(), config)
92 92 } else if is_file(&root)? {
93 93 Err(HgError::unsupported("bundle repository").into())
94 94 } else {
95 95 Err(RepoError::NotFound {
96 96 at: root.to_owned(),
97 97 })
98 98 }
99 99 } else {
100 100 let root = Self::find_repo_root()?;
101 101 Self::new_at_path(root, config)
102 102 }
103 103 }
104 104
105 105 /// To be called after checking that `.hg` is a sub-directory
106 106 fn new_at_path(
107 107 working_directory: PathBuf,
108 108 config: &Config,
109 109 ) -> Result<Self, RepoError> {
110 110 let dot_hg = working_directory.join(".hg");
111 111
112 112 let mut repo_config_files = Vec::new();
113 113 repo_config_files.push(dot_hg.join("hgrc"));
114 114 repo_config_files.push(dot_hg.join("hgrc-not-shared"));
115 115
116 116 let hg_vfs = Vfs { base: &dot_hg };
117 117 let mut reqs = requirements::load_if_exists(hg_vfs)?;
118 118 let relative =
119 119 reqs.contains(requirements::RELATIVE_SHARED_REQUIREMENT);
120 120 let shared =
121 121 reqs.contains(requirements::SHARED_REQUIREMENT) || relative;
122 122
123 123 // From `mercurial/localrepo.py`:
124 124 //
125 125 // if .hg/requires contains the sharesafe requirement, it means
126 126 // there exists a `.hg/store/requires` too and we should read it
127 127 // NOTE: presence of SHARESAFE_REQUIREMENT imply that store requirement
128 128 // is present. We never write SHARESAFE_REQUIREMENT for a repo if store
129 129 // is not present, refer checkrequirementscompat() for that
130 130 //
131 131 // However, if SHARESAFE_REQUIREMENT is not present, it means that the
132 132 // repository was shared the old way. We check the share source
133 133 // .hg/requires for SHARESAFE_REQUIREMENT to detect whether the
134 134 // current repository needs to be reshared
135 135 let share_safe = reqs.contains(requirements::SHARESAFE_REQUIREMENT);
136 136
137 137 let store_path;
138 138 if !shared {
139 139 store_path = dot_hg.join("store");
140 140 } else {
141 141 let bytes = hg_vfs.read("sharedpath")?;
142 142 let mut shared_path =
143 143 get_path_from_bytes(bytes.trim_end_matches(|b| b == b'\n'))
144 144 .to_owned();
145 145 if relative {
146 146 shared_path = dot_hg.join(shared_path)
147 147 }
148 148 if !is_dir(&shared_path)? {
149 149 return Err(HgError::corrupted(format!(
150 150 ".hg/sharedpath points to nonexistent directory {}",
151 151 shared_path.display()
152 152 ))
153 153 .into());
154 154 }
155 155
156 156 store_path = shared_path.join("store");
157 157
158 158 let source_is_share_safe =
159 159 requirements::load(Vfs { base: &shared_path })?
160 160 .contains(requirements::SHARESAFE_REQUIREMENT);
161 161
162 162 if share_safe && !source_is_share_safe {
163 163 return Err(match config
164 164 .get(b"share", b"safe-mismatch.source-not-safe")
165 165 {
166 166 Some(b"abort") | None => HgError::abort(
167 167 "abort: share source does not support share-safe requirement\n\
168 168 (see `hg help config.format.use-share-safe` for more information)",
169 169 exit_codes::ABORT,
170 170 ),
171 171 _ => HgError::unsupported("share-safe downgrade"),
172 172 }
173 173 .into());
174 174 } else if source_is_share_safe && !share_safe {
175 175 return Err(
176 176 match config.get(b"share", b"safe-mismatch.source-safe") {
177 177 Some(b"abort") | None => HgError::abort(
178 178 "abort: version mismatch: source uses share-safe \
179 179 functionality while the current share does not\n\
180 180 (see `hg help config.format.use-share-safe` for more information)",
181 181 exit_codes::ABORT,
182 182 ),
183 183 _ => HgError::unsupported("share-safe upgrade"),
184 184 }
185 185 .into(),
186 186 );
187 187 }
188 188
189 189 if share_safe {
190 190 repo_config_files.insert(0, shared_path.join("hgrc"))
191 191 }
192 192 }
193 193 if share_safe {
194 194 reqs.extend(requirements::load(Vfs { base: &store_path })?);
195 195 }
196 196
197 197 let repo_config = if std::env::var_os("HGRCSKIPREPO").is_none() {
198 198 config.combine_with_repo(&repo_config_files)?
199 199 } else {
200 200 config.clone()
201 201 };
202 202
203 203 let repo = Self {
204 204 requirements: reqs,
205 205 working_directory,
206 206 store: store_path,
207 207 dot_hg,
208 208 config: repo_config,
209 209 dirstate_parents: LazyCell::new(Self::read_dirstate_parents),
210 210 dirstate_data_file_uuid: LazyCell::new(
211 211 Self::read_dirstate_data_file_uuid,
212 212 ),
213 213 dirstate_map: LazyCell::new(Self::new_dirstate_map),
214 214 changelog: LazyCell::new(Changelog::open),
215 215 manifestlog: LazyCell::new(Manifestlog::open),
216 216 };
217 217
218 218 requirements::check(&repo)?;
219 219
220 220 Ok(repo)
221 221 }
222 222
223 223 pub fn working_directory_path(&self) -> &Path {
224 224 &self.working_directory
225 225 }
226 226
227 227 pub fn requirements(&self) -> &HashSet<String> {
228 228 &self.requirements
229 229 }
230 230
231 231 pub fn config(&self) -> &Config {
232 232 &self.config
233 233 }
234 234
235 235 /// For accessing repository files (in `.hg`), except for the store
236 236 /// (`.hg/store`).
237 237 pub fn hg_vfs(&self) -> Vfs<'_> {
238 238 Vfs { base: &self.dot_hg }
239 239 }
240 240
241 241 /// For accessing repository store files (in `.hg/store`)
242 242 pub fn store_vfs(&self) -> Vfs<'_> {
243 243 Vfs { base: &self.store }
244 244 }
245 245
246 246 /// For accessing the working copy
247 247 pub fn working_directory_vfs(&self) -> Vfs<'_> {
248 248 Vfs {
249 249 base: &self.working_directory,
250 250 }
251 251 }
252 252
253 253 pub fn try_with_wlock_no_wait<R>(
254 254 &self,
255 255 f: impl FnOnce() -> R,
256 256 ) -> Result<R, LockError> {
257 257 try_with_lock_no_wait(self.hg_vfs(), "wlock", f)
258 258 }
259 259
260 260 pub fn has_dirstate_v2(&self) -> bool {
261 261 self.requirements
262 262 .contains(requirements::DIRSTATE_V2_REQUIREMENT)
263 263 }
264 264
265 265 pub fn has_sparse(&self) -> bool {
266 266 self.requirements.contains(requirements::SPARSE_REQUIREMENT)
267 267 }
268 268
269 269 pub fn has_narrow(&self) -> bool {
270 270 self.requirements.contains(requirements::NARROW_REQUIREMENT)
271 271 }
272 272
273 273 fn dirstate_file_contents(&self) -> Result<Vec<u8>, HgError> {
274 274 Ok(self
275 275 .hg_vfs()
276 276 .read("dirstate")
277 277 .io_not_found_as_none()?
278 278 .unwrap_or(Vec::new()))
279 279 }
280 280
281 281 pub fn dirstate_parents(&self) -> Result<DirstateParents, HgError> {
282 282 Ok(*self.dirstate_parents.get_or_init(self)?)
283 283 }
284 284
285 285 fn read_dirstate_parents(&self) -> Result<DirstateParents, HgError> {
286 286 let dirstate = self.dirstate_file_contents()?;
287 287 let parents = if dirstate.is_empty() {
288 288 if self.has_dirstate_v2() {
289 289 self.dirstate_data_file_uuid.set(None);
290 290 }
291 291 DirstateParents::NULL
292 292 } else if self.has_dirstate_v2() {
293 293 let docket =
294 294 crate::dirstate_tree::on_disk::read_docket(&dirstate)?;
295 295 self.dirstate_data_file_uuid
296 296 .set(Some(docket.uuid.to_owned()));
297 297 docket.parents()
298 298 } else {
299 299 crate::dirstate::parsers::parse_dirstate_parents(&dirstate)?
300 300 .clone()
301 301 };
302 302 self.dirstate_parents.set(parents);
303 303 Ok(parents)
304 304 }
305 305
306 306 fn read_dirstate_data_file_uuid(
307 307 &self,
308 308 ) -> Result<Option<Vec<u8>>, HgError> {
309 309 assert!(
310 310 self.has_dirstate_v2(),
311 311 "accessing dirstate data file ID without dirstate-v2"
312 312 );
313 313 let dirstate = self.dirstate_file_contents()?;
314 314 if dirstate.is_empty() {
315 315 self.dirstate_parents.set(DirstateParents::NULL);
316 316 Ok(None)
317 317 } else {
318 318 let docket =
319 319 crate::dirstate_tree::on_disk::read_docket(&dirstate)?;
320 320 self.dirstate_parents.set(docket.parents());
321 321 Ok(Some(docket.uuid.to_owned()))
322 322 }
323 323 }
324 324
325 325 fn new_dirstate_map(&self) -> Result<OwningDirstateMap, DirstateError> {
326 326 let dirstate_file_contents = self.dirstate_file_contents()?;
327 327 if dirstate_file_contents.is_empty() {
328 328 self.dirstate_parents.set(DirstateParents::NULL);
329 329 if self.has_dirstate_v2() {
330 330 self.dirstate_data_file_uuid.set(None);
331 331 }
332 332 Ok(OwningDirstateMap::new_empty(Vec::new()))
333 333 } else if self.has_dirstate_v2() {
334 334 let docket = crate::dirstate_tree::on_disk::read_docket(
335 335 &dirstate_file_contents,
336 336 )?;
337 337 self.dirstate_parents.set(docket.parents());
338 338 self.dirstate_data_file_uuid
339 339 .set(Some(docket.uuid.to_owned()));
340 340 let data_size = docket.data_size();
341 341 let metadata = docket.tree_metadata();
342 342 if let Some(data_mmap) = self
343 343 .hg_vfs()
344 344 .mmap_open(docket.data_filename())
345 345 .io_not_found_as_none()?
346 346 {
347 347 OwningDirstateMap::new_v2(data_mmap, data_size, metadata)
348 348 } else {
349 349 OwningDirstateMap::new_v2(Vec::new(), data_size, metadata)
350 350 }
351 351 } else {
352 352 let (map, parents) =
353 353 OwningDirstateMap::new_v1(dirstate_file_contents)?;
354 354 self.dirstate_parents.set(parents);
355 355 Ok(map)
356 356 }
357 357 }
358 358
359 359 pub fn dirstate_map(
360 360 &self,
361 361 ) -> Result<Ref<OwningDirstateMap>, DirstateError> {
362 362 self.dirstate_map.get_or_init(self)
363 363 }
364 364
365 365 pub fn dirstate_map_mut(
366 366 &self,
367 367 ) -> Result<RefMut<OwningDirstateMap>, DirstateError> {
368 368 self.dirstate_map.get_mut_or_init(self)
369 369 }
370 370
371 371 pub fn changelog(&self) -> Result<Ref<Changelog>, HgError> {
372 372 self.changelog.get_or_init(self)
373 373 }
374 374
375 375 pub fn changelog_mut(&self) -> Result<RefMut<Changelog>, HgError> {
376 376 self.changelog.get_mut_or_init(self)
377 377 }
378 378
379 379 pub fn manifestlog(&self) -> Result<Ref<Manifestlog>, HgError> {
380 380 self.manifestlog.get_or_init(self)
381 381 }
382 382
383 383 pub fn manifestlog_mut(&self) -> Result<RefMut<Manifestlog>, HgError> {
384 384 self.manifestlog.get_mut_or_init(self)
385 385 }
386 386
387 387 /// Returns the manifest of the *changeset* with the given node ID
388 388 pub fn manifest_for_node(
389 389 &self,
390 390 node: impl Into<NodePrefix>,
391 391 ) -> Result<Manifest, RevlogError> {
392 392 self.manifestlog()?.data_for_node(
393 393 self.changelog()?
394 394 .data_for_node(node.into())?
395 395 .manifest_node()?
396 396 .into(),
397 397 )
398 398 }
399 399
400 400 /// Returns the manifest of the *changeset* with the given revision number
401 401 pub fn manifest_for_rev(
402 402 &self,
403 403 revision: Revision,
404 404 ) -> Result<Manifest, RevlogError> {
405 405 self.manifestlog()?.data_for_node(
406 406 self.changelog()?
407 407 .data_for_rev(revision)?
408 408 .manifest_node()?
409 409 .into(),
410 410 )
411 411 }
412 412
413 413 pub fn has_subrepos(&self) -> Result<bool, DirstateError> {
414 414 if let Some(entry) = self.dirstate_map()?.get(HgPath::new(".hgsub"))? {
415 415 Ok(entry.state().is_tracked())
416 416 } else {
417 417 Ok(false)
418 418 }
419 419 }
420 420
421 421 pub fn filelog(&self, path: &HgPath) -> Result<Filelog, HgError> {
422 422 Filelog::open(self, path)
423 423 }
424 424
425 425 /// Write to disk any updates that were made through `dirstate_map_mut`.
426 426 ///
427 427 /// The "wlock" must be held while calling this.
428 428 /// See for example `try_with_wlock_no_wait`.
429 429 ///
430 430 /// TODO: have a `WritableRepo` type only accessible while holding the
431 431 /// lock?
432 432 pub fn write_dirstate(&self) -> Result<(), DirstateError> {
433 433 let map = self.dirstate_map()?;
434 434 // TODO: Maintain a `DirstateMap::dirty` flag, and return early here if
435 435 // it’s unset
436 436 let parents = self.dirstate_parents()?;
437 437 let packed_dirstate = if self.has_dirstate_v2() {
438 438 let uuid = self.dirstate_data_file_uuid.get_or_init(self)?;
439 439 let mut uuid = uuid.as_ref();
440 440 let can_append = uuid.is_some();
441 let (data, tree_metadata, append) = map.pack_v2(can_append)?;
441 let (data, tree_metadata, append, old_data_size) =
442 map.pack_v2(can_append)?;
442 443 if !append {
443 444 uuid = None
444 445 }
445 446 let uuid = if let Some(uuid) = uuid {
446 447 std::str::from_utf8(uuid)
447 448 .map_err(|_| {
448 449 HgError::corrupted("non-UTF-8 dirstate data file ID")
449 450 })?
450 451 .to_owned()
451 452 } else {
452 453 DirstateDocket::new_uid()
453 454 };
454 455 let data_filename = format!("dirstate.{}", uuid);
455 456 let data_filename = self.hg_vfs().join(data_filename);
456 457 let mut options = std::fs::OpenOptions::new();
457 458 if append {
458 459 options.append(true);
459 460 } else {
460 461 options.write(true).create_new(true);
461 462 }
462 463 let data_size = (|| {
463 464 // TODO: loop and try another random ID if !append and this
464 465 // returns `ErrorKind::AlreadyExists`? Collision chance of two
465 466 // random IDs is one in 2**32
466 467 let mut file = options.open(&data_filename)?;
467 file.write_all(&data)?;
468 file.flush()?;
469 // TODO: use https://doc.rust-lang.org/std/io/trait.Seek.html#method.stream_position when we require Rust 1.51+
470 file.seek(SeekFrom::Current(0))
468 if data.is_empty() {
469 // If we're not appending anything, the data size is the
470 // same as in the previous docket. It is *not* the file
471 // length, since it could have garbage at the end.
472 // We don't have to worry about it when we do have data
473 // to append since we rewrite the root node in this case.
474 Ok(old_data_size as u64)
475 } else {
476 file.write_all(&data)?;
477 file.flush()?;
478 // TODO: use https://doc.rust-lang.org/std/io/trait.Seek.html#method.stream_position when we require Rust 1.51+
479 file.seek(SeekFrom::Current(0))
480 }
471 481 })()
472 482 .when_writing_file(&data_filename)?;
473 483 DirstateDocket::serialize(
474 484 parents,
475 485 tree_metadata,
476 486 data_size,
477 487 uuid.as_bytes(),
478 488 )
479 489 .map_err(|_: std::num::TryFromIntError| {
480 490 HgError::corrupted("overflow in dirstate docket serialization")
481 491 })?
482 492 } else {
483 493 map.pack_v1(parents)?
484 494 };
485 495 self.hg_vfs().atomic_write("dirstate", &packed_dirstate)?;
486 496 Ok(())
487 497 }
488 498 }
489 499
490 500 /// Lazily-initialized component of `Repo` with interior mutability
491 501 ///
492 502 /// This differs from `OnceCell` in that the value can still be "deinitialized"
493 503 /// later by setting its inner `Option` to `None`.
494 504 struct LazyCell<T, E> {
495 505 value: RefCell<Option<T>>,
496 506 // `Fn`s that don’t capture environment are zero-size, so this box does
497 507 // not allocate:
498 508 init: Box<dyn Fn(&Repo) -> Result<T, E>>,
499 509 }
500 510
501 511 impl<T, E> LazyCell<T, E> {
502 512 fn new(init: impl Fn(&Repo) -> Result<T, E> + 'static) -> Self {
503 513 Self {
504 514 value: RefCell::new(None),
505 515 init: Box::new(init),
506 516 }
507 517 }
508 518
509 519 fn set(&self, value: T) {
510 520 *self.value.borrow_mut() = Some(value)
511 521 }
512 522
513 523 fn get_or_init(&self, repo: &Repo) -> Result<Ref<T>, E> {
514 524 let mut borrowed = self.value.borrow();
515 525 if borrowed.is_none() {
516 526 drop(borrowed);
517 527 // Only use `borrow_mut` if it is really needed to avoid panic in
518 528 // case there is another outstanding borrow but mutation is not
519 529 // needed.
520 530 *self.value.borrow_mut() = Some((self.init)(repo)?);
521 531 borrowed = self.value.borrow()
522 532 }
523 533 Ok(Ref::map(borrowed, |option| option.as_ref().unwrap()))
524 534 }
525 535
526 536 fn get_mut_or_init(&self, repo: &Repo) -> Result<RefMut<T>, E> {
527 537 let mut borrowed = self.value.borrow_mut();
528 538 if borrowed.is_none() {
529 539 *borrowed = Some((self.init)(repo)?);
530 540 }
531 541 Ok(RefMut::map(borrowed, |option| option.as_mut().unwrap()))
532 542 }
533 543 }
@@ -1,490 +1,490 b''
1 1 // dirstate_map.rs
2 2 //
3 3 // Copyright 2019 Raphaël Gomès <rgomes@octobus.net>
4 4 //
5 5 // This software may be used and distributed according to the terms of the
6 6 // GNU General Public License version 2 or any later version.
7 7
8 8 //! Bindings for the `hg::dirstate::dirstate_map` file provided by the
9 9 //! `hg-core` package.
10 10
11 11 use std::cell::{RefCell, RefMut};
12 12 use std::convert::TryInto;
13 13
14 14 use cpython::{
15 15 exc, PyBool, PyBytes, PyClone, PyDict, PyErr, PyList, PyNone, PyObject,
16 16 PyResult, Python, PythonObject, ToPyObject, UnsafePyLeaked,
17 17 };
18 18
19 19 use crate::{
20 20 dirstate::copymap::{CopyMap, CopyMapItemsIterator, CopyMapKeysIterator},
21 21 dirstate::item::DirstateItem,
22 22 pybytes_deref::PyBytesDeref,
23 23 };
24 24 use hg::{
25 25 dirstate::StateMapIter,
26 26 dirstate_tree::on_disk::DirstateV2ParseError,
27 27 dirstate_tree::owning::OwningDirstateMap,
28 28 revlog::Node,
29 29 utils::files::normalize_case,
30 30 utils::hg_path::{HgPath, HgPathBuf},
31 31 DirstateEntry, DirstateError, DirstateParents, EntryState,
32 32 };
33 33
34 34 // TODO
35 35 // This object needs to share references to multiple members of its Rust
36 36 // inner struct, namely `copy_map`, `dirs` and `all_dirs`.
37 37 // Right now `CopyMap` is done, but it needs to have an explicit reference
38 38 // to `RustDirstateMap` which itself needs to have an encapsulation for
39 39 // every method in `CopyMap` (copymapcopy, etc.).
40 40 // This is ugly and hard to maintain.
41 41 // The same logic applies to `dirs` and `all_dirs`, however the `Dirs`
42 42 // `py_class!` is already implemented and does not mention
43 43 // `RustDirstateMap`, rightfully so.
44 44 // All attributes also have to have a separate refcount data attribute for
45 45 // leaks, with all methods that go along for reference sharing.
46 46 py_class!(pub class DirstateMap |py| {
47 47 @shared data inner: OwningDirstateMap;
48 48
49 49 /// Returns a `(dirstate_map, parents)` tuple
50 50 @staticmethod
51 51 def new_v1(
52 52 on_disk: PyBytes,
53 53 ) -> PyResult<PyObject> {
54 54 let on_disk = PyBytesDeref::new(py, on_disk);
55 55 let (map, parents) = OwningDirstateMap::new_v1(on_disk)
56 56 .map_err(|e| dirstate_error(py, e))?;
57 57 let map = Self::create_instance(py, map)?;
58 58 let p1 = PyBytes::new(py, parents.p1.as_bytes());
59 59 let p2 = PyBytes::new(py, parents.p2.as_bytes());
60 60 let parents = (p1, p2);
61 61 Ok((map, parents).to_py_object(py).into_object())
62 62 }
63 63
64 64 /// Returns a DirstateMap
65 65 @staticmethod
66 66 def new_v2(
67 67 on_disk: PyBytes,
68 68 data_size: usize,
69 69 tree_metadata: PyBytes,
70 70 ) -> PyResult<PyObject> {
71 71 let dirstate_error = |e: DirstateError| {
72 72 PyErr::new::<exc::OSError, _>(py, format!("Dirstate error: {:?}", e))
73 73 };
74 74 let on_disk = PyBytesDeref::new(py, on_disk);
75 75 let map = OwningDirstateMap::new_v2(
76 76 on_disk, data_size, tree_metadata.data(py),
77 77 ).map_err(dirstate_error)?;
78 78 let map = Self::create_instance(py, map)?;
79 79 Ok(map.into_object())
80 80 }
81 81
82 82 def clear(&self) -> PyResult<PyObject> {
83 83 self.inner(py).borrow_mut().clear();
84 84 Ok(py.None())
85 85 }
86 86
87 87 def get(
88 88 &self,
89 89 key: PyObject,
90 90 default: Option<PyObject> = None
91 91 ) -> PyResult<Option<PyObject>> {
92 92 let key = key.extract::<PyBytes>(py)?;
93 93 match self
94 94 .inner(py)
95 95 .borrow()
96 96 .get(HgPath::new(key.data(py)))
97 97 .map_err(|e| v2_error(py, e))?
98 98 {
99 99 Some(entry) => {
100 100 Ok(Some(DirstateItem::new_as_pyobject(py, entry)?))
101 101 },
102 102 None => Ok(default)
103 103 }
104 104 }
105 105
106 106 def set_dirstate_item(
107 107 &self,
108 108 path: PyObject,
109 109 item: DirstateItem
110 110 ) -> PyResult<PyObject> {
111 111 let f = path.extract::<PyBytes>(py)?;
112 112 let filename = HgPath::new(f.data(py));
113 113 self.inner(py)
114 114 .borrow_mut()
115 115 .set_entry(filename, item.get_entry(py))
116 116 .map_err(|e| v2_error(py, e))?;
117 117 Ok(py.None())
118 118 }
119 119
120 120 def addfile(
121 121 &self,
122 122 f: PyBytes,
123 123 item: DirstateItem,
124 124 ) -> PyResult<PyNone> {
125 125 let filename = HgPath::new(f.data(py));
126 126 let entry = item.get_entry(py);
127 127 self.inner(py)
128 128 .borrow_mut()
129 129 .add_file(filename, entry)
130 130 .map_err(|e |dirstate_error(py, e))?;
131 131 Ok(PyNone)
132 132 }
133 133
134 134 def removefile(
135 135 &self,
136 136 f: PyObject,
137 137 in_merge: PyObject
138 138 ) -> PyResult<PyObject> {
139 139 self.inner(py).borrow_mut()
140 140 .remove_file(
141 141 HgPath::new(f.extract::<PyBytes>(py)?.data(py)),
142 142 in_merge.extract::<PyBool>(py)?.is_true(),
143 143 )
144 144 .or_else(|_| {
145 145 Err(PyErr::new::<exc::OSError, _>(
146 146 py,
147 147 "Dirstate error".to_string(),
148 148 ))
149 149 })?;
150 150 Ok(py.None())
151 151 }
152 152
153 153 def drop_item_and_copy_source(
154 154 &self,
155 155 f: PyBytes,
156 156 ) -> PyResult<PyNone> {
157 157 self.inner(py)
158 158 .borrow_mut()
159 159 .drop_entry_and_copy_source(HgPath::new(f.data(py)))
160 160 .map_err(|e |dirstate_error(py, e))?;
161 161 Ok(PyNone)
162 162 }
163 163
164 164 def hastrackeddir(&self, d: PyObject) -> PyResult<PyBool> {
165 165 let d = d.extract::<PyBytes>(py)?;
166 166 Ok(self.inner(py).borrow_mut()
167 167 .has_tracked_dir(HgPath::new(d.data(py)))
168 168 .map_err(|e| {
169 169 PyErr::new::<exc::ValueError, _>(py, e.to_string())
170 170 })?
171 171 .to_py_object(py))
172 172 }
173 173
174 174 def hasdir(&self, d: PyObject) -> PyResult<PyBool> {
175 175 let d = d.extract::<PyBytes>(py)?;
176 176 Ok(self.inner(py).borrow_mut()
177 177 .has_dir(HgPath::new(d.data(py)))
178 178 .map_err(|e| {
179 179 PyErr::new::<exc::ValueError, _>(py, e.to_string())
180 180 })?
181 181 .to_py_object(py))
182 182 }
183 183
184 184 def write_v1(
185 185 &self,
186 186 p1: PyObject,
187 187 p2: PyObject,
188 188 ) -> PyResult<PyBytes> {
189 189 let inner = self.inner(py).borrow();
190 190 let parents = DirstateParents {
191 191 p1: extract_node_id(py, &p1)?,
192 192 p2: extract_node_id(py, &p2)?,
193 193 };
194 194 let result = inner.pack_v1(parents);
195 195 match result {
196 196 Ok(packed) => Ok(PyBytes::new(py, &packed)),
197 197 Err(_) => Err(PyErr::new::<exc::OSError, _>(
198 198 py,
199 199 "Dirstate error".to_string(),
200 200 )),
201 201 }
202 202 }
203 203
204 204 /// Returns new data together with whether that data should be appended to
205 205 /// the existing data file whose content is at `self.on_disk` (True),
206 206 /// instead of written to a new data file (False).
207 207 def write_v2(
208 208 &self,
209 209 can_append: bool,
210 210 ) -> PyResult<PyObject> {
211 211 let inner = self.inner(py).borrow();
212 212 let result = inner.pack_v2(can_append);
213 213 match result {
214 Ok((packed, tree_metadata, append)) => {
214 Ok((packed, tree_metadata, append, _old_data_size)) => {
215 215 let packed = PyBytes::new(py, &packed);
216 216 let tree_metadata = PyBytes::new(py, tree_metadata.as_bytes());
217 217 let tuple = (packed, tree_metadata, append);
218 218 Ok(tuple.to_py_object(py).into_object())
219 219 },
220 220 Err(_) => Err(PyErr::new::<exc::OSError, _>(
221 221 py,
222 222 "Dirstate error".to_string(),
223 223 )),
224 224 }
225 225 }
226 226
227 227 def filefoldmapasdict(&self) -> PyResult<PyDict> {
228 228 let dict = PyDict::new(py);
229 229 for item in self.inner(py).borrow_mut().iter() {
230 230 let (path, entry) = item.map_err(|e| v2_error(py, e))?;
231 231 if entry.state() != EntryState::Removed {
232 232 let key = normalize_case(path);
233 233 let value = path;
234 234 dict.set_item(
235 235 py,
236 236 PyBytes::new(py, key.as_bytes()).into_object(),
237 237 PyBytes::new(py, value.as_bytes()).into_object(),
238 238 )?;
239 239 }
240 240 }
241 241 Ok(dict)
242 242 }
243 243
244 244 def __len__(&self) -> PyResult<usize> {
245 245 Ok(self.inner(py).borrow().len())
246 246 }
247 247
248 248 def __contains__(&self, key: PyObject) -> PyResult<bool> {
249 249 let key = key.extract::<PyBytes>(py)?;
250 250 self.inner(py)
251 251 .borrow()
252 252 .contains_key(HgPath::new(key.data(py)))
253 253 .map_err(|e| v2_error(py, e))
254 254 }
255 255
256 256 def __getitem__(&self, key: PyObject) -> PyResult<PyObject> {
257 257 let key = key.extract::<PyBytes>(py)?;
258 258 let key = HgPath::new(key.data(py));
259 259 match self
260 260 .inner(py)
261 261 .borrow()
262 262 .get(key)
263 263 .map_err(|e| v2_error(py, e))?
264 264 {
265 265 Some(entry) => {
266 266 Ok(DirstateItem::new_as_pyobject(py, entry)?)
267 267 },
268 268 None => Err(PyErr::new::<exc::KeyError, _>(
269 269 py,
270 270 String::from_utf8_lossy(key.as_bytes()),
271 271 )),
272 272 }
273 273 }
274 274
275 275 def keys(&self) -> PyResult<DirstateMapKeysIterator> {
276 276 let leaked_ref = self.inner(py).leak_immutable();
277 277 DirstateMapKeysIterator::from_inner(
278 278 py,
279 279 unsafe { leaked_ref.map(py, |o| o.iter()) },
280 280 )
281 281 }
282 282
283 283 def items(&self) -> PyResult<DirstateMapItemsIterator> {
284 284 let leaked_ref = self.inner(py).leak_immutable();
285 285 DirstateMapItemsIterator::from_inner(
286 286 py,
287 287 unsafe { leaked_ref.map(py, |o| o.iter()) },
288 288 )
289 289 }
290 290
291 291 def __iter__(&self) -> PyResult<DirstateMapKeysIterator> {
292 292 let leaked_ref = self.inner(py).leak_immutable();
293 293 DirstateMapKeysIterator::from_inner(
294 294 py,
295 295 unsafe { leaked_ref.map(py, |o| o.iter()) },
296 296 )
297 297 }
298 298
299 299 // TODO all copymap* methods, see docstring above
300 300 def copymapcopy(&self) -> PyResult<PyDict> {
301 301 let dict = PyDict::new(py);
302 302 for item in self.inner(py).borrow().copy_map_iter() {
303 303 let (key, value) = item.map_err(|e| v2_error(py, e))?;
304 304 dict.set_item(
305 305 py,
306 306 PyBytes::new(py, key.as_bytes()),
307 307 PyBytes::new(py, value.as_bytes()),
308 308 )?;
309 309 }
310 310 Ok(dict)
311 311 }
312 312
313 313 def copymapgetitem(&self, key: PyObject) -> PyResult<PyBytes> {
314 314 let key = key.extract::<PyBytes>(py)?;
315 315 match self
316 316 .inner(py)
317 317 .borrow()
318 318 .copy_map_get(HgPath::new(key.data(py)))
319 319 .map_err(|e| v2_error(py, e))?
320 320 {
321 321 Some(copy) => Ok(PyBytes::new(py, copy.as_bytes())),
322 322 None => Err(PyErr::new::<exc::KeyError, _>(
323 323 py,
324 324 String::from_utf8_lossy(key.data(py)),
325 325 )),
326 326 }
327 327 }
328 328 def copymap(&self) -> PyResult<CopyMap> {
329 329 CopyMap::from_inner(py, self.clone_ref(py))
330 330 }
331 331
332 332 def copymaplen(&self) -> PyResult<usize> {
333 333 Ok(self.inner(py).borrow().copy_map_len())
334 334 }
335 335 def copymapcontains(&self, key: PyObject) -> PyResult<bool> {
336 336 let key = key.extract::<PyBytes>(py)?;
337 337 self.inner(py)
338 338 .borrow()
339 339 .copy_map_contains_key(HgPath::new(key.data(py)))
340 340 .map_err(|e| v2_error(py, e))
341 341 }
342 342 def copymapget(
343 343 &self,
344 344 key: PyObject,
345 345 default: Option<PyObject>
346 346 ) -> PyResult<Option<PyObject>> {
347 347 let key = key.extract::<PyBytes>(py)?;
348 348 match self
349 349 .inner(py)
350 350 .borrow()
351 351 .copy_map_get(HgPath::new(key.data(py)))
352 352 .map_err(|e| v2_error(py, e))?
353 353 {
354 354 Some(copy) => Ok(Some(
355 355 PyBytes::new(py, copy.as_bytes()).into_object(),
356 356 )),
357 357 None => Ok(default),
358 358 }
359 359 }
360 360 def copymapsetitem(
361 361 &self,
362 362 key: PyObject,
363 363 value: PyObject
364 364 ) -> PyResult<PyObject> {
365 365 let key = key.extract::<PyBytes>(py)?;
366 366 let value = value.extract::<PyBytes>(py)?;
367 367 self.inner(py)
368 368 .borrow_mut()
369 369 .copy_map_insert(
370 370 HgPathBuf::from_bytes(key.data(py)),
371 371 HgPathBuf::from_bytes(value.data(py)),
372 372 )
373 373 .map_err(|e| v2_error(py, e))?;
374 374 Ok(py.None())
375 375 }
376 376 def copymappop(
377 377 &self,
378 378 key: PyObject,
379 379 default: Option<PyObject>
380 380 ) -> PyResult<Option<PyObject>> {
381 381 let key = key.extract::<PyBytes>(py)?;
382 382 match self
383 383 .inner(py)
384 384 .borrow_mut()
385 385 .copy_map_remove(HgPath::new(key.data(py)))
386 386 .map_err(|e| v2_error(py, e))?
387 387 {
388 388 Some(copy) => Ok(Some(
389 389 PyBytes::new(py, copy.as_bytes()).into_object(),
390 390 )),
391 391 None => Ok(default),
392 392 }
393 393 }
394 394
395 395 def copymapiter(&self) -> PyResult<CopyMapKeysIterator> {
396 396 let leaked_ref = self.inner(py).leak_immutable();
397 397 CopyMapKeysIterator::from_inner(
398 398 py,
399 399 unsafe { leaked_ref.map(py, |o| o.copy_map_iter()) },
400 400 )
401 401 }
402 402
403 403 def copymapitemsiter(&self) -> PyResult<CopyMapItemsIterator> {
404 404 let leaked_ref = self.inner(py).leak_immutable();
405 405 CopyMapItemsIterator::from_inner(
406 406 py,
407 407 unsafe { leaked_ref.map(py, |o| o.copy_map_iter()) },
408 408 )
409 409 }
410 410
411 411 def tracked_dirs(&self) -> PyResult<PyList> {
412 412 let dirs = PyList::new(py, &[]);
413 413 for path in self.inner(py).borrow_mut().iter_tracked_dirs()
414 414 .map_err(|e |dirstate_error(py, e))?
415 415 {
416 416 let path = path.map_err(|e| v2_error(py, e))?;
417 417 let path = PyBytes::new(py, path.as_bytes());
418 418 dirs.append(py, path.into_object())
419 419 }
420 420 Ok(dirs)
421 421 }
422 422
423 423 def debug_iter(&self, all: bool) -> PyResult<PyList> {
424 424 let dirs = PyList::new(py, &[]);
425 425 for item in self.inner(py).borrow().debug_iter(all) {
426 426 let (path, (state, mode, size, mtime)) =
427 427 item.map_err(|e| v2_error(py, e))?;
428 428 let path = PyBytes::new(py, path.as_bytes());
429 429 let item = (path, state, mode, size, mtime);
430 430 dirs.append(py, item.to_py_object(py).into_object())
431 431 }
432 432 Ok(dirs)
433 433 }
434 434 });
435 435
436 436 impl DirstateMap {
437 437 pub fn get_inner_mut<'a>(
438 438 &'a self,
439 439 py: Python<'a>,
440 440 ) -> RefMut<'a, OwningDirstateMap> {
441 441 self.inner(py).borrow_mut()
442 442 }
443 443 fn translate_key(
444 444 py: Python,
445 445 res: Result<(&HgPath, DirstateEntry), DirstateV2ParseError>,
446 446 ) -> PyResult<Option<PyBytes>> {
447 447 let (f, _entry) = res.map_err(|e| v2_error(py, e))?;
448 448 Ok(Some(PyBytes::new(py, f.as_bytes())))
449 449 }
450 450 fn translate_key_value(
451 451 py: Python,
452 452 res: Result<(&HgPath, DirstateEntry), DirstateV2ParseError>,
453 453 ) -> PyResult<Option<(PyBytes, PyObject)>> {
454 454 let (f, entry) = res.map_err(|e| v2_error(py, e))?;
455 455 Ok(Some((
456 456 PyBytes::new(py, f.as_bytes()),
457 457 DirstateItem::new_as_pyobject(py, entry)?,
458 458 )))
459 459 }
460 460 }
461 461
462 462 py_shared_iterator!(
463 463 DirstateMapKeysIterator,
464 464 UnsafePyLeaked<StateMapIter<'static>>,
465 465 DirstateMap::translate_key,
466 466 Option<PyBytes>
467 467 );
468 468
469 469 py_shared_iterator!(
470 470 DirstateMapItemsIterator,
471 471 UnsafePyLeaked<StateMapIter<'static>>,
472 472 DirstateMap::translate_key_value,
473 473 Option<(PyBytes, PyObject)>
474 474 );
475 475
476 476 fn extract_node_id(py: Python, obj: &PyObject) -> PyResult<Node> {
477 477 let bytes = obj.extract::<PyBytes>(py)?;
478 478 match bytes.data(py).try_into() {
479 479 Ok(s) => Ok(s),
480 480 Err(e) => Err(PyErr::new::<exc::ValueError, _>(py, e.to_string())),
481 481 }
482 482 }
483 483
484 484 pub(super) fn v2_error(py: Python<'_>, _: DirstateV2ParseError) -> PyErr {
485 485 PyErr::new::<exc::ValueError, _>(py, "corrupted dirstate-v2")
486 486 }
487 487
488 488 fn dirstate_error(py: Python<'_>, e: DirstateError) -> PyErr {
489 489 PyErr::new::<exc::OSError, _>(py, format!("Dirstate error: {:?}", e))
490 490 }
General Comments 0
You need to be logged in to leave comments. Login now