@@ -203,6 +203,14 @@ impl FileStatus {
203203 matches ! ( self , FileStatus :: Untracked )
204204 }
205205
206+ pub fn is_renamed ( self ) -> bool {
207+ let FileStatus :: Tracked ( tracked) = self else {
208+ return false ;
209+ } ;
210+ tracked. index_status == StatusCode :: Renamed
211+ || tracked. worktree_status == StatusCode :: Renamed
212+ }
213+
206214 pub fn summary ( self ) -> GitSummary {
207215 match self {
208216 FileStatus :: Ignored => GitSummary :: UNCHANGED ,
@@ -430,34 +438,79 @@ impl std::ops::Sub for GitSummary {
430438#[ derive( Clone , Debug ) ]
431439pub struct GitStatus {
432440 pub entries : Arc < [ ( RepoPath , FileStatus ) ] > ,
441+ pub renamed_paths : HashMap < RepoPath , RepoPath > ,
433442}
434443
435444impl FromStr for GitStatus {
436445 type Err = anyhow:: Error ;
437446
438447 fn from_str ( s : & str ) -> Result < Self > {
439- let mut entries = s
440- . split ( '\0' )
441- . filter_map ( |entry| {
442- let sep = entry. get ( 2 ..3 ) ?;
443- if sep != " " {
444- return None ;
448+ let mut parts = s. split ( '\0' ) . peekable ( ) ;
449+ let mut entries = Vec :: new ( ) ;
450+ let mut renamed_paths = HashMap :: default ( ) ;
451+
452+ while let Some ( entry) = parts. next ( ) {
453+ if entry. is_empty ( ) {
454+ continue ;
455+ }
456+
457+ if !matches ! ( entry. get( 2 ..3 ) , Some ( " " ) ) {
458+ continue ;
459+ }
460+
461+ let path_or_old_path = & entry[ 3 ..] ;
462+
463+ if path_or_old_path. ends_with ( '/' ) {
464+ continue ;
465+ }
466+
467+ let status = match entry. as_bytes ( ) [ 0 ..2 ] . try_into ( ) {
468+ Ok ( bytes) => match FileStatus :: from_bytes ( bytes) . log_err ( ) {
469+ Some ( s) => s,
470+ None => continue ,
471+ } ,
472+ Err ( _) => continue ,
473+ } ;
474+
475+ let is_rename = matches ! (
476+ status,
477+ FileStatus :: Tracked ( TrackedStatus {
478+ index_status: StatusCode :: Renamed | StatusCode :: Copied ,
479+ ..
480+ } ) | FileStatus :: Tracked ( TrackedStatus {
481+ worktree_status: StatusCode :: Renamed | StatusCode :: Copied ,
482+ ..
483+ } )
484+ ) ;
485+
486+ let ( old_path_str, new_path_str) = if is_rename {
487+ let new_path = match parts. next ( ) {
488+ Some ( new_path) if !new_path. is_empty ( ) => new_path,
489+ _ => continue ,
445490 } ;
446- let path = & entry[ 3 ..] ;
447- // The git status output includes untracked directories as well as untracked files.
448- // We do our own processing to compute the "summary" status of each directory,
449- // so just skip any directories in the output, since they'll otherwise interfere
450- // with our handling of nested repositories.
451- if path. ends_with ( '/' ) {
452- return None ;
491+ ( path_or_old_path, new_path)
492+ } else {
493+ ( path_or_old_path, path_or_old_path)
494+ } ;
495+
496+ if new_path_str. ends_with ( '/' ) {
497+ continue ;
498+ }
499+
500+ let new_path = match RelPath :: unix ( new_path_str) . log_err ( ) {
501+ Some ( p) => RepoPath :: from_rel_path ( p) ,
502+ None => continue ,
503+ } ;
504+
505+ if is_rename {
506+ if let Some ( old_path_rel) = RelPath :: unix ( old_path_str) . log_err ( ) {
507+ let old_path_repo = RepoPath :: from_rel_path ( old_path_rel) ;
508+ renamed_paths. insert ( new_path. clone ( ) , old_path_repo) ;
453509 }
454- let status = entry. as_bytes ( ) [ 0 ..2 ] . try_into ( ) . unwrap ( ) ;
455- let status = FileStatus :: from_bytes ( status) . log_err ( ) ?;
456- // git-status outputs `/`-delimited repo paths, even on Windows.
457- let path = RepoPath :: from_rel_path ( RelPath :: unix ( path) . log_err ( ) ?) ;
458- Some ( ( path, status) )
459- } )
460- . collect :: < Vec < _ > > ( ) ;
510+ }
511+
512+ entries. push ( ( new_path, status) ) ;
513+ }
461514 entries. sort_unstable_by ( |( a, _) , ( b, _) | a. cmp ( b) ) ;
462515 // When a file exists in HEAD, is deleted in the index, and exists again in the working copy,
463516 // git produces two lines for it, one reading `D ` (deleted in index, unmodified in working copy)
@@ -481,6 +534,7 @@ impl FromStr for GitStatus {
481534 } ) ;
482535 Ok ( Self {
483536 entries : entries. into ( ) ,
537+ renamed_paths,
484538 } )
485539 }
486540}
@@ -489,6 +543,7 @@ impl Default for GitStatus {
489543 fn default ( ) -> Self {
490544 Self {
491545 entries : Arc :: new ( [ ] ) ,
546+ renamed_paths : HashMap :: default ( ) ,
492547 }
493548 }
494549}
0 commit comments