@@ -14,6 +14,8 @@ lazy_static! {
14
14
std:: sync:: Mutex :: new( std:: collections:: HashMap :: new( ) ) ;
15
15
static ref WORKSPACES : std:: sync:: Mutex <std:: collections:: HashMap <git2:: Oid , Filter >> =
16
16
std:: sync:: Mutex :: new( std:: collections:: HashMap :: new( ) ) ;
17
+ static ref ANCESTORS : std:: sync:: Mutex <std:: collections:: HashMap <git2:: Oid , std:: collections:: HashSet <git2:: Oid >>> =
18
+ std:: sync:: Mutex :: new( std:: collections:: HashMap :: new( ) ) ;
17
19
}
18
20
19
21
/// Filters are represented as `git2::Oid`, however they are not ever stored
@@ -482,20 +484,23 @@ fn apply_to_commit2(
482
484
483
485
let id = commit. id ( ) ;
484
486
485
- if let Some ( startfilter) = filters. get ( & id) {
486
- let mut f2 = filters. clone ( ) ;
487
- f2. remove ( & id) ;
488
- f2. insert ( git2:: Oid :: zero ( ) , * startfilter) ;
489
- let op = if f2. len ( ) == 1 {
490
- to_op ( * startfilter)
491
- } else {
492
- Op :: Rev ( f2)
493
- } ;
494
- if let Some ( start) = apply_to_commit2 ( & op, commit, transaction) ? {
495
- transaction. insert ( filter, id, start, true ) ;
496
- return Ok ( Some ( start) ) ;
497
- } else {
498
- return Ok ( None ) ;
487
+ for ( & filter_tip, startfilter) in filters. iter ( ) {
488
+ if filter_tip != git2:: Oid :: zero ( ) && is_ancestor_of ( repo, id, filter_tip) ? {
489
+ // Remove this filter but preserve the others.
490
+ let mut f2 = filters. clone ( ) ;
491
+ f2. remove ( & filter_tip) ;
492
+ f2. insert ( git2:: Oid :: zero ( ) , * startfilter) ;
493
+ let op = if f2. len ( ) == 1 {
494
+ to_op ( * startfilter)
495
+ } else {
496
+ Op :: Rev ( f2)
497
+ } ;
498
+ if let Some ( start) = apply_to_commit2 ( & op, commit, transaction) ? {
499
+ transaction. insert ( filter, id, start, true ) ;
500
+ return Ok ( Some ( start) ) ;
501
+ } else {
502
+ return Ok ( None ) ;
503
+ }
499
504
}
500
505
}
501
506
@@ -980,6 +985,33 @@ pub fn make_permissions_filter(filter: Filter, whitelist: Filter, blacklist: Fil
980
985
opt:: optimize ( filter)
981
986
}
982
987
988
+ /// Check if `commit` is an ancestor of `tip`.
989
+ ///
990
+ /// Creates a cache for a given `tip` so repeated queries with the same `tip` are more efficient.
991
+ fn is_ancestor_of ( repo : & git2:: Repository , commit : git2:: Oid , tip : git2:: Oid ) -> JoshResult < bool > {
992
+ let mut ancestor_cache = ANCESTORS . lock ( ) . unwrap ( ) ;
993
+ let ancestors = match ancestor_cache. entry ( tip) {
994
+ std:: collections:: hash_map:: Entry :: Occupied ( entry) => entry. into_mut ( ) ,
995
+ std:: collections:: hash_map:: Entry :: Vacant ( entry) => {
996
+ tracing:: trace!( "is_ancestor_of tip={tip}" ) ;
997
+ // Recursively compute all ancestors of `tip`.
998
+ // Invariant: Everything in `todo` is also in `ancestors`.
999
+ let mut todo = vec ! [ tip] ;
1000
+ let mut ancestors = std:: collections:: HashSet :: from_iter ( todo. iter ( ) . copied ( ) ) ;
1001
+ while let Some ( commit) = todo. pop ( ) {
1002
+ for parent in repo. find_commit ( commit) ?. parent_ids ( ) {
1003
+ if ancestors. insert ( parent) {
1004
+ // Newly inserted! Also handle its parents.
1005
+ todo. push ( parent) ;
1006
+ }
1007
+ }
1008
+ }
1009
+ entry. insert ( ancestors)
1010
+ }
1011
+ } ;
1012
+ Ok ( ancestors. contains ( & commit) )
1013
+ }
1014
+
983
1015
#[ cfg( test) ]
984
1016
mod tests {
985
1017
use super :: * ;
0 commit comments