1use gix_object::bstr::{BStr, BString, ByteSlice};
2use std::{borrow::Borrow, path::Path};
3
4use crate::{bstr::ByteVec, name::is_pseudo_ref, Category, FullName, FullNameRef, Namespace, PartialNameRef};
5
6impl TryFrom<&str> for FullName {
7 type Error = gix_validate::reference::name::Error;
8
9 fn try_from(value: &str) -> Result<Self, Self::Error> {
10 Ok(FullName(
11 gix_validate::reference::name(value.as_bytes().as_bstr())?.into(),
12 ))
13 }
14}
15
16impl TryFrom<String> for FullName {
17 type Error = gix_validate::reference::name::Error;
18
19 fn try_from(value: String) -> Result<Self, Self::Error> {
20 gix_validate::reference::name(value.as_bytes().as_bstr())?;
21 Ok(FullName(value.into()))
22 }
23}
24
25impl TryFrom<&BStr> for FullName {
26 type Error = gix_validate::reference::name::Error;
27
28 fn try_from(value: &BStr) -> Result<Self, Self::Error> {
29 Ok(FullName(gix_validate::reference::name(value)?.into()))
30 }
31}
32
33impl TryFrom<BString> for FullName {
34 type Error = gix_validate::reference::name::Error;
35
36 fn try_from(value: BString) -> Result<Self, Self::Error> {
37 gix_validate::reference::name(value.as_ref())?;
38 Ok(FullName(value))
39 }
40}
41
42impl TryFrom<&BString> for FullName {
43 type Error = gix_validate::reference::name::Error;
44
45 fn try_from(value: &BString) -> Result<Self, Self::Error> {
46 gix_validate::reference::name(value.as_ref())?;
47 Ok(FullName(value.clone()))
48 }
49}
50
51impl From<FullName> for BString {
52 fn from(name: FullName) -> Self {
53 name.0
54 }
55}
56
57impl<'a> From<&'a FullNameRef> for &'a BStr {
58 fn from(name: &'a FullNameRef) -> Self {
59 &name.0
60 }
61}
62
63impl<'a> From<&'a FullNameRef> for FullName {
64 fn from(value: &'a FullNameRef) -> Self {
65 FullName(value.as_bstr().into())
66 }
67}
68
69impl std::fmt::Display for FullName {
70 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
71 std::fmt::Display::fmt(&self.0, f)
72 }
73}
74
75impl std::fmt::Display for FullNameRef {
76 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
77 std::fmt::Display::fmt(&self.0, f)
78 }
79}
80
81impl FullNameRef {
82 pub fn as_partial_name(&self) -> &PartialNameRef {
84 PartialNameRef::new_unchecked(self.0.as_bstr())
85 }
86
87 pub fn to_path(&self) -> &Path {
89 gix_path::from_byte_slice(&self.0)
90 }
91
92 pub fn as_bstr(&self) -> &BStr {
94 &self.0
95 }
96
97 pub fn shorten(&self) -> &BStr {
101 self.category_and_short_name()
102 .map_or_else(|| self.0.as_bstr(), |(_, short)| short)
103 }
104
105 pub fn category(&self) -> Option<Category<'_>> {
107 self.category_and_short_name().map(|(cat, _)| cat)
108 }
109
110 pub fn category_and_short_name(&self) -> Option<(Category<'_>, &BStr)> {
113 let name = self.0.as_bstr();
114 for category in &[Category::Tag, Category::LocalBranch, Category::RemoteBranch] {
115 if let Some(shortened) = name.strip_prefix(category.prefix().as_bytes()) {
116 return Some((*category, shortened.as_bstr()));
117 }
118 }
119
120 for category in &[
121 Category::Note,
122 Category::Bisect,
123 Category::WorktreePrivate,
124 Category::Rewritten,
125 ] {
126 if name.starts_with(category.prefix().as_ref()) {
127 return Some((
128 *category,
129 name.strip_prefix(b"refs/")
130 .expect("we checked for refs/* above")
131 .as_bstr(),
132 ));
133 }
134 }
135
136 if is_pseudo_ref(name) {
137 Some((Category::PseudoRef, name))
138 } else if let Some(shortened) = name.strip_prefix(Category::MainPseudoRef.prefix().as_bytes()) {
139 if shortened.starts_with_str("refs/") {
140 (Category::MainRef, shortened.as_bstr()).into()
141 } else {
142 is_pseudo_ref(shortened.into()).then(|| (Category::MainPseudoRef, shortened.as_bstr()))
143 }
144 } else if let Some(shortened_with_worktree_name) =
145 name.strip_prefix(Category::LinkedPseudoRef { name: "".into() }.prefix().as_bytes())
146 {
147 let (name, shortened) = shortened_with_worktree_name.find_byte(b'/').map(|pos| {
148 (
149 shortened_with_worktree_name[..pos].as_bstr(),
150 shortened_with_worktree_name[pos + 1..].as_bstr(),
151 )
152 })?;
153 if shortened.starts_with_str("refs/") {
154 (Category::LinkedRef { name }, shortened.as_bstr()).into()
155 } else {
156 is_pseudo_ref(shortened).then(|| (Category::LinkedPseudoRef { name }, shortened.as_bstr()))
157 }
158 } else {
159 None
160 }
161 }
162}
163
164impl Category<'_> {
166 pub fn to_full_name<'a>(&self, short_name: impl Into<&'a BStr>) -> Result<FullName, crate::name::Error> {
169 let mut out: BString = self.prefix().into();
170 let short_name = short_name.into();
171 let partial_name = match self {
172 Category::Note => short_name.strip_prefix("notes/".as_bytes()).unwrap_or(short_name),
173 Category::MainRef => short_name.strip_prefix("refs/".as_bytes()).unwrap_or(short_name),
174 Category::LinkedPseudoRef { name } | Category::LinkedRef { name } => {
175 out.extend_from_slice(name);
176 out.push(b'/');
177 short_name
178 }
179 Category::Bisect => short_name.strip_prefix("bisect/".as_bytes()).unwrap_or(short_name),
180 Category::Rewritten => short_name.strip_prefix("rewritten/".as_bytes()).unwrap_or(short_name),
181 Category::WorktreePrivate => short_name.strip_prefix("worktree/".as_bytes()).unwrap_or(short_name),
182 Category::Tag
183 | Category::LocalBranch
184 | Category::RemoteBranch
185 | Category::PseudoRef
186 | Category::MainPseudoRef => short_name,
187 };
188 out.extend_from_slice(partial_name);
189 FullName::try_from(out)
190 }
191}
192
193impl FullName {
194 pub fn to_path(&self) -> &Path {
196 gix_path::from_byte_slice(&self.0)
197 }
198
199 pub fn into_inner(self) -> BString {
201 self.0
202 }
203
204 pub fn as_bstr(&self) -> &BStr {
206 self.0.as_bstr()
207 }
208
209 pub fn prefix_namespace(&mut self, namespace: &Namespace) -> &mut Self {
211 if !self.0.starts_with_str(&namespace.0) {
212 self.0.insert_str(0, &namespace.0);
213 }
214 self
215 }
216
217 pub fn strip_namespace(&mut self, namespace: &Namespace) -> &mut Self {
219 if self.0.starts_with_str(&namespace.0) {
220 let prev_len = self.0.len();
221 self.0.copy_within(namespace.0.len().., 0);
222 self.0.resize(prev_len - namespace.0.len(), 0);
223 }
224 self
225 }
226
227 pub fn shorten(&self) -> &BStr {
231 self.as_ref().shorten()
232 }
233
234 pub fn category(&self) -> Option<crate::Category<'_>> {
236 self.as_ref().category()
237 }
238
239 pub fn category_and_short_name(&self) -> Option<(crate::Category<'_>, &BStr)> {
242 self.as_ref().category_and_short_name()
243 }
244}
245
246impl FullNameRef {
247 pub fn file_name(&self) -> &BStr {
250 self.0.rsplitn(2, |b| *b == b'/').next().expect("valid ref").as_bstr()
251 }
252}
253
254impl Borrow<FullNameRef> for FullName {
255 #[inline]
256 fn borrow(&self) -> &FullNameRef {
257 FullNameRef::new_unchecked(self.0.as_bstr())
258 }
259}
260
261impl AsRef<FullNameRef> for FullName {
262 fn as_ref(&self) -> &FullNameRef {
263 self.borrow()
264 }
265}
266
267impl ToOwned for FullNameRef {
268 type Owned = FullName;
269
270 fn to_owned(&self) -> Self::Owned {
271 FullName(self.0.to_owned())
272 }
273}