@@ -93,6 +93,12 @@ struct IgnoreOptions {
9393#[ derive( Clone , Debug ) ]
9494pub ( crate ) struct Ignore {
9595 inner : Arc < IgnoreInner > ,
96+ // Parent matchers are cached independently of the path being walked, but
97+ // matching them still needs the canonicalized path originally passed to
98+ // `add_parents`. For example, when walking `/tmp/project/src`, parent
99+ // matchers use `/tmp/project/src` to rewrite `foo.py` before matching it
100+ // against ignore files from `/tmp/project` and its ancestors.
101+ absolute_base : Option < Arc < PathBuf > > ,
96102}
97103
98104#[ derive( Clone , Debug ) ]
@@ -114,12 +120,9 @@ struct IgnoreInner {
114120 ///
115121 /// If this is the root directory or there are otherwise no more
116122 /// directories to match, then `parent` is `None`.
117- parent : Option < Ignore > ,
123+ parent : Option < Arc < IgnoreInner > > ,
118124 /// Whether this is an absolute parent matcher, as added by add_parent.
119125 is_absolute_parent : bool ,
120- /// The absolute base path of this matcher. Populated only if parent
121- /// directories are added.
122- absolute_base : Option < Arc < PathBuf > > ,
123126 /// The directory that gitignores should be interpreted relative to.
124127 ///
125128 /// Usually this is the directory containing the gitignore file. But in
@@ -154,6 +157,7 @@ struct IgnoreInner {
154157
155158impl Ignore {
156159 /// Return the directory path of this matcher.
160+ #[ cfg( test) ]
157161 pub ( crate ) fn path ( & self ) -> & Path {
158162 & self . inner . dir
159163 }
@@ -163,14 +167,12 @@ impl Ignore {
163167 self . inner . parent . is_none ( )
164168 }
165169
166- /// Returns true if this matcher was added via the `add_parents` method.
167- pub ( crate ) fn is_absolute_parent ( & self ) -> bool {
168- self . inner . is_absolute_parent
169- }
170-
171170 /// Return this matcher's parent, if one exists.
172171 pub ( crate ) fn parent ( & self ) -> Option < Ignore > {
173- self . inner . parent . clone ( )
172+ self . inner . parent . as_ref ( ) . map ( |parent| Ignore {
173+ inner : parent. clone ( ) ,
174+ absolute_base : self . absolute_base . clone ( ) ,
175+ } )
174176 }
175177
176178 /// Create a new `Ignore` matcher with the parent directories of `dir`.
@@ -216,22 +218,27 @@ impl Ignore {
216218 let mut compiled = self . inner . compiled . write ( ) . unwrap ( ) ;
217219 if let Some ( weak) = compiled. get ( parent. as_os_str ( ) ) {
218220 if let Some ( prebuilt) = weak. upgrade ( ) {
219- ig = Ignore { inner : prebuilt } ;
221+ ig = Ignore {
222+ inner : prebuilt,
223+ absolute_base : Some ( absolute_base. clone ( ) ) ,
224+ } ;
220225 continue ;
221226 }
222227 }
223228 let ( mut igtmp, err) = ig. add_child_path ( parent) ;
224229 errs. maybe_push ( err) ;
225230 igtmp. is_absolute_parent = true ;
226- igtmp. absolute_base = Some ( absolute_base. clone ( ) ) ;
227231 igtmp. has_git =
228232 if self . inner . opts . require_git && self . inner . opts . git_ignore {
229233 parent. join ( ".git" ) . exists ( ) || parent. join ( ".jj" ) . exists ( )
230234 } else {
231235 false
232236 } ;
233237 let ig_arc = Arc :: new ( igtmp) ;
234- ig = Ignore { inner : ig_arc. clone ( ) } ;
238+ ig = Ignore {
239+ inner : ig_arc. clone ( ) ,
240+ absolute_base : Some ( absolute_base. clone ( ) ) ,
241+ } ;
235242 compiled. insert (
236243 parent. as_os_str ( ) . to_os_string ( ) ,
237244 Arc :: downgrade ( & ig_arc) ,
@@ -253,7 +260,13 @@ impl Ignore {
253260 dir : P ,
254261 ) -> ( Ignore , Option < Error > ) {
255262 let ( ig, err) = self . add_child_path ( dir. as_ref ( ) ) ;
256- ( Ignore { inner : Arc :: new ( ig) } , err)
263+ (
264+ Ignore {
265+ inner : Arc :: new ( ig) ,
266+ absolute_base : self . absolute_base . clone ( ) ,
267+ } ,
268+ err,
269+ )
257270 }
258271
259272 /// Like add_child, but takes a full path and returns an IgnoreInner.
@@ -332,9 +345,8 @@ impl Ignore {
332345 dir : dir. to_path_buf ( ) ,
333346 overrides : self . inner . overrides . clone ( ) ,
334347 types : self . inner . types . clone ( ) ,
335- parent : Some ( self . clone ( ) ) ,
348+ parent : Some ( self . inner . clone ( ) ) ,
336349 is_absolute_parent : false ,
337- absolute_base : self . inner . absolute_base . clone ( ) ,
338350 global_gitignores_relative_to : self
339351 . inner
340352 . global_gitignores_relative_to
@@ -575,29 +587,46 @@ impl Ignore {
575587
576588 /// Returns an iterator over parent ignore matchers, including this one.
577589 pub ( crate ) fn parents ( & self ) -> Parents < ' _ > {
578- Parents ( Some ( self ) )
590+ Parents ( Some ( IgnoreRef { inner : & self . inner } ) )
579591 }
580592
581593 /// Returns the first absolute path of the first absolute parent, if
582594 /// one exists.
583595 fn absolute_base ( & self ) -> Option < & Path > {
584- self . inner . absolute_base . as_ref ( ) . map ( |p| & * * * p)
596+ self . absolute_base . as_ref ( ) . map ( |p| & * * * p)
597+ }
598+ }
599+
600+ #[ derive( Clone , Copy ) ]
601+ pub ( crate ) struct IgnoreRef < ' a > {
602+ inner : & ' a IgnoreInner ,
603+ }
604+
605+ impl IgnoreRef < ' _ > {
606+ pub ( crate ) fn path ( & self ) -> & Path {
607+ & self . inner . dir
608+ }
609+
610+ pub ( crate ) fn is_absolute_parent ( & self ) -> bool {
611+ self . inner . is_absolute_parent
585612 }
586613}
587614
588615/// An iterator over all parents of an ignore matcher, including itself.
589- ///
590- /// The lifetime `'a` refers to the lifetime of the initial `Ignore` matcher.
591- pub ( crate ) struct Parents < ' a > ( Option < & ' a Ignore > ) ;
616+ pub ( crate ) struct Parents < ' a > ( Option < IgnoreRef < ' a > > ) ;
592617
593618impl < ' a > Iterator for Parents < ' a > {
594- type Item = & ' a Ignore ;
619+ type Item = IgnoreRef < ' a > ;
595620
596- fn next ( & mut self ) -> Option < & ' a Ignore > {
621+ fn next ( & mut self ) -> Option < IgnoreRef < ' a > > {
597622 match self . 0 . take ( ) {
598623 None => None ,
599624 Some ( ig) => {
600- self . 0 = ig. inner . parent . as_ref ( ) ;
625+ self . 0 = ig
626+ . inner
627+ . parent
628+ . as_deref ( )
629+ . map ( |inner| IgnoreRef { inner } ) ;
601630 Some ( ig)
602631 }
603632 }
@@ -700,7 +729,6 @@ impl IgnoreBuilder {
700729 types : self . types . clone ( ) ,
701730 parent : None ,
702731 is_absolute_parent : true ,
703- absolute_base : None ,
704732 global_gitignores_relative_to,
705733 explicit_ignores : Arc :: new ( self . explicit_ignores . clone ( ) ) ,
706734 custom_ignore_filenames : Arc :: new (
@@ -714,6 +742,7 @@ impl IgnoreBuilder {
714742 has_git : false ,
715743 opts : self . opts ,
716744 } ) ,
745+ absolute_base : None ,
717746 }
718747 }
719748
@@ -966,7 +995,7 @@ fn strip_if_is_prefix<'a, P: AsRef<Path> + ?Sized>(
966995
967996#[ cfg( test) ]
968997mod tests {
969- use std:: { io:: Write , path:: Path } ;
998+ use std:: { io:: Write , path:: Path , sync :: Arc } ;
970999
9711000 use crate :: {
9721001 Error , dir:: IgnoreBuilder , gitignore:: Gitignore , tests:: TempDir ,
@@ -1278,6 +1307,29 @@ mod tests {
12781307 assert ! ( ig2. matched( "src/foo" , false ) . is_ignore( ) ) ;
12791308 }
12801309
1310+ #[ test]
1311+ fn absolute_parent_matchers_are_cached_across_roots ( ) {
1312+ let td = tmpdir ( ) ;
1313+ mkdirp ( td. path ( ) . join ( ".git" ) ) ;
1314+ mkdirp ( td. path ( ) . join ( "src/build" ) ) ;
1315+ mkdirp ( td. path ( ) . join ( "tests/build" ) ) ;
1316+ wfile ( td. path ( ) . join ( ".gitignore" ) , "tests/**/build/\n " ) ;
1317+
1318+ let ig0 = IgnoreBuilder :: new ( ) . build ( ) ;
1319+ let ( src_parents, err) = ig0. add_parents ( td. path ( ) . join ( "src" ) ) ;
1320+ assert ! ( err. is_none( ) ) ;
1321+ let ( src, err) = src_parents. add_child ( td. path ( ) . join ( "src" ) ) ;
1322+ assert ! ( err. is_none( ) ) ;
1323+ let ( tests_parents, err) = ig0. add_parents ( td. path ( ) . join ( "tests" ) ) ;
1324+ assert ! ( err. is_none( ) ) ;
1325+ let ( tests, err) = tests_parents. add_child ( td. path ( ) . join ( "tests" ) ) ;
1326+ assert ! ( err. is_none( ) ) ;
1327+
1328+ assert ! ( Arc :: ptr_eq( & src_parents. inner, & tests_parents. inner) ) ;
1329+ assert ! ( src. matched( "build" , true ) . is_none( ) ) ;
1330+ assert ! ( tests. matched( "build" , true ) . is_ignore( ) ) ;
1331+ }
1332+
12811333 #[ test]
12821334 fn git_info_exclude_in_linked_worktree ( ) {
12831335 let td = tmpdir ( ) ;
0 commit comments