@@ -9,12 +9,14 @@ use crate::{Error, GitHashes, GitRepo, ls_tree::SortedGitHashes, status::RepoSta
99/// and `git status` so they can be filtered per-package without spawning
1010/// additional subprocesses.
1111///
12- /// Uses a sorted `Vec` for the ls-tree data so that per-package lookups can
13- /// use `partition_point` (binary search) for range queries. This gives the
14- /// same O(log n) asymptotic cost as a `BTreeMap` but with better cache
15- /// locality on the contiguous memory.
12+ /// Both collections are sorted by path so that per-package lookups can use
13+ /// `partition_point` (binary search) for range queries. This gives O(log n)
14+ /// lookup cost with good cache locality on contiguous memory.
1615pub struct RepoGitIndex {
1716 ls_tree_hashes : SortedGitHashes ,
17+ /// Sorted by path so per-package filtering can use binary-search range
18+ /// queries instead of linear scans. With P packages and S status entries
19+ /// the cost drops from O(P*S) to O(P * log S).
1820 status_entries : Vec < RepoStatusEntry > ,
1921}
2022
@@ -259,6 +261,48 @@ mod tests {
259261 assert_eq ! ( to_hash, vec![ path( "new.ts" ) ] ) ;
260262 }
261263
264+ #[ test]
265+ fn test_sorted_status_binary_search_matches_linear_scan ( ) {
266+ let status = vec ! [
267+ ( "apps/docs/new.ts" , false ) ,
268+ ( "apps/web/changed.ts" , false ) ,
269+ ( "apps/web-admin/added.ts" , false ) ,
270+ ( "apps/web/deleted.ts" , true ) ,
271+ ( "packages/ui/modified.ts" , false ) ,
272+ ( "root-new.ts" , false ) ,
273+ ] ;
274+
275+ let index = make_index (
276+ vec ! [
277+ ( "apps/docs/index.ts" , "aaa" ) ,
278+ ( "apps/web/index.ts" , "bbb" ) ,
279+ ( "apps/web/deleted.ts" , "ccc" ) ,
280+ ( "apps/web-admin/index.ts" , "ddd" ) ,
281+ ( "packages/ui/button.tsx" , "eee" ) ,
282+ ] ,
283+ status,
284+ ) ;
285+
286+ // "apps/web" must match apps/web/* but NOT apps/web-admin/*
287+ let ( hashes, to_hash) = index. get_package_hashes ( & path ( "apps/web" ) ) . unwrap ( ) ;
288+ assert_eq ! (
289+ hashes. len( ) ,
290+ 1 ,
291+ "only index.ts should remain (deleted.ts removed)"
292+ ) ;
293+ assert ! ( hashes. contains_key( & path( "index.ts" ) ) ) ;
294+ assert ! ( !hashes. contains_key( & path( "deleted.ts" ) ) ) ;
295+ assert_eq ! ( to_hash, vec![ path( "apps/web/changed.ts" ) ] ) ;
296+
297+ // "apps/web-admin" should get its own status entries only
298+ let ( _, to_hash) = index. get_package_hashes ( & path ( "apps/web-admin" ) ) . unwrap ( ) ;
299+ assert_eq ! ( to_hash, vec![ path( "apps/web-admin/added.ts" ) ] ) ;
300+
301+ // empty prefix collects everything
302+ let ( _, to_hash) = index. get_package_hashes ( & path ( "" ) ) . unwrap ( ) ;
303+ assert_eq ! ( to_hash. len( ) , 5 ) ; // all non-delete status entries
304+ }
305+
262306 // Verifies that BTreeMap range queries produce correct results for
263307 // prefix-based package filtering. This captures the exact behavior that
264308 // must be preserved when switching to a sorted Vec with partition_point.
0 commit comments