@@ -448,6 +448,8 @@ impl From<gix::dir::entry::Kind> for FileType {
448
448
pub struct PathEntry {
449
449
path : PathBuf ,
450
450
ty : FileType ,
451
+ /// Whether this path was visited when traversing a symlink directory.
452
+ under_symlink_dir : bool ,
451
453
}
452
454
453
455
impl PathEntry {
@@ -469,10 +471,21 @@ impl PathEntry {
469
471
470
472
/// Similar to [`std::path::Path::is_symlink`]
471
473
/// but doesn't follow the symbolic link nor make any system call
474
+ ///
475
+ /// If the path is not a symlink but under a symlink parent directory,
476
+ /// this will return false.
477
+ /// See [`PathEntry::is_symlink_or_under_symlink`] for an alternative.
472
478
pub fn is_symlink ( & self ) -> bool {
473
479
matches ! ( self . ty, FileType :: Symlink )
474
480
}
475
481
482
+ /// Whether a path is a symlink or a path under a symlink directory.
483
+ ///
484
+ /// Use [`PathEntry::is_symlink`] to get the exact file type of the path only.
485
+ pub fn is_symlink_or_under_symlink ( & self ) -> bool {
486
+ self . is_symlink ( ) || self . under_symlink_dir
487
+ }
488
+
476
489
/// Whether this path might be a plain text symlink.
477
490
///
478
491
/// Git may check out symlinks as plain text files that contain the link texts,
@@ -826,6 +839,9 @@ fn list_files_gix(
826
839
files. push ( PathEntry {
827
840
path : file_path,
828
841
ty,
842
+ // Git index doesn't include files from symlink diretory,
843
+ // symlink dirs are handled in `list_files_walk`.
844
+ under_symlink_dir : false ,
829
845
} ) ;
830
846
}
831
847
}
@@ -847,6 +863,10 @@ fn list_files_walk(
847
863
) -> CargoResult < ( ) > {
848
864
let walkdir = WalkDir :: new ( path)
849
865
. follow_links ( true )
866
+ // While this is the default, set it explicitly.
867
+ // We need walkdir to visit the directory tree in depth-first order,
868
+ // so we can ensure a path visited later be under a certain directory.
869
+ . contents_first ( false )
850
870
. into_iter ( )
851
871
. filter_entry ( |entry| {
852
872
let path = entry. path ( ) ;
@@ -876,10 +896,27 @@ fn list_files_walk(
876
896
877
897
true
878
898
} ) ;
899
+
900
+ let mut current_symlink_dir = None ;
879
901
for entry in walkdir {
880
902
match entry {
881
903
Ok ( entry) => {
882
904
let file_type = entry. file_type ( ) ;
905
+
906
+ match current_symlink_dir. as_ref ( ) {
907
+ Some ( dir) if entry. path ( ) . starts_with ( dir) => {
908
+ // Still walk under the same parent symlink dir, so keep it
909
+ }
910
+ Some ( _) | None => {
911
+ // Not under any parent symlink dir, update the current one.
912
+ current_symlink_dir = if file_type. is_dir ( ) && entry. path_is_symlink ( ) {
913
+ Some ( entry. path ( ) . to_path_buf ( ) )
914
+ } else {
915
+ None
916
+ } ;
917
+ }
918
+ }
919
+
883
920
if file_type. is_file ( ) || file_type. is_symlink ( ) {
884
921
// We follow_links(true) here so check if entry was created from a symlink
885
922
let ty = if entry. path_is_symlink ( ) {
@@ -890,6 +927,8 @@ fn list_files_walk(
890
927
ret. push ( PathEntry {
891
928
path : entry. into_path ( ) ,
892
929
ty,
930
+ // This rely on contents_first(false), which walks in depth-first order
931
+ under_symlink_dir : current_symlink_dir. is_some ( ) ,
893
932
} ) ;
894
933
}
895
934
}
@@ -907,6 +946,7 @@ fn list_files_walk(
907
946
Some ( path) => ret. push ( PathEntry {
908
947
path : path. to_path_buf ( ) ,
909
948
ty : FileType :: Other ,
949
+ under_symlink_dir : false ,
910
950
} ) ,
911
951
None => return Err ( err. into ( ) ) ,
912
952
} ,
0 commit comments