1use bstr::BStr;
2
3pub mod name {
5 use bstr::BString;
6
7 #[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
30pub 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}