Mercurial > hg
comparison rust/hg-core/src/revlog/mod.rs @ 50744:bca4037306da stable
rust-revlog: fix incorrect results with NULL_NODE prefixes
In case a short hash is a prefix of `NULL_NODE`, the correct revision
number lookup is `NULL_REVISION` only if there is no match in the nodemap.
Indeed, if there is a single nodemap match, then it is an ambiguity with the
always matching `NULL_NODE`.
Before this change, using the Mercurial development repository as a testbed (it
has public changesets with node ID starting with `0005` and `0009`), this is
what `rhg` did (plain `hg` provided for reference)
```
$ rust/target/debug/rhg cat -r 000 README
README: no such file in rev 000000000000
$ hg cat -r 000 README
abort: ambiguous revision identifier: 000
```
Here is the expected output for `rhg` on ambiguous prefixes (again, before
this change):
```
$ rust/target/debug/rhg cat -r 0001 README
abort: ambiguous revision identifier: 0001
```
The test provided by 8c29af0f6d6e in `test-rhg.t` could become flaky with
this change, unless all hashes are fixed. We expect reviewers to be more
sure about that than we are.
author | Georges Racinet <georges.racinet@octobus.net> |
---|---|
date | Thu, 30 Mar 2023 11:34:30 +0200 |
parents | 0159b014f3ab |
children | 3338c6ffdaa3 |
comparison
equal
deleted
inserted
replaced
50743:0159b014f3ab | 50744:bca4037306da |
---|---|
223 /// revlog | 223 /// revlog |
224 pub fn rev_from_node( | 224 pub fn rev_from_node( |
225 &self, | 225 &self, |
226 node: NodePrefix, | 226 node: NodePrefix, |
227 ) -> Result<Revision, RevlogError> { | 227 ) -> Result<Revision, RevlogError> { |
228 let looked_up = if let Some(nodemap) = &self.nodemap { | |
229 nodemap | |
230 .find_bin(&self.index, node)? | |
231 .ok_or(RevlogError::InvalidRevision) | |
232 } else { | |
233 self.rev_from_node_no_persistent_nodemap(node) | |
234 }; | |
235 | |
228 if node.is_prefix_of(&NULL_NODE) { | 236 if node.is_prefix_of(&NULL_NODE) { |
229 return Ok(NULL_REVISION); | 237 return match looked_up { |
230 } | 238 Ok(_) => Err(RevlogError::AmbiguousPrefix), |
231 | 239 Err(RevlogError::InvalidRevision) => Ok(NULL_REVISION), |
232 if let Some(nodemap) = &self.nodemap { | 240 res => res, |
233 return nodemap | 241 }; |
234 .find_bin(&self.index, node)? | 242 }; |
235 .ok_or(RevlogError::InvalidRevision); | 243 |
236 } | 244 looked_up |
237 self.rev_from_node_no_persistent_nodemap(node) | |
238 } | 245 } |
239 | 246 |
240 /// Same as `rev_from_node`, without using a persistent nodemap | 247 /// Same as `rev_from_node`, without using a persistent nodemap |
241 /// | 248 /// |
242 /// This is used as fallback when a persistent nodemap is not present. | 249 /// This is used as fallback when a persistent nodemap is not present. |
675 let revlog = Revlog::open(&vfs, "foo.i", None, false).unwrap(); | 682 let revlog = Revlog::open(&vfs, "foo.i", None, false).unwrap(); |
676 assert!(revlog.is_empty()); | 683 assert!(revlog.is_empty()); |
677 assert_eq!(revlog.len(), 0); | 684 assert_eq!(revlog.len(), 0); |
678 assert!(revlog.get_entry(0).is_err()); | 685 assert!(revlog.get_entry(0).is_err()); |
679 assert!(!revlog.has_rev(0)); | 686 assert!(!revlog.has_rev(0)); |
687 assert_eq!(revlog.rev_from_node(NULL_NODE.into()).unwrap(), -1); | |
680 } | 688 } |
681 | 689 |
682 #[test] | 690 #[test] |
683 fn test_inline() { | 691 fn test_inline() { |
684 let temp = tempfile::tempdir().unwrap(); | 692 let temp = tempfile::tempdir().unwrap(); |
746 assert_eq!(p1_entry.unwrap().revision(), 0); | 754 assert_eq!(p1_entry.unwrap().revision(), 0); |
747 let p2_entry = entry2.p2_entry().unwrap(); | 755 let p2_entry = entry2.p2_entry().unwrap(); |
748 assert!(p2_entry.is_some()); | 756 assert!(p2_entry.is_some()); |
749 assert_eq!(p2_entry.unwrap().revision(), 1); | 757 assert_eq!(p2_entry.unwrap().revision(), 1); |
750 } | 758 } |
751 } | 759 |
760 #[test] | |
761 fn test_nodemap() { | |
762 let temp = tempfile::tempdir().unwrap(); | |
763 let vfs = Vfs { base: temp.path() }; | |
764 | |
765 // building a revlog with a forced Node starting with zeros | |
766 // This is a corruption, but it does not preclude using the nodemap | |
767 // if we don't try and access the data | |
768 let node0 = Node::from_hex("00d2a3912a0b24502043eae84ee4b279c18b90dd") | |
769 .unwrap(); | |
770 let node1 = Node::from_hex("b004912a8510032a0350a74daa2803dadfb00e12") | |
771 .unwrap(); | |
772 let entry0_bytes = IndexEntryBuilder::new() | |
773 .is_first(true) | |
774 .with_version(1) | |
775 .with_inline(true) | |
776 .with_offset(INDEX_ENTRY_SIZE) | |
777 .with_node(node0) | |
778 .build(); | |
779 let entry1_bytes = IndexEntryBuilder::new() | |
780 .with_offset(INDEX_ENTRY_SIZE) | |
781 .with_node(node1) | |
782 .build(); | |
783 let contents = vec![entry0_bytes, entry1_bytes] | |
784 .into_iter() | |
785 .flatten() | |
786 .collect_vec(); | |
787 std::fs::write(temp.path().join("foo.i"), contents).unwrap(); | |
788 let revlog = Revlog::open(&vfs, "foo.i", None, false).unwrap(); | |
789 | |
790 // accessing the data shows the corruption | |
791 revlog.get_entry(0).unwrap().data().unwrap_err(); | |
792 | |
793 assert_eq!(revlog.rev_from_node(NULL_NODE.into()).unwrap(), -1); | |
794 assert_eq!(revlog.rev_from_node(node0.into()).unwrap(), 0); | |
795 assert_eq!(revlog.rev_from_node(node1.into()).unwrap(), 1); | |
796 assert_eq!( | |
797 revlog | |
798 .rev_from_node(NodePrefix::from_hex("000").unwrap()) | |
799 .unwrap(), | |
800 -1 | |
801 ); | |
802 assert_eq!( | |
803 revlog | |
804 .rev_from_node(NodePrefix::from_hex("b00").unwrap()) | |
805 .unwrap(), | |
806 1 | |
807 ); | |
808 // RevlogError does not implement PartialEq | |
809 // (ultimately because io::Error does not) | |
810 match revlog | |
811 .rev_from_node(NodePrefix::from_hex("00").unwrap()) | |
812 .expect_err("Expected to give AmbiguousPrefix error") | |
813 { | |
814 RevlogError::AmbiguousPrefix => (), | |
815 e => { | |
816 panic!("Got another error than AmbiguousPrefix: {:?}", e); | |
817 } | |
818 }; | |
819 } | |
820 } |