1use std::{convert, convert::Infallible, ffi::OsStr, path::Path};
2
3use gix_object::bstr::{BStr, BString, ByteSlice, ByteVec};
4
5use crate::{Category, FullName, FullNameRef, PartialName, PartialNameRef};
6
7pub type Error = gix_validate::reference::name::Error;
9
10impl Category<'_> {
11 pub fn prefix(&self) -> &BStr {
14 match self {
15 Category::Tag => b"refs/tags/".as_bstr(),
16 Category::LocalBranch => b"refs/heads/".as_bstr(),
17 Category::RemoteBranch => b"refs/remotes/".as_bstr(),
18 Category::Note => b"refs/notes/".as_bstr(),
19 Category::MainPseudoRef => b"main-worktree/".as_bstr(),
20 Category::MainRef => b"main-worktree/refs/".as_bstr(),
21 Category::PseudoRef => b"".as_bstr(),
22 Category::LinkedPseudoRef { .. } => b"worktrees/".as_bstr(),
23 Category::LinkedRef { .. } => b"worktrees/".as_bstr(),
24 Category::Bisect => b"refs/bisect/".as_bstr(),
25 Category::Rewritten => b"refs/rewritten/".as_bstr(),
26 Category::WorktreePrivate => b"refs/worktree/".as_bstr(),
27 }
28 }
29
30 pub fn is_worktree_private(&self) -> bool {
32 matches!(
33 self,
34 Category::MainPseudoRef
35 | Category::PseudoRef
36 | Category::LinkedPseudoRef { .. }
37 | Category::WorktreePrivate
38 | Category::Rewritten
39 | Category::Bisect
40 )
41 }
42
43 pub fn is_remote_tracking_branch(&self) -> bool {
45 matches!(self, Category::RemoteBranch)
46 }
47}
48
49impl FullNameRef {
50 pub(crate) fn new_unchecked(v: &BStr) -> &Self {
51 #[expect(unsafe_code)]
53 unsafe {
54 std::mem::transmute(v)
55 }
56 }
57}
58
59impl PartialNameRef {
60 pub(crate) fn new_unchecked(v: &BStr) -> &Self {
61 #[expect(unsafe_code)]
63 unsafe {
64 std::mem::transmute(v)
65 }
66 }
67}
68
69impl PartialNameRef {
70 pub(crate) fn looks_like_full_name(&self, consider_pseudo_ref: bool) -> bool {
71 let name = self.0.as_bstr();
72 name.starts_with_str("refs/")
73 || name.starts_with(Category::MainPseudoRef.prefix())
74 || name.starts_with(Category::LinkedPseudoRef { name: "".into() }.prefix())
75 || (consider_pseudo_ref && is_pseudo_ref(name))
76 }
77 pub(crate) fn construct_full_name_ref<'buf>(
78 &self,
79 inbetween: &str,
80 buf: &'buf mut BString,
81 consider_pseudo_ref: bool,
82 ) -> &'buf FullNameRef {
83 buf.clear();
84 if !self.looks_like_full_name(consider_pseudo_ref) {
85 buf.push_str("refs/");
86 }
87 if !inbetween.is_empty() {
88 buf.push_str(inbetween);
89 buf.push_byte(b'/');
90 }
91 buf.extend_from_slice(&self.0);
92 FullNameRef::new_unchecked(buf.as_bstr())
93 }
94}
95
96impl PartialNameRef {
97 pub fn to_partial_path(&self) -> &Path {
100 gix_path::from_byte_slice(self.0.as_bstr())
101 }
102
103 pub fn as_bstr(&self) -> &BStr {
105 &self.0
106 }
107}
108
109impl PartialName {
110 pub fn join(self, component: &BStr) -> Result<Self, Error> {
112 let mut b = self.0;
113 b.push_byte(b'/');
114 b.extend(component.as_bytes());
115 gix_validate::reference::name_partial(b.as_ref())?;
116 Ok(PartialName(b))
117 }
118}
119
120impl<'a> convert::TryFrom<&'a BStr> for &'a FullNameRef {
121 type Error = Error;
122
123 fn try_from(v: &'a BStr) -> Result<Self, Self::Error> {
124 Ok(FullNameRef::new_unchecked(gix_validate::reference::name(v)?))
125 }
126}
127
128impl<'a> From<&'a FullNameRef> for &'a PartialNameRef {
129 fn from(v: &'a FullNameRef) -> Self {
130 PartialNameRef::new_unchecked(v.0.as_bstr())
131 }
132}
133
134impl<'a> convert::TryFrom<&'a OsStr> for &'a PartialNameRef {
135 type Error = gix_error::Exn<Error>;
136
137 fn try_from(v: &'a OsStr) -> Result<Self, Self::Error> {
138 use gix_error::ResultExt;
139
140 let v = gix_path::os_str_into_bstr(v).or_raise(|| Error::InvalidByte {
141 byte: "<unknown encoding>".into(),
142 })?;
143 Ok(PartialNameRef::new_unchecked(gix_validate::reference::name_partial(
144 v.as_bstr(),
145 )?))
146 }
147}
148
149mod impls {
150 use std::borrow::Borrow;
151
152 use crate::{PartialName, PartialNameRef, bstr::ByteSlice};
153
154 impl Borrow<PartialNameRef> for PartialName {
155 #[inline]
156 fn borrow(&self) -> &PartialNameRef {
157 PartialNameRef::new_unchecked(self.0.as_bstr())
158 }
159 }
160
161 impl AsRef<PartialNameRef> for PartialName {
162 fn as_ref(&self) -> &PartialNameRef {
163 self.borrow()
164 }
165 }
166
167 impl ToOwned for PartialNameRef {
168 type Owned = PartialName;
169
170 fn to_owned(&self) -> Self::Owned {
171 PartialName(self.0.to_owned())
172 }
173 }
174}
175
176impl<'a> convert::TryFrom<&'a BString> for &'a PartialNameRef {
177 type Error = Error;
178
179 fn try_from(v: &'a BString) -> Result<Self, Self::Error> {
180 Ok(PartialNameRef::new_unchecked(gix_validate::reference::name_partial(
181 v.as_ref(),
182 )?))
183 }
184}
185
186impl<'a> convert::TryFrom<&'a BStr> for &'a PartialNameRef {
187 type Error = Error;
188
189 fn try_from(v: &'a BStr) -> Result<Self, Self::Error> {
190 Ok(PartialNameRef::new_unchecked(gix_validate::reference::name_partial(v)?))
191 }
192}
193
194impl<'a> convert::TryFrom<&'a PartialName> for &'a PartialNameRef {
195 type Error = Error;
196
197 fn try_from(v: &'a PartialName) -> Result<Self, Self::Error> {
198 Ok(PartialNameRef::new_unchecked(v.0.as_bstr()))
199 }
200}
201
202impl<'a> convert::TryFrom<&'a str> for &'a FullNameRef {
203 type Error = Error;
204
205 fn try_from(v: &'a str) -> Result<Self, Self::Error> {
206 let v = v.as_bytes().as_bstr();
207 Ok(FullNameRef::new_unchecked(gix_validate::reference::name(v)?))
208 }
209}
210
211impl<'a> convert::TryFrom<&'a str> for &'a PartialNameRef {
212 type Error = Error;
213
214 fn try_from(v: &'a str) -> Result<Self, Self::Error> {
215 let v = v.as_bytes().as_bstr();
216 Ok(PartialNameRef::new_unchecked(gix_validate::reference::name_partial(v)?))
217 }
218}
219
220impl<'a> convert::TryFrom<&'a str> for PartialName {
221 type Error = Error;
222
223 fn try_from(v: &'a str) -> Result<Self, Self::Error> {
224 let v = v.as_bytes().as_bstr();
225 Ok(PartialName(gix_validate::reference::name_partial(v)?.to_owned()))
226 }
227}
228
229#[expect(
230 clippy::infallible_try_from,
231 reason = "it's here so that it can be done, even if infallible. Needed for parameters that use `TryFrom` generically."
232)]
233impl<'a> convert::TryFrom<&'a FullName> for &'a PartialNameRef {
234 type Error = Infallible;
235
236 fn try_from(v: &'a FullName) -> Result<Self, Self::Error> {
237 Ok(v.as_ref().as_partial_name())
238 }
239}
240
241impl<'a> convert::TryFrom<&'a String> for &'a FullNameRef {
242 type Error = Error;
243
244 fn try_from(v: &'a String) -> Result<Self, Self::Error> {
245 let v = v.as_bytes().as_bstr();
246 Ok(FullNameRef::new_unchecked(gix_validate::reference::name(v)?))
247 }
248}
249
250impl<'a> convert::TryFrom<&'a String> for &'a PartialNameRef {
251 type Error = Error;
252
253 fn try_from(v: &'a String) -> Result<Self, Self::Error> {
254 let v = v.as_bytes().as_bstr();
255 Ok(PartialNameRef::new_unchecked(gix_validate::reference::name_partial(v)?))
256 }
257}
258
259impl convert::TryFrom<String> for PartialName {
260 type Error = Error;
261
262 fn try_from(v: String) -> Result<Self, Self::Error> {
263 gix_validate::reference::name_partial(v.as_bytes().as_bstr())?;
264 Ok(PartialName(v.into()))
265 }
266}
267
268impl convert::TryFrom<BString> for PartialName {
269 type Error = Error;
270
271 fn try_from(v: BString) -> Result<Self, Self::Error> {
272 gix_validate::reference::name_partial(v.as_ref())?;
273 Ok(PartialName(v))
274 }
275}
276
277impl std::fmt::Display for PartialName {
278 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
279 std::fmt::Display::fmt(&self.0, f)
280 }
281}
282
283impl std::fmt::Display for PartialNameRef {
284 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
285 std::fmt::Display::fmt(&self.0, f)
286 }
287}
288
289pub(crate) fn is_pseudo_ref(name: &BStr) -> bool {
293 name.bytes().all(|b| b.is_ascii_uppercase() || b == b'_')
294}