Skip to main content

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    /// Returns `true` if this category represents remote-tracking branches, like `refs/remotes/<remote>/<name>`.
44    pub fn is_remote_tracking_branch(&self) -> bool {
45        matches!(self, Category::RemoteBranch)
46    }
47}
48
49impl FullNameRef {
50    pub(crate) fn new_unchecked(v: &BStr) -> &Self {
51        // SAFETY: FullNameRef is transparent and equivalent to a &BStr if provided as reference
52        #[expect(unsafe_code)]
53        unsafe {
54            std::mem::transmute(v)
55        }
56    }
57}
58
59impl PartialNameRef {
60    pub(crate) fn new_unchecked(v: &BStr) -> &Self {
61        // SAFETY: PartialNameRef is transparent and equivalent to a &BStr if provided as reference
62        #[expect(unsafe_code)]
63        unsafe {
64            std::mem::transmute(v)
65        }
66    }
67}
68
69impl PartialNameRef {
70    pub(crate) fn looks_like_full_name(&self, consider_pseudo_ref: bool) -> bool {
71        let name = self.0.as_bstr();
72        name.starts_with_str("refs/")
73            || name.starts_with(Category::MainPseudoRef.prefix())
74            || name.starts_with(Category::LinkedPseudoRef { name: "".into() }.prefix())
75            || (consider_pseudo_ref && is_pseudo_ref(name))
76    }
77    pub(crate) fn construct_full_name_ref<'buf>(
78        &self,
79        inbetween: &str,
80        buf: &'buf mut BString,
81        consider_pseudo_ref: bool,
82    ) -> &'buf FullNameRef {
83        buf.clear();
84        if !self.looks_like_full_name(consider_pseudo_ref) {
85            buf.push_str("refs/");
86        }
87        if !inbetween.is_empty() {
88            buf.push_str(inbetween);
89            buf.push_byte(b'/');
90        }
91        buf.extend_from_slice(&self.0);
92        FullNameRef::new_unchecked(buf.as_bstr())
93    }
94}
95
96impl PartialNameRef {
97    /// Convert this name into the relative path possibly identifying the reference location.
98    /// Note that it may be only a partial path though.
99    pub fn to_partial_path(&self) -> &Path {
100        gix_path::from_byte_slice(self.0.as_bstr())
101    }
102
103    /// Provide the name as binary string which is known to be a valid partial ref name.
104    pub fn as_bstr(&self) -> &BStr {
105        &self.0
106    }
107}
108
109impl PartialName {
110    /// Append the `component` to ourselves and validate the newly created partial path.
111    pub fn join(self, component: &BStr) -> Result<Self, Error> {
112        let mut b = self.0;
113        b.push_byte(b'/');
114        b.extend(component.as_bytes());
115        gix_validate::reference::name_partial(b.as_ref())?;
116        Ok(PartialName(b))
117    }
118}
119
120impl<'a> convert::TryFrom<&'a BStr> for &'a FullNameRef {
121    type Error = Error;
122
123    fn try_from(v: &'a BStr) -> Result<Self, Self::Error> {
124        Ok(FullNameRef::new_unchecked(gix_validate::reference::name(v)?))
125    }
126}
127
128impl<'a> From<&'a FullNameRef> for &'a PartialNameRef {
129    fn from(v: &'a FullNameRef) -> Self {
130        PartialNameRef::new_unchecked(v.0.as_bstr())
131    }
132}
133
134impl<'a> convert::TryFrom<&'a OsStr> for &'a PartialNameRef {
135    type Error = gix_error::Exn<Error>;
136
137    fn try_from(v: &'a OsStr) -> Result<Self, Self::Error> {
138        use gix_error::ResultExt;
139
140        let v = gix_path::os_str_into_bstr(v).or_raise(|| Error::InvalidByte {
141            byte: "<unknown encoding>".into(),
142        })?;
143        Ok(PartialNameRef::new_unchecked(gix_validate::reference::name_partial(
144            v.as_bstr(),
145        )?))
146    }
147}
148
149mod impls {
150    use std::borrow::Borrow;
151
152    use crate::{PartialName, PartialNameRef, bstr::ByteSlice};
153
154    impl Borrow<PartialNameRef> for PartialName {
155        #[inline]
156        fn borrow(&self) -> &PartialNameRef {
157            PartialNameRef::new_unchecked(self.0.as_bstr())
158        }
159    }
160
161    impl AsRef<PartialNameRef> for PartialName {
162        fn as_ref(&self) -> &PartialNameRef {
163            self.borrow()
164        }
165    }
166
167    impl ToOwned for PartialNameRef {
168        type Owned = PartialName;
169
170        fn to_owned(&self) -> Self::Owned {
171            PartialName(self.0.to_owned())
172        }
173    }
174}
175
176impl<'a> convert::TryFrom<&'a BString> for &'a PartialNameRef {
177    type Error = Error;
178
179    fn try_from(v: &'a BString) -> Result<Self, Self::Error> {
180        Ok(PartialNameRef::new_unchecked(gix_validate::reference::name_partial(
181            v.as_ref(),
182        )?))
183    }
184}
185
186impl<'a> convert::TryFrom<&'a BStr> for &'a PartialNameRef {
187    type Error = Error;
188
189    fn try_from(v: &'a BStr) -> Result<Self, Self::Error> {
190        Ok(PartialNameRef::new_unchecked(gix_validate::reference::name_partial(v)?))
191    }
192}
193
194impl<'a> convert::TryFrom<&'a PartialName> for &'a PartialNameRef {
195    type Error = Error;
196
197    fn try_from(v: &'a PartialName) -> Result<Self, Self::Error> {
198        Ok(PartialNameRef::new_unchecked(v.0.as_bstr()))
199    }
200}
201
202impl<'a> convert::TryFrom<&'a str> for &'a FullNameRef {
203    type Error = Error;
204
205    fn try_from(v: &'a str) -> Result<Self, Self::Error> {
206        let v = v.as_bytes().as_bstr();
207        Ok(FullNameRef::new_unchecked(gix_validate::reference::name(v)?))
208    }
209}
210
211impl<'a> convert::TryFrom<&'a str> for &'a PartialNameRef {
212    type Error = Error;
213
214    fn try_from(v: &'a str) -> Result<Self, Self::Error> {
215        let v = v.as_bytes().as_bstr();
216        Ok(PartialNameRef::new_unchecked(gix_validate::reference::name_partial(v)?))
217    }
218}
219
220impl<'a> convert::TryFrom<&'a str> for PartialName {
221    type Error = Error;
222
223    fn try_from(v: &'a str) -> Result<Self, Self::Error> {
224        let v = v.as_bytes().as_bstr();
225        Ok(PartialName(gix_validate::reference::name_partial(v)?.to_owned()))
226    }
227}
228
229#[expect(
230    clippy::infallible_try_from,
231    reason = "it's here so that it can be done, even if infallible. Needed for parameters that use `TryFrom` generically."
232)]
233impl<'a> convert::TryFrom<&'a FullName> for &'a PartialNameRef {
234    type Error = Infallible;
235
236    fn try_from(v: &'a FullName) -> Result<Self, Self::Error> {
237        Ok(v.as_ref().as_partial_name())
238    }
239}
240
241impl<'a> convert::TryFrom<&'a String> for &'a FullNameRef {
242    type Error = Error;
243
244    fn try_from(v: &'a String) -> Result<Self, Self::Error> {
245        let v = v.as_bytes().as_bstr();
246        Ok(FullNameRef::new_unchecked(gix_validate::reference::name(v)?))
247    }
248}
249
250impl<'a> convert::TryFrom<&'a String> for &'a PartialNameRef {
251    type Error = Error;
252
253    fn try_from(v: &'a String) -> Result<Self, Self::Error> {
254        let v = v.as_bytes().as_bstr();
255        Ok(PartialNameRef::new_unchecked(gix_validate::reference::name_partial(v)?))
256    }
257}
258
259impl convert::TryFrom<String> for PartialName {
260    type Error = Error;
261
262    fn try_from(v: String) -> Result<Self, Self::Error> {
263        gix_validate::reference::name_partial(v.as_bytes().as_bstr())?;
264        Ok(PartialName(v.into()))
265    }
266}
267
268impl convert::TryFrom<BString> for PartialName {
269    type Error = Error;
270
271    fn try_from(v: BString) -> Result<Self, Self::Error> {
272        gix_validate::reference::name_partial(v.as_ref())?;
273        Ok(PartialName(v))
274    }
275}
276
277impl std::fmt::Display for PartialName {
278    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
279        std::fmt::Display::fmt(&self.0, f)
280    }
281}
282
283impl std::fmt::Display for PartialNameRef {
284    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
285        std::fmt::Display::fmt(&self.0, f)
286    }
287}
288
289/// Note that this method is disagreeing with `gix_validate` as it allows dashes '-' for some reason.
290/// Since partial names cannot be created with dashes inside we adjusted this as it's probably unintended or git creates pseudo-refs
291/// which wouldn't pass its safety checks.
292pub(crate) fn is_pseudo_ref(name: &BStr) -> bool {
293    name.bytes().all(|b| b.is_ascii_uppercase() || b == b'_')
294}