gix_ref/
fullname.rs

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    /// Interpret this fully qualified reference name as partial name.
83    pub fn as_partial_name(&self) -> &PartialNameRef {
84        PartialNameRef::new_unchecked(self.0.as_bstr())
85    }
86
87    /// Convert this name into the relative path identifying the reference location.
88    pub fn to_path(&self) -> &Path {
89        gix_path::from_byte_slice(&self.0)
90    }
91
92    /// Return ourselves as byte string which is a valid refname
93    pub fn as_bstr(&self) -> &BStr {
94        &self.0
95    }
96
97    /// Strip well-known prefixes from the name and return it.
98    ///
99    /// If there is no such prefix, the original name is returned.
100    pub fn shorten(&self) -> &BStr {
101        self.category_and_short_name()
102            .map_or_else(|| self.0.as_bstr(), |(_, short)| short)
103    }
104
105    /// Classify this name, or return `None` if it's unclassified.
106    pub fn category(&self) -> Option<Category<'_>> {
107        self.category_and_short_name().map(|(cat, _)| cat)
108    }
109
110    /// Classify this name, or return `None` if it's unclassified. If `Some`,
111    /// the shortened name is returned as well.
112    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
164/// Conversion
165impl Category<'_> {
166    /// As the inverse of [`FullNameRef::category_and_short_name()`], use the prefix of this category alongside
167    /// the `short_name` to create a valid fully qualified [reference name](FullName).
168    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    /// Convert this name into the relative path, lossily, identifying the reference location relative to a repository
195    pub fn to_path(&self) -> &Path {
196        gix_path::from_byte_slice(&self.0)
197    }
198
199    /// Dissolve this instance and return the buffer.
200    pub fn into_inner(self) -> BString {
201        self.0
202    }
203
204    /// Return ourselves as byte string which is a valid refname
205    pub fn as_bstr(&self) -> &BStr {
206        self.0.as_bstr()
207    }
208
209    /// Modify ourself so that we use `namespace` as prefix, if it is not yet in the `namespace`
210    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    /// Strip the given `namespace` off the beginning of this name, if it is in this namespace.
218    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    /// Strip well-known prefixes from the name and return it.
228    ///
229    /// If there is no such prefix, the original name is returned.
230    pub fn shorten(&self) -> &BStr {
231        self.as_ref().shorten()
232    }
233
234    /// Classify this name, or return `None` if it's unclassified.
235    pub fn category(&self) -> Option<crate::Category<'_>> {
236        self.as_ref().category()
237    }
238
239    /// Classify this name, or return `None` if it's unclassified. If `Some`,
240    /// the shortened name is returned as well.
241    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    /// Return the file name portion of a full name, for instance `main` if the
248    /// full name was `refs/heads/main`.
249    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}