Skip to main content

gix_hash/
oid.rs

1use std::hash;
2
3use crate::{Kind, ObjectId};
4
5#[cfg(feature = "sha1")]
6use crate::{EMPTY_BLOB_SHA1, EMPTY_TREE_SHA1, SIZE_OF_SHA1_DIGEST};
7
8#[cfg(feature = "sha256")]
9use crate::{EMPTY_BLOB_SHA256, EMPTY_TREE_SHA256, SIZE_OF_SHA256_DIGEST};
10
11/// A borrowed reference to a hash identifying objects.
12///
13/// # Future Proofing
14///
15/// In case we wish to support multiple hashes with the same length we cannot discriminate
16/// using the slice length anymore. To make that work, we will use the high bits of the
17/// internal `bytes` slice length (a fat pointer, pointing to data and its length in bytes)
18/// to encode additional information. Before accessing or returning the bytes, a new adjusted
19/// slice will be constructed, while the high bits will be used to help resolving the
20/// hash [`kind()`][oid::kind()].
21/// We expect to have quite a few bits available for such 'conflict resolution' as most hashes aren't longer
22/// than 64 bytes.
23#[derive(PartialEq, Eq, Ord, PartialOrd)]
24#[repr(transparent)]
25#[expect(non_camel_case_types, reason = "the name mirrors 'str'")]
26#[cfg_attr(feature = "serde", derive(serde::Serialize))]
27pub struct oid {
28    bytes: [u8],
29}
30
31// False positive:
32// Using an automatic implementation of `Hash` for `oid` would lead to
33// it attempting to hash the length of the slice first. On 32 bit systems
34// this can lead to issues with the custom `gix_hashtable` `Hasher` implementation,
35// and it currently ends up being discarded there anyway.
36impl hash::Hash for oid {
37    fn hash<H: hash::Hasher>(&self, state: &mut H) {
38        state.write(self.as_bytes());
39    }
40}
41
42/// A utility able to format itself with the given number of characters in hex.
43#[derive(PartialEq, Eq, Hash, Ord, PartialOrd)]
44pub struct HexDisplay<'a> {
45    inner: &'a oid,
46    hex_len: usize,
47}
48
49impl std::fmt::Display for HexDisplay<'_> {
50    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
51        let mut hex = Kind::hex_buf();
52        let hex = self.inner.hex_to_buf(hex.as_mut());
53        let max_len = hex.len();
54        f.write_str(&hex[..self.hex_len.min(max_len)])
55    }
56}
57
58impl std::fmt::Debug for oid {
59    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
60        write!(
61            f,
62            "{}({})",
63            match self.kind() {
64                #[cfg(feature = "sha1")]
65                Kind::Sha1 => "Sha1",
66                #[cfg(feature = "sha256")]
67                Kind::Sha256 => "Sha256",
68            },
69            self.to_hex(),
70        )
71    }
72}
73
74/// The error returned when trying to convert a byte slice to an [`oid`] or [`ObjectId`]
75#[expect(missing_docs)]
76#[derive(Debug, thiserror::Error)]
77pub enum Error {
78    #[error("Cannot instantiate git hash from a digest of length {0}")]
79    InvalidByteSliceLength(usize),
80}
81
82/// Conversion
83impl oid {
84    /// Try to create a shared object id from a slice of bytes representing a hash `digest`
85    #[inline]
86    pub fn try_from_bytes(digest: &[u8]) -> Result<&Self, Error> {
87        match digest.len() {
88            #[cfg(feature = "sha1")]
89            SIZE_OF_SHA1_DIGEST => Ok(
90                #[expect(unsafe_code)]
91                unsafe {
92                    &*(std::ptr::from_ref::<[u8]>(digest) as *const oid)
93                },
94            ),
95            #[cfg(feature = "sha256")]
96            SIZE_OF_SHA256_DIGEST => Ok(
97                #[expect(unsafe_code)]
98                unsafe {
99                    &*(std::ptr::from_ref::<[u8]>(digest) as *const oid)
100                },
101            ),
102            len => Err(Error::InvalidByteSliceLength(len)),
103        }
104    }
105
106    /// Create an `oid` from the input `value` slice without performing any length check.
107    /// Use only once you are sure that `value` is a hash of valid length, or panics will occur on most uses.
108    pub fn from_bytes_unchecked(value: &[u8]) -> &Self {
109        Self::from_bytes(value)
110    }
111
112    /// Only from code that statically assures correct sizes using array conversions.
113    pub(crate) fn from_bytes(value: &[u8]) -> &Self {
114        #[expect(unsafe_code)]
115        unsafe {
116            &*(std::ptr::from_ref::<[u8]>(value) as *const oid)
117        }
118    }
119}
120
121/// Access
122impl oid {
123    /// The kind of hash used for this instance.
124    #[inline]
125    pub fn kind(&self) -> Kind {
126        Kind::from_len_in_bytes(self.bytes.len())
127    }
128
129    /// The first byte of the hash, commonly used to partition a set of object ids.
130    #[inline]
131    pub fn first_byte(&self) -> u8 {
132        self.bytes[0]
133    }
134
135    /// Interpret this object id as raw byte slice.
136    #[inline]
137    pub fn as_bytes(&self) -> &[u8] {
138        &self.bytes
139    }
140
141    /// Return a type which can display itself in hexadecimal form with the `len` amount of characters.
142    #[inline]
143    pub fn to_hex_with_len(&self, len: usize) -> HexDisplay<'_> {
144        HexDisplay {
145            inner: self,
146            hex_len: len,
147        }
148    }
149
150    /// Return a type which displays this `oid` as hex in full.
151    #[inline]
152    pub fn to_hex(&self) -> HexDisplay<'_> {
153        HexDisplay {
154            inner: self,
155            hex_len: self.bytes.len() * 2,
156        }
157    }
158
159    /// Write ourselves to the `out` in hexadecimal notation, returning the hex-string ready for display.
160    ///
161    /// # Panics
162    ///
163    /// If the buffer isn't big enough to hold twice as many bytes as the current binary size.
164    #[inline]
165    #[must_use]
166    pub fn hex_to_buf<'a>(&self, buf: &'a mut [u8]) -> &'a mut str {
167        let num_hex_bytes = self.bytes.len() * 2;
168        faster_hex::hex_encode(&self.bytes, &mut buf[..num_hex_bytes])
169            .expect("buffer size must be at least twice the hash digest size in bytes")
170    }
171
172    /// Write ourselves to `out` in hexadecimal notation.
173    #[inline]
174    pub fn write_hex_to(&self, out: &mut dyn std::io::Write) -> std::io::Result<()> {
175        let mut hex = Kind::hex_buf();
176        let hex_len = self.hex_to_buf(&mut hex).len();
177        out.write_all(&hex[..hex_len])
178    }
179
180    /// Returns `true` if this hash consists of all null bytes.
181    #[inline]
182    #[doc(alias = "is_zero", alias = "git2")]
183    pub fn is_null(&self) -> bool {
184        match self.kind() {
185            #[cfg(feature = "sha1")]
186            Kind::Sha1 => &self.bytes == oid::null_sha1().as_bytes(),
187            #[cfg(feature = "sha256")]
188            Kind::Sha256 => &self.bytes == oid::null_sha256().as_bytes(),
189        }
190    }
191
192    /// Returns `true` if this hash is equal to an empty blob.
193    #[inline]
194    pub fn is_empty_blob(&self) -> bool {
195        match self.kind() {
196            #[cfg(feature = "sha1")]
197            Kind::Sha1 => &self.bytes == oid::empty_blob_sha1().as_bytes(),
198            #[cfg(feature = "sha256")]
199            Kind::Sha256 => &self.bytes == oid::empty_blob_sha256().as_bytes(),
200        }
201    }
202
203    /// Returns `true` if this hash is equal to an empty tree.
204    #[inline]
205    pub fn is_empty_tree(&self) -> bool {
206        match self.kind() {
207            #[cfg(feature = "sha1")]
208            Kind::Sha1 => &self.bytes == oid::empty_tree_sha1().as_bytes(),
209            #[cfg(feature = "sha256")]
210            Kind::Sha256 => &self.bytes == oid::empty_tree_sha256().as_bytes(),
211        }
212    }
213}
214
215/// Methods for creating special-case `oid`s (null, empty blob, empty tree)
216impl oid {
217    /// Returns a SHA1 digest with all bytes being initialized to zero.
218    #[inline]
219    #[cfg(feature = "sha1")]
220    pub(crate) fn null_sha1() -> &'static Self {
221        oid::from_bytes([0u8; SIZE_OF_SHA1_DIGEST].as_ref())
222    }
223
224    /// Returns a SHA256 digest with all bytes being initialized to zero.
225    #[inline]
226    #[cfg(feature = "sha256")]
227    pub(crate) fn null_sha256() -> &'static Self {
228        oid::from_bytes([0u8; SIZE_OF_SHA256_DIGEST].as_ref())
229    }
230
231    /// Returns an `oid` representing the SHA1 hash of an empty blob.
232    #[inline]
233    #[cfg(feature = "sha1")]
234    pub(crate) fn empty_blob_sha1() -> &'static Self {
235        oid::from_bytes(EMPTY_BLOB_SHA1)
236    }
237
238    /// Returns an `oid` representing the SHA256 hash of an empty blob.
239    #[inline]
240    #[cfg(feature = "sha256")]
241    pub(crate) fn empty_blob_sha256() -> &'static Self {
242        oid::from_bytes(EMPTY_BLOB_SHA256)
243    }
244
245    /// Returns an `oid` representing the SHA1 hash of an empty tree.
246    #[inline]
247    #[cfg(feature = "sha1")]
248    pub(crate) fn empty_tree_sha1() -> &'static Self {
249        oid::from_bytes(EMPTY_TREE_SHA1)
250    }
251
252    /// Returns an `oid` representing the SHA256 hash of an empty tree.
253    #[inline]
254    #[cfg(feature = "sha256")]
255    pub(crate) fn empty_tree_sha256() -> &'static Self {
256        oid::from_bytes(EMPTY_TREE_SHA256)
257    }
258}
259
260impl AsRef<oid> for &oid {
261    fn as_ref(&self) -> &oid {
262        self
263    }
264}
265
266impl<'a> TryFrom<&'a [u8]> for &'a oid {
267    type Error = Error;
268
269    fn try_from(value: &'a [u8]) -> Result<Self, Self::Error> {
270        oid::try_from_bytes(value)
271    }
272}
273
274impl ToOwned for oid {
275    type Owned = ObjectId;
276
277    fn to_owned(&self) -> Self::Owned {
278        match self.kind() {
279            #[cfg(feature = "sha1")]
280            Kind::Sha1 => ObjectId::Sha1(self.bytes.try_into().expect("no bug in hash detection")),
281            #[cfg(feature = "sha256")]
282            Kind::Sha256 => ObjectId::Sha256(self.bytes.try_into().expect("no bug in hash detection")),
283        }
284    }
285}
286
287#[cfg(feature = "sha1")]
288impl<'a> From<&'a [u8; SIZE_OF_SHA1_DIGEST]> for &'a oid {
289    fn from(v: &'a [u8; SIZE_OF_SHA1_DIGEST]) -> Self {
290        oid::from_bytes(v.as_ref())
291    }
292}
293
294#[cfg(feature = "sha256")]
295impl<'a> From<&'a [u8; SIZE_OF_SHA256_DIGEST]> for &'a oid {
296    fn from(v: &'a [u8; SIZE_OF_SHA256_DIGEST]) -> Self {
297        oid::from_bytes(v.as_ref())
298    }
299}
300
301impl std::fmt::Display for &oid {
302    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
303        let mut buf = Kind::hex_buf();
304        f.write_str(self.hex_to_buf(&mut buf))
305    }
306}
307
308impl PartialEq<ObjectId> for &oid {
309    fn eq(&self, other: &ObjectId) -> bool {
310        *self == other.as_ref()
311    }
312}
313
314/// Manually created from a version that uses a slice, and we forcefully try to convert it into a borrowed array of the desired size
315/// Could be improved by fitting this into serde.
316/// Unfortunately the `serde::Deserialize` derive wouldn't work for borrowed arrays.
317#[cfg(feature = "serde")]
318impl<'de: 'a, 'a> serde::Deserialize<'de> for &'a oid {
319    fn deserialize<D>(deserializer: D) -> Result<Self, <D as serde::Deserializer<'de>>::Error>
320    where
321        D: serde::Deserializer<'de>,
322    {
323        struct __Visitor<'de: 'a, 'a> {
324            marker: std::marker::PhantomData<&'a oid>,
325            lifetime: std::marker::PhantomData<&'de ()>,
326        }
327        impl<'de: 'a, 'a> serde::de::Visitor<'de> for __Visitor<'de, 'a> {
328            type Value = &'a oid;
329            fn expecting(&self, __formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
330                std::fmt::Formatter::write_str(__formatter, "tuple struct Digest")
331            }
332            #[inline]
333            fn visit_newtype_struct<__E>(self, __e: __E) -> std::result::Result<Self::Value, __E::Error>
334            where
335                __E: serde::Deserializer<'de>,
336            {
337                let __field0: &'a [u8] = match <&'a [u8] as serde::Deserialize>::deserialize(__e) {
338                    Ok(__val) => __val,
339                    Err(__err) => {
340                        return Err(__err);
341                    }
342                };
343                Ok(oid::try_from_bytes(__field0).expect("hash of known length"))
344            }
345            #[inline]
346            fn visit_seq<__A>(self, mut __seq: __A) -> std::result::Result<Self::Value, __A::Error>
347            where
348                __A: serde::de::SeqAccess<'de>,
349            {
350                let __field0 = match match serde::de::SeqAccess::next_element::<&'a [u8]>(&mut __seq) {
351                    Ok(__val) => __val,
352                    Err(__err) => {
353                        return Err(__err);
354                    }
355                } {
356                    Some(__value) => __value,
357                    None => {
358                        return Err(serde::de::Error::invalid_length(
359                            0usize,
360                            &"tuple struct Digest with 1 element",
361                        ));
362                    }
363                };
364                Ok(oid::try_from_bytes(__field0).expect("hash of known length"))
365            }
366        }
367        serde::Deserializer::deserialize_newtype_struct(
368            deserializer,
369            "Digest",
370            __Visitor {
371                marker: std::marker::PhantomData::<&'a oid>,
372                lifetime: std::marker::PhantomData,
373            },
374        )
375    }
376}