@@ -14,6 +14,8 @@ lazy_static! {
1414 std:: sync:: Mutex :: new( std:: collections:: HashMap :: new( ) ) ;
1515 static ref WORKSPACES : std:: sync:: Mutex <std:: collections:: HashMap <git2:: Oid , Filter >> =
1616 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( ) ) ;
1719}
1820
1921/// Filters are represented as `git2::Oid`, however they are not ever stored
@@ -482,20 +484,23 @@ fn apply_to_commit2(
482484
483485 let id = commit. id ( ) ;
484486
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+ }
499504 }
500505 }
501506
@@ -980,6 +985,33 @@ pub fn make_permissions_filter(filter: Filter, whitelist: Filter, blacklist: Fil
980985 opt:: optimize ( filter)
981986}
982987
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+
9831015#[ cfg( test) ]
9841016mod tests {
9851017 use super :: * ;
0 commit comments