git_validate/
tag.rs

1use bstr::BStr;
2
3///
4pub mod name {
5    use bstr::BString;
6
7    /// The error returned by [`name()`][super::name()].
8    #[derive(Debug, thiserror::Error)]
9    #[allow(missing_docs)]
10    pub enum Error {
11        #[error("A ref must not contain invalid bytes or ascii control characters: {byte:?}")]
12        InvalidByte { byte: BString },
13        #[error("A ref must not contain '..' as it may be mistaken for a range")]
14        DoubleDot,
15        #[error("A ref must not end with '.lock'")]
16        LockFileSuffix,
17        #[error("A ref must not contain '@{{' which is a part of a ref-log")]
18        ReflogPortion,
19        #[error("A ref must not contain '*' character")]
20        Asterisk,
21        #[error("A ref must not start with a '.'")]
22        StartsWithDot,
23        #[error("A ref must not end with a '/'")]
24        EndsWithSlash,
25        #[error("A ref must not be empty")]
26        Empty,
27    }
28}
29
30/// Assure the given `input` resemble a valid git tag name, which is returned unchanged on success.
31pub fn name(input: &BStr) -> Result<&BStr, name::Error> {
32    if input.is_empty() {
33        return Err(name::Error::Empty);
34    }
35    if *input.last().expect("non-empty") == b'/' {
36        return Err(name::Error::EndsWithSlash);
37    }
38
39    let mut previous = 0;
40    for byte in input.iter() {
41        match byte {
42            b'\\' | b'^' | b':' | b'[' | b'?' | b' ' | b'~' | b'\0'..=b'\x1F' | b'\x7F' => {
43                return Err(name::Error::InvalidByte {
44                    byte: (&[*byte][..]).into(),
45                })
46            }
47            b'*' => return Err(name::Error::Asterisk),
48            b'.' if previous == b'.' => return Err(name::Error::DoubleDot),
49            b'{' if previous == b'@' => return Err(name::Error::ReflogPortion),
50            _ => {}
51        }
52        previous = *byte;
53    }
54    if input[0] == b'.' {
55        return Err(name::Error::StartsWithDot);
56    }
57    if input.ends_with(b".lock") {
58        return Err(name::Error::LockFileSuffix);
59    }
60    Ok(input)
61}