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
use bstr::BStr;

///
pub mod name {
    use bstr::BString;

    /// The error returned by [`name()`][super::name()].
    #[derive(Debug, thiserror::Error)]
    #[allow(missing_docs)]
    pub enum Error {
        #[error("A ref must not contain invalid bytes or ascii control characters: {byte:?}")]
        InvalidByte { byte: BString },
        #[error("A ref must not contain '..' as it may be mistaken for a range")]
        DoubleDot,
        #[error("A ref must not end with '.lock'")]
        LockFileSuffix,
        #[error("A ref must not contain '@{{' which is a part of a ref-log")]
        ReflogPortion,
        #[error("A ref must not contain '*' character")]
        Asterisk,
        #[error("A ref must not start with a '.'")]
        StartsWithDot,
        #[error("A ref must not end with a '/'")]
        EndsWithSlash,
        #[error("A ref must not be empty")]
        Empty,
    }
}

/// Assure the given `input` resemble a valid git tag name, which is returned unchanged on success.
pub fn name(input: &BStr) -> Result<&BStr, name::Error> {
    if input.is_empty() {
        return Err(name::Error::Empty);
    }
    if *input.last().expect("non-empty") == b'/' {
        return Err(name::Error::EndsWithSlash);
    }

    let mut previous = 0;
    for byte in input.iter() {
        match byte {
            b'\\' | b'^' | b':' | b'[' | b'?' | b' ' | b'~' | b'\0'..=b'\x1F' | b'\x7F' => {
                return Err(name::Error::InvalidByte {
                    byte: (&[*byte][..]).into(),
                })
            }
            b'*' => return Err(name::Error::Asterisk),
            b'.' if previous == b'.' => return Err(name::Error::DoubleDot),
            b'{' if previous == b'@' => return Err(name::Error::ReflogPortion),
            _ => {}
        }
        previous = *byte;
    }
    if input[0] == b'.' {
        return Err(name::Error::StartsWithDot);
    }
    if input.ends_with(b".lock") {
        return Err(name::Error::LockFileSuffix);
    }
    Ok(input)
}