1use std::ffi::{OsStr, OsString};
2use std::fmt::Display;
3use std::str::FromStr;
4
5use eyre::Context;
6use tracing::instrument;
7
8use crate::git::repo::wrap_git_error;
9
10#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
12pub struct NonZeroOid {
13 pub(super) inner: git2::Oid,
14}
15
16impl NonZeroOid {
17 pub fn as_bytes(&self) -> &[u8] {
19 self.inner.as_bytes()
20 }
21}
22
23impl std::fmt::Debug for NonZeroOid {
24 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
25 write!(f, "NonZeroOid({:?})", self.inner)
26 }
27}
28
29impl Display for NonZeroOid {
30 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
31 write!(f, "{:?}", self.inner)
32 }
33}
34
35impl TryFrom<MaybeZeroOid> for NonZeroOid {
36 type Error = eyre::Error;
37
38 #[instrument]
39 fn try_from(value: MaybeZeroOid) -> Result<Self, Self::Error> {
40 match value {
41 MaybeZeroOid::NonZero(non_zero_oid) => Ok(non_zero_oid),
42 MaybeZeroOid::Zero => eyre::bail!("Expected a non-zero OID"),
43 }
44 }
45}
46
47impl TryFrom<OsString> for NonZeroOid {
48 type Error = eyre::Error;
49
50 #[instrument]
51 fn try_from(value: OsString) -> Result<Self, Self::Error> {
52 let value: &OsStr = &value;
53 value.try_into()
54 }
55}
56
57impl TryFrom<&OsStr> for NonZeroOid {
58 type Error = eyre::Error;
59
60 #[instrument]
61 fn try_from(value: &OsStr) -> Result<Self, Self::Error> {
62 let oid: MaybeZeroOid = value.try_into()?;
63 match oid {
64 MaybeZeroOid::Zero => eyre::bail!("OID was zero, but expected to be non-zero"),
65 MaybeZeroOid::NonZero(oid) => Ok(oid),
66 }
67 }
68}
69
70impl TryFrom<&[u8]> for NonZeroOid {
71 type Error = eyre::Error;
72
73 #[instrument]
74 fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
75 let oid = MaybeZeroOid::try_from(value)?;
76 NonZeroOid::try_from(oid)
77 }
78}
79
80impl FromStr for NonZeroOid {
81 type Err = eyre::Error;
82
83 #[instrument]
84 fn from_str(value: &str) -> Result<Self, Self::Err> {
85 let oid: MaybeZeroOid = value.parse()?;
86 match oid {
87 MaybeZeroOid::NonZero(non_zero_oid) => Ok(non_zero_oid),
88 MaybeZeroOid::Zero => eyre::bail!("Expected a non-zero OID, but got: {:?}", value),
89 }
90 }
91}
92
93impl From<NonZeroOid> for git2::Oid {
94 fn from(oid: NonZeroOid) -> Self {
95 oid.inner
96 }
97}
98
99pub(super) fn make_non_zero_oid(oid: git2::Oid) -> NonZeroOid {
100 assert_ne!(oid, git2::Oid::zero());
101 NonZeroOid { inner: oid }
102}
103
104#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
113pub enum MaybeZeroOid {
114 Zero,
116
117 NonZero(NonZeroOid),
119}
120
121impl Default for MaybeZeroOid {
122 fn default() -> Self {
123 Self::Zero
124 }
125}
126
127impl MaybeZeroOid {
128 pub fn from_bytes(bytes: &[u8]) -> eyre::Result<Self> {
130 let oid = git2::Oid::from_bytes(bytes)?;
131 Ok(oid.into())
132 }
133}
134
135impl std::fmt::Debug for MaybeZeroOid {
136 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
137 write!(f, "{self}")
138 }
139}
140
141impl Display for MaybeZeroOid {
142 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
143 let zero = git2::Oid::zero();
144 write!(
145 f,
146 "{:?}",
147 match self {
148 MaybeZeroOid::NonZero(NonZeroOid { inner }) => inner,
149 MaybeZeroOid::Zero => &zero,
150 }
151 )
152 }
153}
154
155impl FromStr for MaybeZeroOid {
156 type Err = eyre::Error;
157
158 fn from_str(s: &str) -> Result<Self, Self::Err> {
159 match s.parse() {
160 Ok(oid) if oid == git2::Oid::zero() => Ok(MaybeZeroOid::Zero),
161 Ok(oid) => Ok(MaybeZeroOid::NonZero(NonZeroOid { inner: oid })),
162 Err(err) => Err(wrap_git_error(err)).wrap_err("Could not parse OID from string"),
163 }
164 }
165}
166
167impl From<git2::Oid> for MaybeZeroOid {
168 fn from(oid: git2::Oid) -> Self {
169 if oid.is_zero() {
170 Self::Zero
171 } else {
172 Self::NonZero(make_non_zero_oid(oid))
173 }
174 }
175}
176
177impl TryFrom<&OsStr> for MaybeZeroOid {
178 type Error = eyre::Error;
179
180 fn try_from(value: &OsStr) -> Result<Self, Self::Error> {
181 match value.to_str() {
182 None => eyre::bail!("OID value was not a simple ASCII value: {:?}", value),
183 Some(value) => value.parse(),
184 }
185 }
186}
187
188impl TryFrom<OsString> for MaybeZeroOid {
189 type Error = eyre::Error;
190
191 fn try_from(value: OsString) -> Result<Self, Self::Error> {
192 MaybeZeroOid::try_from(value.as_os_str())
193 }
194}
195
196impl TryFrom<&[u8]> for MaybeZeroOid {
197 type Error = git2::Error;
198
199 fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
200 let oid = git2::Oid::from_bytes(value)?;
201 Ok(MaybeZeroOid::from(oid))
202 }
203}
204
205impl From<NonZeroOid> for MaybeZeroOid {
206 fn from(oid: NonZeroOid) -> Self {
207 Self::NonZero(oid)
208 }
209}
210
211impl From<Option<NonZeroOid>> for MaybeZeroOid {
212 fn from(oid: Option<NonZeroOid>) -> Self {
213 match oid {
214 Some(oid) => MaybeZeroOid::NonZero(oid),
215 None => MaybeZeroOid::Zero,
216 }
217 }
218}
219
220impl From<MaybeZeroOid> for Option<NonZeroOid> {
221 fn from(oid: MaybeZeroOid) -> Self {
222 match oid {
223 MaybeZeroOid::Zero => None,
224 MaybeZeroOid::NonZero(oid) => Some(oid),
225 }
226 }
227}
228
229impl From<MaybeZeroOid> for git2::Oid {
230 fn from(oid: MaybeZeroOid) -> Self {
231 match oid {
232 MaybeZeroOid::Zero => git2::Oid::zero(),
233 MaybeZeroOid::NonZero(oid) => oid.into(),
234 }
235 }
236}