summaryrefslogtreecommitdiffstats
path: root/vendor/gix-hash/src
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-04 12:41:41 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-04 12:41:41 +0000
commit10ee2acdd26a7f1298c6f6d6b7af9b469fe29b87 (patch)
treebdffd5d80c26cf4a7a518281a204be1ace85b4c1 /vendor/gix-hash/src
parentReleasing progress-linux version 1.70.0+dfsg1-9~progress7.99u1. (diff)
downloadrustc-10ee2acdd26a7f1298c6f6d6b7af9b469fe29b87.tar.xz
rustc-10ee2acdd26a7f1298c6f6d6b7af9b469fe29b87.zip
Merging upstream version 1.70.0+dfsg2.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'vendor/gix-hash/src')
-rw-r--r--vendor/gix-hash/src/kind.rs122
-rw-r--r--vendor/gix-hash/src/lib.rs42
-rw-r--r--vendor/gix-hash/src/object_id.rs229
-rw-r--r--vendor/gix-hash/src/oid.rs257
-rw-r--r--vendor/gix-hash/src/prefix.rs152
5 files changed, 802 insertions, 0 deletions
diff --git a/vendor/gix-hash/src/kind.rs b/vendor/gix-hash/src/kind.rs
new file mode 100644
index 000000000..86faddda2
--- /dev/null
+++ b/vendor/gix-hash/src/kind.rs
@@ -0,0 +1,122 @@
+use std::{convert::TryFrom, str::FromStr};
+
+use crate::{oid, Kind, ObjectId};
+
+impl Default for Kind {
+ fn default() -> Self {
+ Kind::Sha1
+ }
+}
+
+impl TryFrom<u8> for Kind {
+ type Error = u8;
+
+ fn try_from(value: u8) -> Result<Self, Self::Error> {
+ Ok(match value {
+ 1 => Kind::Sha1,
+ unknown => return Err(unknown),
+ })
+ }
+}
+
+impl FromStr for Kind {
+ type Err = String;
+
+ fn from_str(s: &str) -> Result<Self, Self::Err> {
+ Ok(match s {
+ "sha1" | "SHA1" => Kind::Sha1,
+ other => return Err(other.into()),
+ })
+ }
+}
+
+impl std::fmt::Display for Kind {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ match self {
+ Kind::Sha1 => f.write_str("SHA1"),
+ }
+ }
+}
+
+impl Kind {
+ /// Returns the shortest hash we support
+ #[inline]
+ pub const fn shortest() -> Self {
+ Self::Sha1
+ }
+
+ /// Returns the longest hash we support
+ #[inline]
+ pub const fn longest() -> Self {
+ Self::Sha1
+ }
+
+ /// Returns a buffer suitable to hold the longest possible hash in hex.
+ #[inline]
+ pub const fn hex_buf() -> [u8; Kind::longest().len_in_hex()] {
+ [0u8; Kind::longest().len_in_hex()]
+ }
+
+ /// Returns a buffer suitable to hold the longest possible hash as raw bytes.
+ #[inline]
+ pub const fn buf() -> [u8; Kind::longest().len_in_bytes()] {
+ [0u8; Kind::longest().len_in_bytes()]
+ }
+
+ /// Returns the amount of ascii-characters needed to encode this has in hex
+ #[inline]
+ pub const fn len_in_hex(&self) -> usize {
+ match self {
+ Kind::Sha1 => 40,
+ }
+ }
+ /// Returns the amount of bytes taken up by the hash of the current kind
+ #[inline]
+ pub const fn len_in_bytes(&self) -> usize {
+ match self {
+ Kind::Sha1 => 20,
+ }
+ }
+
+ /// Returns the kind of hash that would fit the given `hex_len`, or `None` if there is no fitting hash.
+ /// Note that 0 as `hex_len` fits always yields Sha1.
+ #[inline]
+ pub const fn from_hex_len(hex_len: usize) -> Option<Self> {
+ Some(match hex_len {
+ 0..=40 => Kind::Sha1,
+ _ => return None,
+ })
+ }
+
+ /// Converts a size in bytes as obtained by `Kind::len_in_bytes()` into the corresponding hash kind, if possible.
+ ///
+ /// **Panics** if the hash length doesn't match a known hash.
+ ///
+ /// NOTE that this method isn't public as it shouldn't be encouraged to assume all hashes have the same length.
+ /// However, if there should be such a thing, our `oid` implementation will have to become an enum and it's pretty breaking
+ /// to the way it's currently being used as auto-dereffing doesn't work anymore. Let's hope it won't happen.
+ // TODO: make 'const' once Rust 1.57 is more readily available in projects using 'gitoxide'.
+ #[inline]
+ pub(crate) fn from_len_in_bytes(bytes: usize) -> Self {
+ match bytes {
+ 20 => Kind::Sha1,
+ _ => panic!("BUG: must be called only with valid hash lengths produced by len_in_bytes()"),
+ }
+ }
+
+ /// Create a null-id of our hash kind.
+ #[inline]
+ pub fn null_ref(&self) -> &'static oid {
+ match self {
+ Kind::Sha1 => oid::null_sha1(),
+ }
+ }
+
+ /// Create a null-id of our hash kind.
+ #[inline]
+ pub const fn null(&self) -> ObjectId {
+ match self {
+ Kind::Sha1 => ObjectId::null_sha1(),
+ }
+ }
+}
diff --git a/vendor/gix-hash/src/lib.rs b/vendor/gix-hash/src/lib.rs
new file mode 100644
index 000000000..c40c4b8f0
--- /dev/null
+++ b/vendor/gix-hash/src/lib.rs
@@ -0,0 +1,42 @@
+//! This crate provides types for identifying git objects using a hash digest.
+//!
+//! These are provided in borrowed versions as well as owned ones.
+//! ## Feature Flags
+#![cfg_attr(
+ feature = "document-features",
+ cfg_attr(doc, doc = ::document_features::document_features!())
+)]
+#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))]
+#![deny(missing_docs, rust_2018_idioms, unsafe_code)]
+
+#[path = "oid.rs"]
+mod borrowed;
+pub use borrowed::oid;
+
+mod object_id;
+pub use object_id::{decode, ObjectId};
+
+///
+pub mod prefix;
+
+/// An partial owned hash possibly identifying an object uniquely,
+/// whose non-prefix bytes are zeroed.
+#[derive(PartialEq, Eq, Hash, Ord, PartialOrd, Clone, Copy, Debug)]
+#[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))]
+pub struct Prefix {
+ bytes: ObjectId,
+ hex_len: usize,
+}
+
+/// The size of a SHA1 hash digest in bytes
+const SIZE_OF_SHA1_DIGEST: usize = 20;
+
+/// Denotes the kind of function to produce a `Id`
+#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone, Copy)]
+#[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))]
+pub enum Kind {
+ /// The Sha1 hash with 160 bits.
+ Sha1 = 1,
+}
+
+mod kind;
diff --git a/vendor/gix-hash/src/object_id.rs b/vendor/gix-hash/src/object_id.rs
new file mode 100644
index 000000000..d295fc555
--- /dev/null
+++ b/vendor/gix-hash/src/object_id.rs
@@ -0,0 +1,229 @@
+use std::{
+ borrow::Borrow,
+ convert::TryInto,
+ fmt,
+ hash::{Hash, Hasher},
+ ops::Deref,
+};
+
+use crate::{borrowed::oid, Kind, SIZE_OF_SHA1_DIGEST};
+
+/// An owned hash identifying objects, most commonly Sha1
+#[derive(PartialEq, Eq, Ord, PartialOrd, Clone, Copy)]
+#[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))]
+pub enum ObjectId {
+ /// A SHA 1 hash digest
+ Sha1([u8; SIZE_OF_SHA1_DIGEST]),
+}
+
+// False positive: https://github.com/rust-lang/rust-clippy/issues/2627
+// ignoring some fields while hashing is perfectly valid and just leads to
+// increased HashCollisions. One Sha1 being a prefix of another Sha256 is
+// extremely unlikely to begin with so it doesn't matter.
+// This implementation matches the `Hash` implementation for `oid`
+// and allows the usage of custom Hashers that only copy a truncated ShaHash
+#[allow(clippy::derive_hash_xor_eq)]
+impl Hash for ObjectId {
+ fn hash<H: Hasher>(&self, state: &mut H) {
+ state.write(self.as_slice())
+ }
+}
+
+#[allow(missing_docs)]
+pub mod decode {
+ use std::str::FromStr;
+
+ use crate::object_id::ObjectId;
+
+ /// An error returned by [`ObjectId::from_hex()`][crate::ObjectId::from_hex()]
+ #[derive(Debug, thiserror::Error)]
+ #[allow(missing_docs)]
+ pub enum Error {
+ #[error("A hash sized {0} hexadecimal characters is invalid")]
+ InvalidHexEncodingLength(usize),
+ #[error("Invalid character {c} at position {index}")]
+ Invalid { c: char, index: usize },
+ }
+
+ /// Hash decoding
+ impl ObjectId {
+ /// Create an instance from a `buffer` of 40 bytes encoded with hexadecimal notation.
+ ///
+ /// Such a buffer can be obtained using [`oid::write_hex_to(buffer)`][super::oid::write_hex_to()]
+ pub fn from_hex(buffer: &[u8]) -> Result<ObjectId, Error> {
+ use hex::FromHex;
+ match buffer.len() {
+ 40 => Ok(ObjectId::Sha1(<[u8; 20]>::from_hex(buffer).map_err(
+ |err| match err {
+ hex::FromHexError::InvalidHexCharacter { c, index } => Error::Invalid { c, index },
+ hex::FromHexError::OddLength | hex::FromHexError::InvalidStringLength => {
+ unreachable!("BUG: This is already checked")
+ }
+ },
+ )?)),
+ len => Err(Error::InvalidHexEncodingLength(len)),
+ }
+ }
+ }
+
+ impl FromStr for ObjectId {
+ type Err = Error;
+
+ fn from_str(s: &str) -> Result<Self, Self::Err> {
+ Self::from_hex(s.as_bytes())
+ }
+ }
+}
+
+/// Access and conversion
+impl ObjectId {
+ /// Returns the kind of hash used in this `Id`
+ #[inline]
+ pub fn kind(&self) -> crate::Kind {
+ match self {
+ ObjectId::Sha1(_) => crate::Kind::Sha1,
+ }
+ }
+ /// Return the raw byte slice representing this hash
+ #[inline]
+ pub fn as_slice(&self) -> &[u8] {
+ match self {
+ Self::Sha1(b) => b.as_ref(),
+ }
+ }
+ /// Return the raw mutable byte slice representing this hash
+ #[inline]
+ pub fn as_mut_slice(&mut self) -> &mut [u8] {
+ match self {
+ Self::Sha1(b) => b.as_mut(),
+ }
+ }
+
+ /// The hash of an empty blob
+ #[inline]
+ pub const fn empty_blob(hash: Kind) -> ObjectId {
+ match hash {
+ Kind::Sha1 => {
+ ObjectId::Sha1(*b"\xe6\x9d\xe2\x9b\xb2\xd1\xd6\x43\x4b\x8b\x29\xae\x77\x5a\xd8\xc2\xe4\x8c\x53\x91")
+ }
+ }
+ }
+
+ /// The hash of an empty tree
+ #[inline]
+ pub const fn empty_tree(hash: Kind) -> ObjectId {
+ match hash {
+ Kind::Sha1 => {
+ ObjectId::Sha1(*b"\x4b\x82\x5d\xc6\x42\xcb\x6e\xb9\xa0\x60\xe5\x4b\xf8\xd6\x92\x88\xfb\xee\x49\x04")
+ }
+ }
+ }
+
+ /// Returns true if this hash consists of all null bytes
+ #[inline]
+ pub fn is_null(&self) -> bool {
+ match self {
+ ObjectId::Sha1(digest) => &digest[..] == oid::null_sha1().as_bytes(),
+ }
+ }
+
+ /// Returns an Digest representing a hash with whose memory is zeroed.
+ #[inline]
+ pub const fn null(kind: crate::Kind) -> ObjectId {
+ match kind {
+ crate::Kind::Sha1 => Self::null_sha1(),
+ }
+ }
+}
+
+/// Sha1 hash specific methods
+impl ObjectId {
+ /// Instantiate an Digest from 20 bytes of a Sha1 digest.
+ #[inline]
+ fn new_sha1(id: [u8; SIZE_OF_SHA1_DIGEST]) -> Self {
+ ObjectId::Sha1(id)
+ }
+
+ /// Instantiate an Digest from a slice 20 borrowed bytes of a Sha1 digest.
+ ///
+ /// Panics of the slice doesn't have a length of 20.
+ #[inline]
+ pub(crate) fn from_20_bytes(b: &[u8]) -> ObjectId {
+ let mut id = [0; SIZE_OF_SHA1_DIGEST];
+ id.copy_from_slice(b);
+ ObjectId::Sha1(id)
+ }
+
+ /// Returns an Digest representing a Sha1 with whose memory is zeroed.
+ #[inline]
+ pub(crate) const fn null_sha1() -> ObjectId {
+ ObjectId::Sha1([0u8; 20])
+ }
+}
+
+impl std::fmt::Debug for ObjectId {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ match self {
+ ObjectId::Sha1(_hash) => f.write_str("Sha1(")?,
+ }
+ for b in self.as_bytes() {
+ write!(f, "{b:02x}")?;
+ }
+ f.write_str(")")
+ }
+}
+
+impl From<[u8; SIZE_OF_SHA1_DIGEST]> for ObjectId {
+ fn from(v: [u8; 20]) -> Self {
+ Self::new_sha1(v)
+ }
+}
+
+impl From<&[u8]> for ObjectId {
+ fn from(v: &[u8]) -> Self {
+ match v.len() {
+ 20 => Self::Sha1(v.try_into().expect("prior length validation")),
+ other => panic!("BUG: unsupported hash len: {other}"),
+ }
+ }
+}
+
+impl From<&crate::oid> for ObjectId {
+ fn from(v: &oid) -> Self {
+ match v.kind() {
+ crate::Kind::Sha1 => ObjectId::from_20_bytes(v.as_bytes()),
+ }
+ }
+}
+
+impl Deref for ObjectId {
+ type Target = oid;
+
+ fn deref(&self) -> &Self::Target {
+ self.as_ref()
+ }
+}
+
+impl AsRef<crate::oid> for ObjectId {
+ fn as_ref(&self) -> &oid {
+ oid::from_bytes_unchecked(self.as_slice())
+ }
+}
+
+impl Borrow<crate::oid> for ObjectId {
+ fn borrow(&self) -> &oid {
+ self.as_ref()
+ }
+}
+
+impl fmt::Display for ObjectId {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ write!(f, "{}", self.to_hex())
+ }
+}
+
+impl PartialEq<&crate::oid> for ObjectId {
+ fn eq(&self, other: &&oid) -> bool {
+ self.as_ref() == *other
+ }
+}
diff --git a/vendor/gix-hash/src/oid.rs b/vendor/gix-hash/src/oid.rs
new file mode 100644
index 000000000..92ded0f87
--- /dev/null
+++ b/vendor/gix-hash/src/oid.rs
@@ -0,0 +1,257 @@
+use std::{convert::TryInto, fmt};
+
+use crate::{ObjectId, SIZE_OF_SHA1_DIGEST};
+
+/// A borrowed reference to a hash identifying objects.
+///
+/// # Future Proofing
+///
+/// In case we wish to support multiple hashes with the same length we cannot discriminate
+/// using the slice length anymore. To make that work, we will use the high bits of the
+/// internal `bytes` slice length (a fat pointer, pointing to data and its length in bytes)
+/// to encode additional information. Before accessing or returning the bytes, a new adjusted
+/// slice will be constructed, while the high bits will be used to help resolving the
+/// hash `[`kind()`][oid::kind()]`.
+/// We expect to have quite a few bits available for such 'conflict resolution' as most hashes aren't longer
+/// than 64 bytes.
+#[derive(PartialEq, Eq, Hash, Ord, PartialOrd)]
+#[repr(transparent)]
+#[allow(non_camel_case_types)]
+#[cfg_attr(feature = "serde1", derive(serde::Serialize))]
+pub struct oid {
+ bytes: [u8],
+}
+
+/// A utility able to format itself with the given amount of characters in hex
+#[derive(PartialEq, Eq, Hash, Ord, PartialOrd)]
+pub struct HexDisplay<'a> {
+ inner: &'a oid,
+ hex_len: usize,
+}
+
+impl<'a> fmt::Display for HexDisplay<'a> {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ let mut hex = crate::Kind::hex_buf();
+ let max_len = self.inner.hex_to_buf(hex.as_mut());
+ let hex = std::str::from_utf8(&hex[..self.hex_len.min(max_len)]).expect("ascii only in hex");
+ f.write_str(hex)
+ }
+}
+
+impl fmt::Debug for oid {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> std::fmt::Result {
+ write!(
+ f,
+ "{}({})",
+ match self.kind() {
+ crate::Kind::Sha1 => "Sha1",
+ },
+ self.to_hex(),
+ )
+ }
+}
+
+#[derive(Debug, thiserror::Error)]
+pub enum Error {
+ #[error("Cannot instantiate git hash from a digest of length {0}")]
+ InvalidByteSliceLength(usize),
+}
+
+/// Conversion
+impl oid {
+ /// Try to create a shared object id from a slice of bytes representing a hash `digest`
+ #[inline]
+ pub fn try_from_bytes(digest: &[u8]) -> Result<&Self, Error> {
+ match digest.len() {
+ 20 => Ok(
+ #[allow(unsafe_code)]
+ unsafe {
+ &*(digest as *const [u8] as *const oid)
+ },
+ ),
+ len => Err(Error::InvalidByteSliceLength(len)),
+ }
+ }
+
+ /// Create an OID from the input `value` slice without performing any safety check.
+ /// Use only once sure that `value` is a hash of valid length.
+ pub fn from_bytes_unchecked(value: &[u8]) -> &Self {
+ Self::from_bytes(value)
+ }
+
+ /// Only from code that statically assures correct sizes using array conversions
+ pub(crate) fn from_bytes(value: &[u8]) -> &Self {
+ #[allow(unsafe_code)]
+ unsafe {
+ &*(value as *const [u8] as *const oid)
+ }
+ }
+}
+
+/// Access
+impl oid {
+ /// The kind of hash used for this Digest
+ #[inline]
+ pub fn kind(&self) -> crate::Kind {
+ crate::Kind::from_len_in_bytes(self.bytes.len())
+ }
+
+ /// The first byte of the hash, commonly used to partition a set of `Id`s
+ #[inline]
+ pub fn first_byte(&self) -> u8 {
+ self.bytes[0]
+ }
+
+ /// Interpret this object id as raw byte slice.
+ #[inline]
+ pub fn as_bytes(&self) -> &[u8] {
+ &self.bytes
+ }
+
+ /// Return a type which can display itself in hexadecimal form with the `len` amount of characters.
+ #[inline]
+ pub fn to_hex_with_len(&self, len: usize) -> HexDisplay<'_> {
+ HexDisplay {
+ inner: self,
+ hex_len: len,
+ }
+ }
+
+ /// Return a type which displays this oid as hex in full.
+ #[inline]
+ pub fn to_hex(&self) -> HexDisplay<'_> {
+ HexDisplay {
+ inner: self,
+ hex_len: self.bytes.len() * 2,
+ }
+ }
+}
+
+/// Sha1 specific methods
+impl oid {
+ /// Write ourselves to the `out` in hexadecimal notation, returning the amount of written bytes.
+ ///
+ /// **Panics** if the buffer isn't big enough to hold twice as many bytes as the current binary size.
+ #[inline]
+ #[must_use]
+ pub fn hex_to_buf(&self, buf: &mut [u8]) -> usize {
+ let num_hex_bytes = self.bytes.len() * 2;
+ hex::encode_to_slice(&self.bytes, &mut buf[..num_hex_bytes]).expect("to count correctly");
+ num_hex_bytes
+ }
+
+ /// Write ourselves to `out` in hexadecimal notation
+ #[inline]
+ pub fn write_hex_to(&self, mut out: impl std::io::Write) -> std::io::Result<()> {
+ let mut hex = crate::Kind::hex_buf();
+ let hex_len = self.hex_to_buf(&mut hex);
+ out.write_all(&hex[..hex_len])
+ }
+
+ /// Returns a Sha1 digest with all bytes being initialized to zero.
+ #[inline]
+ pub(crate) fn null_sha1() -> &'static Self {
+ oid::from_bytes([0u8; SIZE_OF_SHA1_DIGEST].as_ref())
+ }
+}
+
+impl AsRef<oid> for &oid {
+ fn as_ref(&self) -> &oid {
+ self
+ }
+}
+
+impl ToOwned for oid {
+ type Owned = crate::ObjectId;
+
+ fn to_owned(&self) -> Self::Owned {
+ match self.kind() {
+ crate::Kind::Sha1 => crate::ObjectId::Sha1(self.bytes.try_into().expect("no bug in hash detection")),
+ }
+ }
+}
+
+impl<'a> From<&'a [u8; SIZE_OF_SHA1_DIGEST]> for &'a oid {
+ fn from(v: &'a [u8; SIZE_OF_SHA1_DIGEST]) -> Self {
+ oid::from_bytes(v.as_ref())
+ }
+}
+
+impl fmt::Display for &oid {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ for b in self.as_bytes() {
+ write!(f, "{b:02x}")?;
+ }
+ Ok(())
+ }
+}
+
+impl PartialEq<crate::ObjectId> for &oid {
+ fn eq(&self, other: &ObjectId) -> bool {
+ *self == other.as_ref()
+ }
+}
+
+/// Manually created from a version that uses a slice, and we forcefully try to convert it into a borrowed array of the desired size
+/// Could be improved by fitting this into serde
+/// Unfortunately the serde::Deserialize derive wouldn't work for borrowed arrays.
+#[cfg(feature = "serde1")]
+impl<'de: 'a, 'a> serde::Deserialize<'de> for &'a oid {
+ fn deserialize<D>(deserializer: D) -> Result<Self, <D as serde::Deserializer<'de>>::Error>
+ where
+ D: serde::Deserializer<'de>,
+ {
+ struct __Visitor<'de: 'a, 'a> {
+ marker: std::marker::PhantomData<&'a oid>,
+ lifetime: std::marker::PhantomData<&'de ()>,
+ }
+ impl<'de: 'a, 'a> serde::de::Visitor<'de> for __Visitor<'de, 'a> {
+ type Value = &'a oid;
+ fn expecting(&self, __formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ std::fmt::Formatter::write_str(__formatter, "tuple struct Digest")
+ }
+ #[inline]
+ fn visit_newtype_struct<__E>(self, __e: __E) -> std::result::Result<Self::Value, __E::Error>
+ where
+ __E: serde::Deserializer<'de>,
+ {
+ let __field0: &'a [u8] = match <&'a [u8] as serde::Deserialize>::deserialize(__e) {
+ Ok(__val) => __val,
+ Err(__err) => {
+ return Err(__err);
+ }
+ };
+ Ok(oid::try_from_bytes(__field0).expect("hash of known length"))
+ }
+ #[inline]
+ fn visit_seq<__A>(self, mut __seq: __A) -> std::result::Result<Self::Value, __A::Error>
+ where
+ __A: serde::de::SeqAccess<'de>,
+ {
+ let __field0 = match match serde::de::SeqAccess::next_element::<&'a [u8]>(&mut __seq) {
+ Ok(__val) => __val,
+ Err(__err) => {
+ return Err(__err);
+ }
+ } {
+ Some(__value) => __value,
+ None => {
+ return Err(serde::de::Error::invalid_length(
+ 0usize,
+ &"tuple struct Digest with 1 element",
+ ));
+ }
+ };
+ Ok(oid::try_from_bytes(__field0).expect("hash of known length"))
+ }
+ }
+ serde::Deserializer::deserialize_newtype_struct(
+ deserializer,
+ "Digest",
+ __Visitor {
+ marker: std::marker::PhantomData::<&'a oid>,
+ lifetime: std::marker::PhantomData,
+ },
+ )
+ }
+}
diff --git a/vendor/gix-hash/src/prefix.rs b/vendor/gix-hash/src/prefix.rs
new file mode 100644
index 000000000..a8b55922e
--- /dev/null
+++ b/vendor/gix-hash/src/prefix.rs
@@ -0,0 +1,152 @@
+use std::{cmp::Ordering, convert::TryFrom};
+
+use crate::{oid, ObjectId, Prefix};
+
+/// The error returned by [Prefix::new()].
+#[derive(Debug, thiserror::Error)]
+#[allow(missing_docs)]
+pub enum Error {
+ #[error(
+ "The minimum hex length of a short object id is {}, got {hex_len}",
+ Prefix::MIN_HEX_LEN
+ )]
+ TooShort { hex_len: usize },
+ #[error("An object of kind {object_kind} cannot be larger than {} in hex, but {hex_len} was requested", object_kind.len_in_hex())]
+ TooLong { object_kind: crate::Kind, hex_len: usize },
+}
+
+///
+pub mod from_hex {
+ /// The error returned by [Prefix::from_hex][super::Prefix::from_hex()].
+ #[derive(Debug, Eq, PartialEq, thiserror::Error)]
+ #[allow(missing_docs)]
+ pub enum Error {
+ #[error(
+ "The minimum hex length of a short object id is {}, got {hex_len}",
+ super::Prefix::MIN_HEX_LEN
+ )]
+ TooShort { hex_len: usize },
+ #[error("An id cannot be larger than {} chars in hex, but {hex_len} was requested", crate::Kind::longest().len_in_hex())]
+ TooLong { hex_len: usize },
+ #[error("Invalid character {c} at position {index}")]
+ Invalid { c: char, index: usize },
+ }
+}
+
+impl Prefix {
+ /// The smallest allowed prefix length below which chances for collisions are too high even in small repositories.
+ pub const MIN_HEX_LEN: usize = 4;
+
+ /// Create a new instance by taking a full `id` as input and truncating it to `hex_len`.
+ ///
+ /// For instance, with `hex_len` of 7 the resulting prefix is 3.5 bytes, or 3 bytes and 4 bits
+ /// wide, with all other bytes and bits set to zero.
+ pub fn new(id: impl AsRef<oid>, hex_len: usize) -> Result<Self, Error> {
+ let id = id.as_ref();
+ if hex_len > id.kind().len_in_hex() {
+ Err(Error::TooLong {
+ object_kind: id.kind(),
+ hex_len,
+ })
+ } else if hex_len < Self::MIN_HEX_LEN {
+ Err(Error::TooShort { hex_len })
+ } else {
+ let mut prefix = ObjectId::null(id.kind());
+ let b = prefix.as_mut_slice();
+ let copy_len = (hex_len + 1) / 2;
+ b[..copy_len].copy_from_slice(&id.as_bytes()[..copy_len]);
+ if hex_len % 2 == 1 {
+ b[hex_len / 2] &= 0xf0;
+ }
+
+ Ok(Prefix { bytes: prefix, hex_len })
+ }
+ }
+
+ /// Returns the prefix as object id.
+ ///
+ /// Note that it may be deceptive to use given that it looks like a full
+ /// object id, even though its post-prefix bytes/bits are set to zero.
+ pub fn as_oid(&self) -> &oid {
+ &self.bytes
+ }
+
+ /// Return the amount of hexadecimal characters that are set in the prefix.
+ ///
+ /// This gives the prefix a granularity of 4 bits.
+ pub fn hex_len(&self) -> usize {
+ self.hex_len
+ }
+
+ /// Provided with candidate id which is a full hash, determine how this prefix compares to it,
+ /// only looking at the prefix bytes, ignoring everything behind that.
+ pub fn cmp_oid(&self, candidate: &oid) -> Ordering {
+ let common_len = self.hex_len / 2;
+
+ self.bytes.as_bytes()[..common_len]
+ .cmp(&candidate.as_bytes()[..common_len])
+ .then(if self.hex_len % 2 == 1 {
+ let half_byte_idx = self.hex_len / 2;
+ self.bytes.as_bytes()[half_byte_idx].cmp(&(candidate.as_bytes()[half_byte_idx] & 0xf0))
+ } else {
+ Ordering::Equal
+ })
+ }
+
+ /// Create an instance from the given hexadecimal prefix `value`, e.g. `35e77c16` would yield a `Prefix` with `hex_len()` = 8.
+ pub fn from_hex(value: &str) -> Result<Self, from_hex::Error> {
+ use hex::FromHex;
+ let hex_len = value.len();
+
+ if hex_len > crate::Kind::longest().len_in_hex() {
+ return Err(from_hex::Error::TooLong { hex_len });
+ } else if hex_len < Self::MIN_HEX_LEN {
+ return Err(from_hex::Error::TooShort { hex_len });
+ };
+
+ let src = if value.len() % 2 == 0 {
+ Vec::from_hex(value)
+ } else {
+ let mut buf = [0u8; crate::Kind::longest().len_in_hex()];
+ buf[..value.len()].copy_from_slice(value.as_bytes());
+ buf[value.len()] = b'0';
+ Vec::from_hex(&buf[..value.len() + 1])
+ }
+ .map_err(|e| match e {
+ hex::FromHexError::InvalidHexCharacter { c, index } => from_hex::Error::Invalid { c, index },
+ hex::FromHexError::OddLength | hex::FromHexError::InvalidStringLength => panic!("This is already checked"),
+ })?;
+
+ let mut bytes = ObjectId::null(crate::Kind::from_hex_len(value.len()).expect("hex-len is already checked"));
+ let dst = bytes.as_mut_slice();
+ let copy_len = src.len();
+ dst[..copy_len].copy_from_slice(&src);
+
+ Ok(Prefix { bytes, hex_len })
+ }
+}
+
+/// Create an instance from the given hexadecimal prefix, e.g. `35e77c16` would yield a `Prefix`
+/// with `hex_len()` = 8.
+impl TryFrom<&str> for Prefix {
+ type Error = from_hex::Error;
+
+ fn try_from(value: &str) -> Result<Self, Self::Error> {
+ Prefix::from_hex(value)
+ }
+}
+
+impl std::fmt::Display for Prefix {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ self.bytes.to_hex_with_len(self.hex_len).fmt(f)
+ }
+}
+
+impl From<ObjectId> for Prefix {
+ fn from(oid: ObjectId) -> Self {
+ Prefix {
+ bytes: oid,
+ hex_len: oid.kind().len_in_hex(),
+ }
+ }
+}