@@ -163,15 +163,32 @@ impl AbsolutePath {
163163 let Ok ( stripped_path) = self . 0 . strip_prefix ( & base. 0 ) else {
164164 return Ok ( None ) ;
165165 } ;
166- match RelativePathBuf :: new ( stripped_path) {
167- Ok ( relative_path) => Ok ( Some ( relative_path) ) ,
168- Err ( FromPathError :: NonRelative ) => {
169- unreachable ! ( "stripped path should always be relative" )
170- }
171- Err ( FromPathError :: InvalidPathData ( invalid_path_data_error) ) => {
172- Err ( StripPrefixError { stripped_path, invalid_path_data_error } )
173- }
174- }
166+ stripped_path_to_relative ( stripped_path) . map ( Some )
167+ }
168+
169+ /// Returns a path that, when joined onto base, yields self.
170+ ///
171+ /// Unlike [`AbsolutePath::strip_prefix`], this normalizes Windows path
172+ /// namespace prefixes (`\\?\`, `\\.\`, and `\??\`) before matching.
173+ ///
174+ /// If `base` is not a prefix of `self`, returns [`None`].
175+ ///
176+ /// If the stripped path is not a valid `RelativePath`. Returns an error with the reason and the stripped path.
177+ ///
178+ /// # Errors
179+ ///
180+ /// Returns an error if the stripped path contains invalid UTF-8 or other path data issues.
181+ pub fn strip_path_prefix < P : AsRef < Self > > (
182+ & self ,
183+ base : P ,
184+ ) -> Result < Option < RelativePathBuf > , StripPrefixError < ' _ > > {
185+ let base = base. as_ref ( ) ;
186+ let Ok ( stripped_path) =
187+ crate :: strip_path_prefix ( self . as_path ( ) . as_os_str ( ) , base. as_path ( ) . as_os_str ( ) )
188+ else {
189+ return Ok ( None ) ;
190+ } ;
191+ stripped_path_to_relative ( stripped_path) . map ( Some )
175192 }
176193
177194 /// Creates an owned [`AbsolutePathBuf`] with `path` adjoined to `self`.
@@ -220,6 +237,20 @@ impl AbsolutePath {
220237 }
221238}
222239
240+ fn stripped_path_to_relative (
241+ stripped_path : & Path ,
242+ ) -> Result < RelativePathBuf , StripPrefixError < ' _ > > {
243+ match RelativePathBuf :: new ( stripped_path) {
244+ Ok ( relative_path) => Ok ( relative_path) ,
245+ Err ( FromPathError :: NonRelative ) => {
246+ unreachable ! ( "stripped path should always be relative" )
247+ }
248+ Err ( FromPathError :: InvalidPathData ( invalid_path_data_error) ) => {
249+ Err ( StripPrefixError { stripped_path, invalid_path_data_error } )
250+ }
251+ }
252+ }
253+
223254/// An Error returned from [`AbsolutePath::strip_prefix`] if the stripped path is not a valid `RelativePath`
224255#[ derive( thiserror:: Error , Debug ) ]
225256pub struct StripPrefixError < ' a > {
@@ -397,6 +428,54 @@ mod tests {
397428 assert ! ( rel_path. is_none( ) ) ;
398429 }
399430
431+ #[ test]
432+ fn strip_path_prefix ( ) {
433+ let abs_path = AbsolutePath :: new ( Path :: new ( if cfg ! ( windows) {
434+ "C:\\ Users\\ foo\\ bar"
435+ } else {
436+ "/home/foo/bar"
437+ } ) )
438+ . unwrap ( ) ;
439+
440+ let prefix =
441+ AbsolutePath :: new ( Path :: new ( if cfg ! ( windows) { "C:\\ Users" } else { "/home" } ) )
442+ . unwrap ( ) ;
443+
444+ let rel_path = abs_path. strip_path_prefix ( prefix) . unwrap ( ) . unwrap ( ) ;
445+ assert_eq ! ( rel_path. as_str( ) , "foo/bar" ) ;
446+ }
447+
448+ #[ test]
449+ fn strip_path_prefix_not_found ( ) {
450+ let abs_path = AbsolutePath :: new ( Path :: new ( if cfg ! ( windows) {
451+ "C:\\ Users\\ foo\\ bar"
452+ } else {
453+ "/home/foo/bar"
454+ } ) )
455+ . unwrap ( ) ;
456+
457+ let prefix = AbsolutePath :: new ( Path :: new ( if cfg ! ( windows) {
458+ "C:\\ Users\\ barz"
459+ } else {
460+ "/home/baz"
461+ } ) )
462+ . unwrap ( ) ;
463+
464+ let rel_path = abs_path. strip_path_prefix ( prefix) . unwrap ( ) ;
465+ assert ! ( rel_path. is_none( ) ) ;
466+ }
467+
468+ #[ cfg( windows) ]
469+ #[ test]
470+ fn strip_path_prefix_ignores_windows_namespace_prefixes ( ) {
471+ let abs_path = AbsolutePath :: new ( Path :: new ( r"\\?\C:\Users\foo\bar" ) ) . unwrap ( ) ;
472+ let prefix = AbsolutePath :: new ( Path :: new ( r"C:\Users" ) ) . unwrap ( ) ;
473+
474+ let rel_path = abs_path. strip_path_prefix ( prefix) . unwrap ( ) . unwrap ( ) ;
475+
476+ assert_eq ! ( rel_path. as_str( ) , "foo/bar" ) ;
477+ }
478+
400479 #[ cfg( unix) ]
401480 #[ test]
402481 fn strip_prefix_invalid_relative ( ) {
0 commit comments