git_ref/
fullname.rs

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    /// Interpret this fully qualified reference name as partial name.
76    pub fn as_partial_name(&self) -> &PartialNameRef {
77        PartialNameRef::new_unchecked(self.0.as_bstr())
78    }
79
80    /// Convert this name into the relative path identifying the reference location.
81    pub fn to_path(&self) -> &Path {
82        git_path::from_byte_slice(&self.0)
83    }
84
85    /// Return ourselves as byte string which is a valid refname
86    pub fn as_bstr(&self) -> &BStr {
87        &self.0
88    }
89
90    /// Strip well-known prefixes from the name and return it.
91    ///
92    /// If there is no such prefix, the original name is returned.
93    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    /// Classify this name, or return `None` if it's unclassified.
100    pub fn category(&self) -> Option<Category<'_>> {
101        self.category_and_short_name().map(|(cat, _)| cat)
102    }
103
104    /// Classify this name, or return `None` if it's unclassified. If `Some`,
105    /// the shortened name is returned as well.
106    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    /// Convert this name into the relative path, lossily, identifying the reference location relative to a repository
160    pub fn to_path(&self) -> &Path {
161        git_path::from_byte_slice(&self.0)
162    }
163
164    /// Dissolve this instance and return the buffer.
165    pub fn into_inner(self) -> BString {
166        self.0
167    }
168
169    /// Return ourselves as byte string which is a valid refname
170    pub fn as_bstr(&self) -> &BStr {
171        self.0.as_bstr()
172    }
173
174    /// Modify ourself so that we use `namespace` as prefix, if it is not yet in the `namespace`
175    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    /// Strip the given `namespace` off the beginning of this name, if it is in this namespace.
183    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    /// Strip well-known prefixes from the name and return it.
193    ///
194    /// If there is no such prefix, the original name is returned.
195    pub fn shorten(&self) -> &BStr {
196        self.as_ref().shorten()
197    }
198
199    /// Classify this name, or return `None` if it's unclassified.
200    pub fn category(&self) -> Option<crate::Category<'_>> {
201        self.as_ref().category()
202    }
203
204    /// Classify this name, or return `None` if it's unclassified. If `Some`,
205    /// the shortened name is returned as well.
206    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    /// Return the file name portion of a full name, for instance `main` if the
213    /// full name was `refs/heads/main`.
214    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}