gix_ref/
fullname.rs

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