@@ -23,6 +23,10 @@ static ANCESTORS: LazyLock<
2323 std:: sync:: Mutex < std:: collections:: HashMap < git2:: Oid , std:: collections:: HashSet < git2:: Oid > > > ,
2424> = LazyLock :: new ( || std:: sync:: Mutex :: new ( std:: collections:: HashMap :: new ( ) ) ) ;
2525
26+ static LEGALIZED : LazyLock <
27+ std:: sync:: Mutex < std:: collections:: HashMap < ( Filter , git2:: Oid ) , Filter > > ,
28+ > = LazyLock :: new ( || std:: sync:: Mutex :: new ( std:: collections:: HashMap :: new ( ) ) ) ;
29+
2630/// Match-all regex pattern used as the default for Op::Message when no regex is specified.
2731/// The pattern `(?s)^.*$` matches any string (including newlines) from start to end.
2832static MESSAGE_MATCH_ALL_REGEX : LazyLock < regex:: Regex > =
@@ -442,7 +446,19 @@ fn lazy_refs2(op: &Op) -> Vec<String> {
442446 av. append ( & mut lazy_refs ( * b) ) ;
443447 av
444448 }
445- Op :: Rev ( filters) => lazy_refs2 ( & Op :: Join ( filters. clone ( ) ) ) ,
449+ Op :: Rev ( filters) => {
450+ let mut lr = lazy_refs2 ( & Op :: Compose ( filters. values ( ) . copied ( ) . collect ( ) ) ) ;
451+ lr. extend ( filters. keys ( ) . filter_map ( |x| {
452+ if let LazyRef :: Lazy ( s) = x {
453+ Some ( s. to_owned ( ) )
454+ } else {
455+ None
456+ }
457+ } ) ) ;
458+ lr. sort ( ) ;
459+ lr. dedup ( ) ;
460+ lr
461+ }
446462 Op :: HistoryConcat ( r, f) => {
447463 let mut lr = Vec :: new ( ) ;
448464 if let LazyRef :: Lazy ( s) = r {
@@ -757,53 +773,51 @@ fn resolve_workspace_redirect<'a>(
757773 }
758774}
759775
760- fn get_workspace < ' a > ( repo : & ' a git2:: Repository , tree : & ' a git2:: Tree < ' a > , path : & Path ) -> Filter {
776+ fn get_workspace < ' a > (
777+ transaction : & cache:: Transaction ,
778+ tree : & ' a git2:: Tree < ' a > ,
779+ path : & Path ,
780+ ) -> Filter {
761781 let wsj_file = file ( "workspace.josh" ) ;
762782 let base = to_filter ( Op :: Subdir ( path. to_owned ( ) ) ) ;
763783 let wsj_file = chain ( base, wsj_file) ;
764- compose (
784+ let ws = compose (
765785 wsj_file,
766- compose ( get_filter ( repo, tree, & path. join ( "workspace.josh" ) ) , base) ,
767- )
786+ compose (
787+ get_filter ( transaction, tree, & path. join ( "workspace.josh" ) ) ,
788+ base,
789+ ) ,
790+ ) ;
791+ ws
768792}
769793
770- // Handle stored.josh files that contain ":stored=..." as their only filter as
771- // a "redirect" to that other stored. We chain an exclude of the redirecting stored
772- // in front to prevent infinite recursion.
773- fn resolve_stored_redirect < ' a > (
774- repo : & ' a git2:: Repository ,
794+ fn get_stored < ' a > (
795+ transaction : & cache:: Transaction ,
775796 tree : & ' a git2:: Tree < ' a > ,
776797 path : & Path ,
777- ) -> Option < ( Filter , std:: path:: PathBuf ) > {
778- let stored_path = path. with_extension ( "josh" ) ;
779- let f = parse:: parse ( & tree:: get_blob ( repo, tree, & stored_path) )
780- . unwrap_or_else ( |_| to_filter ( Op :: Empty ) ) ;
781-
782- if let Op :: Stored ( p) = to_op ( f) {
783- Some ( ( chain ( to_filter ( Op :: Exclude ( file ( stored_path) ) ) , f) , p) )
784- } else {
785- None
786- }
787- }
788-
789- fn get_stored < ' a > ( repo : & ' a git2:: Repository , tree : & ' a git2:: Tree < ' a > , path : & Path ) -> Filter {
798+ ) -> Filter {
790799 let stored_path = path. with_extension ( "josh" ) ;
791800 let sj_file = file ( stored_path. clone ( ) ) ;
792- compose ( sj_file, get_filter ( repo , tree, & stored_path) )
801+ compose ( sj_file, get_filter ( transaction , tree, & stored_path) )
793802}
794803
795- fn get_filter < ' a > ( repo : & ' a git2:: Repository , tree : & ' a git2:: Tree < ' a > , path : & Path ) -> Filter {
804+ fn get_filter < ' a > (
805+ transaction : & cache:: Transaction ,
806+ tree : & ' a git2:: Tree < ' a > ,
807+ path : & Path ,
808+ ) -> Filter {
796809 let ws_path = normalize_path ( path) ;
797810 let ws_id = ok_or ! ( tree. get_path( & ws_path) , {
798811 return to_filter( Op :: Empty ) ;
799812 } )
800813 . id ( ) ;
801- let ws_blob = tree:: get_blob ( repo, tree, & ws_path) ;
814+ let ws_blob = tree:: get_blob ( transaction . repo ( ) , tree, & ws_path) ;
802815
803816 if let Some ( f) = WORKSPACES . lock ( ) . unwrap ( ) . get ( & ws_id) {
804817 * f
805818 } else {
806819 let f = parse:: parse ( & ws_blob) . unwrap_or_else ( |_| to_filter ( Op :: Empty ) ) ;
820+ let f = legalize_stored ( transaction, f, tree) . unwrap_or_else ( |_| empty ( ) ) ;
807821
808822 let f = if invert ( f) . is_ok ( ) {
809823 f
@@ -1060,14 +1074,14 @@ fn apply_to_commit2(
10601074 }
10611075 }
10621076
1063- let commit_filter = get_workspace ( repo , & commit. tree ( ) ?, & ws_path) ;
1077+ let commit_filter = get_workspace ( transaction , & commit. tree ( ) ?, & ws_path) ;
10641078
10651079 let parent_filters = commit
10661080 . parents ( )
10671081 . map ( |parent| {
10681082 rs_tracing:: trace_scoped!( "parent" , "id" : parent. id( ) . to_string( ) ) ;
10691083 let pcw = get_workspace (
1070- repo ,
1084+ transaction ,
10711085 & parent. tree ( ) . unwrap_or_else ( |_| tree:: empty ( repo) ) ,
10721086 & ws_path,
10731087 ) ;
@@ -1078,23 +1092,14 @@ fn apply_to_commit2(
10781092 return per_rev_filter ( transaction, commit, filter, commit_filter, parent_filters) ;
10791093 }
10801094 Op :: Stored ( s_path) => {
1081- if let Some ( ( redirect, _) ) = resolve_stored_redirect ( repo, & commit. tree ( ) ?, s_path) {
1082- if let Some ( r) = apply_to_commit2 ( & to_op ( redirect) , commit, transaction) ? {
1083- transaction. insert ( filter, commit. id ( ) , r, true ) ;
1084- return Ok ( Some ( r) ) ;
1085- } else {
1086- return Ok ( None ) ;
1087- }
1088- }
1089-
1090- let commit_filter = get_stored ( repo, & commit. tree ( ) ?, & s_path) ;
1095+ let commit_filter = get_stored ( transaction, & commit. tree ( ) ?, & s_path) ;
10911096
10921097 let parent_filters = commit
10931098 . parents ( )
10941099 . map ( |parent| {
10951100 rs_tracing:: trace_scoped!( "parent" , "id" : parent. id( ) . to_string( ) ) ;
10961101 let pcs = get_stored (
1097- repo ,
1102+ transaction ,
10981103 & parent. tree ( ) . unwrap_or_else ( |_| tree:: empty ( repo) ) ,
10991104 & s_path,
11001105 ) ;
@@ -1302,8 +1307,8 @@ fn apply2<'a>(transaction: &'a cache::Transaction, op: &Op, x: Apply<'a>) -> Jos
13021307 . with_tree ( tree:: invert_paths ( transaction, "" , x. tree ( ) . clone ( ) ) ?) )
13031308 }
13041309
1305- Op :: Workspace ( path) => apply ( transaction, get_workspace ( repo , & x. tree ( ) , & path) , x) ,
1306- Op :: Stored ( path) => apply ( transaction, get_stored ( repo , & x. tree ( ) , & path) , x) ,
1310+ Op :: Workspace ( path) => apply ( transaction, get_workspace ( transaction , & x. tree ( ) , & path) , x) ,
1311+ Op :: Stored ( path) => apply ( transaction, get_stored ( transaction , & x. tree ( ) , & path) , x) ,
13071312
13081313 Op :: Compose ( filters) => {
13091314 let filtered: Vec < _ > = filters
@@ -1394,12 +1399,9 @@ fn unapply_workspace<'a>(
13941399 match op {
13951400 Op :: Workspace ( path) => {
13961401 let tree = pre_process_tree ( transaction. repo ( ) , tree) ?;
1397- let workspace = get_filter ( transaction. repo ( ) , & tree, Path :: new ( "workspace.josh" ) ) ;
1398- let original_workspace = get_filter (
1399- transaction. repo ( ) ,
1400- & parent_tree,
1401- & path. join ( "workspace.josh" ) ,
1402- ) ;
1402+ let workspace = get_filter ( transaction, & tree, Path :: new ( "workspace.josh" ) ) ;
1403+ let original_workspace =
1404+ get_filter ( transaction, & parent_tree, & path. join ( "workspace.josh" ) ) ;
14031405
14041406 let root = to_filter ( Op :: Subdir ( path. to_owned ( ) ) ) ;
14051407 let wsj_file = to_filter ( Op :: File (
@@ -1428,8 +1430,8 @@ fn unapply_workspace<'a>(
14281430 }
14291431 Op :: Stored ( path) => {
14301432 let stored_path = path. with_extension ( "josh" ) ;
1431- let stored = get_filter ( transaction. repo ( ) , & tree, & stored_path) ;
1432- let original_stored = get_filter ( transaction. repo ( ) , & parent_tree, & stored_path) ;
1433+ let stored = get_filter ( transaction, & tree, & stored_path) ;
1434+ let original_stored = get_filter ( transaction, & parent_tree, & stored_path) ;
14331435
14341436 let sj_file = file ( stored_path. clone ( ) ) ;
14351437 let filter = compose ( sj_file, stored) ;
@@ -1628,6 +1630,44 @@ where
16281630 }
16291631}
16301632
1633+ fn legalize_stored ( t : & cache:: Transaction , f : Filter , tree : & git2:: Tree ) -> JoshResult < Filter > {
1634+ if let Some ( f) = LEGALIZED . lock ( ) . unwrap ( ) . get ( & ( f, tree. id ( ) ) ) {
1635+ return Ok ( * f) ;
1636+ }
1637+
1638+ // Put an entry into the hashtable to prevent infinite recursion.
1639+ // If we get called with the same arguments again before we return,
1640+ // Above check breaks the recursion.
1641+ LEGALIZED . lock ( ) . unwrap ( ) . insert ( ( f, tree. id ( ) ) , empty ( ) ) ;
1642+
1643+ let r = match to_op ( f) {
1644+ Op :: Compose ( f) => {
1645+ let f = f
1646+ . into_iter ( )
1647+ . map ( |f| legalize_stored ( t, f, tree) )
1648+ . collect :: < JoshResult < Vec < _ > > > ( ) ?;
1649+ to_filter ( Op :: Compose ( f) )
1650+ }
1651+ Op :: Chain ( a, b) => {
1652+ let first = legalize_stored ( t, a, tree) ?;
1653+ let second =
1654+ legalize_stored ( t, b, & apply ( t, first, Apply :: from_tree ( tree. clone ( ) ) ) ?. tree ) ?;
1655+ to_filter ( Op :: Chain ( first, second) )
1656+ }
1657+ Op :: Subtract ( a, b) => to_filter ( Op :: Subtract (
1658+ legalize_stored ( t, a, tree) ?,
1659+ legalize_stored ( t, b, tree) ?,
1660+ ) ) ,
1661+ Op :: Exclude ( f) => to_filter ( Op :: Exclude ( legalize_stored ( t, f, tree) ?) ) ,
1662+ Op :: Stored ( path) => get_stored ( t, tree, & path) ,
1663+ _ => f,
1664+ } ;
1665+
1666+ LEGALIZED . lock ( ) . unwrap ( ) . insert ( ( f, tree. id ( ) ) , r) ;
1667+
1668+ Ok ( r)
1669+ }
1670+
16311671fn per_rev_filter (
16321672 transaction : & cache:: Transaction ,
16331673 commit : & git2:: Commit ,
0 commit comments