@@ -672,7 +672,7 @@ fn rename_with_fallback(
672672 to : & Path ,
673673 multi_progress : Option < & MultiProgress > ,
674674) -> io:: Result < ( ) > {
675- if let Err ( err ) = fs:: rename ( from, to) {
675+ fs:: rename ( from, to) . or_else ( |err| {
676676 #[ cfg( windows) ]
677677 const EXDEV : i32 = windows_sys:: Win32 :: Foundation :: ERROR_NOT_SAME_DEVICE as _ ;
678678 #[ cfg( unix) ]
@@ -687,131 +687,139 @@ fn rename_with_fallback(
687687 if !should_fallback {
688688 return Err ( err) ;
689689 }
690-
691690 // Get metadata without following symlinks
692691 let metadata = from. symlink_metadata ( ) ?;
693692 let file_type = metadata. file_type ( ) ;
694-
695693 if file_type. is_symlink ( ) {
696- rename_symlink_fallback ( from, to) ? ;
694+ rename_symlink_fallback ( from, to)
697695 } else if file_type. is_dir ( ) {
698- // We remove the destination directory if it exists to match the
699- // behavior of `fs::rename`. As far as I can tell, `fs_extra`'s
700- // `move_dir` would otherwise behave differently.
701- if to. exists ( ) {
702- fs:: remove_dir_all ( to) ?;
703- }
704- let options = DirCopyOptions {
705- // From the `fs_extra` documentation:
706- // "Recursively copy a directory with a new name or place it
707- // inside the destination. (same behaviors like cp -r in Unix)"
708- copy_inside : true ,
709- ..DirCopyOptions :: new ( )
710- } ;
711-
712- // Calculate total size of directory
713- // Silently degrades:
714- // If finding the total size fails for whatever reason,
715- // the progress bar wont be shown for this file / dir.
716- // (Move will probably fail due to permission error later?)
717- let total_size = dir_get_size ( from) . ok ( ) ;
718-
719- let progress_bar =
720- if let ( Some ( multi_progress) , Some ( total_size) ) = ( multi_progress, total_size) {
721- let bar = ProgressBar :: new ( total_size) . with_style (
722- ProgressStyle :: with_template (
723- "{msg}: [{elapsed_precise}] {wide_bar} {bytes:>7}/{total_bytes:7}" ,
724- )
725- . unwrap ( ) ,
726- ) ;
727-
728- Some ( multi_progress. add ( bar) )
729- } else {
730- None
731- } ;
696+ rename_dir_fallback ( from, to, multi_progress)
697+ } else {
698+ rename_file_fallback ( from, to)
699+ }
700+ } )
701+ }
732702
733- #[ cfg( all( unix, not( any( target_os = "macos" , target_os = "redox" ) ) ) ) ]
734- let xattrs =
735- fsxattr:: retrieve_xattrs ( from) . unwrap_or_else ( |_| std:: collections:: HashMap :: new ( ) ) ;
703+ /// Move the given symlink to the given destination. On Windows, dangling
704+ /// symlinks return an error.
705+ #[ cfg( unix) ]
706+ fn rename_symlink_fallback ( from : & Path , to : & Path ) -> io:: Result < ( ) > {
707+ let path_symlink_points_to = fs:: read_link ( from) ?;
708+ unix:: fs:: symlink ( path_symlink_points_to, to) . and_then ( |_| fs:: remove_file ( from) )
709+ }
736710
737- let result = if let Some ( ref pb) = progress_bar {
738- move_dir_with_progress ( from, to, & options, |process_info : TransitProcess | {
739- pb. set_position ( process_info. copied_bytes ) ;
740- pb. set_message ( process_info. file_name ) ;
741- TransitProcessResult :: ContinueOrAbort
742- } )
743- } else {
744- move_dir ( from, to, & options)
745- } ;
746-
747- #[ cfg( all( unix, not( any( target_os = "macos" , target_os = "redox" ) ) ) ) ]
748- fsxattr:: apply_xattrs ( to, xattrs) ?;
749-
750- if let Err ( err) = result {
751- return match err. kind {
752- fs_extra:: error:: ErrorKind :: PermissionDenied => Err ( io:: Error :: new (
753- io:: ErrorKind :: PermissionDenied ,
754- "Permission denied" ,
755- ) ) ,
756- _ => Err ( io:: Error :: other ( format ! ( "{err:?}" ) ) ) ,
757- } ;
758- }
711+ #[ cfg( windows) ]
712+ fn rename_symlink_fallback ( from : & Path , to : & Path ) -> io:: Result < ( ) > {
713+ let path_symlink_points_to = fs:: read_link ( from) ?;
714+ if path_symlink_points_to. exists ( ) {
715+ if path_symlink_points_to. is_dir ( ) {
716+ windows:: fs:: symlink_dir ( & path_symlink_points_to, to) ?;
759717 } else {
760- if to. is_symlink ( ) {
761- fs:: remove_file ( to) . map_err ( |err| {
762- let to = to. to_string_lossy ( ) ;
763- let from = from. to_string_lossy ( ) ;
764- io:: Error :: new (
765- err. kind ( ) ,
766- format ! (
767- "inter-device move failed: '{from}' to '{to}'\
768- ; unable to remove target: {err}"
769- ) ,
770- )
771- } ) ?;
772- }
773- #[ cfg( all( unix, not( any( target_os = "macos" , target_os = "redox" ) ) ) ) ]
774- fs:: copy ( from, to)
775- . and_then ( |_| fsxattr:: copy_xattrs ( & from, & to) )
776- . and_then ( |_| fs:: remove_file ( from) ) ?;
777- #[ cfg( any( target_os = "macos" , target_os = "redox" , not( unix) ) ) ]
778- fs:: copy ( from, to) . and_then ( |_| fs:: remove_file ( from) ) ?;
718+ windows:: fs:: symlink_file ( & path_symlink_points_to, to) ?;
779719 }
720+ fs:: remove_file ( from)
721+ } else {
722+ Err ( io:: Error :: new (
723+ io:: ErrorKind :: NotFound ,
724+ "can't determine symlink type, since it is dangling" ,
725+ ) )
780726 }
781- Ok ( ( ) )
782727}
783728
784- /// Move the given symlink to the given destination. On Windows, dangling
785- /// symlinks return an error.
786- #[ inline]
729+ #[ cfg( not( any( windows, unix) ) ) ]
787730fn rename_symlink_fallback ( from : & Path , to : & Path ) -> io:: Result < ( ) > {
788731 let path_symlink_points_to = fs:: read_link ( from) ?;
789- #[ cfg( unix) ]
790- {
791- unix:: fs:: symlink ( path_symlink_points_to, to) . and_then ( |_| fs:: remove_file ( from) ) ?;
732+ Err ( io:: Error :: new (
733+ io:: ErrorKind :: Other ,
734+ "your operating system does not support symlinks" ,
735+ ) )
736+ }
737+
738+ fn rename_dir_fallback (
739+ from : & Path ,
740+ to : & Path ,
741+ multi_progress : Option < & MultiProgress > ,
742+ ) -> io:: Result < ( ) > {
743+ // We remove the destination directory if it exists to match the
744+ // behavior of `fs::rename`. As far as I can tell, `fs_extra`'s
745+ // `move_dir` would otherwise behave differently.
746+ if to. exists ( ) {
747+ fs:: remove_dir_all ( to) ?;
792748 }
793- #[ cfg( windows) ]
794- {
795- if path_symlink_points_to. exists ( ) {
796- if path_symlink_points_to. is_dir ( ) {
797- windows:: fs:: symlink_dir ( & path_symlink_points_to, to) ?;
798- } else {
799- windows:: fs:: symlink_file ( & path_symlink_points_to, to) ?;
800- }
801- fs:: remove_file ( from) ?;
802- } else {
803- return Err ( io:: Error :: new (
804- io:: ErrorKind :: NotFound ,
805- "can't determine symlink type, since it is dangling" ,
806- ) ) ;
749+ let options = DirCopyOptions {
750+ // From the `fs_extra` documentation:
751+ // "Recursively copy a directory with a new name or place it
752+ // inside the destination. (same behaviors like cp -r in Unix)"
753+ copy_inside : true ,
754+ ..DirCopyOptions :: new ( )
755+ } ;
756+
757+ // Calculate total size of directory
758+ // Silently degrades:
759+ // If finding the total size fails for whatever reason,
760+ // the progress bar wont be shown for this file / dir.
761+ // (Move will probably fail due to permission error later?)
762+ let total_size = dir_get_size ( from) . ok ( ) ;
763+
764+ let progress_bar = match ( multi_progress, total_size) {
765+ ( Some ( multi_progress) , Some ( total_size) ) => {
766+ let template = "{msg}: [{elapsed_precise}] {wide_bar} {bytes:>7}/{total_bytes:7}" ;
767+ let style = ProgressStyle :: with_template ( template) . unwrap ( ) ;
768+ let bar = ProgressBar :: new ( total_size) . with_style ( style) ;
769+ Some ( multi_progress. add ( bar) )
807770 }
771+ ( _, _) => None ,
772+ } ;
773+
774+ #[ cfg( all( unix, not( any( target_os = "macos" , target_os = "redox" ) ) ) ) ]
775+ let xattrs =
776+ fsxattr:: retrieve_xattrs ( from) . unwrap_or_else ( |_| std:: collections:: HashMap :: new ( ) ) ;
777+
778+ let result = if let Some ( ref pb) = progress_bar {
779+ move_dir_with_progress ( from, to, & options, |process_info : TransitProcess | {
780+ pb. set_position ( process_info. copied_bytes ) ;
781+ pb. set_message ( process_info. file_name ) ;
782+ TransitProcessResult :: ContinueOrAbort
783+ } )
784+ } else {
785+ move_dir ( from, to, & options)
786+ } ;
787+
788+ #[ cfg( all( unix, not( any( target_os = "macos" , target_os = "redox" ) ) ) ) ]
789+ fsxattr:: apply_xattrs ( to, xattrs) ?;
790+
791+ match result {
792+ Err ( err) => match err. kind {
793+ fs_extra:: error:: ErrorKind :: PermissionDenied => Err ( io:: Error :: new (
794+ io:: ErrorKind :: PermissionDenied ,
795+ "Permission denied" ,
796+ ) ) ,
797+ _ => Err ( io:: Error :: new ( io:: ErrorKind :: Other , format ! ( "{err:?}" ) ) ) ,
798+ } ,
799+ _ => Ok ( ( ) ) ,
808800 }
809- #[ cfg( not( any( windows, unix) ) ) ]
810- {
811- return Err ( io:: Error :: other (
812- "your operating system does not support symlinks" ,
813- ) ) ;
801+ }
802+
803+ fn rename_file_fallback ( from : & Path , to : & Path ) -> io:: Result < ( ) > {
804+ if to. is_symlink ( ) {
805+ fs:: remove_file ( to) . map_err ( |err| {
806+ let to = to. to_string_lossy ( ) ;
807+ let from = from. to_string_lossy ( ) ;
808+ io:: Error :: new (
809+ err. kind ( ) ,
810+ format ! (
811+ "inter-device move failed: '{from}' to '{to}'\
812+ ; unable to remove target: {err}"
813+ ) ,
814+ )
815+ } ) ?;
814816 }
817+ #[ cfg( all( unix, not( any( target_os = "macos" , target_os = "redox" ) ) ) ) ]
818+ fs:: copy ( from, to)
819+ . and_then ( |_| fsxattr:: copy_xattrs ( & from, & to) )
820+ . and_then ( |_| fs:: remove_file ( from) ) ?;
821+ #[ cfg( any( target_os = "macos" , target_os = "redox" , not( unix) ) ) ]
822+ fs:: copy ( from, to) . and_then ( |_| fs:: remove_file ( from) ) ?;
815823 Ok ( ( ) )
816824}
817825
0 commit comments