1use std::{
2 borrow::Borrow,
3 hash::{Hash, Hasher},
4 ops::Deref,
5};
6
7#[cfg(feature = "bstr")]
8use bstr::{BStr, BString, ByteSlice};
9
10use crate::{Kind, borrowed::oid};
11
12#[cfg(feature = "sha1")]
13use crate::{EMPTY_BLOB_SHA1, EMPTY_TREE_SHA1, SIZE_OF_SHA1_DIGEST};
14
15#[cfg(feature = "sha256")]
16use crate::{EMPTY_BLOB_SHA256, EMPTY_TREE_SHA256, SIZE_OF_SHA256_DIGEST};
17
18#[derive(PartialEq, Eq, Ord, PartialOrd, Clone, Copy)]
20#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
21#[non_exhaustive]
22pub enum ObjectId {
23 #[cfg(feature = "sha1")]
25 Sha1([u8; SIZE_OF_SHA1_DIGEST]),
26 #[cfg(feature = "sha256")]
28 Sha256([u8; SIZE_OF_SHA256_DIGEST]),
29}
30
31impl Hash for ObjectId {
38 fn hash<H: Hasher>(&self, state: &mut H) {
39 state.write(self.as_slice());
40 }
41}
42
43#[expect(missing_docs)]
44pub mod decode {
45 use std::str::FromStr;
46
47 use crate::object_id::ObjectId;
48
49 #[cfg(feature = "sha1")]
50 use crate::{SIZE_OF_SHA1_DIGEST, SIZE_OF_SHA1_HEX_DIGEST};
51
52 #[cfg(feature = "sha256")]
53 use crate::{SIZE_OF_SHA256_DIGEST, SIZE_OF_SHA256_HEX_DIGEST};
54
55 #[derive(Debug, thiserror::Error)]
57 #[expect(missing_docs)]
58 pub enum Error {
59 #[error("A hash sized {0} hexadecimal characters is invalid")]
60 InvalidHexEncodingLength(usize),
61 #[error("Invalid character encountered")]
62 Invalid,
63 }
64
65 impl ObjectId {
67 pub fn from_hex(buffer: &[u8]) -> Result<ObjectId, Error> {
73 match buffer.len() {
74 #[cfg(feature = "sha1")]
75 SIZE_OF_SHA1_HEX_DIGEST => Ok({
76 ObjectId::Sha1({
77 let mut buf = [0; SIZE_OF_SHA1_DIGEST];
78 faster_hex::hex_decode(buffer, &mut buf).map_err(|err| match err {
79 faster_hex::Error::InvalidChar | faster_hex::Error::Overflow => Error::Invalid,
80 faster_hex::Error::InvalidLength(_) => {
81 unreachable!("BUG: This is already checked")
82 }
83 })?;
84 buf
85 })
86 }),
87 #[cfg(feature = "sha256")]
88 SIZE_OF_SHA256_HEX_DIGEST => Ok({
89 ObjectId::Sha256({
90 let mut buf = [0; SIZE_OF_SHA256_DIGEST];
91 faster_hex::hex_decode(buffer, &mut buf).map_err(|err| match err {
92 faster_hex::Error::InvalidChar | faster_hex::Error::Overflow => Error::Invalid,
93 faster_hex::Error::InvalidLength(_) => {
94 unreachable!("BUG: This is already checked")
95 }
96 })?;
97 buf
98 })
99 }),
100 len => Err(Error::InvalidHexEncodingLength(len)),
101 }
102 }
103 }
104
105 impl FromStr for ObjectId {
106 type Err = Error;
107
108 fn from_str(s: &str) -> Result<Self, Self::Err> {
109 Self::from_hex(s.as_bytes())
110 }
111 }
112}
113
114impl ObjectId {
116 #[inline]
118 pub fn kind(&self) -> Kind {
119 match self {
120 #[cfg(feature = "sha1")]
121 ObjectId::Sha1(_) => Kind::Sha1,
122 #[cfg(feature = "sha256")]
123 ObjectId::Sha256(_) => Kind::Sha256,
124 }
125 }
126 #[inline]
128 pub fn as_slice(&self) -> &[u8] {
129 match self {
130 #[cfg(feature = "sha1")]
131 Self::Sha1(b) => b.as_ref(),
132 #[cfg(feature = "sha256")]
133 Self::Sha256(b) => b.as_ref(),
134 }
135 }
136 #[inline]
138 pub fn as_mut_slice(&mut self) -> &mut [u8] {
139 match self {
140 #[cfg(feature = "sha1")]
141 Self::Sha1(b) => b.as_mut(),
142 #[cfg(feature = "sha256")]
143 Self::Sha256(b) => b.as_mut(),
144 }
145 }
146
147 #[inline]
149 pub const fn empty_blob(hash: Kind) -> ObjectId {
150 match hash {
151 #[cfg(feature = "sha1")]
152 Kind::Sha1 => ObjectId::Sha1(*EMPTY_BLOB_SHA1),
153 #[cfg(feature = "sha256")]
154 Kind::Sha256 => ObjectId::Sha256(*EMPTY_BLOB_SHA256),
155 }
156 }
157
158 #[inline]
160 pub const fn empty_tree(hash: Kind) -> ObjectId {
161 match hash {
162 #[cfg(feature = "sha1")]
163 Kind::Sha1 => ObjectId::Sha1(*EMPTY_TREE_SHA1),
164 #[cfg(feature = "sha256")]
165 Kind::Sha256 => ObjectId::Sha256(*EMPTY_TREE_SHA256),
166 }
167 }
168
169 #[inline]
171 #[doc(alias = "zero", alias = "git2")]
172 pub const fn null(kind: Kind) -> ObjectId {
173 match kind {
174 #[cfg(feature = "sha1")]
175 Kind::Sha1 => Self::null_sha1(),
176 #[cfg(feature = "sha256")]
177 Kind::Sha256 => Self::null_sha256(),
178 }
179 }
180
181 #[inline]
183 #[doc(alias = "is_zero", alias = "git2")]
184 pub fn is_null(&self) -> bool {
185 match self {
186 #[cfg(feature = "sha1")]
187 ObjectId::Sha1(digest) => &digest[..] == oid::null_sha1().as_bytes(),
188 #[cfg(feature = "sha256")]
189 ObjectId::Sha256(digest) => &digest[..] == oid::null_sha256().as_bytes(),
190 }
191 }
192
193 #[inline]
195 pub fn is_empty_blob(&self) -> bool {
196 self == &Self::empty_blob(self.kind())
197 }
198
199 #[inline]
201 pub fn is_empty_tree(&self) -> bool {
202 self == &Self::empty_tree(self.kind())
203 }
204}
205
206impl ObjectId {
208 pub fn from_bytes_or_panic(bytes: &[u8]) -> Self {
212 match bytes.len() {
213 #[cfg(feature = "sha1")]
214 SIZE_OF_SHA1_DIGEST => Self::Sha1(bytes.try_into().expect("prior length validation")),
215 #[cfg(feature = "sha256")]
216 SIZE_OF_SHA256_DIGEST => Self::Sha256(bytes.try_into().expect("prior length validation")),
217 other => panic!("BUG: unsupported hash len: {other}"),
218 }
219 }
220}
221
222impl ObjectId {
224 #[inline]
226 #[cfg(feature = "sha1")]
227 fn new_sha1(id: [u8; SIZE_OF_SHA1_DIGEST]) -> Self {
228 ObjectId::Sha1(id)
229 }
230
231 #[inline]
233 #[cfg(feature = "sha256")]
234 fn new_sha256(id: [u8; SIZE_OF_SHA256_DIGEST]) -> Self {
235 ObjectId::Sha256(id)
236 }
237
238 #[inline]
242 #[cfg(feature = "sha1")]
243 pub(crate) fn from_20_bytes(b: &[u8]) -> ObjectId {
244 let mut id = [0; SIZE_OF_SHA1_DIGEST];
245 id.copy_from_slice(b);
246 ObjectId::Sha1(id)
247 }
248
249 #[inline]
253 #[cfg(feature = "sha256")]
254 pub(crate) fn from_32_bytes(b: &[u8]) -> ObjectId {
255 let mut id = [0; SIZE_OF_SHA256_DIGEST];
256 id.copy_from_slice(b);
257 ObjectId::Sha256(id)
258 }
259
260 #[inline]
262 #[cfg(feature = "sha1")]
263 pub(crate) const fn null_sha1() -> ObjectId {
264 ObjectId::Sha1([0u8; SIZE_OF_SHA1_DIGEST])
265 }
266
267 #[inline]
269 #[cfg(feature = "sha256")]
270 pub(crate) const fn null_sha256() -> ObjectId {
271 ObjectId::Sha256([0u8; SIZE_OF_SHA256_DIGEST])
272 }
273}
274
275impl std::fmt::Debug for ObjectId {
276 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
277 match self {
278 #[cfg(feature = "sha1")]
279 ObjectId::Sha1(_hash) => f.write_str("Sha1(")?,
280 #[cfg(feature = "sha256")]
281 ObjectId::Sha256(_) => f.write_str("Sha256(")?,
282 }
283 for b in self.as_bytes() {
284 write!(f, "{b:02x}")?;
285 }
286 f.write_str(")")
287 }
288}
289
290#[cfg(feature = "sha1")]
291impl From<[u8; SIZE_OF_SHA1_DIGEST]> for ObjectId {
292 fn from(v: [u8; SIZE_OF_SHA1_DIGEST]) -> Self {
293 Self::new_sha1(v)
294 }
295}
296
297#[cfg(feature = "sha256")]
298impl From<[u8; SIZE_OF_SHA256_DIGEST]> for ObjectId {
299 fn from(v: [u8; SIZE_OF_SHA256_DIGEST]) -> Self {
300 Self::new_sha256(v)
301 }
302}
303
304impl From<&oid> for ObjectId {
305 fn from(v: &oid) -> Self {
306 match v.kind() {
307 #[cfg(feature = "sha1")]
308 Kind::Sha1 => ObjectId::from_20_bytes(v.as_bytes()),
309 #[cfg(feature = "sha256")]
310 Kind::Sha256 => ObjectId::from_32_bytes(v.as_bytes()),
311 }
312 }
313}
314
315impl TryFrom<&[u8]> for ObjectId {
316 type Error = crate::Error;
317
318 fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
319 Ok(oid::try_from_bytes(bytes)?.into())
320 }
321}
322
323impl Deref for ObjectId {
324 type Target = oid;
325
326 fn deref(&self) -> &Self::Target {
327 self.as_ref()
328 }
329}
330
331impl AsRef<oid> for ObjectId {
332 fn as_ref(&self) -> &oid {
333 oid::from_bytes_unchecked(self.as_slice())
334 }
335}
336
337impl Borrow<oid> for ObjectId {
338 fn borrow(&self) -> &oid {
339 self.as_ref()
340 }
341}
342
343impl std::fmt::Display for ObjectId {
344 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
345 write!(f, "{}", self.to_hex())
346 }
347}
348
349impl ObjectId {
350 fn eq_str(&self, other: &str) -> bool {
351 self.as_ref().eq_str(other)
352 }
353
354 #[cfg(feature = "bstr")]
355 fn eq_bstr(&self, other: &BStr) -> bool {
356 let mut hex = Kind::hex_buf();
357 self.as_ref().hex_to_buf(&mut hex).as_bytes() == other.as_bytes()
358 }
359}
360
361impl_partial_eq_str!(ObjectId);
362
363#[cfg(feature = "bstr")]
364impl PartialEq<BStr> for ObjectId {
365 fn eq(&self, other: &BStr) -> bool {
366 self.eq_bstr(other)
367 }
368}
369
370#[cfg(feature = "bstr")]
371impl PartialEq<&BStr> for ObjectId {
372 fn eq(&self, other: &&BStr) -> bool {
373 self.eq_bstr(other)
374 }
375}
376
377#[cfg(feature = "bstr")]
378impl PartialEq<BString> for ObjectId {
379 fn eq(&self, other: &BString) -> bool {
380 self.eq_bstr(other.as_bstr())
381 }
382}
383
384#[cfg(feature = "bstr")]
385impl PartialEq<ObjectId> for BStr {
386 fn eq(&self, other: &ObjectId) -> bool {
387 other.eq_bstr(self)
388 }
389}
390
391#[cfg(feature = "bstr")]
392impl PartialEq<ObjectId> for &BStr {
393 fn eq(&self, other: &ObjectId) -> bool {
394 other.eq_bstr(self)
395 }
396}
397
398#[cfg(feature = "bstr")]
399impl PartialEq<ObjectId> for BString {
400 fn eq(&self, other: &ObjectId) -> bool {
401 other.eq_bstr(self.as_bstr())
402 }
403}
404
405impl PartialEq<&oid> for ObjectId {
406 fn eq(&self, other: &&oid) -> bool {
407 self.as_ref() == *other
408 }
409}