@@ -90,6 +90,13 @@ use crate::fsmonitor::FsmonitorSettings;
9090use crate :: fsmonitor:: WatchmanConfig ;
9191#[ cfg( feature = "watchman" ) ]
9292use crate :: fsmonitor:: watchman;
93+ #[ cfg( feature = "git" ) ]
94+ use crate :: git:: GitSettings ;
95+ use crate :: gitattributes:: DiskFileLoader ;
96+ use crate :: gitattributes:: GitAttributes ;
97+ use crate :: gitattributes:: GitAttributesFilter as _;
98+ use crate :: gitattributes:: SearchPriority ;
99+ use crate :: gitattributes:: TreeFileLoader ;
93100use crate :: gitignore:: GitIgnoreFile ;
94101use crate :: lock:: FileLock ;
95102use crate :: matchers:: DifferenceMatcher ;
@@ -952,6 +959,10 @@ pub struct TreeStateSettings {
952959 pub exec_change_setting : ExecChangeSetting ,
953960 /// The fsmonitor (e.g. Watchman) to use, if any.
954961 pub fsmonitor_settings : FsmonitorSettings ,
962+
963+ /// Names of .gitattributes filters whose matching files should be ignored
964+ /// in the working copy.
965+ pub ignore_filters : HashSet < String > ,
955966}
956967
957968impl TreeStateSettings {
@@ -962,6 +973,19 @@ impl TreeStateSettings {
962973 eol_conversion_mode : EolConversionMode :: try_from_settings ( user_settings) ?,
963974 exec_change_setting : user_settings. get ( "working-copy.exec-bit-change" ) ?,
964975 fsmonitor_settings : FsmonitorSettings :: from_settings ( user_settings) ?,
976+ ignore_filters : {
977+ #[ cfg( feature = "git" ) ]
978+ {
979+ GitSettings :: from_settings ( user_settings) ?
980+ . ignore_filters
981+ . into_iter ( )
982+ . collect ( )
983+ }
984+ #[ cfg( not( feature = "git" ) ) ]
985+ {
986+ HashSet :: new ( )
987+ }
988+ } ,
965989 } )
966990 }
967991}
@@ -986,6 +1010,10 @@ pub struct TreeState {
9861010 exec_policy : ExecChangePolicy ,
9871011 fsmonitor_settings : FsmonitorSettings ,
9881012 target_eol_strategy : TargetEolStrategy ,
1013+
1014+ // attributes
1015+ git_attributes : Arc < GitAttributes > ,
1016+ ignore_filters : HashSet < String > ,
9891017}
9901018
9911019#[ derive( Debug , Error ) ]
@@ -1046,14 +1074,21 @@ impl TreeState {
10461074 eol_conversion_mode,
10471075 exec_change_setting,
10481076 fsmonitor_settings,
1077+ ignore_filters,
10491078 } : & TreeStateSettings ,
10501079 ) -> Self {
10511080 let exec_policy = ExecChangePolicy :: new ( * exec_change_setting, & state_path) ;
1081+ let tree = store. empty_merged_tree ( ) ;
1082+
1083+ let store_file_loader = TreeFileLoader :: new ( tree. clone ( ) ) ;
1084+ let disk_file_loader = DiskFileLoader :: new ( working_copy_path. clone ( ) ) ;
1085+
1086+ let git_attributes = Arc :: new ( GitAttributes :: new ( store_file_loader, disk_file_loader) ) ;
10521087 Self {
10531088 store : store. clone ( ) ,
10541089 working_copy_path,
10551090 state_path,
1056- tree : store . empty_merged_tree ( ) ,
1091+ tree,
10571092 file_states : FileStatesMap :: new ( ) ,
10581093 sparse_patterns : vec ! [ RepoPathBuf :: root( ) ] ,
10591094 own_mtime : MillisSinceEpoch ( 0 ) ,
@@ -1063,6 +1098,9 @@ impl TreeState {
10631098 exec_policy,
10641099 fsmonitor_settings : fsmonitor_settings. clone ( ) ,
10651100 target_eol_strategy : TargetEolStrategy :: new ( * eol_conversion_mode) ,
1101+ // TODO We should update git_attributes every time TreeState::tree_id is updated
1102+ git_attributes,
1103+ ignore_filters : ignore_filters. clone ( ) ,
10661104 }
10671105 }
10681106
@@ -1311,6 +1349,8 @@ impl TreeState {
13111349 error : OnceLock :: new ( ) ,
13121350 progress : * progress,
13131351 max_new_file_size : * max_new_file_size,
1352+ git_attributes : self . git_attributes . clone ( ) ,
1353+ ignore_filters : self . ignore_filters . clone ( ) ,
13141354 } ;
13151355 let directory_to_visit = DirectoryToVisit {
13161356 dir : RepoPathBuf :: root ( ) ,
@@ -1482,6 +1522,9 @@ struct FileSnapshotter<'a> {
14821522 error : OnceLock < SnapshotError > ,
14831523 progress : Option < & ' a SnapshotProgress < ' a > > ,
14841524 max_new_file_size : u64 ,
1525+
1526+ git_attributes : Arc < GitAttributes > ,
1527+ ignore_filters : HashSet < String > ,
14851528}
14861529
14871530impl FileSnapshotter < ' _ > {
@@ -1623,7 +1666,16 @@ impl FileSnapshotter<'_> {
16231666 if let Some ( progress) = self . progress {
16241667 progress ( & path) ;
16251668 }
1626- if maybe_current_file_state. is_none ( )
1669+ if self
1670+ . git_attributes
1671+ . filter_matches ( & path, & self . ignore_filters , SearchPriority :: Disk )
1672+ . block_on ( )
1673+ {
1674+ // Skip gitattributes files that we want to ignore - this
1675+ // would result in them showing up as deleted, but we also
1676+ // omit them in `emit_deleted_files` to avoid that.
1677+ Ok ( None )
1678+ } else if maybe_current_file_state. is_none ( )
16271679 && ( git_ignore. matches ( path. as_internal_file_string ( ) )
16281680 && !self . force_tracking_matcher . matches ( & path) )
16291681 {
@@ -1767,6 +1819,13 @@ impl FileSnapshotter<'_> {
17671819 . flat_map ( |( _, chunk) | chunk)
17681820 // Whether or not the entry exists, submodule should be ignored
17691821 . filter ( |( _, state) | state. file_type != FileType :: GitSubmodule )
1822+ // Whether or not the entry exists, ignored gitattributes files should be omitted
1823+ . filter ( |( path, _) | {
1824+ !self
1825+ . git_attributes
1826+ . filter_matches ( path, & self . ignore_filters , SearchPriority :: Disk )
1827+ . block_on ( )
1828+ } )
17701829 . filter ( |( path, _) | self . matcher . matches ( path) )
17711830 . try_for_each ( |( path, _) | self . deleted_files_tx . send ( path. to_owned ( ) ) )
17721831 . ok ( ) ;
0 commit comments