1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
use std::convert::{TryFrom, TryInto};

use git_hash::ObjectId;
use git_object::bstr::BString;
use nom::{
    bytes::complete::{tag, take_while},
    combinator::{map, opt},
    sequence::terminated,
    IResult,
};
use quick_error::quick_error;

use crate::{
    parse::{hex_hash, newline},
    store_impl::file::loose::Reference,
    FullName, Target,
};

enum MaybeUnsafeState {
    Id(ObjectId),
    UnvalidatedPath(BString),
}

quick_error! {
    /// The error returned by [`Reference::try_from_path()`].
    #[derive(Debug)]
    #[allow(missing_docs)]
    pub enum Error {
        Parse(content: BString) {
            display("{:?} could not be parsed", content)
        }
        RefnameValidation{err: git_validate::reference::name::Error, path: BString} {
            display("The path to a symbolic reference within a ref file is invalid")
            source(err)
        }
    }
}

impl TryFrom<MaybeUnsafeState> for Target {
    type Error = Error;

    fn try_from(v: MaybeUnsafeState) -> Result<Self, Self::Error> {
        Ok(match v {
            MaybeUnsafeState::Id(id) => Target::Peeled(id),
            MaybeUnsafeState::UnvalidatedPath(name) => Target::Symbolic(match git_validate::refname(name.as_ref()) {
                Ok(_) => FullName(name),
                Err(err) => return Err(Error::RefnameValidation { err, path: name }),
            }),
        })
    }
}

impl Reference {
    /// Create a new reference of the given `parent` store with `relative_path` service as unique identifier
    /// at which the `path_contents` was read to obtain the refs value.
    pub fn try_from_path(name: FullName, path_contents: &[u8]) -> Result<Self, Error> {
        Ok(Reference {
            name,
            target: parse(path_contents)
                .map_err(|_| Error::Parse(path_contents.into()))?
                .1
                .try_into()?,
        })
    }
}

fn parse(bytes: &[u8]) -> IResult<&[u8], MaybeUnsafeState> {
    let is_space = |b: u8| b == b' ';
    if let (path, Some(_ref_prefix)) = opt(terminated(tag("ref: "), take_while(is_space)))(bytes)? {
        map(
            terminated(take_while(|b| b != b'\r' && b != b'\n'), opt(newline)),
            |path| MaybeUnsafeState::UnvalidatedPath(path.into()),
        )(path)
    } else {
        map(terminated(hex_hash, opt(newline)), |hex| {
            MaybeUnsafeState::Id(ObjectId::from_hex(hex).expect("prior validation"))
        })(bytes)
    }
}