1use std::{hash, ops::Range};
2
3use crate::{Kind, ObjectId, Prefix};
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 HexDisplay<'_> {
59 pub(crate) fn eq_str(&self, other: &str) -> bool {
60 let mut hex = Kind::hex_buf();
61 let hex = self.inner.hex_to_buf(hex.as_mut());
62 hex[..self.hex_len.min(hex.len())] == *other
63 }
64}
65
66impl_partial_eq_str_one_way!(HexDisplay<'_>);
68
69impl std::fmt::Debug for oid {
70 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
71 write!(
72 f,
73 "{}({})",
74 match self.kind() {
75 #[cfg(feature = "sha1")]
76 Kind::Sha1 => "Sha1",
77 #[cfg(feature = "sha256")]
78 Kind::Sha256 => "Sha256",
79 },
80 self.to_hex(),
81 )
82 }
83}
84
85#[expect(missing_docs)]
87#[derive(Debug, thiserror::Error)]
88pub enum Error {
89 #[error("Cannot instantiate git hash from a digest of length {0}")]
90 InvalidByteSliceLength(usize),
91}
92
93impl oid {
95 #[inline]
97 pub fn try_from_bytes(digest: &[u8]) -> Result<&Self, Error> {
98 match digest.len() {
99 #[cfg(feature = "sha1")]
100 SIZE_OF_SHA1_DIGEST => Ok(
101 #[expect(unsafe_code)]
102 unsafe {
103 &*(std::ptr::from_ref::<[u8]>(digest) as *const oid)
104 },
105 ),
106 #[cfg(feature = "sha256")]
107 SIZE_OF_SHA256_DIGEST => Ok(
108 #[expect(unsafe_code)]
109 unsafe {
110 &*(std::ptr::from_ref::<[u8]>(digest) as *const oid)
111 },
112 ),
113 len => Err(Error::InvalidByteSliceLength(len)),
114 }
115 }
116
117 pub fn from_bytes_unchecked(value: &[u8]) -> &Self {
120 Self::from_bytes(value)
121 }
122
123 pub(crate) fn from_bytes(value: &[u8]) -> &Self {
125 #[expect(unsafe_code)]
126 unsafe {
127 &*(std::ptr::from_ref::<[u8]>(value) as *const oid)
128 }
129 }
130}
131
132impl oid {
134 #[inline]
136 pub fn kind(&self) -> Kind {
137 Kind::from_len_in_bytes(self.bytes.len())
138 }
139
140 #[inline]
142 pub fn first_byte(&self) -> u8 {
143 self.bytes[0]
144 }
145
146 #[inline]
148 pub fn as_bytes(&self) -> &[u8] {
149 &self.bytes
150 }
151
152 #[inline]
154 pub fn to_hex_with_len(&self, len: usize) -> HexDisplay<'_> {
155 HexDisplay {
156 inner: self,
157 hex_len: len,
158 }
159 }
160
161 #[inline]
163 pub fn to_hex(&self) -> HexDisplay<'_> {
164 HexDisplay {
165 inner: self,
166 hex_len: self.bytes.len() * 2,
167 }
168 }
169
170 #[inline]
180 pub fn to_prefix(&self, range: Range<usize>) -> Prefix {
181 let selected = &self.bytes[range];
182 let mut bytes = ObjectId::null(self.kind());
183 bytes.as_mut_slice()[..selected.len()].copy_from_slice(selected);
184 Prefix {
185 bytes,
186 hex_len: selected.len() * 2,
187 }
188 }
189
190 #[inline]
196 #[must_use]
197 pub fn hex_to_buf<'a>(&self, buf: &'a mut [u8]) -> &'a mut str {
198 let num_hex_bytes = self.bytes.len() * 2;
199 faster_hex::hex_encode(&self.bytes, &mut buf[..num_hex_bytes])
200 .expect("buffer size must be at least twice the hash digest size in bytes")
201 }
202
203 #[inline]
205 pub fn write_hex_to(&self, out: &mut dyn std::io::Write) -> std::io::Result<()> {
206 let mut hex = Kind::hex_buf();
207 let hex_len = self.hex_to_buf(&mut hex).len();
208 out.write_all(&hex[..hex_len])
209 }
210
211 pub(crate) fn eq_str(&self, other: &str) -> bool {
212 self.to_hex().eq_str(other)
213 }
214
215 #[inline]
217 #[doc(alias = "is_zero", alias = "git2")]
218 pub fn is_null(&self) -> bool {
219 match self.kind() {
220 #[cfg(feature = "sha1")]
221 Kind::Sha1 => &self.bytes == oid::null_sha1().as_bytes(),
222 #[cfg(feature = "sha256")]
223 Kind::Sha256 => &self.bytes == oid::null_sha256().as_bytes(),
224 }
225 }
226
227 #[inline]
229 pub fn is_empty_blob(&self) -> bool {
230 match self.kind() {
231 #[cfg(feature = "sha1")]
232 Kind::Sha1 => &self.bytes == oid::empty_blob_sha1().as_bytes(),
233 #[cfg(feature = "sha256")]
234 Kind::Sha256 => &self.bytes == oid::empty_blob_sha256().as_bytes(),
235 }
236 }
237
238 #[inline]
240 pub fn is_empty_tree(&self) -> bool {
241 match self.kind() {
242 #[cfg(feature = "sha1")]
243 Kind::Sha1 => &self.bytes == oid::empty_tree_sha1().as_bytes(),
244 #[cfg(feature = "sha256")]
245 Kind::Sha256 => &self.bytes == oid::empty_tree_sha256().as_bytes(),
246 }
247 }
248}
249
250impl oid {
252 #[inline]
254 #[cfg(feature = "sha1")]
255 pub(crate) fn null_sha1() -> &'static Self {
256 oid::from_bytes([0u8; SIZE_OF_SHA1_DIGEST].as_ref())
257 }
258
259 #[inline]
261 #[cfg(feature = "sha256")]
262 pub(crate) fn null_sha256() -> &'static Self {
263 oid::from_bytes([0u8; SIZE_OF_SHA256_DIGEST].as_ref())
264 }
265
266 #[inline]
268 #[cfg(feature = "sha1")]
269 pub(crate) fn empty_blob_sha1() -> &'static Self {
270 oid::from_bytes(EMPTY_BLOB_SHA1)
271 }
272
273 #[inline]
275 #[cfg(feature = "sha256")]
276 pub(crate) fn empty_blob_sha256() -> &'static Self {
277 oid::from_bytes(EMPTY_BLOB_SHA256)
278 }
279
280 #[inline]
282 #[cfg(feature = "sha1")]
283 pub(crate) fn empty_tree_sha1() -> &'static Self {
284 oid::from_bytes(EMPTY_TREE_SHA1)
285 }
286
287 #[inline]
289 #[cfg(feature = "sha256")]
290 pub(crate) fn empty_tree_sha256() -> &'static Self {
291 oid::from_bytes(EMPTY_TREE_SHA256)
292 }
293}
294
295impl AsRef<oid> for &oid {
296 fn as_ref(&self) -> &oid {
297 self
298 }
299}
300
301impl<'a> TryFrom<&'a [u8]> for &'a oid {
302 type Error = Error;
303
304 fn try_from(value: &'a [u8]) -> Result<Self, Self::Error> {
305 oid::try_from_bytes(value)
306 }
307}
308
309impl ToOwned for oid {
310 type Owned = ObjectId;
311
312 fn to_owned(&self) -> Self::Owned {
313 match self.kind() {
314 #[cfg(feature = "sha1")]
315 Kind::Sha1 => ObjectId::Sha1(self.bytes.try_into().expect("no bug in hash detection")),
316 #[cfg(feature = "sha256")]
317 Kind::Sha256 => ObjectId::Sha256(self.bytes.try_into().expect("no bug in hash detection")),
318 }
319 }
320}
321
322#[cfg(feature = "sha1")]
323impl<'a> From<&'a [u8; SIZE_OF_SHA1_DIGEST]> for &'a oid {
324 fn from(v: &'a [u8; SIZE_OF_SHA1_DIGEST]) -> Self {
325 oid::from_bytes(v.as_ref())
326 }
327}
328
329#[cfg(feature = "sha256")]
330impl<'a> From<&'a [u8; SIZE_OF_SHA256_DIGEST]> for &'a oid {
331 fn from(v: &'a [u8; SIZE_OF_SHA256_DIGEST]) -> Self {
332 oid::from_bytes(v.as_ref())
333 }
334}
335
336impl std::fmt::Display for &oid {
337 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
338 let mut buf = Kind::hex_buf();
339 f.write_str(self.hex_to_buf(&mut buf))
340 }
341}
342
343impl PartialEq<ObjectId> for &oid {
344 fn eq(&self, other: &ObjectId) -> bool {
345 *self == other.as_ref()
346 }
347}
348
349impl PartialEq<String> for &oid {
350 fn eq(&self, other: &String) -> bool {
351 self.eq_str(other)
352 }
353}
354
355impl PartialEq<&oid> for String {
356 fn eq(&self, other: &&oid) -> bool {
357 other.eq_str(self)
358 }
359}
360
361impl_partial_eq_str!(oid);
362
363#[cfg(feature = "serde")]
367impl<'de: 'a, 'a> serde::Deserialize<'de> for &'a oid {
368 fn deserialize<D>(deserializer: D) -> Result<Self, <D as serde::Deserializer<'de>>::Error>
369 where
370 D: serde::Deserializer<'de>,
371 {
372 struct __Visitor<'de: 'a, 'a> {
373 marker: std::marker::PhantomData<&'a oid>,
374 lifetime: std::marker::PhantomData<&'de ()>,
375 }
376 impl<'de: 'a, 'a> serde::de::Visitor<'de> for __Visitor<'de, 'a> {
377 type Value = &'a oid;
378 fn expecting(&self, __formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
379 std::fmt::Formatter::write_str(__formatter, "tuple struct Digest")
380 }
381 #[inline]
382 fn visit_newtype_struct<__E>(self, __e: __E) -> std::result::Result<Self::Value, __E::Error>
383 where
384 __E: serde::Deserializer<'de>,
385 {
386 let __field0: &'a [u8] = match <&'a [u8] as serde::Deserialize>::deserialize(__e) {
387 Ok(__val) => __val,
388 Err(__err) => {
389 return Err(__err);
390 }
391 };
392 Ok(oid::try_from_bytes(__field0).expect("hash of known length"))
393 }
394 #[inline]
395 fn visit_seq<__A>(self, mut __seq: __A) -> std::result::Result<Self::Value, __A::Error>
396 where
397 __A: serde::de::SeqAccess<'de>,
398 {
399 let __field0 = match match serde::de::SeqAccess::next_element::<&'a [u8]>(&mut __seq) {
400 Ok(__val) => __val,
401 Err(__err) => {
402 return Err(__err);
403 }
404 } {
405 Some(__value) => __value,
406 None => {
407 return Err(serde::de::Error::invalid_length(
408 0usize,
409 &"tuple struct Digest with 1 element",
410 ));
411 }
412 };
413 Ok(oid::try_from_bytes(__field0).expect("hash of known length"))
414 }
415 }
416 serde::Deserializer::deserialize_newtype_struct(
417 deserializer,
418 "Digest",
419 __Visitor {
420 marker: std::marker::PhantomData::<&'a oid>,
421 lifetime: std::marker::PhantomData,
422 },
423 )
424 }
425}