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

1use std::convert::{TryFrom, TryInto};
2
3use git_hash::ObjectId;
4use git_object::bstr::BString;
5use nom::{
6    bytes::complete::{tag, take_while},
7    combinator::{map, opt},
8    sequence::terminated,
9    IResult,
10};
11
12use crate::{
13    parse::{hex_hash, newline},
14    store_impl::file::loose::Reference,
15    FullName, Target,
16};
17
18enum MaybeUnsafeState {
19    Id(ObjectId),
20    UnvalidatedPath(BString),
21}
22
23/// The error returned by [`Reference::try_from_path()`].
24#[derive(Debug, thiserror::Error)]
25#[allow(missing_docs)]
26pub enum Error {
27    #[error("{content:?} could not be parsed")]
28    Parse { content: BString },
29    #[error("The path {path:?} to a symbolic reference within a ref file is invalid")]
30    RefnameValidation {
31        source: git_validate::reference::name::Error,
32        path: BString,
33    },
34}
35
36impl TryFrom<MaybeUnsafeState> for Target {
37    type Error = Error;
38
39    fn try_from(v: MaybeUnsafeState) -> Result<Self, Self::Error> {
40        Ok(match v {
41            MaybeUnsafeState::Id(id) => Target::Peeled(id),
42            MaybeUnsafeState::UnvalidatedPath(name) => Target::Symbolic(match git_validate::refname(name.as_ref()) {
43                Ok(_) => FullName(name),
44                Err(err) => {
45                    return Err(Error::RefnameValidation {
46                        source: err,
47                        path: name,
48                    })
49                }
50            }),
51        })
52    }
53}
54
55impl Reference {
56    /// Create a new reference of the given `parent` store with `relative_path` service as unique identifier
57    /// at which the `path_contents` was read to obtain the refs value.
58    pub fn try_from_path(name: FullName, path_contents: &[u8]) -> Result<Self, Error> {
59        Ok(Reference {
60            name,
61            target: parse(path_contents)
62                .map_err(|_| Error::Parse {
63                    content: path_contents.into(),
64                })?
65                .1
66                .try_into()?,
67        })
68    }
69}
70
71fn parse(bytes: &[u8]) -> IResult<&[u8], MaybeUnsafeState> {
72    let is_space = |b: u8| b == b' ';
73    if let (path, Some(_ref_prefix)) = opt(terminated(tag("ref: "), take_while(is_space)))(bytes)? {
74        map(
75            terminated(take_while(|b| b != b'\r' && b != b'\n'), opt(newline)),
76            |path| MaybeUnsafeState::UnvalidatedPath(path.into()),
77        )(path)
78    } else {
79        map(terminated(hex_hash, opt(newline)), |hex| {
80            MaybeUnsafeState::Id(ObjectId::from_hex(hex).expect("prior validation"))
81        })(bytes)
82    }
83}