1use std::{borrow::Borrow, convert::TryFrom, path::Path};
2
3use git_object::bstr::{BStr, BString, ByteSlice};
4
5use crate::{bstr::ByteVec, name::is_pseudo_ref, Category, FullName, FullNameRef, Namespace, PartialNameRef};
6
7impl TryFrom<&str> for FullName {
8 type Error = git_validate::refname::Error;
9
10 fn try_from(value: &str) -> Result<Self, Self::Error> {
11 Ok(FullName(git_validate::refname(value.as_bytes().as_bstr())?.into()))
12 }
13}
14
15impl TryFrom<String> for FullName {
16 type Error = git_validate::refname::Error;
17
18 fn try_from(value: String) -> Result<Self, Self::Error> {
19 git_validate::refname(value.as_bytes().as_bstr())?;
20 Ok(FullName(value.into()))
21 }
22}
23
24impl TryFrom<&BStr> for FullName {
25 type Error = git_validate::refname::Error;
26
27 fn try_from(value: &BStr) -> Result<Self, Self::Error> {
28 Ok(FullName(git_validate::refname(value)?.into()))
29 }
30}
31
32impl TryFrom<BString> for FullName {
33 type Error = git_validate::refname::Error;
34
35 fn try_from(value: BString) -> Result<Self, Self::Error> {
36 git_validate::refname(value.as_ref())?;
37 Ok(FullName(value))
38 }
39}
40
41impl TryFrom<&BString> for FullName {
42 type Error = git_validate::refname::Error;
43
44 fn try_from(value: &BString) -> Result<Self, Self::Error> {
45 git_validate::refname(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 FullNameRef {
75 pub fn as_partial_name(&self) -> &PartialNameRef {
77 PartialNameRef::new_unchecked(self.0.as_bstr())
78 }
79
80 pub fn to_path(&self) -> &Path {
82 git_path::from_byte_slice(&self.0)
83 }
84
85 pub fn as_bstr(&self) -> &BStr {
87 &self.0
88 }
89
90 pub fn shorten(&self) -> &BStr {
94 self.category_and_short_name()
95 .map(|(_, short)| short)
96 .unwrap_or_else(|| self.0.as_bstr())
97 }
98
99 pub fn category(&self) -> Option<Category<'_>> {
101 self.category_and_short_name().map(|(cat, _)| cat)
102 }
103
104 pub fn category_and_short_name(&self) -> Option<(Category<'_>, &BStr)> {
107 let name = self.0.as_bstr();
108 for category in &[Category::Tag, Category::LocalBranch, Category::RemoteBranch] {
109 if let Some(shortened) = name.strip_prefix(category.prefix().as_ref()) {
110 return Some((*category, shortened.as_bstr()));
111 }
112 }
113
114 for category in &[
115 Category::Note,
116 Category::Bisect,
117 Category::WorktreePrivate,
118 Category::Rewritten,
119 ] {
120 if name.starts_with(category.prefix().as_ref()) {
121 return Some((
122 *category,
123 name.strip_prefix(b"refs/")
124 .expect("we checked for refs/* above")
125 .as_bstr(),
126 ));
127 }
128 }
129
130 if is_pseudo_ref(name) {
131 Some((Category::PseudoRef, name))
132 } else if let Some(shortened) = name.strip_prefix(Category::MainPseudoRef.prefix().as_ref()) {
133 if shortened.starts_with_str("refs/") {
134 (Category::MainRef, shortened.as_bstr()).into()
135 } else {
136 is_pseudo_ref(shortened).then(|| (Category::MainPseudoRef, shortened.as_bstr()))
137 }
138 } else if let Some(shortened_with_worktree_name) =
139 name.strip_prefix(Category::LinkedPseudoRef { name: "".into() }.prefix().as_ref())
140 {
141 let (name, shortened) = shortened_with_worktree_name.find_byte(b'/').map(|pos| {
142 (
143 shortened_with_worktree_name[..pos].as_bstr(),
144 shortened_with_worktree_name[pos + 1..].as_bstr(),
145 )
146 })?;
147 if shortened.starts_with_str("refs/") {
148 (Category::LinkedRef { name }, shortened.as_bstr()).into()
149 } else {
150 is_pseudo_ref(shortened).then(|| (Category::LinkedPseudoRef { name }, shortened.as_bstr()))
151 }
152 } else {
153 None
154 }
155 }
156}
157
158impl FullName {
159 pub fn to_path(&self) -> &Path {
161 git_path::from_byte_slice(&self.0)
162 }
163
164 pub fn into_inner(self) -> BString {
166 self.0
167 }
168
169 pub fn as_bstr(&self) -> &BStr {
171 self.0.as_bstr()
172 }
173
174 pub fn prefix_namespace(&mut self, namespace: &Namespace) -> &mut Self {
176 if !self.0.starts_with_str(&namespace.0) {
177 self.0.insert_str(0, &namespace.0);
178 }
179 self
180 }
181
182 pub fn strip_namespace(&mut self, namespace: &Namespace) -> &mut Self {
184 if self.0.starts_with_str(&namespace.0) {
185 let prev_len = self.0.len();
186 self.0.copy_within(namespace.0.len().., 0);
187 self.0.resize(prev_len - namespace.0.len(), 0);
188 }
189 self
190 }
191
192 pub fn shorten(&self) -> &BStr {
196 self.as_ref().shorten()
197 }
198
199 pub fn category(&self) -> Option<crate::Category<'_>> {
201 self.as_ref().category()
202 }
203
204 pub fn category_and_short_name(&self) -> Option<(crate::Category<'_>, &BStr)> {
207 self.as_ref().category_and_short_name()
208 }
209}
210
211impl FullNameRef {
212 pub fn file_name(&self) -> &BStr {
215 self.0.rsplitn(2, |b| *b == b'/').next().expect("valid ref").as_bstr()
216 }
217}
218
219impl Borrow<FullNameRef> for FullName {
220 #[inline]
221 fn borrow(&self) -> &FullNameRef {
222 FullNameRef::new_unchecked(self.0.as_bstr())
223 }
224}
225
226impl AsRef<FullNameRef> for FullName {
227 fn as_ref(&self) -> &FullNameRef {
228 self.borrow()
229 }
230}
231
232impl ToOwned for FullNameRef {
233 type Owned = FullName;
234
235 fn to_owned(&self) -> Self::Owned {
236 FullName(self.0.to_owned())
237 }
238}