Just like in Git, it's considered invalid.
Unified · Split
| @@ -15,8 +15,8 @@ pub mod name { · 14 unmodified lines | ||
| 15 | 15 | StartsWithSlash, |
| 16 | 16 | #[error("Multiple slashes in a row are not allowed as they may change the reference's meaning")] |
| 17 | 17 | RepeatedSlash, |
| 18 | − #[error("Names must not be a single '.', but may contain it.")] | |
| 19 | − SingleDot, | |
| 18 | + #[error("Path components must not start with '.'")] | |
| 19 | + StartsWithDot, | |
| 20 | 20 | } |
| 21 | 21 | |
| 22 | 22 | impl From<Infallible> for Error { |
| @@ -28,8 +28,8 @@ pub mod name { · 5 unmodified lines | ||
| 28 | 28 | |
| 29 | 29 | use bstr::BStr; |
| 30 | 30 | |
| 31 | −/// Validate a reference name running all the tests in the book. This disallows lower-case references, but allows | |
| 32 | −/// ones like `HEAD`. | |
| 31 | +/// Validate a reference name running all the tests in the book. This disallows lower-case references like `lower`, but also allows | |
| 32 | +/// ones like `HEAD`, and `refs/lower`. | |
| 33 | 33 | pub fn name(path: &BStr) -> Result<&BStr, name::Error> { |
| 34 | 34 | validate(path, Mode::Complete) |
| 35 | 35 | } |
| @@ -51,19 +51,17 @@ fn validate(path: &BStr, mode: Mode) -> Result<&BStr, name::Error> { · 15 unmodified lines | ||
| 51 | 51 | return Err(name::Error::StartsWithSlash); |
| 52 | 52 | } |
| 53 | 53 | let mut previous = 0; |
| 54 | − let mut one_before_previous = 0; | |
| 55 | 54 | let mut saw_slash = false; |
| 56 | 55 | for byte in path.iter() { |
| 57 | 56 | match *byte { |
| 58 | − b'/' if previous == b'.' && one_before_previous == b'/' => return Err(name::Error::SingleDot), | |
| 59 | 57 | b'/' if previous == b'/' => return Err(name::Error::RepeatedSlash), |
| 58 | + b'.' if previous == b'/' => return Err(name::Error::StartsWithDot), | |
| 60 | 59 | _ => {} |
| 61 | 60 | } |
| 62 | 61 | |
| 63 | 62 | if *byte == b'/' { |
| 64 | 63 | saw_slash = true; |
| 65 | 64 | } |
| 66 | − one_before_previous = previous; | |
| 67 | 65 | previous = *byte; |
| 68 | 66 | } |
| 69 | 67 | |
| @@ -21,6 +21,8 @@ pub mod name { · 20 unmodified lines | ||
| 21 | 21 | Asterisk, |
| 22 | 22 | #[error("A ref must not start with a '.'")] |
| 23 | 23 | StartsWithDot, |
| 24 | + #[error("A ref must not end with a '.'")] | |
| 25 | + EndsWithDot, | |
| 24 | 26 | #[error("A ref must not end with a '/'")] |
| 25 | 27 | EndsWithSlash, |
| 26 | 28 | #[error("A ref must not be empty")] |
| @@ -29,6 +31,7 @@ pub mod name { · 2 unmodified lines | ||
| 29 | 31 | } |
| 30 | 32 | |
| 31 | 33 | /// Assure the given `input` resemble a valid git tag name, which is returned unchanged on success. |
| 34 | +/// Tag names are provided as names, lik` v1.0` or `alpha-1`, without paths. | |
| 32 | 35 | pub fn name(input: &BStr) -> Result<&BStr, name::Error> { |
| 33 | 36 | if input.is_empty() { |
| 34 | 37 | return Err(name::Error::Empty); |
| @@ -55,6 +58,9 @@ pub fn name(input: &BStr) -> Result<&BStr, name::Error> { · 20 unmodified lines | ||
| 55 | 58 | if input[0] == b'.' { |
| 56 | 59 | return Err(name::Error::StartsWithDot); |
| 57 | 60 | } |
| 61 | + if input[input.len() - 1] == b'.' { | |
| 62 | + return Err(name::Error::EndsWithDot); | |
| 63 | + } | |
| 58 | 64 | if input.ends_with(b".lock") { |
| 59 | 65 | return Err(name::Error::LockFileSuffix); |
| 60 | 66 | } |
| @@ -55,7 +55,7 @@ mod name_partial { · 54 unmodified lines | ||
| 55 | 55 | mktest!( |
| 56 | 56 | refs_path_component_is_singular_dot, |
| 57 | 57 | b"refs/./still-inside-but-not-cool", |
| 58 | − RefError::SingleDot | |
| 58 | + RefError::StartsWithDot | |
| 59 | 59 | ); |
| 60 | 60 | mktest!(any_path_starts_with_slash, b"/etc/foo", RefError::StartsWithSlash); |
| 61 | 61 | mktest!(empty_path, b"", RefError::Tag(TagError::Empty)); |
| @@ -107,6 +107,7 @@ mod name { · 45 unmodified lines | ||
| 107 | 107 | mktest!(all_uppercase, b"MAIN"); |
| 108 | 108 | mktest!(all_uppercase_with_underscore, b"NEW_HEAD"); |
| 109 | 109 | mktest!(chinese_utf8, "refs/heads/你好吗".as_bytes()); |
| 110 | + mktest!(dot_in_directory_component, b"this./totally./works"); | |
| 110 | 111 | } |
| 111 | 112 | |
| 112 | 113 | mod invalid { |
| @@ -136,10 +137,21 @@ mod name { · 23 unmodified lines | ||
| 136 | 137 | b".refs/somewhere", |
| 137 | 138 | RefError::Tag(TagError::StartsWithDot) |
| 138 | 139 | ); |
| 140 | + | |
| 141 | + mktest!( | |
| 142 | + refs_path_name_starts_with_dot_in_name, | |
| 143 | + b"refs/.somewhere", | |
| 144 | + RefError::StartsWithDot | |
| 145 | + ); | |
| 146 | + mktest!( | |
| 147 | + refs_path_name_ends_with_dot_in_name, | |
| 148 | + b"refs/somewhere.", | |
| 149 | + RefError::Tag(TagError::EndsWithDot) | |
| 150 | + ); | |
| 139 | 151 | mktest!( |
| 140 | 152 | refs_path_component_is_singular_dot, |
| 141 | 153 | b"refs/./still-inside-but-not-cool", |
| 142 | − RefError::SingleDot | |
| 154 | + RefError::StartsWithDot | |
| 143 | 155 | ); |
| 144 | 156 | mktest!(capitalized_name_without_path, b"Main", RefError::SomeLowercase); |
| 145 | 157 | mktest!(lowercase_name_without_path, b"main", RefError::SomeLowercase); |
| @@ -19,7 +19,6 @@ mod name { · 18 unmodified lines | ||
| 19 | 19 | mktest!(contains_brackets, b"this_{is-fine}_too"); |
| 20 | 20 | mktest!(contains_brackets_and_at, b"this_{@is-fine@}_too"); |
| 21 | 21 | mktest!(dot_in_the_middle, b"token.other"); |
| 22 | − mktest!(dot_at_the_end, b"hello."); | |
| 23 | 22 | mktest!(slash_inbetween, b"hello/world"); |
| 24 | 23 | } |
| 25 | 24 | |
| @@ -76,6 +75,7 @@ mod name { · 50 unmodified lines | ||
| 76 | 75 | mktestb!(contains_newline, b"prefix\nsuffix"); |
| 77 | 76 | mktestb!(contains_carriage_return, b"prefix\rsuffix"); |
| 78 | 77 | mktest!(starts_with_dot, b".with-dot", StartsWithDot); |
| 78 | + mktest!(ends_with_dot, b"with-dot.", EndsWithDot); | |
| 79 | 79 | mktest!(empty, b"", Empty); |
| 80 | 80 | } |
| 81 | 81 | } |