git_validate/
reference.rs1pub mod name {
3 use std::convert::Infallible;
4
5 #[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
30pub fn name(path: &BStr) -> Result<&BStr, name::Error> {
33 validate(path, Mode::Complete)
34}
35
36pub 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}