88use std:: path:: Path ;
99use std:: path:: PathBuf ;
1010use std:: process:: Stdio ;
11+ use std:: sync:: LazyLock ;
12+ use std:: sync:: Mutex ;
1113
1214use async_process_traits:: Child ;
1315use async_process_traits:: Command ;
1416use async_process_traits:: CommandSpawner ;
1517use async_process_traits:: TokioCommandSpawner ;
18+ use lru_cache:: LruCache ;
1619use tokio:: io:: AsyncBufReadExt ;
1720use tokio:: io:: BufReader ;
1821
@@ -23,13 +26,20 @@ use crate::utils::get_sapling_executable_path;
2326use crate :: utils:: get_sapling_options;
2427use crate :: utils:: process_one_status_line;
2528
29+ // NOTE: We might wish to cache Results here, but we would want to add a way to evict
30+ // Err entries from the cache based on some policy - e.g. a TTL in seconds.
31+ // For now, we just cache Ok entries.
32+ const STATUS_LRU_CACHE_SIZE : usize = 32 ;
33+ static STATUS_LRU_CACHE : LazyLock < Mutex < LruCache < GetStatusParams , SaplingGetStatusResult > > > =
34+ LazyLock :: new ( || Mutex :: new ( LruCache :: new ( STATUS_LRU_CACHE_SIZE ) ) ) ;
35+
2636#[ derive( Clone , Debug , PartialEq ) ]
2737pub enum SaplingGetStatusResult {
2838 Normal ( Vec < ( SaplingStatus , String ) > ) ,
2939 TooManyChanges ,
3040}
3141
32- #[ derive( Clone , Debug , Hash , PartialEq ) ]
42+ #[ derive( Clone , Debug , Eq , Hash , PartialEq ) ]
3343struct GetStatusParams {
3444 first : String ,
3545 second : Option < String > ,
@@ -130,52 +140,66 @@ where
130140 . collect :: < Vec < String > > ( )
131141 } ) ;
132142
133- let mut args = vec ! [ "status" , "-mardu" , "--rev" , & params . first ] ;
134- let second : String ;
135- if let Some ( second_ ) = params. second {
136- second = second_ ;
137- args . push ( "--rev" ) ;
138- args . push ( & second ) ;
143+ {
144+ let mut lru_cache = STATUS_LRU_CACHE . lock ( ) . unwrap ( ) ;
145+ let entry = lru_cache . get_mut ( & params) . cloned ( ) ;
146+ if let Some ( entry ) = entry {
147+ return Ok ( entry ) ;
148+ }
139149 }
140150
141- let root_path_arg: String ;
142- if let Some ( root) = params. root {
143- root_path_arg = format ! ( "path:{}" , root. display( ) ) ;
144- args. push ( & root_path_arg) ;
145- } ;
151+ let result = {
152+ let mut args = vec ! [ "status" , "-mardu" , "--rev" , & params. first] ;
153+ let second: String ;
154+ if let Some ( second_) = & params. second {
155+ second = second_. to_string ( ) ;
156+ args. push ( "--rev" ) ;
157+ args. push ( & second) ;
158+ }
159+
160+ let root_path_arg: String ;
161+ if let Some ( root) = & params. root {
162+ root_path_arg = format ! ( "path:{}" , root. display( ) ) ;
163+ args. push ( & root_path_arg) ;
164+ } ;
146165
147- let mut command = Spawner :: Command :: new ( get_sapling_executable_path ( ) ) ;
148- command
149- . envs ( get_sapling_options ( ) )
150- . args ( args)
151- . stdout ( Stdio :: piped ( ) ) ;
152- let mut child = spawner. spawn ( & mut command) ?;
153- let stdout = child. stdout ( ) . take ( ) . ok_or_else ( || {
154- SaplingError :: Other ( "Failed to read stdout when invoking 'sl status'." . to_string ( ) )
155- } ) ?;
156- let reader = BufReader :: new ( stdout) ;
166+ let mut command = Spawner :: Command :: new ( get_sapling_executable_path ( ) ) ;
167+ command
168+ . envs ( get_sapling_options ( ) )
169+ . args ( args)
170+ . stdout ( Stdio :: piped ( ) ) ;
171+ let mut child = spawner. spawn ( & mut command) ?;
172+ let stdout = child. stdout ( ) . take ( ) . ok_or_else ( || {
173+ SaplingError :: Other ( "Failed to read stdout when invoking 'sl status'." . to_string ( ) )
174+ } ) ?;
175+ let reader = BufReader :: new ( stdout) ;
157176
158- let mut status = vec ! [ ] ;
159- let mut lines = reader. lines ( ) ;
160- while let Some ( line) = lines. next_line ( ) . await ? {
161- if let Some ( status_line) = process_one_status_line ( & line) ? {
162- if is_path_included (
163- params. case_insensitive_suffix_compares ,
164- & status_line. 1 ,
165- & params. included_roots ,
166- & params. included_suffixes ,
167- & params. excluded_roots ,
168- & params. excluded_suffixes ,
169- ) {
170- if status. len ( ) >= params. limit_results {
171- return Ok ( SaplingGetStatusResult :: TooManyChanges ) ;
177+ let mut status = vec ! [ ] ;
178+ let mut lines = reader. lines ( ) ;
179+ while let Some ( line) = lines. next_line ( ) . await ? {
180+ if let Some ( status_line) = process_one_status_line ( & line) ? {
181+ if is_path_included (
182+ params. case_insensitive_suffix_compares ,
183+ & status_line. 1 ,
184+ & params. included_roots ,
185+ & params. included_suffixes ,
186+ & params. excluded_roots ,
187+ & params. excluded_suffixes ,
188+ ) {
189+ if status. len ( ) >= params. limit_results {
190+ return Ok ( SaplingGetStatusResult :: TooManyChanges ) ;
191+ }
192+ status. push ( status_line) ;
172193 }
173- status. push ( status_line) ;
174194 }
175195 }
176- }
177196
178- Ok ( SaplingGetStatusResult :: Normal ( status) )
197+ SaplingGetStatusResult :: Normal ( status)
198+ } ;
199+
200+ let mut lru_cache = STATUS_LRU_CACHE . lock ( ) . unwrap ( ) ;
201+ lru_cache. insert ( params, result. clone ( ) ) ;
202+ Ok ( result)
179203}
180204
181205fn is_path_included (
0 commit comments