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