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#[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
31impl hash::Hash for oid {
37 fn hash<H: hash::Hasher>(&self, state: &mut H) {
38 state.write(self.as_bytes());
39 }
40}
41
42#[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#[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
82impl oid {
84 #[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 pub fn from_bytes_unchecked(value: &[u8]) -> &Self {
109 Self::from_bytes(value)
110 }
111
112 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
121impl oid {
123 #[inline]
125 pub fn kind(&self) -> Kind {
126 Kind::from_len_in_bytes(self.bytes.len())
127 }
128
129 #[inline]
131 pub fn first_byte(&self) -> u8 {
132 self.bytes[0]
133 }
134
135 #[inline]
137 pub fn as_bytes(&self) -> &[u8] {
138 &self.bytes
139 }
140
141 #[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 #[inline]
152 pub fn to_hex(&self) -> HexDisplay<'_> {
153 HexDisplay {
154 inner: self,
155 hex_len: self.bytes.len() * 2,
156 }
157 }
158
159 #[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 #[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 #[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 #[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 #[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
215impl oid {
217 #[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 #[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 #[inline]
233 #[cfg(feature = "sha1")]
234 pub(crate) fn empty_blob_sha1() -> &'static Self {
235 oid::from_bytes(EMPTY_BLOB_SHA1)
236 }
237
238 #[inline]
240 #[cfg(feature = "sha256")]
241 pub(crate) fn empty_blob_sha256() -> &'static Self {
242 oid::from_bytes(EMPTY_BLOB_SHA256)
243 }
244
245 #[inline]
247 #[cfg(feature = "sha1")]
248 pub(crate) fn empty_tree_sha1() -> &'static Self {
249 oid::from_bytes(EMPTY_TREE_SHA1)
250 }
251
252 #[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#[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}