1- use bstr:: BStr ;
1+ use bstr:: { BStr , BString } ;
22
33///
44#[ allow( clippy:: empty_docs) ]
@@ -11,8 +11,12 @@ pub mod name {
1111 pub enum Error {
1212 #[ error( "A ref must not contain invalid bytes or ascii control characters: {byte:?}" ) ]
1313 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 ,
1418 #[ error( "A ref must not contain '..' as it may be mistaken for a range" ) ]
15- DoubleDot ,
19+ RepeatedDot ,
1620 #[ error( "A ref must not end with '.lock'" ) ]
1721 LockFileSuffix ,
1822 #[ error( "A ref must not contain '@{{' which is a part of a ref-log" ) ]
@@ -33,36 +37,130 @@ pub mod name {
3337/// Assure the given `input` resemble a valid git tag name, which is returned unchanged on success.
3438/// Tag names are provided as names, lik` v1.0` or `alpha-1`, without paths.
3539pub 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 ( ) ) ) ) ;
3657 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+ } ;
3864 }
39- if * input. last ( ) . expect ( "non-empty" ) == b'/' {
65+ if * input. last ( ) . expect ( "non-empty" ) == b'/' && out . is_none ( ) {
4066 return Err ( name:: Error :: EndsWithSlash ) ;
4167 }
68+ if input. first ( ) == Some ( & b'/' ) && out. is_none ( ) {
69+ return Err ( name:: Error :: StartsWithSlash ) ;
70+ }
4271
4372 let mut previous = 0 ;
4473 for byte in input. iter ( ) {
4574 match byte {
4675 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+ }
50127 }
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- _ => { }
55128 }
56129 previous = * byte;
57130 }
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+ }
58140 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+ }
60146 }
61147 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+ }
63154 }
64155 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+ }
66164 }
67- Ok ( input )
165+ Ok ( out )
68166}
0 commit comments