Skip to main content

gix_ref/store/file/loose/reference/
decode.rs

1use gix_hash::ObjectId;
2use gix_object::bstr::BString;
3
4use crate::{parse::hex_hash, store_impl::file::loose::Reference, FullName, Target};
5
6enum MaybeUnsafeState {
7    Id(ObjectId),
8    UnvalidatedPath(BString),
9}
10
11/// The error returned by [`Reference::try_from_path()`].
12#[derive(Debug, thiserror::Error)]
13#[allow(missing_docs)]
14pub enum Error {
15    #[error("{content:?} could not be parsed")]
16    Parse { content: BString },
17    #[error("The path {path:?} to a symbolic reference within a ref file is invalid")]
18    RefnameValidation {
19        source: gix_validate::reference::name::Error,
20        path: BString,
21    },
22}
23
24impl TryFrom<MaybeUnsafeState> for Target {
25    type Error = Error;
26
27    fn try_from(v: MaybeUnsafeState) -> Result<Self, Self::Error> {
28        Ok(match v {
29            MaybeUnsafeState::Id(id) => Target::Object(id),
30            MaybeUnsafeState::UnvalidatedPath(name) => {
31                Target::Symbolic(match gix_validate::reference::name(name.as_ref()) {
32                    Ok(_) => FullName(name),
33                    Err(err) => {
34                        return Err(Error::RefnameValidation {
35                            source: err,
36                            path: name,
37                        })
38                    }
39                })
40            }
41        })
42    }
43}
44
45impl Reference {
46    /// Create a new reference named `name` from the loose reference file contents in `path_contents`,
47    /// parsing object ids as `hash_kind`.
48    pub fn try_from_path(name: FullName, path_contents: &[u8], hash_kind: gix_hash::Kind) -> Result<Self, Error> {
49        Ok(Reference {
50            name,
51            target: parse(path_contents, hash_kind)
52                .map_err(|_| Error::Parse {
53                    content: path_contents.into(),
54                })?
55                .try_into()?,
56        })
57    }
58}
59
60/// Parse the contents of a loose reference file.
61///
62/// A *symbolic* reference starts with `ref: `, may have additional spaces before
63/// the path, and returns [`MaybeUnsafeState::UnvalidatedPath`] with the path
64/// bytes up to the next line ending or the end of input. The path is validated
65/// later when it is converted into a [`Target`].
66///
67/// A *direct* reference starts with a hexadecimal object id and returns
68/// [`MaybeUnsafeState::Id`].
69///
70/// If neither reference form can be parsed, an error is returned.
71fn parse(mut i: &[u8], hash_kind: gix_hash::Kind) -> Result<MaybeUnsafeState, ()> {
72    if let Some(rest) = i.strip_prefix(b"ref: ") {
73        i = rest;
74        while i.first() == Some(&b' ') {
75            i = &i[1..];
76        }
77        let path_end = i.iter().position(|b| *b == b'\r' || *b == b'\n').unwrap_or(i.len());
78        let path = i[..path_end].into();
79        Ok(MaybeUnsafeState::UnvalidatedPath(path))
80    } else {
81        let hex = hex_hash(&mut i, hash_kind)?;
82        if i.first().is_some_and(u8::is_ascii_hexdigit) {
83            return Err(());
84        }
85        Ok(MaybeUnsafeState::Id(ObjectId::from_hex(hex).expect("prior validation")))
86    }
87}