gix_ref/
fullname.rs

1use std::{borrow::Borrow, path::Path};
2
3use gix_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 = gix_validate::reference::name::Error;
9
10    fn try_from(value: &str) -> Result<Self, Self::Error> {
11        Ok(FullName(
12            gix_validate::reference::name(value.as_bytes().as_bstr())?.into(),
13        ))
14    }
15}
16
17impl TryFrom<String> for FullName {
18    type Error = gix_validate::reference::name::Error;
19
20    fn try_from(value: String) -> Result<Self, Self::Error> {
21        gix_validate::reference::name(value.as_bytes().as_bstr())?;
22        Ok(FullName(value.into()))
23    }
24}
25
26impl TryFrom<&BStr> for FullName {
27    type Error = gix_validate::reference::name::Error;
28
29    fn try_from(value: &BStr) -> Result<Self, Self::Error> {
30        Ok(FullName(gix_validate::reference::name(value)?.into()))
31    }
32}
33
34impl TryFrom<BString> for FullName {
35    type Error = gix_validate::reference::name::Error;
36
37    fn try_from(value: BString) -> Result<Self, Self::Error> {
38        gix_validate::reference::name(value.as_ref())?;
39        Ok(FullName(value))
40    }
41}
42
43impl TryFrom<&BString> for FullName {
44    type Error = gix_validate::reference::name::Error;
45
46    fn try_from(value: &BString) -> Result<Self, Self::Error> {
47        gix_validate::reference::name(value.as_ref())?;
48        Ok(FullName(value.clone()))
49    }
50}
51
52impl From<FullName> for BString {
53    fn from(name: FullName) -> Self {
54        name.0
55    }
56}
57
58impl<'a> From<&'a FullNameRef> for &'a BStr {
59    fn from(name: &'a FullNameRef) -> Self {
60        &name.0
61    }
62}
63
64impl<'a> From<&'a FullNameRef> for FullName {
65    fn from(value: &'a FullNameRef) -> Self {
66        FullName(value.as_bstr().into())
67    }
68}
69
70impl std::fmt::Display for FullName {
71    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
72        std::fmt::Display::fmt(&self.0, f)
73    }
74}
75
76impl FullNameRef {
77    /// Interpret this fully qualified reference name as partial name.
78    pub fn as_partial_name(&self) -> &PartialNameRef {
79        PartialNameRef::new_unchecked(self.0.as_bstr())
80    }
81
82    /// Convert this name into the relative path identifying the reference location.
83    pub fn to_path(&self) -> &Path {
84        gix_path::from_byte_slice(&self.0)
85    }
86
87    /// Return ourselves as byte string which is a valid refname
88    pub fn as_bstr(&self) -> &BStr {
89        &self.0
90    }
91
92    /// Strip well-known prefixes from the name and return it.
93    ///
94    /// If there is no such prefix, the original name is returned.
95    pub fn shorten(&self) -> &BStr {
96        self.category_and_short_name()
97            .map_or_else(|| self.0.as_bstr(), |(_, short)| short)
98    }
99
100    /// Classify this name, or return `None` if it's unclassified.
101    pub fn category(&self) -> Option<Category<'_>> {
102        self.category_and_short_name().map(|(cat, _)| cat)
103    }
104
105    /// Classify this name, or return `None` if it's unclassified. If `Some`,
106    /// the shortened name is returned as well.
107    pub fn category_and_short_name(&self) -> Option<(Category<'_>, &BStr)> {
108        let name = self.0.as_bstr();
109        for category in &[Category::Tag, Category::LocalBranch, Category::RemoteBranch] {
110            if let Some(shortened) = name.strip_prefix(category.prefix().as_bytes()) {
111                return Some((*category, shortened.as_bstr()));
112            }
113        }
114
115        for category in &[
116            Category::Note,
117            Category::Bisect,
118            Category::WorktreePrivate,
119            Category::Rewritten,
120        ] {
121            if name.starts_with(category.prefix().as_ref()) {
122                return Some((
123                    *category,
124                    name.strip_prefix(b"refs/")
125                        .expect("we checked for refs/* above")
126                        .as_bstr(),
127                ));
128            }
129        }
130
131        if is_pseudo_ref(name) {
132            Some((Category::PseudoRef, name))
133        } else if let Some(shortened) = name.strip_prefix(Category::MainPseudoRef.prefix().as_bytes()) {
134            if shortened.starts_with_str("refs/") {
135                (Category::MainRef, shortened.as_bstr()).into()
136            } else {
137                is_pseudo_ref(shortened.into()).then(|| (Category::MainPseudoRef, shortened.as_bstr()))
138            }
139        } else if let Some(shortened_with_worktree_name) =
140            name.strip_prefix(Category::LinkedPseudoRef { name: "".into() }.prefix().as_bytes())
141        {
142            let (name, shortened) = shortened_with_worktree_name.find_byte(b'/').map(|pos| {
143                (
144                    shortened_with_worktree_name[..pos].as_bstr(),
145                    shortened_with_worktree_name[pos + 1..].as_bstr(),
146                )
147            })?;
148            if shortened.starts_with_str("refs/") {
149                (Category::LinkedRef { name }, shortened.as_bstr()).into()
150            } else {
151                is_pseudo_ref(shortened).then(|| (Category::LinkedPseudoRef { name }, shortened.as_bstr()))
152            }
153        } else {
154            None
155        }
156    }
157}
158
159/// Conversion
160impl Category<'_> {
161    /// As the inverse of [`FullNameRef::category_and_short_name()`], use the prefix of this category alongside
162    /// the `short_name` to create a valid fully qualified [reference name](FullName).
163    pub fn to_full_name<'a>(&self, short_name: impl Into<&'a BStr>) -> Result<FullName, crate::name::Error> {
164        let mut out: BString = self.prefix().into();
165        let short_name = short_name.into();
166        let partial_name = match self {
167            Category::Note => short_name.strip_prefix("notes/".as_bytes()).unwrap_or(short_name),
168            Category::MainRef => short_name.strip_prefix("refs/".as_bytes()).unwrap_or(short_name),
169            Category::LinkedPseudoRef { name } | Category::LinkedRef { name } => {
170                out.extend_from_slice(name);
171                out.push(b'/');
172                short_name
173            }
174            Category::Bisect => short_name.strip_prefix("bisect/".as_bytes()).unwrap_or(short_name),
175            Category::Rewritten => short_name.strip_prefix("rewritten/".as_bytes()).unwrap_or(short_name),
176            Category::WorktreePrivate => short_name.strip_prefix("worktree/".as_bytes()).unwrap_or(short_name),
177            Category::Tag
178            | Category::LocalBranch
179            | Category::RemoteBranch
180            | Category::PseudoRef
181            | Category::MainPseudoRef => short_name,
182        };
183        out.extend_from_slice(partial_name);
184        FullName::try_from(out)
185    }
186}
187
188impl FullName {
189    /// Convert this name into the relative path, lossily, identifying the reference location relative to a repository
190    pub fn to_path(&self) -> &Path {
191        gix_path::from_byte_slice(&self.0)
192    }
193
194    /// Dissolve this instance and return the buffer.
195    pub fn into_inner(self) -> BString {
196        self.0
197    }
198
199    /// Return ourselves as byte string which is a valid refname
200    pub fn as_bstr(&self) -> &BStr {
201        self.0.as_bstr()
202    }
203
204    /// Modify ourself so that we use `namespace` as prefix, if it is not yet in the `namespace`
205    pub fn prefix_namespace(&mut self, namespace: &Namespace) -> &mut Self {
206        if !self.0.starts_with_str(&namespace.0) {
207            self.0.insert_str(0, &namespace.0);
208        }
209        self
210    }
211
212    /// Strip the given `namespace` off the beginning of this name, if it is in this namespace.
213    pub fn strip_namespace(&mut self, namespace: &Namespace) -> &mut Self {
214        if self.0.starts_with_str(&namespace.0) {
215            let prev_len = self.0.len();
216            self.0.copy_within(namespace.0.len().., 0);
217            self.0.resize(prev_len - namespace.0.len(), 0);
218        }
219        self
220    }
221
222    /// Strip well-known prefixes from the name and return it.
223    ///
224    /// If there is no such prefix, the original name is returned.
225    pub fn shorten(&self) -> &BStr {
226        self.as_ref().shorten()
227    }
228
229    /// Classify this name, or return `None` if it's unclassified.
230    pub fn category(&self) -> Option<crate::Category<'_>> {
231        self.as_ref().category()
232    }
233
234    /// Classify this name, or return `None` if it's unclassified. If `Some`,
235    /// the shortened name is returned as well.
236    pub fn category_and_short_name(&self) -> Option<(crate::Category<'_>, &BStr)> {
237        self.as_ref().category_and_short_name()
238    }
239}
240
241impl FullNameRef {
242    /// Return the file name portion of a full name, for instance `main` if the
243    /// full name was `refs/heads/main`.
244    pub fn file_name(&self) -> &BStr {
245        self.0.rsplitn(2, |b| *b == b'/').next().expect("valid ref").as_bstr()
246    }
247}
248
249impl Borrow<FullNameRef> for FullName {
250    #[inline]
251    fn borrow(&self) -> &FullNameRef {
252        FullNameRef::new_unchecked(self.0.as_bstr())
253    }
254}
255
256impl AsRef<FullNameRef> for FullName {
257    fn as_ref(&self) -> &FullNameRef {
258        self.borrow()
259    }
260}
261
262impl ToOwned for FullNameRef {
263    type Owned = FullName;
264
265    fn to_owned(&self) -> Self::Owned {
266        FullName(self.0.to_owned())
267    }
268}