@@ -72,6 +72,7 @@ enum Op {
7272 Prefix ( std:: path:: PathBuf ) ,
7373 Subdir ( std:: path:: PathBuf ) ,
7474 Workspace ( std:: path:: PathBuf ) ,
75+ Include ( std:: path:: PathBuf ) ,
7576
7677 Glob ( String ) ,
7778
@@ -169,6 +170,9 @@ fn spec2(op: &Op) -> String {
169170 Op :: Workspace ( path) => {
170171 format ! ( ":workspace={}" , path. to_string_lossy( ) )
171172 }
173+ Op :: Include ( path) => {
174+ format ! ( ":include={}" , path. to_string_lossy( ) )
175+ }
172176
173177 Op :: Chain ( a, b) => match ( to_op ( * a) , to_op ( * b) ) {
174178 ( Op :: Subdir ( p1) , Op :: Prefix ( p2) ) if p1 == p2 => {
@@ -354,6 +358,54 @@ fn apply_to_commit2(
354358 ) )
355359 . transpose ( ) ;
356360 }
361+ Op :: Include ( include_path) => {
362+ let normal_parents = commit
363+ . parent_ids ( )
364+ . map ( |parent| transaction. get ( filter, parent) )
365+ . collect :: < Option < Vec < git2:: Oid > > > ( ) ;
366+
367+ let normal_parents = some_or ! ( normal_parents, { return Ok ( None ) } ) ;
368+
369+ let cw = parse:: parse ( & tree:: get_blob ( repo, & commit. tree ( ) ?, & include_path) )
370+ . unwrap_or ( to_filter ( Op :: Empty ) ) ;
371+
372+ let extra_parents = commit
373+ . parents ( )
374+ . map ( |parent| {
375+ rs_tracing:: trace_scoped!( "parent" , "id" : parent. id( ) . to_string( ) ) ;
376+ let pcw = parse:: parse ( & tree:: get_blob (
377+ repo,
378+ & parent. tree ( ) . unwrap_or ( tree:: empty ( repo) ) ,
379+ & include_path,
380+ ) )
381+ . unwrap_or ( to_filter ( Op :: Empty ) ) ;
382+
383+ apply_to_commit2 (
384+ & to_op ( opt:: optimize ( to_filter ( Op :: Subtract ( cw, pcw) ) ) ) ,
385+ & parent,
386+ transaction,
387+ )
388+ } )
389+ . collect :: < JoshResult < Option < Vec < _ > > > > ( ) ?;
390+
391+ let extra_parents = some_or ! ( extra_parents, { return Ok ( None ) } ) ;
392+
393+ let filtered_parent_ids = normal_parents
394+ . into_iter ( )
395+ . chain ( extra_parents. into_iter ( ) )
396+ . collect ( ) ;
397+
398+ let filtered_tree = apply ( transaction, filter, commit. tree ( ) ?) ?;
399+
400+ return Some ( history:: create_filtered_commit (
401+ commit,
402+ filtered_parent_ids,
403+ filtered_tree,
404+ transaction,
405+ filter,
406+ ) )
407+ . transpose ( ) ;
408+ }
357409 Op :: Fold => {
358410 let filtered_parent_ids = commit
359411 . parents ( )
@@ -521,6 +573,15 @@ fn apply2<'a>(
521573 }
522574 }
523575
576+ Op :: Include ( path) => {
577+ let file = to_filter ( Op :: File ( path. to_owned ( ) ) ) ;
578+ if let Ok ( cw) = parse:: parse ( & tree:: get_blob ( repo, & tree, & path) ) {
579+ apply ( transaction, compose ( file, cw) , tree)
580+ } else {
581+ apply ( transaction, file, tree)
582+ }
583+ }
584+
524585 Op :: Compose ( filters) => {
525586 let filtered: Vec < _ > = filters
526587 . iter ( )
@@ -626,6 +687,71 @@ fn unapply2<'a>(
626687
627688 return Ok ( r) ;
628689 }
690+ Op :: Include ( path) => {
691+ let root = to_filter ( Op :: File ( path. to_owned ( ) ) ) ;
692+ let mapped = & tree:: get_blob ( transaction. repo ( ) , & tree, path) ;
693+ let parsed = parse ( mapped) ?;
694+
695+ let mut blob = String :: new ( ) ;
696+ if let Ok ( c) = get_comments ( mapped) {
697+ if !c. is_empty ( ) {
698+ blob = c;
699+ }
700+ }
701+ let blob = & format ! ( "{}{}\n " , & blob, pretty( parsed, 0 ) ) ;
702+
703+ // TODO: is this still necessary?
704+ // Remove filters file from the tree to prevent it from being parsed again
705+ // further down the callstack leading to endless recursion.
706+ let tree = tree:: insert (
707+ transaction. repo ( ) ,
708+ & tree,
709+ path,
710+ git2:: Oid :: zero ( ) ,
711+ 0o0100644 ,
712+ ) ?;
713+
714+ // Insert a dummy file to prevent the directory from dissappearing through becoming
715+ // empty.
716+ let tree = tree:: insert (
717+ transaction. repo ( ) ,
718+ & tree,
719+ Path :: new ( "DUMMY-df97a89d-b11f-4e1c-8400-345f895f0d40" ) ,
720+ transaction. repo ( ) . blob ( "" . as_bytes ( ) ) ?,
721+ 0o0100644 ,
722+ ) ?;
723+
724+ let r = unapply (
725+ transaction,
726+ compose ( root, parsed) ,
727+ tree. clone ( ) ,
728+ parent_tree,
729+ ) ?;
730+
731+ // Remove the dummy file inserted above
732+ let r = tree:: insert (
733+ transaction. repo ( ) ,
734+ & r,
735+ & path. join ( "DUMMY-df97a89d-b11f-4e1c-8400-345f895f0d40" ) ,
736+ git2:: Oid :: zero ( ) ,
737+ 0o0100644 ,
738+ ) ?;
739+
740+ // Put the filters file back to it's target location.
741+ let r = if !mapped. is_empty ( ) {
742+ tree:: insert (
743+ transaction. repo ( ) ,
744+ & r,
745+ & path,
746+ transaction. repo ( ) . blob ( blob. as_bytes ( ) ) ?,
747+ 0o0100644 , // Should this handle filemode?
748+ ) ?
749+ } else {
750+ r
751+ } ;
752+
753+ return Ok ( r) ;
754+ }
629755 Op :: Compose ( filters) => {
630756 let mut remaining = tree. clone ( ) ;
631757 let mut result = parent_tree. clone ( ) ;
@@ -748,6 +874,16 @@ pub fn compute_warnings<'a>(
748874 }
749875 }
750876
877+ if let Op :: Include ( path) = to_op ( filter) {
878+ let full_filter = & tree:: get_blob ( transaction. repo ( ) , & tree, & path) ;
879+ if let Ok ( res) = parse ( full_filter) {
880+ filter = res;
881+ } else {
882+ warnings. push ( "couldn't parse include file\n " . to_string ( ) ) ;
883+ return warnings;
884+ }
885+ }
886+
751887 let filter = opt:: flatten ( filter) ;
752888 if let Op :: Compose ( filters) = to_op ( filter) {
753889 for f in filters {
0 commit comments