##// END OF EJS Templates
rust-revlog: explicit naming for `RevlogEntry` lifetime...
Georges Racinet -
r51269:7ef51fff default
parent child Browse files
Show More
@@ -1,710 +1,710
1 1 // Copyright 2018-2023 Georges Racinet <georges.racinet@octobus.net>
2 2 // and Mercurial contributors
3 3 //
4 4 // This software may be used and distributed according to the terms of the
5 5 // GNU General Public License version 2 or any later version.
6 6 //! Mercurial concepts for handling revision history
7 7
8 8 pub mod node;
9 9 pub mod nodemap;
10 10 mod nodemap_docket;
11 11 pub mod path_encode;
12 12 pub use node::{FromHexError, Node, NodePrefix};
13 13 pub mod changelog;
14 14 pub mod filelog;
15 15 pub mod index;
16 16 pub mod manifest;
17 17 pub mod patch;
18 18
19 19 use std::borrow::Cow;
20 20 use std::io::Read;
21 21 use std::ops::Deref;
22 22 use std::path::Path;
23 23
24 24 use flate2::read::ZlibDecoder;
25 25 use sha1::{Digest, Sha1};
26 26 use zstd;
27 27
28 28 use self::node::{NODE_BYTES_LENGTH, NULL_NODE};
29 29 use self::nodemap_docket::NodeMapDocket;
30 30 use super::index::Index;
31 31 use super::nodemap::{NodeMap, NodeMapError};
32 32 use crate::errors::HgError;
33 33 use crate::vfs::Vfs;
34 34
35 35 /// Mercurial revision numbers
36 36 ///
37 37 /// As noted in revlog.c, revision numbers are actually encoded in
38 38 /// 4 bytes, and are liberally converted to ints, whence the i32
39 39 pub type Revision = i32;
40 40
41 41 /// Marker expressing the absence of a parent
42 42 ///
43 43 /// Independently of the actual representation, `NULL_REVISION` is guaranteed
44 44 /// to be smaller than all existing revisions.
45 45 pub const NULL_REVISION: Revision = -1;
46 46
47 47 /// Same as `mercurial.node.wdirrev`
48 48 ///
49 49 /// This is also equal to `i32::max_value()`, but it's better to spell
50 50 /// it out explicitely, same as in `mercurial.node`
51 51 #[allow(clippy::unreadable_literal)]
52 52 pub const WORKING_DIRECTORY_REVISION: Revision = 0x7fffffff;
53 53
54 54 pub const WORKING_DIRECTORY_HEX: &str =
55 55 "ffffffffffffffffffffffffffffffffffffffff";
56 56
57 57 /// The simplest expression of what we need of Mercurial DAGs.
58 58 pub trait Graph {
59 59 /// Return the two parents of the given `Revision`.
60 60 ///
61 61 /// Each of the parents can be independently `NULL_REVISION`
62 62 fn parents(&self, rev: Revision) -> Result<[Revision; 2], GraphError>;
63 63 }
64 64
65 65 #[derive(Clone, Debug, PartialEq)]
66 66 pub enum GraphError {
67 67 ParentOutOfRange(Revision),
68 68 WorkingDirectoryUnsupported,
69 69 }
70 70
71 71 /// The Mercurial Revlog Index
72 72 ///
73 73 /// This is currently limited to the minimal interface that is needed for
74 74 /// the [`nodemap`](nodemap/index.html) module
75 75 pub trait RevlogIndex {
76 76 /// Total number of Revisions referenced in this index
77 77 fn len(&self) -> usize;
78 78
79 79 fn is_empty(&self) -> bool {
80 80 self.len() == 0
81 81 }
82 82
83 83 /// Return a reference to the Node or `None` if rev is out of bounds
84 84 ///
85 85 /// `NULL_REVISION` is not considered to be out of bounds.
86 86 fn node(&self, rev: Revision) -> Option<&Node>;
87 87 }
88 88
89 89 const REVISION_FLAG_CENSORED: u16 = 1 << 15;
90 90 const REVISION_FLAG_ELLIPSIS: u16 = 1 << 14;
91 91 const REVISION_FLAG_EXTSTORED: u16 = 1 << 13;
92 92 const REVISION_FLAG_HASCOPIESINFO: u16 = 1 << 12;
93 93
94 94 // Keep this in sync with REVIDX_KNOWN_FLAGS in
95 95 // mercurial/revlogutils/flagutil.py
96 96 const REVIDX_KNOWN_FLAGS: u16 = REVISION_FLAG_CENSORED
97 97 | REVISION_FLAG_ELLIPSIS
98 98 | REVISION_FLAG_EXTSTORED
99 99 | REVISION_FLAG_HASCOPIESINFO;
100 100
101 101 const NULL_REVLOG_ENTRY_FLAGS: u16 = 0;
102 102
103 103 #[derive(Debug, derive_more::From)]
104 104 pub enum RevlogError {
105 105 InvalidRevision,
106 106 /// Working directory is not supported
107 107 WDirUnsupported,
108 108 /// Found more than one entry whose ID match the requested prefix
109 109 AmbiguousPrefix,
110 110 #[from]
111 111 Other(HgError),
112 112 }
113 113
114 114 impl From<NodeMapError> for RevlogError {
115 115 fn from(error: NodeMapError) -> Self {
116 116 match error {
117 117 NodeMapError::MultipleResults => RevlogError::AmbiguousPrefix,
118 118 NodeMapError::RevisionNotInIndex(rev) => RevlogError::corrupted(
119 119 format!("nodemap point to revision {} not in index", rev),
120 120 ),
121 121 }
122 122 }
123 123 }
124 124
125 125 fn corrupted<S: AsRef<str>>(context: S) -> HgError {
126 126 HgError::corrupted(format!("corrupted revlog, {}", context.as_ref()))
127 127 }
128 128
129 129 impl RevlogError {
130 130 fn corrupted<S: AsRef<str>>(context: S) -> Self {
131 131 RevlogError::Other(corrupted(context))
132 132 }
133 133 }
134 134
135 135 /// Read only implementation of revlog.
136 136 pub struct Revlog {
137 137 /// When index and data are not interleaved: bytes of the revlog index.
138 138 /// When index and data are interleaved: bytes of the revlog index and
139 139 /// data.
140 140 index: Index,
141 141 /// When index and data are not interleaved: bytes of the revlog data
142 142 data_bytes: Option<Box<dyn Deref<Target = [u8]> + Send>>,
143 143 /// When present on disk: the persistent nodemap for this revlog
144 144 nodemap: Option<nodemap::NodeTree>,
145 145 }
146 146
147 147 impl Revlog {
148 148 /// Open a revlog index file.
149 149 ///
150 150 /// It will also open the associated data file if index and data are not
151 151 /// interleaved.
152 152 pub fn open(
153 153 store_vfs: &Vfs,
154 154 index_path: impl AsRef<Path>,
155 155 data_path: Option<&Path>,
156 156 use_nodemap: bool,
157 157 ) -> Result<Self, HgError> {
158 158 let index_path = index_path.as_ref();
159 159 let index = {
160 160 match store_vfs.mmap_open_opt(&index_path)? {
161 161 None => Index::new(Box::new(vec![])),
162 162 Some(index_mmap) => {
163 163 let index = Index::new(Box::new(index_mmap))?;
164 164 Ok(index)
165 165 }
166 166 }
167 167 }?;
168 168
169 169 let default_data_path = index_path.with_extension("d");
170 170
171 171 // type annotation required
172 172 // won't recognize Mmap as Deref<Target = [u8]>
173 173 let data_bytes: Option<Box<dyn Deref<Target = [u8]> + Send>> =
174 174 if index.is_inline() {
175 175 None
176 176 } else {
177 177 let data_path = data_path.unwrap_or(&default_data_path);
178 178 let data_mmap = store_vfs.mmap_open(data_path)?;
179 179 Some(Box::new(data_mmap))
180 180 };
181 181
182 182 let nodemap = if index.is_inline() || !use_nodemap {
183 183 None
184 184 } else {
185 185 NodeMapDocket::read_from_file(store_vfs, index_path)?.map(
186 186 |(docket, data)| {
187 187 nodemap::NodeTree::load_bytes(
188 188 Box::new(data),
189 189 docket.data_length,
190 190 )
191 191 },
192 192 )
193 193 };
194 194
195 195 Ok(Revlog {
196 196 index,
197 197 data_bytes,
198 198 nodemap,
199 199 })
200 200 }
201 201
202 202 /// Return number of entries of the `Revlog`.
203 203 pub fn len(&self) -> usize {
204 204 self.index.len()
205 205 }
206 206
207 207 /// Returns `true` if the `Revlog` has zero `entries`.
208 208 pub fn is_empty(&self) -> bool {
209 209 self.index.is_empty()
210 210 }
211 211
212 212 /// Returns the node ID for the given revision number, if it exists in this
213 213 /// revlog
214 214 pub fn node_from_rev(&self, rev: Revision) -> Option<&Node> {
215 215 if rev == NULL_REVISION {
216 216 return Some(&NULL_NODE);
217 217 }
218 218 Some(self.index.get_entry(rev)?.hash())
219 219 }
220 220
221 221 /// Return the revision number for the given node ID, if it exists in this
222 222 /// revlog
223 223 pub fn rev_from_node(
224 224 &self,
225 225 node: NodePrefix,
226 226 ) -> Result<Revision, RevlogError> {
227 227 if node.is_prefix_of(&NULL_NODE) {
228 228 return Ok(NULL_REVISION);
229 229 }
230 230
231 231 if let Some(nodemap) = &self.nodemap {
232 232 return nodemap
233 233 .find_bin(&self.index, node)?
234 234 .ok_or(RevlogError::InvalidRevision);
235 235 }
236 236
237 237 // Fallback to linear scan when a persistent nodemap is not present.
238 238 // This happens when the persistent-nodemap experimental feature is not
239 239 // enabled, or for small revlogs.
240 240 //
241 241 // TODO: consider building a non-persistent nodemap in memory to
242 242 // optimize these cases.
243 243 let mut found_by_prefix = None;
244 244 for rev in (0..self.len() as Revision).rev() {
245 245 let index_entry = self.index.get_entry(rev).ok_or_else(|| {
246 246 HgError::corrupted(
247 247 "revlog references a revision not in the index",
248 248 )
249 249 })?;
250 250 if node == *index_entry.hash() {
251 251 return Ok(rev);
252 252 }
253 253 if node.is_prefix_of(index_entry.hash()) {
254 254 if found_by_prefix.is_some() {
255 255 return Err(RevlogError::AmbiguousPrefix);
256 256 }
257 257 found_by_prefix = Some(rev)
258 258 }
259 259 }
260 260 found_by_prefix.ok_or(RevlogError::InvalidRevision)
261 261 }
262 262
263 263 /// Returns whether the given revision exists in this revlog.
264 264 pub fn has_rev(&self, rev: Revision) -> bool {
265 265 self.index.get_entry(rev).is_some()
266 266 }
267 267
268 268 /// Return the full data associated to a revision.
269 269 ///
270 270 /// All entries required to build the final data out of deltas will be
271 271 /// retrieved as needed, and the deltas will be applied to the inital
272 272 /// snapshot to rebuild the final data.
273 273 pub fn get_rev_data(
274 274 &self,
275 275 rev: Revision,
276 276 ) -> Result<Cow<[u8]>, RevlogError> {
277 277 if rev == NULL_REVISION {
278 278 return Ok(Cow::Borrowed(&[]));
279 279 };
280 280 Ok(self.get_entry(rev)?.data()?)
281 281 }
282 282
283 283 /// Check the hash of some given data against the recorded hash.
284 284 pub fn check_hash(
285 285 &self,
286 286 p1: Revision,
287 287 p2: Revision,
288 288 expected: &[u8],
289 289 data: &[u8],
290 290 ) -> bool {
291 291 let e1 = self.index.get_entry(p1);
292 292 let h1 = match e1 {
293 293 Some(ref entry) => entry.hash(),
294 294 None => &NULL_NODE,
295 295 };
296 296 let e2 = self.index.get_entry(p2);
297 297 let h2 = match e2 {
298 298 Some(ref entry) => entry.hash(),
299 299 None => &NULL_NODE,
300 300 };
301 301
302 302 hash(data, h1.as_bytes(), h2.as_bytes()) == expected
303 303 }
304 304
305 305 /// Build the full data of a revision out its snapshot
306 306 /// and its deltas.
307 307 fn build_data_from_deltas(
308 308 snapshot: RevlogEntry,
309 309 deltas: &[RevlogEntry],
310 310 ) -> Result<Vec<u8>, HgError> {
311 311 let snapshot = snapshot.data_chunk()?;
312 312 let deltas = deltas
313 313 .iter()
314 314 .rev()
315 315 .map(RevlogEntry::data_chunk)
316 316 .collect::<Result<Vec<_>, _>>()?;
317 317 let patches: Vec<_> =
318 318 deltas.iter().map(|d| patch::PatchList::new(d)).collect();
319 319 let patch = patch::fold_patch_lists(&patches);
320 320 Ok(patch.apply(&snapshot))
321 321 }
322 322
323 323 /// Return the revlog data.
324 324 fn data(&self) -> &[u8] {
325 325 match &self.data_bytes {
326 326 Some(data_bytes) => data_bytes,
327 327 None => panic!(
328 328 "forgot to load the data or trying to access inline data"
329 329 ),
330 330 }
331 331 }
332 332
333 333 pub fn make_null_entry(&self) -> RevlogEntry {
334 334 RevlogEntry {
335 335 revlog: self,
336 336 rev: NULL_REVISION,
337 337 bytes: b"",
338 338 compressed_len: 0,
339 339 uncompressed_len: 0,
340 340 base_rev_or_base_of_delta_chain: None,
341 341 p1: NULL_REVISION,
342 342 p2: NULL_REVISION,
343 343 flags: NULL_REVLOG_ENTRY_FLAGS,
344 344 hash: NULL_NODE,
345 345 }
346 346 }
347 347
348 348 /// Get an entry of the revlog.
349 349 pub fn get_entry(
350 350 &self,
351 351 rev: Revision,
352 352 ) -> Result<RevlogEntry, RevlogError> {
353 353 if rev == NULL_REVISION {
354 354 return Ok(self.make_null_entry());
355 355 }
356 356 let index_entry = self
357 357 .index
358 358 .get_entry(rev)
359 359 .ok_or(RevlogError::InvalidRevision)?;
360 360 let start = index_entry.offset();
361 361 let end = start + index_entry.compressed_len() as usize;
362 362 let data = if self.index.is_inline() {
363 363 self.index.data(start, end)
364 364 } else {
365 365 &self.data()[start..end]
366 366 };
367 367 let entry = RevlogEntry {
368 368 revlog: self,
369 369 rev,
370 370 bytes: data,
371 371 compressed_len: index_entry.compressed_len(),
372 372 uncompressed_len: index_entry.uncompressed_len(),
373 373 base_rev_or_base_of_delta_chain: if index_entry
374 374 .base_revision_or_base_of_delta_chain()
375 375 == rev
376 376 {
377 377 None
378 378 } else {
379 379 Some(index_entry.base_revision_or_base_of_delta_chain())
380 380 },
381 381 p1: index_entry.p1(),
382 382 p2: index_entry.p2(),
383 383 flags: index_entry.flags(),
384 384 hash: *index_entry.hash(),
385 385 };
386 386 Ok(entry)
387 387 }
388 388
389 389 /// when resolving internal references within revlog, any errors
390 390 /// should be reported as corruption, instead of e.g. "invalid revision"
391 391 fn get_entry_internal(
392 392 &self,
393 393 rev: Revision,
394 394 ) -> Result<RevlogEntry, HgError> {
395 395 self.get_entry(rev)
396 396 .map_err(|_| corrupted(format!("revision {} out of range", rev)))
397 397 }
398 398 }
399 399
400 400 /// The revlog entry's bytes and the necessary informations to extract
401 401 /// the entry's data.
402 402 #[derive(Clone)]
403 pub struct RevlogEntry<'a> {
404 revlog: &'a Revlog,
403 pub struct RevlogEntry<'revlog> {
404 revlog: &'revlog Revlog,
405 405 rev: Revision,
406 bytes: &'a [u8],
406 bytes: &'revlog [u8],
407 407 compressed_len: u32,
408 408 uncompressed_len: i32,
409 409 base_rev_or_base_of_delta_chain: Option<Revision>,
410 410 p1: Revision,
411 411 p2: Revision,
412 412 flags: u16,
413 413 hash: Node,
414 414 }
415 415
416 impl<'a> RevlogEntry<'a> {
416 impl<'revlog> RevlogEntry<'revlog> {
417 417 pub fn revision(&self) -> Revision {
418 418 self.rev
419 419 }
420 420
421 421 pub fn node(&self) -> &Node {
422 422 &self.hash
423 423 }
424 424
425 425 pub fn uncompressed_len(&self) -> Option<u32> {
426 426 u32::try_from(self.uncompressed_len).ok()
427 427 }
428 428
429 429 pub fn has_p1(&self) -> bool {
430 430 self.p1 != NULL_REVISION
431 431 }
432 432
433 433 pub fn p1_entry(&self) -> Result<Option<RevlogEntry>, RevlogError> {
434 434 if self.p1 == NULL_REVISION {
435 435 Ok(None)
436 436 } else {
437 437 Ok(Some(self.revlog.get_entry(self.p1)?))
438 438 }
439 439 }
440 440
441 441 pub fn p2_entry(&self) -> Result<Option<RevlogEntry>, RevlogError> {
442 442 if self.p2 == NULL_REVISION {
443 443 Ok(None)
444 444 } else {
445 445 Ok(Some(self.revlog.get_entry(self.p2)?))
446 446 }
447 447 }
448 448
449 449 pub fn p1(&self) -> Option<Revision> {
450 450 if self.p1 == NULL_REVISION {
451 451 None
452 452 } else {
453 453 Some(self.p1)
454 454 }
455 455 }
456 456
457 457 pub fn p2(&self) -> Option<Revision> {
458 458 if self.p2 == NULL_REVISION {
459 459 None
460 460 } else {
461 461 Some(self.p2)
462 462 }
463 463 }
464 464
465 465 pub fn is_censored(&self) -> bool {
466 466 (self.flags & REVISION_FLAG_CENSORED) != 0
467 467 }
468 468
469 469 pub fn has_length_affecting_flag_processor(&self) -> bool {
470 470 // Relevant Python code: revlog.size()
471 471 // note: ELLIPSIS is known to not change the content
472 472 (self.flags & (REVIDX_KNOWN_FLAGS ^ REVISION_FLAG_ELLIPSIS)) != 0
473 473 }
474 474
475 475 /// The data for this entry, after resolving deltas if any.
476 pub fn rawdata(&self) -> Result<Cow<'a, [u8]>, HgError> {
476 pub fn rawdata(&self) -> Result<Cow<'revlog, [u8]>, HgError> {
477 477 let mut entry = self.clone();
478 478 let mut delta_chain = vec![];
479 479
480 480 // The meaning of `base_rev_or_base_of_delta_chain` depends on
481 481 // generaldelta. See the doc on `ENTRY_DELTA_BASE` in
482 482 // `mercurial/revlogutils/constants.py` and the code in
483 483 // [_chaininfo] and in [index_deltachain].
484 484 let uses_generaldelta = self.revlog.index.uses_generaldelta();
485 485 while let Some(base_rev) = entry.base_rev_or_base_of_delta_chain {
486 486 let base_rev = if uses_generaldelta {
487 487 base_rev
488 488 } else {
489 489 entry.rev - 1
490 490 };
491 491 delta_chain.push(entry);
492 492 entry = self.revlog.get_entry_internal(base_rev)?;
493 493 }
494 494
495 495 let data = if delta_chain.is_empty() {
496 496 entry.data_chunk()?
497 497 } else {
498 498 Revlog::build_data_from_deltas(entry, &delta_chain)?.into()
499 499 };
500 500
501 501 Ok(data)
502 502 }
503 503
504 504 fn check_data(
505 505 &self,
506 data: Cow<'a, [u8]>,
507 ) -> Result<Cow<'a, [u8]>, HgError> {
506 data: Cow<'revlog, [u8]>,
507 ) -> Result<Cow<'revlog, [u8]>, HgError> {
508 508 if self.revlog.check_hash(
509 509 self.p1,
510 510 self.p2,
511 511 self.hash.as_bytes(),
512 512 &data,
513 513 ) {
514 514 Ok(data)
515 515 } else {
516 516 if (self.flags & REVISION_FLAG_ELLIPSIS) != 0 {
517 517 return Err(HgError::unsupported(
518 518 "ellipsis revisions are not supported by rhg",
519 519 ));
520 520 }
521 521 Err(corrupted(format!(
522 522 "hash check failed for revision {}",
523 523 self.rev
524 524 )))
525 525 }
526 526 }
527 527
528 pub fn data(&self) -> Result<Cow<'a, [u8]>, HgError> {
528 pub fn data(&self) -> Result<Cow<'revlog, [u8]>, HgError> {
529 529 let data = self.rawdata()?;
530 530 if self.is_censored() {
531 531 return Err(HgError::CensoredNodeError);
532 532 }
533 533 self.check_data(data)
534 534 }
535 535
536 536 /// Extract the data contained in the entry.
537 537 /// This may be a delta. (See `is_delta`.)
538 fn data_chunk(&self) -> Result<Cow<'a, [u8]>, HgError> {
538 fn data_chunk(&self) -> Result<Cow<'revlog, [u8]>, HgError> {
539 539 if self.bytes.is_empty() {
540 540 return Ok(Cow::Borrowed(&[]));
541 541 }
542 542 match self.bytes[0] {
543 543 // Revision data is the entirety of the entry, including this
544 544 // header.
545 545 b'\0' => Ok(Cow::Borrowed(self.bytes)),
546 546 // Raw revision data follows.
547 547 b'u' => Ok(Cow::Borrowed(&self.bytes[1..])),
548 548 // zlib (RFC 1950) data.
549 549 b'x' => Ok(Cow::Owned(self.uncompressed_zlib_data()?)),
550 550 // zstd data.
551 551 b'\x28' => Ok(Cow::Owned(self.uncompressed_zstd_data()?)),
552 552 // A proper new format should have had a repo/store requirement.
553 553 format_type => Err(corrupted(format!(
554 554 "unknown compression header '{}'",
555 555 format_type
556 556 ))),
557 557 }
558 558 }
559 559
560 560 fn uncompressed_zlib_data(&self) -> Result<Vec<u8>, HgError> {
561 561 let mut decoder = ZlibDecoder::new(self.bytes);
562 562 if self.is_delta() {
563 563 let mut buf = Vec::with_capacity(self.compressed_len as usize);
564 564 decoder
565 565 .read_to_end(&mut buf)
566 566 .map_err(|e| corrupted(e.to_string()))?;
567 567 Ok(buf)
568 568 } else {
569 569 let cap = self.uncompressed_len.max(0) as usize;
570 570 let mut buf = vec![0; cap];
571 571 decoder
572 572 .read_exact(&mut buf)
573 573 .map_err(|e| corrupted(e.to_string()))?;
574 574 Ok(buf)
575 575 }
576 576 }
577 577
578 578 fn uncompressed_zstd_data(&self) -> Result<Vec<u8>, HgError> {
579 579 if self.is_delta() {
580 580 let mut buf = Vec::with_capacity(self.compressed_len as usize);
581 581 zstd::stream::copy_decode(self.bytes, &mut buf)
582 582 .map_err(|e| corrupted(e.to_string()))?;
583 583 Ok(buf)
584 584 } else {
585 585 let cap = self.uncompressed_len.max(0) as usize;
586 586 let mut buf = vec![0; cap];
587 587 let len = zstd::bulk::decompress_to_buffer(self.bytes, &mut buf)
588 588 .map_err(|e| corrupted(e.to_string()))?;
589 589 if len != self.uncompressed_len as usize {
590 590 Err(corrupted("uncompressed length does not match"))
591 591 } else {
592 592 Ok(buf)
593 593 }
594 594 }
595 595 }
596 596
597 597 /// Tell if the entry is a snapshot or a delta
598 598 /// (influences on decompression).
599 599 fn is_delta(&self) -> bool {
600 600 self.base_rev_or_base_of_delta_chain.is_some()
601 601 }
602 602 }
603 603
604 604 /// Calculate the hash of a revision given its data and its parents.
605 605 fn hash(
606 606 data: &[u8],
607 607 p1_hash: &[u8],
608 608 p2_hash: &[u8],
609 609 ) -> [u8; NODE_BYTES_LENGTH] {
610 610 let mut hasher = Sha1::new();
611 611 let (a, b) = (p1_hash, p2_hash);
612 612 if a > b {
613 613 hasher.update(b);
614 614 hasher.update(a);
615 615 } else {
616 616 hasher.update(a);
617 617 hasher.update(b);
618 618 }
619 619 hasher.update(data);
620 620 *hasher.finalize().as_ref()
621 621 }
622 622
623 623 #[cfg(test)]
624 624 mod tests {
625 625 use super::*;
626 626 use crate::index::{IndexEntryBuilder, INDEX_ENTRY_SIZE};
627 627 use itertools::Itertools;
628 628
629 629 #[test]
630 630 fn test_empty() {
631 631 let temp = tempfile::tempdir().unwrap();
632 632 let vfs = Vfs { base: temp.path() };
633 633 std::fs::write(temp.path().join("foo.i"), b"").unwrap();
634 634 let revlog = Revlog::open(&vfs, "foo.i", None, false).unwrap();
635 635 assert!(revlog.is_empty());
636 636 assert_eq!(revlog.len(), 0);
637 637 assert!(revlog.get_entry(0).is_err());
638 638 assert!(!revlog.has_rev(0));
639 639 }
640 640
641 641 #[test]
642 642 fn test_inline() {
643 643 let temp = tempfile::tempdir().unwrap();
644 644 let vfs = Vfs { base: temp.path() };
645 645 let node0 = Node::from_hex("2ed2a3912a0b24502043eae84ee4b279c18b90dd")
646 646 .unwrap();
647 647 let node1 = Node::from_hex("b004912a8510032a0350a74daa2803dadfb00e12")
648 648 .unwrap();
649 649 let node2 = Node::from_hex("dd6ad206e907be60927b5a3117b97dffb2590582")
650 650 .unwrap();
651 651 let entry0_bytes = IndexEntryBuilder::new()
652 652 .is_first(true)
653 653 .with_version(1)
654 654 .with_inline(true)
655 655 .with_offset(INDEX_ENTRY_SIZE)
656 656 .with_node(node0)
657 657 .build();
658 658 let entry1_bytes = IndexEntryBuilder::new()
659 659 .with_offset(INDEX_ENTRY_SIZE)
660 660 .with_node(node1)
661 661 .build();
662 662 let entry2_bytes = IndexEntryBuilder::new()
663 663 .with_offset(INDEX_ENTRY_SIZE)
664 664 .with_p1(0)
665 665 .with_p2(1)
666 666 .with_node(node2)
667 667 .build();
668 668 let contents = vec![entry0_bytes, entry1_bytes, entry2_bytes]
669 669 .into_iter()
670 670 .flatten()
671 671 .collect_vec();
672 672 std::fs::write(temp.path().join("foo.i"), contents).unwrap();
673 673 let revlog = Revlog::open(&vfs, "foo.i", None, false).unwrap();
674 674
675 675 let entry0 = revlog.get_entry(0).ok().unwrap();
676 676 assert_eq!(entry0.revision(), 0);
677 677 assert_eq!(*entry0.node(), node0);
678 678 assert!(!entry0.has_p1());
679 679 assert_eq!(entry0.p1(), None);
680 680 assert_eq!(entry0.p2(), None);
681 681 let p1_entry = entry0.p1_entry().unwrap();
682 682 assert!(p1_entry.is_none());
683 683 let p2_entry = entry0.p2_entry().unwrap();
684 684 assert!(p2_entry.is_none());
685 685
686 686 let entry1 = revlog.get_entry(1).ok().unwrap();
687 687 assert_eq!(entry1.revision(), 1);
688 688 assert_eq!(*entry1.node(), node1);
689 689 assert!(!entry1.has_p1());
690 690 assert_eq!(entry1.p1(), None);
691 691 assert_eq!(entry1.p2(), None);
692 692 let p1_entry = entry1.p1_entry().unwrap();
693 693 assert!(p1_entry.is_none());
694 694 let p2_entry = entry1.p2_entry().unwrap();
695 695 assert!(p2_entry.is_none());
696 696
697 697 let entry2 = revlog.get_entry(2).ok().unwrap();
698 698 assert_eq!(entry2.revision(), 2);
699 699 assert_eq!(*entry2.node(), node2);
700 700 assert!(entry2.has_p1());
701 701 assert_eq!(entry2.p1(), Some(0));
702 702 assert_eq!(entry2.p2(), Some(1));
703 703 let p1_entry = entry2.p1_entry().unwrap();
704 704 assert!(p1_entry.is_some());
705 705 assert_eq!(p1_entry.unwrap().revision(), 0);
706 706 let p2_entry = entry2.p2_entry().unwrap();
707 707 assert!(p2_entry.is_some());
708 708 assert_eq!(p2_entry.unwrap().revision(), 1);
709 709 }
710 710 }
General Comments 0
You need to be logged in to leave comments. Login now