@@ -4,9 +4,9 @@ use std::{
4
4
sync:: { Arc , Mutex } ,
5
5
} ;
6
6
7
- use error_stack:: { Result , ResultExt } ;
7
+ use error_stack:: { Report , Result , ResultExt } ;
8
8
use fast_glob:: glob_match;
9
- use ignore:: { WalkBuilder , WalkParallel , WalkState } ;
9
+ use ignore:: { DirEntry , WalkBuilder , WalkParallel , WalkState } ;
10
10
use rayon:: iter:: { IntoParallelIterator , ParallelIterator } ;
11
11
use tracing:: { instrument, warn} ;
12
12
@@ -51,53 +51,81 @@ impl<'a> ProjectBuilder<'a> {
51
51
52
52
#[ instrument( level = "debug" , skip_all) ]
53
53
pub fn build ( & mut self ) -> Result < Project , Error > {
54
- let mut entry_types = Vec :: with_capacity ( INITIAL_VECTOR_CAPACITY ) ;
55
54
let mut builder = WalkBuilder :: new ( & self . base_path ) ;
56
55
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
+
57
77
let walk_parallel: WalkParallel = builder. build_parallel ( ) ;
58
78
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 ;
61
84
62
85
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 ( ) ;
64
88
Box :: new ( move |res| {
65
89
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
+ }
68
101
}
69
102
}
70
103
WalkState :: Continue
71
104
} )
72
105
} ) ;
73
106
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) {
77
112
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,
81
114
Err ( poisoned) => poisoned. into_inner ( ) ,
82
115
} ,
83
- // There are still other Arc references; lock and take the contents
84
116
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 ( ) ,
91
119
} ,
92
120
} ;
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 ) ;
95
123
}
96
124
97
125
self . build_project_from_entry_types ( entry_types)
98
126
}
99
127
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 > {
101
129
let absolute_path = entry. path ( ) ;
102
130
103
131
let is_dir = entry. file_type ( ) . ok_or ( Error :: Io ) . change_context ( Error :: Io ) ?. is_dir ( ) ;
0 commit comments