git_hash/
object_id.rs

1use std::{
2    borrow::Borrow,
3    convert::TryInto,
4    fmt,
5    hash::{Hash, Hasher},
6    ops::Deref,
7};
8
9use crate::{borrowed::oid, Kind, SIZE_OF_SHA1_DIGEST};
10
11/// An owned hash identifying objects, most commonly Sha1
12#[derive(PartialEq, Eq, Ord, PartialOrd, Clone, Copy)]
13#[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))]
14pub enum ObjectId {
15    /// A SHA 1 hash digest
16    Sha1([u8; SIZE_OF_SHA1_DIGEST]),
17}
18
19// False positive: https://github.com/rust-lang/rust-clippy/issues/2627
20// ignoring some fields while hashing is perfectly valid and just leads to
21// increased HashCollisions. One Sha1 being a prefix of another Sha256 is
22// extremely unlikely to begin with so it doesn't matter.
23// This implementation matches the `Hash` implementation for `oid`
24// and allows the usage of custom Hashers that only copy a truncated ShaHash
25#[allow(clippy::derive_hash_xor_eq)]
26impl Hash for ObjectId {
27    fn hash<H: Hasher>(&self, state: &mut H) {
28        state.write(self.as_slice())
29    }
30}
31
32#[allow(missing_docs)]
33pub mod decode {
34    use std::str::FromStr;
35
36    use crate::object_id::ObjectId;
37
38    /// An error returned by [`ObjectId::from_hex()`][crate::ObjectId::from_hex()]
39    #[derive(Debug, thiserror::Error)]
40    #[allow(missing_docs)]
41    pub enum Error {
42        #[error("A hash sized {0} hexadecimal characters is invalid")]
43        InvalidHexEncodingLength(usize),
44        #[error("Invalid character {c} at position {index}")]
45        Invalid { c: char, index: usize },
46    }
47
48    /// Hash decoding
49    impl ObjectId {
50        /// Create an instance from a `buffer` of 40 bytes encoded with hexadecimal notation.
51        ///
52        /// Such a buffer can be obtained using [`oid::write_hex_to(buffer)`][super::oid::write_hex_to()]
53        pub fn from_hex(buffer: &[u8]) -> Result<ObjectId, Error> {
54            use hex::FromHex;
55            match buffer.len() {
56                40 => Ok(ObjectId::Sha1(<[u8; 20]>::from_hex(buffer).map_err(
57                    |err| match err {
58                        hex::FromHexError::InvalidHexCharacter { c, index } => Error::Invalid { c, index },
59                        hex::FromHexError::OddLength | hex::FromHexError::InvalidStringLength => {
60                            unreachable!("BUG: This is already checked")
61                        }
62                    },
63                )?)),
64                len => Err(Error::InvalidHexEncodingLength(len)),
65            }
66        }
67    }
68
69    impl FromStr for ObjectId {
70        type Err = Error;
71
72        fn from_str(s: &str) -> Result<Self, Self::Err> {
73            Self::from_hex(s.as_bytes())
74        }
75    }
76}
77
78/// Access and conversion
79impl ObjectId {
80    /// Returns the kind of hash used in this `Id`
81    #[inline]
82    pub fn kind(&self) -> crate::Kind {
83        match self {
84            ObjectId::Sha1(_) => crate::Kind::Sha1,
85        }
86    }
87    /// Return the raw byte slice representing this hash
88    #[inline]
89    pub fn as_slice(&self) -> &[u8] {
90        match self {
91            Self::Sha1(b) => b.as_ref(),
92        }
93    }
94    /// Return the raw mutable byte slice representing this hash
95    #[inline]
96    pub fn as_mut_slice(&mut self) -> &mut [u8] {
97        match self {
98            Self::Sha1(b) => b.as_mut(),
99        }
100    }
101
102    /// The hash of an empty tree
103    #[inline]
104    pub const fn empty_tree(hash: Kind) -> ObjectId {
105        match hash {
106            Kind::Sha1 => {
107                ObjectId::Sha1(*b"\x4b\x82\x5d\xc6\x42\xcb\x6e\xb9\xa0\x60\xe5\x4b\xf8\xd6\x92\x88\xfb\xee\x49\x04")
108            }
109        }
110    }
111
112    /// Returns true if this hash consists of all null bytes
113    #[inline]
114    pub fn is_null(&self) -> bool {
115        match self {
116            ObjectId::Sha1(digest) => &digest[..] == oid::null_sha1().as_bytes(),
117        }
118    }
119
120    /// Returns an Digest representing a hash with whose memory is zeroed.
121    #[inline]
122    pub const fn null(kind: crate::Kind) -> ObjectId {
123        match kind {
124            crate::Kind::Sha1 => Self::null_sha1(),
125        }
126    }
127}
128
129/// Sha1 hash specific methods
130impl ObjectId {
131    /// Instantiate an Digest from 20 bytes of a Sha1 digest.
132    #[inline]
133    fn new_sha1(id: [u8; SIZE_OF_SHA1_DIGEST]) -> Self {
134        ObjectId::Sha1(id)
135    }
136
137    /// Instantiate an Digest from a slice 20 borrowed bytes of a Sha1 digest.
138    ///
139    /// Panics of the slice doesn't have a length of 20.
140    #[inline]
141    pub(crate) fn from_20_bytes(b: &[u8]) -> ObjectId {
142        let mut id = [0; SIZE_OF_SHA1_DIGEST];
143        id.copy_from_slice(b);
144        ObjectId::Sha1(id)
145    }
146
147    /// Returns an Digest representing a Sha1 with whose memory is zeroed.
148    #[inline]
149    pub(crate) const fn null_sha1() -> ObjectId {
150        ObjectId::Sha1([0u8; 20])
151    }
152}
153
154impl std::fmt::Debug for ObjectId {
155    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
156        match self {
157            ObjectId::Sha1(_hash) => f.write_str("Sha1(")?,
158        }
159        for b in self.as_bytes() {
160            write!(f, "{b:02x}")?;
161        }
162        f.write_str(")")
163    }
164}
165
166impl From<[u8; SIZE_OF_SHA1_DIGEST]> for ObjectId {
167    fn from(v: [u8; 20]) -> Self {
168        Self::new_sha1(v)
169    }
170}
171
172impl From<&[u8]> for ObjectId {
173    fn from(v: &[u8]) -> Self {
174        match v.len() {
175            20 => Self::Sha1(v.try_into().expect("prior length validation")),
176            other => panic!("BUG: unsupported hash len: {other}"),
177        }
178    }
179}
180
181impl From<&crate::oid> for ObjectId {
182    fn from(v: &oid) -> Self {
183        match v.kind() {
184            crate::Kind::Sha1 => ObjectId::from_20_bytes(v.as_bytes()),
185        }
186    }
187}
188
189impl Deref for ObjectId {
190    type Target = oid;
191
192    fn deref(&self) -> &Self::Target {
193        self.as_ref()
194    }
195}
196
197impl AsRef<crate::oid> for ObjectId {
198    fn as_ref(&self) -> &oid {
199        oid::from_bytes_unchecked(self.as_slice())
200    }
201}
202
203impl Borrow<crate::oid> for ObjectId {
204    fn borrow(&self) -> &oid {
205        self.as_ref()
206    }
207}
208
209impl fmt::Display for ObjectId {
210    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
211        write!(f, "{}", self.to_hex())
212    }
213}
214
215impl PartialEq<&crate::oid> for ObjectId {
216    fn eq(&self, other: &&oid) -> bool {
217        self.as_ref() == *other
218    }
219}