@@ -185,7 +185,45 @@ pub fn canonicalizes_path(os: &Os, path_as_str: &str) -> Result<String> {
185185 let context = |input : & str | Ok ( os. env . get ( input) . ok ( ) ) ;
186186 let home_dir = || os. env . home ( ) . map ( |p| p. to_string_lossy ( ) . to_string ( ) ) ;
187187
188- Ok ( shellexpand:: full_with_context ( path_as_str, home_dir, context) ?. to_string ( ) )
188+ let expanded = shellexpand:: full_with_context ( path_as_str, home_dir, context) ?;
189+ let path_buf = if !expanded. starts_with ( "/" ) {
190+ // Convert relative paths to absolute paths
191+ let current_dir = os. env . current_dir ( ) ?;
192+ current_dir. join ( expanded. as_ref ( ) as & str )
193+ } else {
194+ // Already absolute path
195+ PathBuf :: from ( expanded. as_ref ( ) as & str )
196+ } ;
197+
198+ // Try canonicalize first, fallback to manual normalization if it fails
199+ match path_buf. canonicalize ( ) {
200+ Ok ( normalized) => Ok ( normalized. as_path ( ) . to_string_lossy ( ) . to_string ( ) ) ,
201+ Err ( _) => {
202+ // If canonicalize fails (e.g., path doesn't exist), do manual normalization
203+ let normalized = normalize_path ( & path_buf) ;
204+ Ok ( normalized. to_string_lossy ( ) . to_string ( ) )
205+ } ,
206+ }
207+ }
208+
209+ /// Manually normalize a path by resolving . and .. components
210+ fn normalize_path ( path : & std:: path:: Path ) -> std:: path:: PathBuf {
211+ let mut components = Vec :: new ( ) ;
212+ for component in path. components ( ) {
213+ match component {
214+ std:: path:: Component :: CurDir => {
215+ // Skip current directory components
216+ } ,
217+ std:: path:: Component :: ParentDir => {
218+ // Pop the last component for parent directory
219+ components. pop ( ) ;
220+ } ,
221+ _ => {
222+ components. push ( component) ;
223+ } ,
224+ }
225+ }
226+ components. iter ( ) . collect ( )
189227}
190228
191229/// Given a globset builder and a path, build globs for both the file and directory patterns
@@ -449,24 +487,37 @@ mod tests {
449487
450488 // Test environment variable expansion
451489 let result = canonicalizes_path ( & test_os, "$TEST_VAR/path" ) . unwrap ( ) ;
452- assert_eq ! ( result, "test_value/path" ) ;
490+ assert_eq ! ( result, "/ test_value/path" ) ;
453491
454492 // Test combined expansion
455493 let result = canonicalizes_path ( & test_os, "~/$TEST_VAR" ) . unwrap ( ) ;
456494 assert_eq ! ( result, "/home/testuser/test_value" ) ;
457495
496+ // Test ~, . and .. expansion
497+ let result = canonicalizes_path ( & test_os, "~/./.././testuser" ) . unwrap ( ) ;
498+ assert_eq ! ( result, "/home/testuser" ) ;
499+
458500 // Test absolute path (no expansion needed)
459501 let result = canonicalizes_path ( & test_os, "/absolute/path" ) . unwrap ( ) ;
460502 assert_eq ! ( result, "/absolute/path" ) ;
461503
462- // Test relative path (no expansion needed)
504+ // Test ~, . and .. expansion for a path that does not exist
505+ let result = canonicalizes_path ( & test_os, "~/./.././testuser/new/path/../../new" ) . unwrap ( ) ;
506+ assert_eq ! ( result, "/home/testuser/new" ) ;
507+
508+ // Test path with . and ..
509+ let result = canonicalizes_path ( & test_os, "/absolute/./../path" ) . unwrap ( ) ;
510+ assert_eq ! ( result, "/path" ) ;
511+
512+ // Test relative path (which should be expanded because now all inputs are converted to
513+ // absolute)
463514 let result = canonicalizes_path ( & test_os, "relative/path" ) . unwrap ( ) ;
464- assert_eq ! ( result, "relative/path" ) ;
515+ assert_eq ! ( result, "/ relative/path" ) ;
465516
466517 // Test glob prefixed paths
467518 let result = canonicalizes_path ( & test_os, "**/path" ) . unwrap ( ) ;
468- assert_eq ! ( result, "**/path" ) ;
519+ assert_eq ! ( result, "/ **/path" ) ;
469520 let result = canonicalizes_path ( & test_os, "**/middle/**/path" ) . unwrap ( ) ;
470- assert_eq ! ( result, "**/middle/**/path" ) ;
521+ assert_eq ! ( result, "/ **/middle/**/path" ) ;
471522 }
472523}
0 commit comments