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
use bstr::{BStr, BString};
use quick_error::quick_error;

quick_error! {
    #[derive(Debug)]
    pub enum NameError {
        InvalidByte(name: BString) {
            display("A ref must not contain invalid bytes or ascii control characters: '{}'", name)
        }
        DoubleDot {
            display("A ref must not contain '..' as it may be mistaken for a range")
        }
        LockFileSuffix {
            display("A ref must not end with '.lock'")
        }
        ReflogPortion {
            display("A ref must not contain '@{{' which is a part of a ref-log")
        }
        Asterisk {
            display("A ref must not contain '*' character")
        }
        StartsWithDot {
            display("A ref must not start with a '.'")
        }
        EndsWithSlash {
            display("A ref must not end with a '/'")
        }
        Empty {
            display("A ref must not be empty")
        }
    }
}

pub fn name(name: &BStr) -> Result<&BStr, NameError> {
    if name.is_empty() {
        return Err(NameError::Empty);
    }

    let mut last = 0;
    for byte in name.iter() {
        match byte {
            b'\\' | b'^' | b':' | b'[' | b'?' | b' ' | b'~' | b'\0'..=b'\x1F' | b'\x7F' => {
                return Err(NameError::InvalidByte(name.into()))
            }
            b'*' => return Err(NameError::Asterisk),
            b'.' if last == b'.' => return Err(NameError::DoubleDot),
            b'{' if last == b'@' => return Err(NameError::ReflogPortion),
            _ => {}
        }
        last = *byte;
    }
    if name[0] == b'.' {
        return Err(NameError::StartsWithDot);
    }
    if *name.last().expect("non-empty") == b'/' {
        return Err(NameError::EndsWithSlash);
    }
    if name.ends_with(b".lock") {
        return Err(NameError::LockFileSuffix);
    }
    Ok(name)
}