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 }