@@ -4,9 +4,9 @@ use std::{
44 sync:: { Arc , Mutex } ,
55} ;
66
7- use error_stack:: { Result , ResultExt } ;
7+ use error_stack:: { Report , Result , ResultExt } ;
88use fast_glob:: glob_match;
9- use ignore:: { WalkBuilder , WalkParallel , WalkState } ;
9+ use ignore:: { DirEntry , WalkBuilder , WalkParallel , WalkState } ;
1010use rayon:: iter:: { IntoParallelIterator , ParallelIterator } ;
1111use tracing:: { instrument, warn} ;
1212
@@ -51,53 +51,81 @@ impl<'a> ProjectBuilder<'a> {
5151
5252 #[ instrument( level = "debug" , skip_all) ]
5353 pub fn build ( & mut self ) -> Result < Project , Error > {
54- let mut entry_types = Vec :: with_capacity ( INITIAL_VECTOR_CAPACITY ) ;
5554 let mut builder = WalkBuilder :: new ( & self . base_path ) ;
5655 builder. hidden ( false ) ;
56+ builder. follow_links ( false ) ;
57+ // Prune traversal early: skip heavy and irrelevant directories
58+ let ignore_dirs = self . config . ignore_dirs . clone ( ) ;
59+ let base_path = self . base_path . clone ( ) ;
60+
61+ builder. filter_entry ( move |entry : & DirEntry | {
62+ let path = entry. path ( ) ;
63+ let file_name = entry. file_name ( ) . to_str ( ) . unwrap_or ( "" ) ;
64+ if let Some ( ft) = entry. file_type ( ) {
65+ if ft. is_dir ( ) {
66+ if let Ok ( rel) = path. strip_prefix ( & base_path) {
67+ if rel. components ( ) . count ( ) == 1 && ignore_dirs. iter ( ) . any ( |d| * d == file_name) {
68+ return false ;
69+ }
70+ }
71+ }
72+ }
73+
74+ true
75+ } ) ;
76+
5777 let walk_parallel: WalkParallel = builder. build_parallel ( ) ;
5878
59- let collected = Arc :: new ( Mutex :: new ( Vec :: with_capacity ( INITIAL_VECTOR_CAPACITY ) ) ) ;
60- let collected_for_threads = Arc :: clone ( & collected) ;
79+ let ( tx, rx) = crossbeam_channel:: unbounded :: < EntryType > ( ) ;
80+ let error_holder: Arc < Mutex < Option < Report < Error > > > > = Arc :: new ( Mutex :: new ( None ) ) ;
81+ let error_holder_for_threads = Arc :: clone ( & error_holder) ;
82+
83+ let this: & ProjectBuilder < ' a > = self ;
6184
6285 walk_parallel. run ( move || {
63- let collected = Arc :: clone ( & collected_for_threads) ;
86+ let error_holder = Arc :: clone ( & error_holder_for_threads) ;
87+ let tx = tx. clone ( ) ;
6488 Box :: new ( move |res| {
6589 if let Ok ( entry) = res {
66- if let Ok ( mut v) = collected. lock ( ) {
67- v. push ( entry) ;
90+ match this. build_entry_type ( entry) {
91+ Ok ( entry_type) => {
92+ let _ = tx. send ( entry_type) ;
93+ }
94+ Err ( report) => {
95+ if let Ok ( mut slot) = error_holder. lock ( ) {
96+ if slot. is_none ( ) {
97+ * slot = Some ( report) ;
98+ }
99+ }
100+ }
68101 }
69102 }
70103 WalkState :: Continue
71104 } )
72105 } ) ;
73106
74- // Process sequentially with &mut self without panicking on Arc/Mutex unwraps
75- let collected_entries = match Arc :: try_unwrap ( collected) {
76- // We are the sole owner of the Arc
107+ // Take ownership of the collected entry types
108+ let entry_types: Vec < EntryType > = rx. iter ( ) . collect ( ) ;
109+
110+ // If any error occurred while building entry types, return it
111+ let maybe_error = match Arc :: try_unwrap ( error_holder) {
77112 Ok ( mutex) => match mutex. into_inner ( ) {
78- // Mutex not poisoned
79- Ok ( entries) => entries,
80- // Recover entries even if the mutex was poisoned
113+ Ok ( err_opt) => err_opt,
81114 Err ( poisoned) => poisoned. into_inner ( ) ,
82115 } ,
83- // There are still other Arc references; lock and take the contents
84116 Err ( arc) => match arc. lock ( ) {
85- Ok ( mut guard) => std:: mem:: take ( & mut * guard) ,
86- // Recover guard even if poisoned, then take contents
87- Err ( poisoned) => {
88- let mut guard = poisoned. into_inner ( ) ;
89- std:: mem:: take ( & mut * guard)
90- }
117+ Ok ( mut guard) => guard. take ( ) ,
118+ Err ( poisoned) => poisoned. into_inner ( ) . take ( ) ,
91119 } ,
92120 } ;
93- for entry in collected_entries {
94- entry_types . push ( self . build_entry_type ( entry ) ? ) ;
121+ if let Some ( report ) = maybe_error {
122+ return Err ( report ) ;
95123 }
96124
97125 self . build_project_from_entry_types ( entry_types)
98126 }
99127
100- fn build_entry_type ( & mut self , entry : ignore:: DirEntry ) -> Result < EntryType , Error > {
128+ fn build_entry_type ( & self , entry : ignore:: DirEntry ) -> Result < EntryType , Error > {
101129 let absolute_path = entry. path ( ) ;
102130
103131 let is_dir = entry. file_type ( ) . ok_or ( Error :: Io ) . change_context ( Error :: Io ) ?. is_dir ( ) ;
0 commit comments