gix_ref/
name.rs

1use std::{convert, convert::Infallible, ffi::OsStr, path::Path};
2
3use gix_object::bstr::{BStr, BString, ByteSlice, ByteVec};
4
5use crate::{Category, FullName, FullNameRef, PartialName, PartialNameRef};
6
7/// The error used in the [`PartialNameRef`]`::try_from`(…) implementations.
8pub type Error = gix_validate::reference::name::Error;
9
10impl Category<'_> {
11    /// Return the prefix that would contain all references of our kind, or an empty string if the reference would
12    /// be directly inside of the [`git_dir()`][crate::file::Store::git_dir()].
13    pub fn prefix(&self) -> &BStr {
14        match self {
15            Category::Tag => b"refs/tags/".as_bstr(),
16            Category::LocalBranch => b"refs/heads/".as_bstr(),
17            Category::RemoteBranch => b"refs/remotes/".as_bstr(),
18            Category::Note => b"refs/notes/".as_bstr(),
19            Category::MainPseudoRef => b"main-worktree/".as_bstr(),
20            Category::MainRef => b"main-worktree/refs/".as_bstr(),
21            Category::PseudoRef => b"".as_bstr(),
22            Category::LinkedPseudoRef { .. } => b"worktrees/".as_bstr(),
23            Category::LinkedRef { .. } => b"worktrees/".as_bstr(),
24            Category::Bisect => b"refs/bisect/".as_bstr(),
25            Category::Rewritten => b"refs/rewritten/".as_bstr(),
26            Category::WorktreePrivate => b"refs/worktree/".as_bstr(),
27        }
28    }
29
30    /// Returns true if the category is private to their worktrees, and never shared with other worktrees.
31    pub fn is_worktree_private(&self) -> bool {
32        matches!(
33            self,
34            Category::MainPseudoRef
35                | Category::PseudoRef
36                | Category::LinkedPseudoRef { .. }
37                | Category::WorktreePrivate
38                | Category::Rewritten
39                | Category::Bisect
40        )
41    }
42}
43
44impl FullNameRef {
45    pub(crate) fn new_unchecked(v: &BStr) -> &Self {
46        // SAFETY: FullNameRef is transparent and equivalent to a &BStr if provided as reference
47        #[allow(unsafe_code)]
48        unsafe {
49            std::mem::transmute(v)
50        }
51    }
52}
53
54impl PartialNameRef {
55    pub(crate) fn new_unchecked(v: &BStr) -> &Self {
56        // SAFETY: PartialNameRef is transparent and equivalent to a &BStr if provided as reference
57        #[allow(unsafe_code)]
58        unsafe {
59            std::mem::transmute(v)
60        }
61    }
62}
63
64impl PartialNameRef {
65    pub(crate) fn looks_like_full_name(&self, consider_pseudo_ref: bool) -> bool {
66        let name = self.0.as_bstr();
67        name.starts_with_str("refs/")
68            || name.starts_with(Category::MainPseudoRef.prefix())
69            || name.starts_with(Category::LinkedPseudoRef { name: "".into() }.prefix())
70            || (consider_pseudo_ref && is_pseudo_ref(name))
71    }
72    pub(crate) fn construct_full_name_ref<'buf>(
73        &self,
74        inbetween: &str,
75        buf: &'buf mut BString,
76        consider_pseudo_ref: bool,
77    ) -> &'buf FullNameRef {
78        buf.clear();
79        if !self.looks_like_full_name(consider_pseudo_ref) {
80            buf.push_str("refs/");
81        }
82        if !inbetween.is_empty() {
83            buf.push_str(inbetween);
84            buf.push_byte(b'/');
85        }
86        buf.extend_from_slice(&self.0);
87        FullNameRef::new_unchecked(buf.as_bstr())
88    }
89}
90
91impl PartialNameRef {
92    /// Convert this name into the relative path possibly identifying the reference location.
93    /// Note that it may be only a partial path though.
94    pub fn to_partial_path(&self) -> &Path {
95        gix_path::from_byte_slice(self.0.as_bstr())
96    }
97
98    /// Provide the name as binary string which is known to be a valid partial ref name.
99    pub fn as_bstr(&self) -> &BStr {
100        &self.0
101    }
102}
103
104impl PartialName {
105    /// Append the `component` to ourselves and validate the newly created partial path.
106    pub fn join(self, component: &BStr) -> Result<Self, Error> {
107        let mut b = self.0;
108        b.push_byte(b'/');
109        b.extend(component.as_bytes());
110        gix_validate::reference::name_partial(b.as_ref())?;
111        Ok(PartialName(b))
112    }
113}
114
115impl<'a> convert::TryFrom<&'a BStr> for &'a FullNameRef {
116    type Error = Error;
117
118    fn try_from(v: &'a BStr) -> Result<Self, Self::Error> {
119        Ok(FullNameRef::new_unchecked(gix_validate::reference::name(v)?))
120    }
121}
122
123impl<'a> From<&'a FullNameRef> for &'a PartialNameRef {
124    fn from(v: &'a FullNameRef) -> Self {
125        PartialNameRef::new_unchecked(v.0.as_bstr())
126    }
127}
128
129impl<'a> convert::TryFrom<&'a OsStr> for &'a PartialNameRef {
130    type Error = Error;
131
132    fn try_from(v: &'a OsStr) -> Result<Self, Self::Error> {
133        let v = gix_path::os_str_into_bstr(v).map_err(|_| Error::InvalidByte {
134            byte: "<unknown encoding>".into(),
135        })?;
136        Ok(PartialNameRef::new_unchecked(gix_validate::reference::name_partial(
137            v.as_bstr(),
138        )?))
139    }
140}
141
142mod impls {
143    use std::borrow::Borrow;
144
145    use crate::{bstr::ByteSlice, PartialName, PartialNameRef};
146
147    impl Borrow<PartialNameRef> for PartialName {
148        #[inline]
149        fn borrow(&self) -> &PartialNameRef {
150            PartialNameRef::new_unchecked(self.0.as_bstr())
151        }
152    }
153
154    impl AsRef<PartialNameRef> for PartialName {
155        fn as_ref(&self) -> &PartialNameRef {
156            self.borrow()
157        }
158    }
159
160    impl ToOwned for PartialNameRef {
161        type Owned = PartialName;
162
163        fn to_owned(&self) -> Self::Owned {
164            PartialName(self.0.to_owned())
165        }
166    }
167}
168
169impl<'a> convert::TryFrom<&'a BString> for &'a PartialNameRef {
170    type Error = Error;
171
172    fn try_from(v: &'a BString) -> Result<Self, Self::Error> {
173        Ok(PartialNameRef::new_unchecked(gix_validate::reference::name_partial(
174            v.as_ref(),
175        )?))
176    }
177}
178
179impl<'a> convert::TryFrom<&'a BStr> for &'a PartialNameRef {
180    type Error = Error;
181
182    fn try_from(v: &'a BStr) -> Result<Self, Self::Error> {
183        Ok(PartialNameRef::new_unchecked(gix_validate::reference::name_partial(v)?))
184    }
185}
186
187impl<'a> convert::TryFrom<&'a PartialName> for &'a PartialNameRef {
188    type Error = Error;
189
190    fn try_from(v: &'a PartialName) -> Result<Self, Self::Error> {
191        Ok(PartialNameRef::new_unchecked(v.0.as_bstr()))
192    }
193}
194
195impl<'a> convert::TryFrom<&'a str> for &'a FullNameRef {
196    type Error = Error;
197
198    fn try_from(v: &'a str) -> Result<Self, Self::Error> {
199        let v = v.as_bytes().as_bstr();
200        Ok(FullNameRef::new_unchecked(gix_validate::reference::name(v)?))
201    }
202}
203
204impl<'a> convert::TryFrom<&'a str> for &'a PartialNameRef {
205    type Error = Error;
206
207    fn try_from(v: &'a str) -> Result<Self, Self::Error> {
208        let v = v.as_bytes().as_bstr();
209        Ok(PartialNameRef::new_unchecked(gix_validate::reference::name_partial(v)?))
210    }
211}
212
213impl<'a> convert::TryFrom<&'a str> for PartialName {
214    type Error = Error;
215
216    fn try_from(v: &'a str) -> Result<Self, Self::Error> {
217        let v = v.as_bytes().as_bstr();
218        Ok(PartialName(gix_validate::reference::name_partial(v)?.to_owned()))
219    }
220}
221
222#[allow(clippy::infallible_try_from)]
223impl<'a> convert::TryFrom<&'a FullName> for &'a PartialNameRef {
224    type Error = Infallible;
225
226    fn try_from(v: &'a FullName) -> Result<Self, Self::Error> {
227        Ok(v.as_ref().as_partial_name())
228    }
229}
230
231impl<'a> convert::TryFrom<&'a String> for &'a FullNameRef {
232    type Error = Error;
233
234    fn try_from(v: &'a String) -> Result<Self, Self::Error> {
235        let v = v.as_bytes().as_bstr();
236        Ok(FullNameRef::new_unchecked(gix_validate::reference::name(v)?))
237    }
238}
239
240impl<'a> convert::TryFrom<&'a String> for &'a PartialNameRef {
241    type Error = Error;
242
243    fn try_from(v: &'a String) -> Result<Self, Self::Error> {
244        let v = v.as_bytes().as_bstr();
245        Ok(PartialNameRef::new_unchecked(gix_validate::reference::name_partial(v)?))
246    }
247}
248
249impl convert::TryFrom<String> for PartialName {
250    type Error = Error;
251
252    fn try_from(v: String) -> Result<Self, Self::Error> {
253        gix_validate::reference::name_partial(v.as_bytes().as_bstr())?;
254        Ok(PartialName(v.into()))
255    }
256}
257
258impl convert::TryFrom<BString> for PartialName {
259    type Error = Error;
260
261    fn try_from(v: BString) -> Result<Self, Self::Error> {
262        gix_validate::reference::name_partial(v.as_ref())?;
263        Ok(PartialName(v))
264    }
265}
266
267impl std::fmt::Display for PartialName {
268    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
269        std::fmt::Display::fmt(&self.0, f)
270    }
271}
272
273impl std::fmt::Display for PartialNameRef {
274    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
275        std::fmt::Display::fmt(&self.0, f)
276    }
277}
278
279/// Note that this method is disagreeing with `gix_validate` as it allows dashes '-' for some reason.
280/// Since partial names cannot be created with dashes inside we adjusted this as it's probably unintended or git creates pseudo-refs
281/// which wouldn't pass its safety checks.
282pub(crate) fn is_pseudo_ref(name: &BStr) -> bool {
283    name.bytes().all(|b| b.is_ascii_uppercase() || b == b'_')
284}