diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-04 12:41:41 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-04 12:41:41 +0000 |
commit | 10ee2acdd26a7f1298c6f6d6b7af9b469fe29b87 (patch) | |
tree | bdffd5d80c26cf4a7a518281a204be1ace85b4c1 /vendor/gix-hash/src | |
parent | Releasing progress-linux version 1.70.0+dfsg1-9~progress7.99u1. (diff) | |
download | rustc-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.rs | 122 | ||||
-rw-r--r-- | vendor/gix-hash/src/lib.rs | 42 | ||||
-rw-r--r-- | vendor/gix-hash/src/object_id.rs | 229 | ||||
-rw-r--r-- | vendor/gix-hash/src/oid.rs | 257 | ||||
-rw-r--r-- | vendor/gix-hash/src/prefix.rs | 152 |
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(), + } + } +} |