1use std::{
2 borrow::Borrow,
3 hash::{Hash, Hasher},
4 ops::Deref,
5};
6
7use crate::{borrowed::oid, Kind, SIZE_OF_SHA1_DIGEST};
8
9#[derive(PartialEq, Eq, Ord, PartialOrd, Clone, Copy)]
11#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
12#[non_exhaustive]
13pub enum ObjectId {
14 Sha1([u8; SIZE_OF_SHA1_DIGEST]),
16}
17
18#[allow(clippy::derived_hash_with_manual_eq)]
25impl Hash for ObjectId {
26 fn hash<H: Hasher>(&self, state: &mut H) {
27 state.write(self.as_slice());
28 }
29}
30
31#[allow(missing_docs)]
32pub mod decode {
33 use std::str::FromStr;
34
35 use crate::object_id::ObjectId;
36
37 #[derive(Debug, thiserror::Error)]
39 #[allow(missing_docs)]
40 pub enum Error {
41 #[error("A hash sized {0} hexadecimal characters is invalid")]
42 InvalidHexEncodingLength(usize),
43 #[error("Invalid character encountered")]
44 Invalid,
45 }
46
47 impl ObjectId {
49 pub fn from_hex(buffer: &[u8]) -> Result<ObjectId, Error> {
53 match buffer.len() {
54 40 => Ok({
55 ObjectId::Sha1({
56 let mut buf = [0; 20];
57 faster_hex::hex_decode(buffer, &mut buf).map_err(|err| match err {
58 faster_hex::Error::InvalidChar | faster_hex::Error::Overflow => Error::Invalid,
59 faster_hex::Error::InvalidLength(_) => {
60 unreachable!("BUG: This is already checked")
61 }
62 })?;
63 buf
64 })
65 }),
66 len => Err(Error::InvalidHexEncodingLength(len)),
67 }
68 }
69 }
70
71 impl FromStr for ObjectId {
72 type Err = Error;
73
74 fn from_str(s: &str) -> Result<Self, Self::Err> {
75 Self::from_hex(s.as_bytes())
76 }
77 }
78}
79
80impl ObjectId {
82 #[inline]
84 pub fn kind(&self) -> Kind {
85 match self {
86 ObjectId::Sha1(_) => Kind::Sha1,
87 }
88 }
89 #[inline]
91 pub fn as_slice(&self) -> &[u8] {
92 match self {
93 Self::Sha1(b) => b.as_ref(),
94 }
95 }
96 #[inline]
98 pub fn as_mut_slice(&mut self) -> &mut [u8] {
99 match self {
100 Self::Sha1(b) => b.as_mut(),
101 }
102 }
103
104 #[inline]
106 pub const fn empty_blob(hash: Kind) -> ObjectId {
107 match hash {
108 Kind::Sha1 => {
109 ObjectId::Sha1(*b"\xe6\x9d\xe2\x9b\xb2\xd1\xd6\x43\x4b\x8b\x29\xae\x77\x5a\xd8\xc2\xe4\x8c\x53\x91")
110 }
111 }
112 }
113
114 #[inline]
116 pub const fn empty_tree(hash: Kind) -> ObjectId {
117 match hash {
118 Kind::Sha1 => {
119 ObjectId::Sha1(*b"\x4b\x82\x5d\xc6\x42\xcb\x6e\xb9\xa0\x60\xe5\x4b\xf8\xd6\x92\x88\xfb\xee\x49\x04")
120 }
121 }
122 }
123
124 #[inline]
126 #[doc(alias = "zero", alias = "git2")]
127 pub const fn null(kind: Kind) -> ObjectId {
128 match kind {
129 Kind::Sha1 => Self::null_sha1(),
130 }
131 }
132
133 #[inline]
135 #[doc(alias = "is_zero", alias = "git2")]
136 pub fn is_null(&self) -> bool {
137 match self {
138 ObjectId::Sha1(digest) => &digest[..] == oid::null_sha1().as_bytes(),
139 }
140 }
141
142 #[inline]
144 pub fn is_empty_blob(&self) -> bool {
145 self == &Self::empty_blob(self.kind())
146 }
147
148 #[inline]
150 pub fn is_empty_tree(&self) -> bool {
151 self == &Self::empty_tree(self.kind())
152 }
153}
154
155impl ObjectId {
157 pub fn from_bytes_or_panic(bytes: &[u8]) -> Self {
161 match bytes.len() {
162 20 => Self::Sha1(bytes.try_into().expect("prior length validation")),
163 other => panic!("BUG: unsupported hash len: {other}"),
164 }
165 }
166}
167
168impl ObjectId {
170 #[inline]
172 fn new_sha1(id: [u8; SIZE_OF_SHA1_DIGEST]) -> Self {
173 ObjectId::Sha1(id)
174 }
175
176 #[inline]
180 pub(crate) fn from_20_bytes(b: &[u8]) -> ObjectId {
181 let mut id = [0; SIZE_OF_SHA1_DIGEST];
182 id.copy_from_slice(b);
183 ObjectId::Sha1(id)
184 }
185
186 #[inline]
188 pub(crate) const fn null_sha1() -> ObjectId {
189 ObjectId::Sha1([0u8; 20])
190 }
191}
192
193impl std::fmt::Debug for ObjectId {
194 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
195 match self {
196 ObjectId::Sha1(_hash) => f.write_str("Sha1(")?,
197 }
198 for b in self.as_bytes() {
199 write!(f, "{b:02x}")?;
200 }
201 f.write_str(")")
202 }
203}
204
205impl From<[u8; SIZE_OF_SHA1_DIGEST]> for ObjectId {
206 fn from(v: [u8; 20]) -> Self {
207 Self::new_sha1(v)
208 }
209}
210
211impl From<&oid> for ObjectId {
212 fn from(v: &oid) -> Self {
213 match v.kind() {
214 Kind::Sha1 => ObjectId::from_20_bytes(v.as_bytes()),
215 }
216 }
217}
218
219impl TryFrom<&[u8]> for ObjectId {
220 type Error = crate::Error;
221
222 fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
223 Ok(oid::try_from_bytes(bytes)?.into())
224 }
225}
226
227impl Deref for ObjectId {
228 type Target = oid;
229
230 fn deref(&self) -> &Self::Target {
231 self.as_ref()
232 }
233}
234
235impl AsRef<oid> for ObjectId {
236 fn as_ref(&self) -> &oid {
237 oid::from_bytes_unchecked(self.as_slice())
238 }
239}
240
241impl Borrow<oid> for ObjectId {
242 fn borrow(&self) -> &oid {
243 self.as_ref()
244 }
245}
246
247impl std::fmt::Display for ObjectId {
248 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
249 write!(f, "{}", self.to_hex())
250 }
251}
252
253impl PartialEq<&oid> for ObjectId {
254 fn eq(&self, other: &&oid) -> bool {
255 self.as_ref() == *other
256 }
257}