1
- use bstr:: BStr ;
1
+ use bstr:: { BStr , BString } ;
2
2
3
3
///
4
4
#[ allow( clippy:: empty_docs) ]
@@ -11,8 +11,12 @@ pub mod name {
11
11
pub enum Error {
12
12
#[ error( "A ref must not contain invalid bytes or ascii control characters: {byte:?}" ) ]
13
13
InvalidByte { byte : BString } ,
14
+ #[ error( "A reference name must not start with a slash '/'" ) ]
15
+ StartsWithSlash ,
16
+ #[ error( "Multiple slashes in a row are not allowed as they may change the reference's meaning" ) ]
17
+ RepeatedSlash ,
14
18
#[ error( "A ref must not contain '..' as it may be mistaken for a range" ) ]
15
- DoubleDot ,
19
+ RepeatedDot ,
16
20
#[ error( "A ref must not end with '.lock'" ) ]
17
21
LockFileSuffix ,
18
22
#[ error( "A ref must not contain '@{{' which is a part of a ref-log" ) ]
@@ -33,36 +37,130 @@ pub mod name {
33
37
/// Assure the given `input` resemble a valid git tag name, which is returned unchanged on success.
34
38
/// Tag names are provided as names, lik` v1.0` or `alpha-1`, without paths.
35
39
pub fn name ( input : & BStr ) -> Result < & BStr , name:: Error > {
40
+ match name_inner ( input, Mode :: Validate ) ? {
41
+ None => Ok ( input) ,
42
+ Some ( _) => {
43
+ unreachable ! ( "When validating, the input isn't changed" )
44
+ }
45
+ }
46
+ }
47
+
48
+ #[ derive( Eq , PartialEq ) ]
49
+ pub ( crate ) enum Mode {
50
+ Sanitize ,
51
+ Validate ,
52
+ }
53
+
54
+ pub ( crate ) fn name_inner ( input : & BStr , mode : Mode ) -> Result < Option < BString > , name:: Error > {
55
+ let mut out: Option < BString > =
56
+ matches ! ( mode, Mode :: Sanitize ) . then ( || BString :: from ( Vec :: with_capacity ( input. len ( ) ) ) ) ;
36
57
if input. is_empty ( ) {
37
- return Err ( name:: Error :: Empty ) ;
58
+ return if let Some ( mut out) = out {
59
+ out. push ( b'-' ) ;
60
+ Ok ( Some ( out) )
61
+ } else {
62
+ Err ( name:: Error :: Empty )
63
+ } ;
38
64
}
39
- if * input. last ( ) . expect ( "non-empty" ) == b'/' {
65
+ if * input. last ( ) . expect ( "non-empty" ) == b'/' && out . is_none ( ) {
40
66
return Err ( name:: Error :: EndsWithSlash ) ;
41
67
}
68
+ if input. first ( ) == Some ( & b'/' ) && out. is_none ( ) {
69
+ return Err ( name:: Error :: StartsWithSlash ) ;
70
+ }
42
71
43
72
let mut previous = 0 ;
44
73
for byte in input. iter ( ) {
45
74
match byte {
46
75
b'\\' | b'^' | b':' | b'[' | b'?' | b' ' | b'~' | b'\0' ..=b'\x1F' | b'\x7F' => {
47
- return Err ( name:: Error :: InvalidByte {
48
- byte : ( & [ * byte] [ ..] ) . into ( ) ,
49
- } )
76
+ if let Some ( out) = out. as_mut ( ) {
77
+ out. push ( b'-' ) ;
78
+ } else {
79
+ return Err ( name:: Error :: InvalidByte {
80
+ byte : ( & [ * byte] [ ..] ) . into ( ) ,
81
+ } ) ;
82
+ }
83
+ }
84
+ b'*' => {
85
+ if let Some ( out) = out. as_mut ( ) {
86
+ out. push ( b'-' ) ;
87
+ } else {
88
+ return Err ( name:: Error :: Asterisk ) ;
89
+ }
90
+ }
91
+
92
+ b'.' if previous == b'.' => {
93
+ if out. is_none ( ) {
94
+ return Err ( name:: Error :: RepeatedDot ) ;
95
+ }
96
+ }
97
+ b'.' if previous == b'/' => {
98
+ if let Some ( out) = out. as_mut ( ) {
99
+ out. push ( b'-' ) ;
100
+ } else {
101
+ return Err ( name:: Error :: StartsWithDot ) ;
102
+ }
103
+ }
104
+ b'{' if previous == b'@' => {
105
+ if let Some ( out) = out. as_mut ( ) {
106
+ out. push ( b'-' ) ;
107
+ } else {
108
+ return Err ( name:: Error :: ReflogPortion ) ;
109
+ }
110
+ }
111
+ b'/' if previous == b'/' => {
112
+ if out. is_none ( ) {
113
+ return Err ( name:: Error :: RepeatedSlash ) ;
114
+ }
115
+ }
116
+ b'.' if previous == b'/' => {
117
+ if let Some ( out) = out. as_mut ( ) {
118
+ out. push ( b'-' ) ;
119
+ } else {
120
+ return Err ( name:: Error :: StartsWithDot ) ;
121
+ }
122
+ }
123
+ c => {
124
+ if let Some ( out) = out. as_mut ( ) {
125
+ out. push ( * c)
126
+ }
50
127
}
51
- b'*' => return Err ( name:: Error :: Asterisk ) ,
52
- b'.' if previous == b'.' => return Err ( name:: Error :: DoubleDot ) ,
53
- b'{' if previous == b'@' => return Err ( name:: Error :: ReflogPortion ) ,
54
- _ => { }
55
128
}
56
129
previous = * byte;
57
130
}
131
+
132
+ if let Some ( out) = out. as_mut ( ) {
133
+ while out. last ( ) == Some ( & b'/' ) {
134
+ out. pop ( ) ;
135
+ }
136
+ while out. first ( ) == Some ( & b'/' ) {
137
+ out. remove ( 0 ) ;
138
+ }
139
+ }
58
140
if input[ 0 ] == b'.' {
59
- return Err ( name:: Error :: StartsWithDot ) ;
141
+ if let Some ( out) = out. as_mut ( ) {
142
+ out[ 0 ] = b'-' ;
143
+ } else {
144
+ return Err ( name:: Error :: StartsWithDot ) ;
145
+ }
60
146
}
61
147
if input[ input. len ( ) - 1 ] == b'.' {
62
- return Err ( name:: Error :: EndsWithDot ) ;
148
+ if let Some ( out) = out. as_mut ( ) {
149
+ let last = out. len ( ) - 1 ;
150
+ out[ last] = b'-' ;
151
+ } else {
152
+ return Err ( name:: Error :: EndsWithDot ) ;
153
+ }
63
154
}
64
155
if input. ends_with ( b".lock" ) {
65
- return Err ( name:: Error :: LockFileSuffix ) ;
156
+ if let Some ( out) = out. as_mut ( ) {
157
+ while out. ends_with ( b".lock" ) {
158
+ let len_without_suffix = out. len ( ) - b".lock" . len ( ) ;
159
+ out. truncate ( len_without_suffix) ;
160
+ }
161
+ } else {
162
+ return Err ( name:: Error :: LockFileSuffix ) ;
163
+ }
66
164
}
67
- Ok ( input )
165
+ Ok ( out )
68
166
}
0 commit comments