##// END OF EJS Templates
rhg: Add lazy/cached dirstate data file ID parsing on Repo...
Simon Sapin -
r49248:c7c23bb0 default
parent child Browse files
Show More
@@ -1,791 +1,791 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 std::borrow::Cow;
18 18 use std::convert::{TryFrom, TryInto};
19 19
20 20 /// Added at the start of `.hg/dirstate` when the "v2" format is used.
21 21 /// This a redundant sanity check more than an actual "magic number" since
22 22 /// `.hg/requires` already governs which format should be used.
23 23 pub const V2_FORMAT_MARKER: &[u8; 12] = b"dirstate-v2\n";
24 24
25 25 /// Keep space for 256-bit hashes
26 26 const STORED_NODE_ID_BYTES: usize = 32;
27 27
28 28 /// … even though only 160 bits are used for now, with SHA-1
29 29 const USED_NODE_ID_BYTES: usize = 20;
30 30
31 31 pub(super) const IGNORE_PATTERNS_HASH_LEN: usize = 20;
32 32 pub(super) type IgnorePatternsHash = [u8; IGNORE_PATTERNS_HASH_LEN];
33 33
34 34 /// Must match constants of the same names in `mercurial/dirstateutils/v2.py`
35 35 const TREE_METADATA_SIZE: usize = 44;
36 36 const NODE_SIZE: usize = 44;
37 37
38 38 /// Make sure that size-affecting changes are made knowingly
39 39 #[allow(unused)]
40 40 fn static_assert_size_of() {
41 41 let _ = std::mem::transmute::<TreeMetadata, [u8; TREE_METADATA_SIZE]>;
42 42 let _ = std::mem::transmute::<DocketHeader, [u8; TREE_METADATA_SIZE + 81]>;
43 43 let _ = std::mem::transmute::<Node, [u8; NODE_SIZE]>;
44 44 }
45 45
46 46 // Must match `HEADER` in `mercurial/dirstateutils/docket.py`
47 47 #[derive(BytesCast)]
48 48 #[repr(C)]
49 49 struct DocketHeader {
50 50 marker: [u8; V2_FORMAT_MARKER.len()],
51 51 parent_1: [u8; STORED_NODE_ID_BYTES],
52 52 parent_2: [u8; STORED_NODE_ID_BYTES],
53 53
54 54 metadata: TreeMetadata,
55 55
56 56 /// Counted in bytes
57 57 data_size: Size,
58 58
59 59 uuid_size: u8,
60 60 }
61 61
62 62 pub struct Docket<'on_disk> {
63 63 header: &'on_disk DocketHeader,
64 uuid: &'on_disk [u8],
64 pub uuid: &'on_disk [u8],
65 65 }
66 66
67 67 /// Fields are documented in the *Tree metadata in the docket file*
68 68 /// section of `mercurial/helptext/internals/dirstate-v2.txt`
69 69 #[derive(BytesCast)]
70 70 #[repr(C)]
71 71 struct TreeMetadata {
72 72 root_nodes: ChildNodes,
73 73 nodes_with_entry_count: Size,
74 74 nodes_with_copy_source_count: Size,
75 75 unreachable_bytes: Size,
76 76 unused: [u8; 4],
77 77
78 78 /// See *Optional hash of ignore patterns* section of
79 79 /// `mercurial/helptext/internals/dirstate-v2.txt`
80 80 ignore_patterns_hash: IgnorePatternsHash,
81 81 }
82 82
83 83 /// Fields are documented in the *The data file format*
84 84 /// section of `mercurial/helptext/internals/dirstate-v2.txt`
85 85 #[derive(BytesCast)]
86 86 #[repr(C)]
87 87 pub(super) struct Node {
88 88 full_path: PathSlice,
89 89
90 90 /// In bytes from `self.full_path.start`
91 91 base_name_start: PathSize,
92 92
93 93 copy_source: OptPathSlice,
94 94 children: ChildNodes,
95 95 pub(super) descendants_with_entry_count: Size,
96 96 pub(super) tracked_descendants_count: Size,
97 97 flags: U16Be,
98 98 size: U32Be,
99 99 mtime: PackedTruncatedTimestamp,
100 100 }
101 101
102 102 bitflags! {
103 103 #[repr(C)]
104 104 struct Flags: u16 {
105 105 const WDIR_TRACKED = 1 << 0;
106 106 const P1_TRACKED = 1 << 1;
107 107 const P2_INFO = 1 << 2;
108 108 const MODE_EXEC_PERM = 1 << 3;
109 109 const MODE_IS_SYMLINK = 1 << 4;
110 110 const HAS_FALLBACK_EXEC = 1 << 5;
111 111 const FALLBACK_EXEC = 1 << 6;
112 112 const HAS_FALLBACK_SYMLINK = 1 << 7;
113 113 const FALLBACK_SYMLINK = 1 << 8;
114 114 const EXPECTED_STATE_IS_MODIFIED = 1 << 9;
115 115 const HAS_MODE_AND_SIZE = 1 <<10;
116 116 const HAS_MTIME = 1 <<11;
117 117 const MTIME_SECOND_AMBIGUOUS = 1 << 12;
118 118 const DIRECTORY = 1 <<13;
119 119 const ALL_UNKNOWN_RECORDED = 1 <<14;
120 120 const ALL_IGNORED_RECORDED = 1 <<15;
121 121 }
122 122 }
123 123
124 124 /// Duration since the Unix epoch
125 125 #[derive(BytesCast, Copy, Clone)]
126 126 #[repr(C)]
127 127 struct PackedTruncatedTimestamp {
128 128 truncated_seconds: U32Be,
129 129 nanoseconds: U32Be,
130 130 }
131 131
132 132 /// Counted in bytes from the start of the file
133 133 ///
134 134 /// NOTE: not supporting `.hg/dirstate` files larger than 4 GiB.
135 135 type Offset = U32Be;
136 136
137 137 /// Counted in number of items
138 138 ///
139 139 /// NOTE: we choose not to support counting more than 4 billion nodes anywhere.
140 140 type Size = U32Be;
141 141
142 142 /// Counted in bytes
143 143 ///
144 144 /// NOTE: we choose not to support file names/paths longer than 64 KiB.
145 145 type PathSize = U16Be;
146 146
147 147 /// A contiguous sequence of `len` times `Node`, representing the child nodes
148 148 /// of either some other node or of the repository root.
149 149 ///
150 150 /// Always sorted by ascending `full_path`, to allow binary search.
151 151 /// Since nodes with the same parent nodes also have the same parent path,
152 152 /// only the `base_name`s need to be compared during binary search.
153 153 #[derive(BytesCast, Copy, Clone)]
154 154 #[repr(C)]
155 155 struct ChildNodes {
156 156 start: Offset,
157 157 len: Size,
158 158 }
159 159
160 160 /// A `HgPath` of `len` bytes
161 161 #[derive(BytesCast, Copy, Clone)]
162 162 #[repr(C)]
163 163 struct PathSlice {
164 164 start: Offset,
165 165 len: PathSize,
166 166 }
167 167
168 168 /// Either nothing if `start == 0`, or a `HgPath` of `len` bytes
169 169 type OptPathSlice = PathSlice;
170 170
171 171 /// Unexpected file format found in `.hg/dirstate` with the "v2" format.
172 172 ///
173 173 /// This should only happen if Mercurial is buggy or a repository is corrupted.
174 174 #[derive(Debug)]
175 175 pub struct DirstateV2ParseError;
176 176
177 177 impl From<DirstateV2ParseError> for HgError {
178 178 fn from(_: DirstateV2ParseError) -> Self {
179 179 HgError::corrupted("dirstate-v2 parse error")
180 180 }
181 181 }
182 182
183 183 impl From<DirstateV2ParseError> for crate::DirstateError {
184 184 fn from(error: DirstateV2ParseError) -> Self {
185 185 HgError::from(error).into()
186 186 }
187 187 }
188 188
189 189 impl<'on_disk> Docket<'on_disk> {
190 190 pub fn parents(&self) -> DirstateParents {
191 191 use crate::Node;
192 192 let p1 = Node::try_from(&self.header.parent_1[..USED_NODE_ID_BYTES])
193 193 .unwrap()
194 194 .clone();
195 195 let p2 = Node::try_from(&self.header.parent_2[..USED_NODE_ID_BYTES])
196 196 .unwrap()
197 197 .clone();
198 198 DirstateParents { p1, p2 }
199 199 }
200 200
201 201 pub fn tree_metadata(&self) -> &[u8] {
202 202 self.header.metadata.as_bytes()
203 203 }
204 204
205 205 pub fn data_size(&self) -> usize {
206 206 // This `unwrap` could only panic on a 16-bit CPU
207 207 self.header.data_size.get().try_into().unwrap()
208 208 }
209 209
210 210 pub fn data_filename(&self) -> String {
211 211 String::from_utf8(format_bytes!(b"dirstate.{}", self.uuid)).unwrap()
212 212 }
213 213 }
214 214
215 215 pub fn read_docket(
216 216 on_disk: &[u8],
217 217 ) -> Result<Docket<'_>, DirstateV2ParseError> {
218 218 let (header, uuid) =
219 219 DocketHeader::from_bytes(on_disk).map_err(|_| DirstateV2ParseError)?;
220 220 let uuid_size = header.uuid_size as usize;
221 221 if header.marker == *V2_FORMAT_MARKER && uuid.len() == uuid_size {
222 222 Ok(Docket { header, uuid })
223 223 } else {
224 224 Err(DirstateV2ParseError)
225 225 }
226 226 }
227 227
228 228 pub(super) fn read<'on_disk>(
229 229 on_disk: &'on_disk [u8],
230 230 metadata: &[u8],
231 231 ) -> Result<DirstateMap<'on_disk>, DirstateV2ParseError> {
232 232 if on_disk.is_empty() {
233 233 return Ok(DirstateMap::empty(on_disk));
234 234 }
235 235 let (meta, _) = TreeMetadata::from_bytes(metadata)
236 236 .map_err(|_| DirstateV2ParseError)?;
237 237 let dirstate_map = DirstateMap {
238 238 on_disk,
239 239 root: dirstate_map::ChildNodes::OnDisk(read_nodes(
240 240 on_disk,
241 241 meta.root_nodes,
242 242 )?),
243 243 nodes_with_entry_count: meta.nodes_with_entry_count.get(),
244 244 nodes_with_copy_source_count: meta.nodes_with_copy_source_count.get(),
245 245 ignore_patterns_hash: meta.ignore_patterns_hash,
246 246 unreachable_bytes: meta.unreachable_bytes.get(),
247 247 };
248 248 Ok(dirstate_map)
249 249 }
250 250
251 251 impl Node {
252 252 pub(super) fn full_path<'on_disk>(
253 253 &self,
254 254 on_disk: &'on_disk [u8],
255 255 ) -> Result<&'on_disk HgPath, DirstateV2ParseError> {
256 256 read_hg_path(on_disk, self.full_path)
257 257 }
258 258
259 259 pub(super) fn base_name_start<'on_disk>(
260 260 &self,
261 261 ) -> Result<usize, DirstateV2ParseError> {
262 262 let start = self.base_name_start.get();
263 263 if start < self.full_path.len.get() {
264 264 let start = usize::try_from(start)
265 265 // u32 -> usize, could only panic on a 16-bit CPU
266 266 .expect("dirstate-v2 base_name_start out of bounds");
267 267 Ok(start)
268 268 } else {
269 269 Err(DirstateV2ParseError)
270 270 }
271 271 }
272 272
273 273 pub(super) fn base_name<'on_disk>(
274 274 &self,
275 275 on_disk: &'on_disk [u8],
276 276 ) -> Result<&'on_disk HgPath, DirstateV2ParseError> {
277 277 let full_path = self.full_path(on_disk)?;
278 278 let base_name_start = self.base_name_start()?;
279 279 Ok(HgPath::new(&full_path.as_bytes()[base_name_start..]))
280 280 }
281 281
282 282 pub(super) fn path<'on_disk>(
283 283 &self,
284 284 on_disk: &'on_disk [u8],
285 285 ) -> Result<dirstate_map::NodeKey<'on_disk>, DirstateV2ParseError> {
286 286 Ok(WithBasename::from_raw_parts(
287 287 Cow::Borrowed(self.full_path(on_disk)?),
288 288 self.base_name_start()?,
289 289 ))
290 290 }
291 291
292 292 pub(super) fn has_copy_source<'on_disk>(&self) -> bool {
293 293 self.copy_source.start.get() != 0
294 294 }
295 295
296 296 pub(super) fn copy_source<'on_disk>(
297 297 &self,
298 298 on_disk: &'on_disk [u8],
299 299 ) -> Result<Option<&'on_disk HgPath>, DirstateV2ParseError> {
300 300 Ok(if self.has_copy_source() {
301 301 Some(read_hg_path(on_disk, self.copy_source)?)
302 302 } else {
303 303 None
304 304 })
305 305 }
306 306
307 307 fn flags(&self) -> Flags {
308 308 Flags::from_bits_truncate(self.flags.get())
309 309 }
310 310
311 311 fn has_entry(&self) -> bool {
312 312 self.flags().intersects(
313 313 Flags::WDIR_TRACKED | Flags::P1_TRACKED | Flags::P2_INFO,
314 314 )
315 315 }
316 316
317 317 pub(super) fn node_data(
318 318 &self,
319 319 ) -> Result<dirstate_map::NodeData, DirstateV2ParseError> {
320 320 if self.has_entry() {
321 321 Ok(dirstate_map::NodeData::Entry(self.assume_entry()?))
322 322 } else if let Some(mtime) = self.cached_directory_mtime()? {
323 323 Ok(dirstate_map::NodeData::CachedDirectory { mtime })
324 324 } else {
325 325 Ok(dirstate_map::NodeData::None)
326 326 }
327 327 }
328 328
329 329 pub(super) fn cached_directory_mtime(
330 330 &self,
331 331 ) -> Result<Option<TruncatedTimestamp>, DirstateV2ParseError> {
332 332 // For now we do not have code to handle the absence of
333 333 // ALL_UNKNOWN_RECORDED, so we ignore the mtime if the flag is
334 334 // unset.
335 335 if self.flags().contains(Flags::DIRECTORY)
336 336 && self.flags().contains(Flags::HAS_MTIME)
337 337 && self.flags().contains(Flags::ALL_UNKNOWN_RECORDED)
338 338 {
339 339 Ok(Some(self.mtime.try_into()?))
340 340 } else {
341 341 Ok(None)
342 342 }
343 343 }
344 344
345 345 fn synthesize_unix_mode(&self) -> u32 {
346 346 let file_type = if self.flags().contains(Flags::MODE_IS_SYMLINK) {
347 347 libc::S_IFLNK
348 348 } else {
349 349 libc::S_IFREG
350 350 };
351 351 let permisions = if self.flags().contains(Flags::MODE_EXEC_PERM) {
352 352 0o755
353 353 } else {
354 354 0o644
355 355 };
356 356 file_type | permisions
357 357 }
358 358
359 359 fn assume_entry(&self) -> Result<DirstateEntry, DirstateV2ParseError> {
360 360 // TODO: convert through raw bits instead?
361 361 let wdir_tracked = self.flags().contains(Flags::WDIR_TRACKED);
362 362 let p1_tracked = self.flags().contains(Flags::P1_TRACKED);
363 363 let p2_info = self.flags().contains(Flags::P2_INFO);
364 364 let mode_size = if self.flags().contains(Flags::HAS_MODE_AND_SIZE)
365 365 && !self.flags().contains(Flags::EXPECTED_STATE_IS_MODIFIED)
366 366 {
367 367 Some((self.synthesize_unix_mode(), self.size.into()))
368 368 } else {
369 369 None
370 370 };
371 371 let mtime = if self.flags().contains(Flags::HAS_MTIME)
372 372 && !self.flags().contains(Flags::DIRECTORY)
373 373 && !self.flags().contains(Flags::EXPECTED_STATE_IS_MODIFIED)
374 374 {
375 375 let mut m: TruncatedTimestamp = self.mtime.try_into()?;
376 376 if self.flags().contains(Flags::MTIME_SECOND_AMBIGUOUS) {
377 377 m.second_ambiguous = true;
378 378 }
379 379 Some(m)
380 380 } else {
381 381 None
382 382 };
383 383 let fallback_exec = if self.flags().contains(Flags::HAS_FALLBACK_EXEC)
384 384 {
385 385 Some(self.flags().contains(Flags::FALLBACK_EXEC))
386 386 } else {
387 387 None
388 388 };
389 389 let fallback_symlink =
390 390 if self.flags().contains(Flags::HAS_FALLBACK_SYMLINK) {
391 391 Some(self.flags().contains(Flags::FALLBACK_SYMLINK))
392 392 } else {
393 393 None
394 394 };
395 395 Ok(DirstateEntry::from_v2_data(
396 396 wdir_tracked,
397 397 p1_tracked,
398 398 p2_info,
399 399 mode_size,
400 400 mtime,
401 401 fallback_exec,
402 402 fallback_symlink,
403 403 ))
404 404 }
405 405
406 406 pub(super) fn entry(
407 407 &self,
408 408 ) -> Result<Option<DirstateEntry>, DirstateV2ParseError> {
409 409 if self.has_entry() {
410 410 Ok(Some(self.assume_entry()?))
411 411 } else {
412 412 Ok(None)
413 413 }
414 414 }
415 415
416 416 pub(super) fn children<'on_disk>(
417 417 &self,
418 418 on_disk: &'on_disk [u8],
419 419 ) -> Result<&'on_disk [Node], DirstateV2ParseError> {
420 420 read_nodes(on_disk, self.children)
421 421 }
422 422
423 423 pub(super) fn to_in_memory_node<'on_disk>(
424 424 &self,
425 425 on_disk: &'on_disk [u8],
426 426 ) -> Result<dirstate_map::Node<'on_disk>, DirstateV2ParseError> {
427 427 Ok(dirstate_map::Node {
428 428 children: dirstate_map::ChildNodes::OnDisk(
429 429 self.children(on_disk)?,
430 430 ),
431 431 copy_source: self.copy_source(on_disk)?.map(Cow::Borrowed),
432 432 data: self.node_data()?,
433 433 descendants_with_entry_count: self
434 434 .descendants_with_entry_count
435 435 .get(),
436 436 tracked_descendants_count: self.tracked_descendants_count.get(),
437 437 })
438 438 }
439 439
440 440 fn from_dirstate_entry(
441 441 entry: &DirstateEntry,
442 442 ) -> (Flags, U32Be, PackedTruncatedTimestamp) {
443 443 let (
444 444 wdir_tracked,
445 445 p1_tracked,
446 446 p2_info,
447 447 mode_size_opt,
448 448 mtime_opt,
449 449 fallback_exec,
450 450 fallback_symlink,
451 451 ) = entry.v2_data();
452 452 // TODO: convert throug raw flag bits instead?
453 453 let mut flags = Flags::empty();
454 454 flags.set(Flags::WDIR_TRACKED, wdir_tracked);
455 455 flags.set(Flags::P1_TRACKED, p1_tracked);
456 456 flags.set(Flags::P2_INFO, p2_info);
457 457 let size = if let Some((m, s)) = mode_size_opt {
458 458 let exec_perm = m & libc::S_IXUSR != 0;
459 459 let is_symlink = m & libc::S_IFMT == libc::S_IFLNK;
460 460 flags.set(Flags::MODE_EXEC_PERM, exec_perm);
461 461 flags.set(Flags::MODE_IS_SYMLINK, is_symlink);
462 462 flags.insert(Flags::HAS_MODE_AND_SIZE);
463 463 s.into()
464 464 } else {
465 465 0.into()
466 466 };
467 467 let mtime = if let Some(m) = mtime_opt {
468 468 flags.insert(Flags::HAS_MTIME);
469 469 if m.second_ambiguous {
470 470 flags.insert(Flags::MTIME_SECOND_AMBIGUOUS);
471 471 };
472 472 m.into()
473 473 } else {
474 474 PackedTruncatedTimestamp::null()
475 475 };
476 476 if let Some(f_exec) = fallback_exec {
477 477 flags.insert(Flags::HAS_FALLBACK_EXEC);
478 478 if f_exec {
479 479 flags.insert(Flags::FALLBACK_EXEC);
480 480 }
481 481 }
482 482 if let Some(f_symlink) = fallback_symlink {
483 483 flags.insert(Flags::HAS_FALLBACK_SYMLINK);
484 484 if f_symlink {
485 485 flags.insert(Flags::FALLBACK_SYMLINK);
486 486 }
487 487 }
488 488 (flags, size, mtime)
489 489 }
490 490 }
491 491
492 492 fn read_hg_path(
493 493 on_disk: &[u8],
494 494 slice: PathSlice,
495 495 ) -> Result<&HgPath, DirstateV2ParseError> {
496 496 read_slice(on_disk, slice.start, slice.len.get()).map(HgPath::new)
497 497 }
498 498
499 499 fn read_nodes(
500 500 on_disk: &[u8],
501 501 slice: ChildNodes,
502 502 ) -> Result<&[Node], DirstateV2ParseError> {
503 503 read_slice(on_disk, slice.start, slice.len.get())
504 504 }
505 505
506 506 fn read_slice<T, Len>(
507 507 on_disk: &[u8],
508 508 start: Offset,
509 509 len: Len,
510 510 ) -> Result<&[T], DirstateV2ParseError>
511 511 where
512 512 T: BytesCast,
513 513 Len: TryInto<usize>,
514 514 {
515 515 // Either `usize::MAX` would result in "out of bounds" error since a single
516 516 // `&[u8]` cannot occupy the entire addess space.
517 517 let start = start.get().try_into().unwrap_or(std::usize::MAX);
518 518 let len = len.try_into().unwrap_or(std::usize::MAX);
519 519 on_disk
520 520 .get(start..)
521 521 .and_then(|bytes| T::slice_from_bytes(bytes, len).ok())
522 522 .map(|(slice, _rest)| slice)
523 523 .ok_or_else(|| DirstateV2ParseError)
524 524 }
525 525
526 526 pub(crate) fn for_each_tracked_path<'on_disk>(
527 527 on_disk: &'on_disk [u8],
528 528 metadata: &[u8],
529 529 mut f: impl FnMut(&'on_disk HgPath),
530 530 ) -> Result<(), DirstateV2ParseError> {
531 531 let (meta, _) = TreeMetadata::from_bytes(metadata)
532 532 .map_err(|_| DirstateV2ParseError)?;
533 533 fn recur<'on_disk>(
534 534 on_disk: &'on_disk [u8],
535 535 nodes: ChildNodes,
536 536 f: &mut impl FnMut(&'on_disk HgPath),
537 537 ) -> Result<(), DirstateV2ParseError> {
538 538 for node in read_nodes(on_disk, nodes)? {
539 539 if let Some(entry) = node.entry()? {
540 540 if entry.state().is_tracked() {
541 541 f(node.full_path(on_disk)?)
542 542 }
543 543 }
544 544 recur(on_disk, node.children, f)?
545 545 }
546 546 Ok(())
547 547 }
548 548 recur(on_disk, meta.root_nodes, &mut f)
549 549 }
550 550
551 551 /// Returns new data and metadata, together with whether that data should be
552 552 /// appended to the existing data file whose content is at
553 553 /// `dirstate_map.on_disk` (true), instead of written to a new data file
554 554 /// (false).
555 555 pub(super) fn write(
556 556 dirstate_map: &DirstateMap,
557 557 can_append: bool,
558 558 ) -> Result<(Vec<u8>, Vec<u8>, bool), DirstateError> {
559 559 let append = can_append && dirstate_map.write_should_append();
560 560
561 561 // This ignores the space for paths, and for nodes without an entry.
562 562 // TODO: better estimate? Skip the `Vec` and write to a file directly?
563 563 let size_guess = std::mem::size_of::<Node>()
564 564 * dirstate_map.nodes_with_entry_count as usize;
565 565
566 566 let mut writer = Writer {
567 567 dirstate_map,
568 568 append,
569 569 out: Vec::with_capacity(size_guess),
570 570 };
571 571
572 572 let root_nodes = writer.write_nodes(dirstate_map.root.as_ref())?;
573 573
574 574 let meta = TreeMetadata {
575 575 root_nodes,
576 576 nodes_with_entry_count: dirstate_map.nodes_with_entry_count.into(),
577 577 nodes_with_copy_source_count: dirstate_map
578 578 .nodes_with_copy_source_count
579 579 .into(),
580 580 unreachable_bytes: dirstate_map.unreachable_bytes.into(),
581 581 unused: [0; 4],
582 582 ignore_patterns_hash: dirstate_map.ignore_patterns_hash,
583 583 };
584 584 Ok((writer.out, meta.as_bytes().to_vec(), append))
585 585 }
586 586
587 587 struct Writer<'dmap, 'on_disk> {
588 588 dirstate_map: &'dmap DirstateMap<'on_disk>,
589 589 append: bool,
590 590 out: Vec<u8>,
591 591 }
592 592
593 593 impl Writer<'_, '_> {
594 594 fn write_nodes(
595 595 &mut self,
596 596 nodes: dirstate_map::ChildNodesRef,
597 597 ) -> Result<ChildNodes, DirstateError> {
598 598 // Reuse already-written nodes if possible
599 599 if self.append {
600 600 if let dirstate_map::ChildNodesRef::OnDisk(nodes_slice) = nodes {
601 601 let start = self.on_disk_offset_of(nodes_slice).expect(
602 602 "dirstate-v2 OnDisk nodes not found within on_disk",
603 603 );
604 604 let len = child_nodes_len_from_usize(nodes_slice.len());
605 605 return Ok(ChildNodes { start, len });
606 606 }
607 607 }
608 608
609 609 // `dirstate_map::ChildNodes::InMemory` contains a `HashMap` which has
610 610 // undefined iteration order. Sort to enable binary search in the
611 611 // written file.
612 612 let nodes = nodes.sorted();
613 613 let nodes_len = nodes.len();
614 614
615 615 // First accumulate serialized nodes in a `Vec`
616 616 let mut on_disk_nodes = Vec::with_capacity(nodes_len);
617 617 for node in nodes {
618 618 let children =
619 619 self.write_nodes(node.children(self.dirstate_map.on_disk)?)?;
620 620 let full_path = node.full_path(self.dirstate_map.on_disk)?;
621 621 let full_path = self.write_path(full_path.as_bytes());
622 622 let copy_source = if let Some(source) =
623 623 node.copy_source(self.dirstate_map.on_disk)?
624 624 {
625 625 self.write_path(source.as_bytes())
626 626 } else {
627 627 PathSlice {
628 628 start: 0.into(),
629 629 len: 0.into(),
630 630 }
631 631 };
632 632 on_disk_nodes.push(match node {
633 633 NodeRef::InMemory(path, node) => {
634 634 let (flags, size, mtime) = match &node.data {
635 635 dirstate_map::NodeData::Entry(entry) => {
636 636 Node::from_dirstate_entry(entry)
637 637 }
638 638 dirstate_map::NodeData::CachedDirectory { mtime } => (
639 639 // we currently never set a mtime if unknown file
640 640 // are present.
641 641 // So if we have a mtime for a directory, we know
642 642 // they are no unknown
643 643 // files and we
644 644 // blindly set ALL_UNKNOWN_RECORDED.
645 645 //
646 646 // We never set ALL_IGNORED_RECORDED since we
647 647 // don't track that case
648 648 // currently.
649 649 Flags::DIRECTORY
650 650 | Flags::HAS_MTIME
651 651 | Flags::ALL_UNKNOWN_RECORDED,
652 652 0.into(),
653 653 (*mtime).into(),
654 654 ),
655 655 dirstate_map::NodeData::None => (
656 656 Flags::DIRECTORY,
657 657 0.into(),
658 658 PackedTruncatedTimestamp::null(),
659 659 ),
660 660 };
661 661 Node {
662 662 children,
663 663 copy_source,
664 664 full_path,
665 665 base_name_start: u16::try_from(path.base_name_start())
666 666 // Could only panic for paths over 64 KiB
667 667 .expect("dirstate-v2 path length overflow")
668 668 .into(),
669 669 descendants_with_entry_count: node
670 670 .descendants_with_entry_count
671 671 .into(),
672 672 tracked_descendants_count: node
673 673 .tracked_descendants_count
674 674 .into(),
675 675 flags: flags.bits().into(),
676 676 size,
677 677 mtime,
678 678 }
679 679 }
680 680 NodeRef::OnDisk(node) => Node {
681 681 children,
682 682 copy_source,
683 683 full_path,
684 684 ..*node
685 685 },
686 686 })
687 687 }
688 688 // … so we can write them contiguously, after writing everything else
689 689 // they refer to.
690 690 let start = self.current_offset();
691 691 let len = child_nodes_len_from_usize(nodes_len);
692 692 self.out.extend(on_disk_nodes.as_bytes());
693 693 Ok(ChildNodes { start, len })
694 694 }
695 695
696 696 /// If the given slice of items is within `on_disk`, returns its offset
697 697 /// from the start of `on_disk`.
698 698 fn on_disk_offset_of<T>(&self, slice: &[T]) -> Option<Offset>
699 699 where
700 700 T: BytesCast,
701 701 {
702 702 fn address_range(slice: &[u8]) -> std::ops::RangeInclusive<usize> {
703 703 let start = slice.as_ptr() as usize;
704 704 let end = start + slice.len();
705 705 start..=end
706 706 }
707 707 let slice_addresses = address_range(slice.as_bytes());
708 708 let on_disk_addresses = address_range(self.dirstate_map.on_disk);
709 709 if on_disk_addresses.contains(slice_addresses.start())
710 710 && on_disk_addresses.contains(slice_addresses.end())
711 711 {
712 712 let offset = slice_addresses.start() - on_disk_addresses.start();
713 713 Some(offset_from_usize(offset))
714 714 } else {
715 715 None
716 716 }
717 717 }
718 718
719 719 fn current_offset(&mut self) -> Offset {
720 720 let mut offset = self.out.len();
721 721 if self.append {
722 722 offset += self.dirstate_map.on_disk.len()
723 723 }
724 724 offset_from_usize(offset)
725 725 }
726 726
727 727 fn write_path(&mut self, slice: &[u8]) -> PathSlice {
728 728 let len = path_len_from_usize(slice.len());
729 729 // Reuse an already-written path if possible
730 730 if self.append {
731 731 if let Some(start) = self.on_disk_offset_of(slice) {
732 732 return PathSlice { start, len };
733 733 }
734 734 }
735 735 let start = self.current_offset();
736 736 self.out.extend(slice.as_bytes());
737 737 PathSlice { start, len }
738 738 }
739 739 }
740 740
741 741 fn offset_from_usize(x: usize) -> Offset {
742 742 u32::try_from(x)
743 743 // Could only panic for a dirstate file larger than 4 GiB
744 744 .expect("dirstate-v2 offset overflow")
745 745 .into()
746 746 }
747 747
748 748 fn child_nodes_len_from_usize(x: usize) -> Size {
749 749 u32::try_from(x)
750 750 // Could only panic with over 4 billion nodes
751 751 .expect("dirstate-v2 slice length overflow")
752 752 .into()
753 753 }
754 754
755 755 fn path_len_from_usize(x: usize) -> PathSize {
756 756 u16::try_from(x)
757 757 // Could only panic for paths over 64 KiB
758 758 .expect("dirstate-v2 path length overflow")
759 759 .into()
760 760 }
761 761
762 762 impl From<TruncatedTimestamp> for PackedTruncatedTimestamp {
763 763 fn from(timestamp: TruncatedTimestamp) -> Self {
764 764 Self {
765 765 truncated_seconds: timestamp.truncated_seconds().into(),
766 766 nanoseconds: timestamp.nanoseconds().into(),
767 767 }
768 768 }
769 769 }
770 770
771 771 impl TryFrom<PackedTruncatedTimestamp> for TruncatedTimestamp {
772 772 type Error = DirstateV2ParseError;
773 773
774 774 fn try_from(
775 775 timestamp: PackedTruncatedTimestamp,
776 776 ) -> Result<Self, Self::Error> {
777 777 Self::from_already_truncated(
778 778 timestamp.truncated_seconds.get(),
779 779 timestamp.nanoseconds.get(),
780 780 false,
781 781 )
782 782 }
783 783 }
784 784 impl PackedTruncatedTimestamp {
785 785 fn null() -> Self {
786 786 Self {
787 787 truncated_seconds: 0.into(),
788 788 nanoseconds: 0.into(),
789 789 }
790 790 }
791 791 }
@@ -1,429 +1,464 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::dirstate_map::DirstateMap;
5 5 use crate::dirstate_tree::owning::OwningDirstateMap;
6 6 use crate::errors::HgError;
7 7 use crate::errors::HgResultExt;
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::path::{Path, PathBuf};
22 22
23 23 /// A repository on disk
24 24 pub struct Repo {
25 25 working_directory: PathBuf,
26 26 dot_hg: PathBuf,
27 27 store: PathBuf,
28 28 requirements: HashSet<String>,
29 29 config: Config,
30 30 dirstate_parents: LazyCell<DirstateParents, HgError>,
31 dirstate_data_file_uuid: LazyCell<Option<Vec<u8>>, HgError>,
31 32 dirstate_map: LazyCell<OwningDirstateMap, DirstateError>,
32 33 changelog: LazyCell<Changelog, HgError>,
33 34 manifestlog: LazyCell<Manifestlog, HgError>,
34 35 }
35 36
36 37 #[derive(Debug, derive_more::From)]
37 38 pub enum RepoError {
38 39 NotFound {
39 40 at: PathBuf,
40 41 },
41 42 #[from]
42 43 ConfigParseError(ConfigParseError),
43 44 #[from]
44 45 Other(HgError),
45 46 }
46 47
47 48 impl From<ConfigError> for RepoError {
48 49 fn from(error: ConfigError) -> Self {
49 50 match error {
50 51 ConfigError::Parse(error) => error.into(),
51 52 ConfigError::Other(error) => error.into(),
52 53 }
53 54 }
54 55 }
55 56
56 57 impl Repo {
57 58 /// tries to find nearest repository root in current working directory or
58 59 /// its ancestors
59 60 pub fn find_repo_root() -> Result<PathBuf, RepoError> {
60 61 let current_directory = crate::utils::current_dir()?;
61 62 // ancestors() is inclusive: it first yields `current_directory`
62 63 // as-is.
63 64 for ancestor in current_directory.ancestors() {
64 65 if is_dir(ancestor.join(".hg"))? {
65 66 return Ok(ancestor.to_path_buf());
66 67 }
67 68 }
68 69 return Err(RepoError::NotFound {
69 70 at: current_directory,
70 71 });
71 72 }
72 73
73 74 /// Find a repository, either at the given path (which must contain a `.hg`
74 75 /// sub-directory) or by searching the current directory and its
75 76 /// ancestors.
76 77 ///
77 78 /// A method with two very different "modes" like this usually a code smell
78 79 /// to make two methods instead, but in this case an `Option` is what rhg
79 80 /// sub-commands get from Clap for the `-R` / `--repository` CLI argument.
80 81 /// Having two methods would just move that `if` to almost all callers.
81 82 pub fn find(
82 83 config: &Config,
83 84 explicit_path: Option<PathBuf>,
84 85 ) -> Result<Self, RepoError> {
85 86 if let Some(root) = explicit_path {
86 87 if is_dir(root.join(".hg"))? {
87 88 Self::new_at_path(root.to_owned(), config)
88 89 } else if is_file(&root)? {
89 90 Err(HgError::unsupported("bundle repository").into())
90 91 } else {
91 92 Err(RepoError::NotFound {
92 93 at: root.to_owned(),
93 94 })
94 95 }
95 96 } else {
96 97 let root = Self::find_repo_root()?;
97 98 Self::new_at_path(root, config)
98 99 }
99 100 }
100 101
101 102 /// To be called after checking that `.hg` is a sub-directory
102 103 fn new_at_path(
103 104 working_directory: PathBuf,
104 105 config: &Config,
105 106 ) -> Result<Self, RepoError> {
106 107 let dot_hg = working_directory.join(".hg");
107 108
108 109 let mut repo_config_files = Vec::new();
109 110 repo_config_files.push(dot_hg.join("hgrc"));
110 111 repo_config_files.push(dot_hg.join("hgrc-not-shared"));
111 112
112 113 let hg_vfs = Vfs { base: &dot_hg };
113 114 let mut reqs = requirements::load_if_exists(hg_vfs)?;
114 115 let relative =
115 116 reqs.contains(requirements::RELATIVE_SHARED_REQUIREMENT);
116 117 let shared =
117 118 reqs.contains(requirements::SHARED_REQUIREMENT) || relative;
118 119
119 120 // From `mercurial/localrepo.py`:
120 121 //
121 122 // if .hg/requires contains the sharesafe requirement, it means
122 123 // there exists a `.hg/store/requires` too and we should read it
123 124 // NOTE: presence of SHARESAFE_REQUIREMENT imply that store requirement
124 125 // is present. We never write SHARESAFE_REQUIREMENT for a repo if store
125 126 // is not present, refer checkrequirementscompat() for that
126 127 //
127 128 // However, if SHARESAFE_REQUIREMENT is not present, it means that the
128 129 // repository was shared the old way. We check the share source
129 130 // .hg/requires for SHARESAFE_REQUIREMENT to detect whether the
130 131 // current repository needs to be reshared
131 132 let share_safe = reqs.contains(requirements::SHARESAFE_REQUIREMENT);
132 133
133 134 let store_path;
134 135 if !shared {
135 136 store_path = dot_hg.join("store");
136 137 } else {
137 138 let bytes = hg_vfs.read("sharedpath")?;
138 139 let mut shared_path =
139 140 get_path_from_bytes(bytes.trim_end_matches(|b| b == b'\n'))
140 141 .to_owned();
141 142 if relative {
142 143 shared_path = dot_hg.join(shared_path)
143 144 }
144 145 if !is_dir(&shared_path)? {
145 146 return Err(HgError::corrupted(format!(
146 147 ".hg/sharedpath points to nonexistent directory {}",
147 148 shared_path.display()
148 149 ))
149 150 .into());
150 151 }
151 152
152 153 store_path = shared_path.join("store");
153 154
154 155 let source_is_share_safe =
155 156 requirements::load(Vfs { base: &shared_path })?
156 157 .contains(requirements::SHARESAFE_REQUIREMENT);
157 158
158 159 if share_safe && !source_is_share_safe {
159 160 return Err(match config
160 161 .get(b"share", b"safe-mismatch.source-not-safe")
161 162 {
162 163 Some(b"abort") | None => HgError::abort(
163 164 "abort: share source does not support share-safe requirement\n\
164 165 (see `hg help config.format.use-share-safe` for more information)",
165 166 exit_codes::ABORT,
166 167 ),
167 168 _ => HgError::unsupported("share-safe downgrade"),
168 169 }
169 170 .into());
170 171 } else if source_is_share_safe && !share_safe {
171 172 return Err(
172 173 match config.get(b"share", b"safe-mismatch.source-safe") {
173 174 Some(b"abort") | None => HgError::abort(
174 175 "abort: version mismatch: source uses share-safe \
175 176 functionality while the current share does not\n\
176 177 (see `hg help config.format.use-share-safe` for more information)",
177 178 exit_codes::ABORT,
178 179 ),
179 180 _ => HgError::unsupported("share-safe upgrade"),
180 181 }
181 182 .into(),
182 183 );
183 184 }
184 185
185 186 if share_safe {
186 187 repo_config_files.insert(0, shared_path.join("hgrc"))
187 188 }
188 189 }
189 190 if share_safe {
190 191 reqs.extend(requirements::load(Vfs { base: &store_path })?);
191 192 }
192 193
193 194 let repo_config = if std::env::var_os("HGRCSKIPREPO").is_none() {
194 195 config.combine_with_repo(&repo_config_files)?
195 196 } else {
196 197 config.clone()
197 198 };
198 199
199 200 let repo = Self {
200 201 requirements: reqs,
201 202 working_directory,
202 203 store: store_path,
203 204 dot_hg,
204 205 config: repo_config,
205 206 dirstate_parents: LazyCell::new(Self::read_dirstate_parents),
207 dirstate_data_file_uuid: LazyCell::new(
208 Self::read_dirstate_data_file_uuid,
209 ),
206 210 dirstate_map: LazyCell::new(Self::new_dirstate_map),
207 211 changelog: LazyCell::new(Changelog::open),
208 212 manifestlog: LazyCell::new(Manifestlog::open),
209 213 };
210 214
211 215 requirements::check(&repo)?;
212 216
213 217 Ok(repo)
214 218 }
215 219
216 220 pub fn working_directory_path(&self) -> &Path {
217 221 &self.working_directory
218 222 }
219 223
220 224 pub fn requirements(&self) -> &HashSet<String> {
221 225 &self.requirements
222 226 }
223 227
224 228 pub fn config(&self) -> &Config {
225 229 &self.config
226 230 }
227 231
228 232 /// For accessing repository files (in `.hg`), except for the store
229 233 /// (`.hg/store`).
230 234 pub fn hg_vfs(&self) -> Vfs<'_> {
231 235 Vfs { base: &self.dot_hg }
232 236 }
233 237
234 238 /// For accessing repository store files (in `.hg/store`)
235 239 pub fn store_vfs(&self) -> Vfs<'_> {
236 240 Vfs { base: &self.store }
237 241 }
238 242
239 243 /// For accessing the working copy
240 244 pub fn working_directory_vfs(&self) -> Vfs<'_> {
241 245 Vfs {
242 246 base: &self.working_directory,
243 247 }
244 248 }
245 249
246 250 pub fn try_with_wlock_no_wait<R>(
247 251 &self,
248 252 f: impl FnOnce() -> R,
249 253 ) -> Result<R, LockError> {
250 254 try_with_lock_no_wait(self.hg_vfs(), "wlock", f)
251 255 }
252 256
253 257 pub fn has_dirstate_v2(&self) -> bool {
254 258 self.requirements
255 259 .contains(requirements::DIRSTATE_V2_REQUIREMENT)
256 260 }
257 261
258 262 pub fn has_sparse(&self) -> bool {
259 263 self.requirements.contains(requirements::SPARSE_REQUIREMENT)
260 264 }
261 265
262 266 pub fn has_narrow(&self) -> bool {
263 267 self.requirements.contains(requirements::NARROW_REQUIREMENT)
264 268 }
265 269
266 270 fn dirstate_file_contents(&self) -> Result<Vec<u8>, HgError> {
267 271 Ok(self
268 272 .hg_vfs()
269 273 .read("dirstate")
270 274 .io_not_found_as_none()?
271 275 .unwrap_or(Vec::new()))
272 276 }
273 277
274 278 pub fn dirstate_parents(&self) -> Result<DirstateParents, HgError> {
275 279 Ok(*self.dirstate_parents.get_or_init(self)?)
276 280 }
277 281
278 282 fn read_dirstate_parents(&self) -> Result<DirstateParents, HgError> {
279 283 let dirstate = self.dirstate_file_contents()?;
280 284 let parents = if dirstate.is_empty() {
285 if self.has_dirstate_v2() {
286 self.dirstate_data_file_uuid.set(None);
287 }
281 288 DirstateParents::NULL
282 289 } else if self.has_dirstate_v2() {
283 crate::dirstate_tree::on_disk::read_docket(&dirstate)?.parents()
290 let docket =
291 crate::dirstate_tree::on_disk::read_docket(&dirstate)?;
292 self.dirstate_data_file_uuid
293 .set(Some(docket.uuid.to_owned()));
294 docket.parents()
284 295 } else {
285 296 crate::dirstate::parsers::parse_dirstate_parents(&dirstate)?
286 297 .clone()
287 298 };
288 299 self.dirstate_parents.set(parents);
289 300 Ok(parents)
290 301 }
291 302
303 fn read_dirstate_data_file_uuid(
304 &self,
305 ) -> Result<Option<Vec<u8>>, HgError> {
306 assert!(
307 self.has_dirstate_v2(),
308 "accessing dirstate data file ID without dirstate-v2"
309 );
310 let dirstate = self.dirstate_file_contents()?;
311 if dirstate.is_empty() {
312 self.dirstate_parents.set(DirstateParents::NULL);
313 Ok(None)
314 } else {
315 let docket =
316 crate::dirstate_tree::on_disk::read_docket(&dirstate)?;
317 self.dirstate_parents.set(docket.parents());
318 Ok(Some(docket.uuid.to_owned()))
319 }
320 }
321
292 322 fn new_dirstate_map(&self) -> Result<OwningDirstateMap, DirstateError> {
293 323 let dirstate_file_contents = self.dirstate_file_contents()?;
294 324 if dirstate_file_contents.is_empty() {
295 325 self.dirstate_parents.set(DirstateParents::NULL);
326 if self.has_dirstate_v2() {
327 self.dirstate_data_file_uuid.set(None);
328 }
296 329 Ok(OwningDirstateMap::new_empty(Vec::new()))
297 330 } else if self.has_dirstate_v2() {
298 331 let docket = crate::dirstate_tree::on_disk::read_docket(
299 332 &dirstate_file_contents,
300 333 )?;
301 334 self.dirstate_parents.set(docket.parents());
335 self.dirstate_data_file_uuid
336 .set(Some(docket.uuid.to_owned()));
302 337 let data_size = docket.data_size();
303 338 let metadata = docket.tree_metadata();
304 339 let mut map = if let Some(data_mmap) = self
305 340 .hg_vfs()
306 341 .mmap_open(docket.data_filename())
307 342 .io_not_found_as_none()?
308 343 {
309 344 OwningDirstateMap::new_empty(data_mmap)
310 345 } else {
311 346 OwningDirstateMap::new_empty(Vec::new())
312 347 };
313 348 let (on_disk, placeholder) = map.get_pair_mut();
314 349 *placeholder = DirstateMap::new_v2(on_disk, data_size, metadata)?;
315 350 Ok(map)
316 351 } else {
317 352 let mut map = OwningDirstateMap::new_empty(dirstate_file_contents);
318 353 let (on_disk, placeholder) = map.get_pair_mut();
319 354 let (inner, parents) = DirstateMap::new_v1(on_disk)?;
320 355 self.dirstate_parents
321 356 .set(parents.unwrap_or(DirstateParents::NULL));
322 357 *placeholder = inner;
323 358 Ok(map)
324 359 }
325 360 }
326 361
327 362 pub fn dirstate_map(
328 363 &self,
329 364 ) -> Result<Ref<OwningDirstateMap>, DirstateError> {
330 365 self.dirstate_map.get_or_init(self)
331 366 }
332 367
333 368 pub fn dirstate_map_mut(
334 369 &self,
335 370 ) -> Result<RefMut<OwningDirstateMap>, DirstateError> {
336 371 self.dirstate_map.get_mut_or_init(self)
337 372 }
338 373
339 374 pub fn changelog(&self) -> Result<Ref<Changelog>, HgError> {
340 375 self.changelog.get_or_init(self)
341 376 }
342 377
343 378 pub fn changelog_mut(&self) -> Result<RefMut<Changelog>, HgError> {
344 379 self.changelog.get_mut_or_init(self)
345 380 }
346 381
347 382 pub fn manifestlog(&self) -> Result<Ref<Manifestlog>, HgError> {
348 383 self.manifestlog.get_or_init(self)
349 384 }
350 385
351 386 pub fn manifestlog_mut(&self) -> Result<RefMut<Manifestlog>, HgError> {
352 387 self.manifestlog.get_mut_or_init(self)
353 388 }
354 389
355 390 /// Returns the manifest of the *changeset* with the given node ID
356 391 pub fn manifest_for_node(
357 392 &self,
358 393 node: impl Into<NodePrefix>,
359 394 ) -> Result<Manifest, RevlogError> {
360 395 self.manifestlog()?.data_for_node(
361 396 self.changelog()?
362 397 .data_for_node(node.into())?
363 398 .manifest_node()?
364 399 .into(),
365 400 )
366 401 }
367 402
368 403 /// Returns the manifest of the *changeset* with the given revision number
369 404 pub fn manifest_for_rev(
370 405 &self,
371 406 revision: Revision,
372 407 ) -> Result<Manifest, RevlogError> {
373 408 self.manifestlog()?.data_for_node(
374 409 self.changelog()?
375 410 .data_for_rev(revision)?
376 411 .manifest_node()?
377 412 .into(),
378 413 )
379 414 }
380 415
381 416 pub fn filelog(&self, path: &HgPath) -> Result<Filelog, HgError> {
382 417 Filelog::open(self, path)
383 418 }
384 419 }
385 420
386 421 /// Lazily-initialized component of `Repo` with interior mutability
387 422 ///
388 423 /// This differs from `OnceCell` in that the value can still be "deinitialized"
389 424 /// later by setting its inner `Option` to `None`.
390 425 struct LazyCell<T, E> {
391 426 value: RefCell<Option<T>>,
392 427 // `Fn`s that don’t capture environment are zero-size, so this box does
393 428 // not allocate:
394 429 init: Box<dyn Fn(&Repo) -> Result<T, E>>,
395 430 }
396 431
397 432 impl<T, E> LazyCell<T, E> {
398 433 fn new(init: impl Fn(&Repo) -> Result<T, E> + 'static) -> Self {
399 434 Self {
400 435 value: RefCell::new(None),
401 436 init: Box::new(init),
402 437 }
403 438 }
404 439
405 440 fn set(&self, value: T) {
406 441 *self.value.borrow_mut() = Some(value)
407 442 }
408 443
409 444 fn get_or_init(&self, repo: &Repo) -> Result<Ref<T>, E> {
410 445 let mut borrowed = self.value.borrow();
411 446 if borrowed.is_none() {
412 447 drop(borrowed);
413 448 // Only use `borrow_mut` if it is really needed to avoid panic in
414 449 // case there is another outstanding borrow but mutation is not
415 450 // needed.
416 451 *self.value.borrow_mut() = Some((self.init)(repo)?);
417 452 borrowed = self.value.borrow()
418 453 }
419 454 Ok(Ref::map(borrowed, |option| option.as_ref().unwrap()))
420 455 }
421 456
422 457 fn get_mut_or_init(&self, repo: &Repo) -> Result<RefMut<T>, E> {
423 458 let mut borrowed = self.value.borrow_mut();
424 459 if borrowed.is_none() {
425 460 *borrowed = Some((self.init)(repo)?);
426 461 }
427 462 Ok(RefMut::map(borrowed, |option| option.as_mut().unwrap()))
428 463 }
429 464 }
General Comments 0
You need to be logged in to leave comments. Login now