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