git_validate/
reference.rs

1///
2pub mod name {
3    use std::convert::Infallible;
4
5    /// The error used in [name()][super::name()] and [name_partial()][super::name_partial()]
6    #[derive(Debug, thiserror::Error)]
7    #[allow(missing_docs)]
8    pub enum Error {
9        #[error("A reference must be a valid tag name as well")]
10        Tag(#[from] crate::tag::name::Error),
11        #[error("Standalone references must be all uppercased, like 'HEAD'")]
12        SomeLowercase,
13        #[error("A reference name must not start with a slash '/'")]
14        StartsWithSlash,
15        #[error("Multiple slashes in a row are not allowed as they may change the reference's meaning")]
16        RepeatedSlash,
17        #[error("Names must not be a single '.', but may contain it.")]
18        SingleDot,
19    }
20
21    impl From<Infallible> for Error {
22        fn from(_: Infallible) -> Self {
23            unreachable!("this impl is needed to allow passing a known valid partial path as parameter")
24        }
25    }
26}
27
28use bstr::BStr;
29
30/// Validate a reference name running all the tests in the book. This disallows lower-case references, but allows
31/// ones like `HEAD`.
32pub fn name(path: &BStr) -> Result<&BStr, name::Error> {
33    validate(path, Mode::Complete)
34}
35
36/// Validate a partial reference name. As it is assumed to be partial, names like `some-name` is allowed
37/// even though these would be disallowed with when using [`name()`].
38pub fn name_partial(path: &BStr) -> Result<&BStr, name::Error> {
39    validate(path, Mode::Partial)
40}
41
42enum Mode {
43    Complete,
44    Partial,
45}
46
47fn validate(path: &BStr, mode: Mode) -> Result<&BStr, name::Error> {
48    crate::tagname(path)?;
49    if path[0] == b'/' {
50        return Err(name::Error::StartsWithSlash);
51    }
52    let mut previous = 0;
53    let mut one_before_previous = 0;
54    let mut saw_slash = false;
55    for byte in path.iter() {
56        match *byte {
57            b'/' if previous == b'.' && one_before_previous == b'/' => return Err(name::Error::SingleDot),
58            b'/' if previous == b'/' => return Err(name::Error::RepeatedSlash),
59            _ => {}
60        }
61
62        if *byte == b'/' {
63            saw_slash = true;
64        }
65        one_before_previous = previous;
66        previous = *byte;
67    }
68
69    if let Mode::Complete = mode {
70        if !saw_slash && !path.iter().all(|c| c.is_ascii_uppercase() || *c == b'_') {
71            return Err(name::Error::SomeLowercase);
72        }
73    }
74    Ok(path)
75}