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