@@ -1016,11 +1016,11 @@ impl Glob {
10161016 fn ensure_trailing_slash ( & self , path : & str ) -> String {
10171017 if path. ends_with ( '/' ) || path. ends_with ( '\\' ) {
10181018 path. to_string ( )
1019- } else if self . posix || ! cfg ! ( windows ) {
1019+ } else if self . should_normalize_backslashes ( ) {
10201020 format ! ( "{path}/" )
10211021 } else {
10221022 // On Windows without posix option, use the native separator
1023- format ! ( "{path}/ " )
1023+ format ! ( "{path}\\ " )
10241024 }
10251025 }
10261026
@@ -1104,28 +1104,36 @@ impl Glob {
11041104 prefix_to_strip : & Option < String > ,
11051105 is_walk_root : bool ,
11061106 ) -> Cow < ' a , str > {
1107- // Fast path: no prefix and no backslashes
1108- if prefix_to_strip. is_none ( ) && !rel_str_from_walk_root. contains ( '\\' ) {
1107+ let normalize = self . should_normalize_backslashes ( ) ;
1108+ let has_backslash = rel_str_from_walk_root. contains ( '\\' ) ;
1109+ let needs_backslash_conversion = normalize && has_backslash;
1110+
1111+ // Fast path: no prefix and no conversion needed
1112+ if prefix_to_strip. is_none ( ) && !needs_backslash_conversion {
11091113 return Cow :: Borrowed ( rel_str_from_walk_root) ;
11101114 }
11111115
1116+ // Determine the separator to use when building paths
1117+ let sep = if normalize { "/" } else { "\\ " } ;
1118+
11121119 // Need to construct the path
11131120 match prefix_to_strip {
11141121 Some ( prefix) => {
11151122 if is_walk_root {
11161123 Cow :: Owned ( prefix. clone ( ) )
1117- } else if rel_str_from_walk_root . contains ( '\\' ) {
1124+ } else if needs_backslash_conversion {
11181125 Cow :: Owned ( format ! (
1119- "{}/ {}" ,
1126+ "{}{} {}" ,
11201127 prefix,
1128+ sep,
11211129 rel_str_from_walk_root. replace( '\\' , "/" )
11221130 ) )
11231131 } else {
1124- Cow :: Owned ( format ! ( "{prefix}/ {rel_str_from_walk_root}" ) )
1132+ Cow :: Owned ( format ! ( "{prefix}{sep} {rel_str_from_walk_root}" ) )
11251133 }
11261134 }
11271135 None => {
1128- // Has backslashes, needs conversion
1136+ // Has backslashes and needs conversion
11291137 Cow :: Owned ( rel_str_from_walk_root. replace ( '\\' , "/" ) )
11301138 }
11311139 }
@@ -1138,29 +1146,31 @@ impl Glob {
11381146 /// # Arguments
11391147 /// * `rel_str_from_walk_root` - The path relative to the walk root
11401148 /// * `prefix_to_strip` - The original prefix (without trailing slash)
1141- /// * `prefix_with_slash` - Pre-computed "prefix/" for fast concatenation
1149+ /// * `prefix_with_slash` - Pre-computed "prefix/" or "prefix\\" for fast concatenation
11421150 /// * `is_walk_root` - True if this is the walk root entry itself
1151+ /// * `normalize_backslashes` - Whether to convert backslashes to forward slashes
11431152 /// * `buffer` - Reusable string buffer
11441153 #[ inline]
11451154 fn normalize_path_buffered < ' a > (
11461155 rel_str_from_walk_root : & str ,
11471156 prefix_to_strip : & Option < String > ,
11481157 prefix_with_slash : & Option < String > ,
11491158 is_walk_root : bool ,
1159+ normalize_backslashes : bool ,
11501160 buffer : & ' a mut String ,
11511161 ) -> & ' a str {
1152- // Fast path: no prefix and no backslashes - can't return borrowed reference
1153- // from the input because we need to return from buffer for consistency
1162+ let has_backslash = rel_str_from_walk_root. contains ( '\\' ) ;
1163+ let needs_conversion = normalize_backslashes && has_backslash;
1164+
1165+ // Fast path: no prefix and no conversion needed
11541166 if prefix_to_strip. is_none ( ) {
1155- if !rel_str_from_walk_root. contains ( '\\' ) {
1156- buffer. clear ( ) ;
1157- buffer. push_str ( rel_str_from_walk_root) ;
1158- return buffer. as_str ( ) ;
1159- }
1160- // Has backslashes, needs conversion
11611167 buffer. clear ( ) ;
1162- for c in rel_str_from_walk_root. chars ( ) {
1163- buffer. push ( if c == '\\' { '/' } else { c } ) ;
1168+ if needs_conversion {
1169+ for c in rel_str_from_walk_root. chars ( ) {
1170+ buffer. push ( if c == '\\' { '/' } else { c } ) ;
1171+ }
1172+ } else {
1173+ buffer. push_str ( rel_str_from_walk_root) ;
11641174 }
11651175 return buffer. as_str ( ) ;
11661176 }
@@ -1178,10 +1188,10 @@ impl Glob {
11781188 buffer. push_str ( prefix_slash) ;
11791189 } else {
11801190 buffer. push_str ( prefix) ;
1181- buffer. push ( '/' ) ;
1191+ buffer. push ( if normalize_backslashes { '/' } else { '\\' } ) ;
11821192 }
11831193
1184- if rel_str_from_walk_root . contains ( '\\' ) {
1194+ if needs_conversion {
11851195 // Convert backslashes while appending
11861196 for c in rel_str_from_walk_root. chars ( ) {
11871197 buffer. push ( if c == '\\' { '/' } else { c } ) ;
@@ -1206,6 +1216,8 @@ impl Glob {
12061216 ) -> String {
12071217 // When mark:true, add trailing slash to directories but NOT to symlinks
12081218 let should_mark_as_dir = is_dir && !is_symlink && self . mark ;
1219+ let sep = if self . should_normalize_backslashes ( ) { '/' } else { '\\' } ;
1220+ let dot_prefix = if self . should_normalize_backslashes ( ) { "./" } else { ".\\ " } ;
12091221
12101222 if self . absolute {
12111223 // Build absolute path
@@ -1215,16 +1227,16 @@ impl Glob {
12151227
12161228 if should_mark_as_dir && !formatted. ends_with ( '/' ) && !formatted. ends_with ( '\\' ) {
12171229 let mut result = formatted. to_string ( ) ;
1218- result. push ( '/' ) ;
1230+ result. push ( sep ) ;
12191231 result
12201232 } else {
12211233 formatted. to_string ( )
12221234 }
12231235 } else {
12241236 // Build relative path
1225- let base = if self . dot_relative && !normalized. starts_with ( "../" ) {
1237+ let base = if self . dot_relative && !normalized. starts_with ( "../" ) && !normalized . starts_with ( ".. \\ " ) {
12261238 result_buffer. clear ( ) ;
1227- result_buffer. push_str ( "./" ) ;
1239+ result_buffer. push_str ( dot_prefix ) ;
12281240 result_buffer. push_str ( normalized) ;
12291241 result_buffer. as_str ( )
12301242 } else {
@@ -1233,7 +1245,7 @@ impl Glob {
12331245
12341246 if should_mark_as_dir && !base. ends_with ( '/' ) && !base. ends_with ( '\\' ) {
12351247 let mut result = base. to_string ( ) ;
1236- result. push ( '/' ) ;
1248+ result. push ( sep ) ;
12371249 result
12381250 } else {
12391251 base. to_string ( )
@@ -1254,7 +1266,8 @@ impl Glob {
12541266 let ignored_bytes = ignored_dir. as_bytes ( ) ;
12551267 normalized_bytes. starts_with ( ignored_bytes)
12561268 && ( normalized_bytes. len ( ) == ignored_bytes. len ( )
1257- || normalized_bytes. get ( ignored_bytes. len ( ) ) == Some ( & b'/' ) )
1269+ || normalized_bytes. get ( ignored_bytes. len ( ) ) == Some ( & b'/' )
1270+ || normalized_bytes. get ( ignored_bytes. len ( ) ) == Some ( & b'\\' ) )
12581271 } )
12591272 }
12601273
@@ -1271,7 +1284,8 @@ impl Glob {
12711284 let matched_bytes = matched_path. as_bytes ( ) ;
12721285 normalized_bytes. starts_with ( matched_bytes)
12731286 && normalized_bytes. len ( ) > matched_bytes. len ( )
1274- && normalized_bytes. get ( matched_bytes. len ( ) ) == Some ( & b'/' )
1287+ && ( normalized_bytes. get ( matched_bytes. len ( ) ) == Some ( & b'/' )
1288+ || normalized_bytes. get ( matched_bytes. len ( ) ) == Some ( & b'\\' ) )
12751289 } )
12761290 }
12771291
@@ -1811,6 +1825,12 @@ impl Glob {
18111825 /// resolve to a single path and can be checked with a direct stat() call
18121826 /// instead of walking the entire directory tree.
18131827 fn all_patterns_static ( & self ) -> bool {
1828+ // When nocase is true on a case-sensitive filesystem (Linux), we can't use
1829+ // the static pattern fast path because we need to scan directories to find
1830+ // case-insensitive matches.
1831+ if self . nocase && !self . is_case_insensitive_platform ( ) {
1832+ return false ;
1833+ }
18141834 !self . patterns . is_empty ( ) && self . patterns . iter ( ) . all ( |p| p. is_static ( ) )
18151835 }
18161836
@@ -1837,6 +1857,31 @@ impl Glob {
18371857 cfg ! ( target_os = "macos" ) || cfg ! ( target_os = "windows" )
18381858 }
18391859
1860+ /// Check if backslashes should be normalized to forward slashes.
1861+ ///
1862+ /// On Windows, when `posix: false` (the default), paths should use native backslashes
1863+ /// to match glob's behavior. On all other platforms, or when `posix: true`, we normalize
1864+ /// to forward slashes.
1865+ #[ inline]
1866+ fn should_normalize_backslashes ( & self ) -> bool {
1867+ // On Windows with posix: false, keep backslashes
1868+ // Otherwise, normalize to forward slashes
1869+ self . posix || !cfg ! ( target_os = "windows" )
1870+ }
1871+
1872+ /// Normalize path separators based on platform and posix option.
1873+ ///
1874+ /// Returns the original string if no normalization is needed (either no backslashes,
1875+ /// or on Windows with posix: false where backslashes are kept).
1876+ #[ inline]
1877+ fn normalize_separators < ' a > ( & self , path : & ' a str ) -> Cow < ' a , str > {
1878+ if !self . should_normalize_backslashes ( ) || !path. contains ( '\\' ) {
1879+ Cow :: Borrowed ( path)
1880+ } else {
1881+ Cow :: Owned ( path. replace ( '\\' , "/" ) )
1882+ }
1883+ }
1884+
18401885 /// Resolve shallow patterns using direct readdir.
18411886 ///
18421887 /// This is a fast path for patterns like `*.js` that only match at the root level.
@@ -1929,13 +1974,14 @@ impl Glob {
19291974 formatted
19301975 }
19311976 } else {
1977+ let sep = if self . should_normalize_backslashes ( ) { '/' } else { '\\' } ;
19321978 let base = if self . dot_relative {
1933- format ! ( "./ {file_name}" )
1979+ format ! ( ".{sep} {file_name}" )
19341980 } else {
19351981 file_name. clone ( )
19361982 } ;
1937- if self . mark && is_dir && !is_symlink && !base. ends_with ( '/' ) {
1938- format ! ( "{base}/ " )
1983+ if self . mark && is_dir && !is_symlink && !base. ends_with ( '/' ) && !base . ends_with ( '\\' ) {
1984+ format ! ( "{base}{sep} " )
19391985 } else {
19401986 base
19411987 }
@@ -2026,13 +2072,14 @@ impl Glob {
20262072 formatted
20272073 }
20282074 } else {
2029- let base = if self . dot_relative && !base_path. starts_with ( "../" ) {
2030- format ! ( "./{base_path}" )
2075+ let sep = if self . should_normalize_backslashes ( ) { '/' } else { '\\' } ;
2076+ let base = if self . dot_relative && !base_path. starts_with ( "../" ) && !base_path. starts_with ( "..\\ " ) {
2077+ format ! ( ".{sep}{base_path}" )
20312078 } else {
20322079 base_path. to_string ( )
20332080 } ;
2034- if self . mark && is_dir && !is_symlink && !base. ends_with ( '/' ) {
2035- format ! ( "{base}/ " )
2081+ if self . mark && is_dir && !is_symlink && !base. ends_with ( '/' ) && !base . ends_with ( '\\' ) {
2082+ format ! ( "{base}{sep} " )
20362083 } else {
20372084 base
20382085 }
0 commit comments