15
15
16
16
use std:: collections:: HashMap ;
17
17
use std:: ffi:: { OsStr , OsString } ;
18
+ use std:: fs:: File ;
19
+ use std:: io:: { self , BufRead } ;
18
20
use std:: path:: { Path , PathBuf } ;
19
21
use std:: sync:: { Arc , RwLock } ;
20
22
@@ -225,6 +227,7 @@ impl Ignore {
225
227
Gitignore :: empty ( )
226
228
} else {
227
229
let ( m, err) = create_gitignore (
230
+ & dir,
228
231
& dir,
229
232
& self . 0 . custom_ignore_filenames ,
230
233
self . 0 . opts . ignore_case_insensitive ,
@@ -236,28 +239,37 @@ impl Ignore {
236
239
Gitignore :: empty ( )
237
240
} else {
238
241
let ( m, err) =
239
- create_gitignore ( & dir, & [ ".ignore" ] , self . 0 . opts . ignore_case_insensitive ) ;
242
+ create_gitignore ( & dir, & dir , & [ ".ignore" ] , self . 0 . opts . ignore_case_insensitive ) ;
240
243
errs. maybe_push ( err) ;
241
244
m
242
245
} ;
243
246
let gi_matcher = if !self . 0 . opts . git_ignore {
244
247
Gitignore :: empty ( )
245
248
} else {
246
249
let ( m, err) =
247
- create_gitignore ( & dir, & [ ".gitignore" ] , self . 0 . opts . ignore_case_insensitive ) ;
250
+ create_gitignore ( & dir, & dir , & [ ".gitignore" ] , self . 0 . opts . ignore_case_insensitive ) ;
248
251
errs. maybe_push ( err) ;
249
252
m
250
253
} ;
251
254
let gi_exclude_matcher = if !self . 0 . opts . git_exclude {
252
255
Gitignore :: empty ( )
253
256
} else {
254
- let ( m, err) = create_gitignore (
255
- & dir,
256
- & [ ".git/info/exclude" ] ,
257
- self . 0 . opts . ignore_case_insensitive ,
258
- ) ;
259
- errs. maybe_push ( err) ;
260
- m
257
+ match resolve_git_commondir ( dir) {
258
+ Ok ( git_dir) => {
259
+ let ( m, err) = create_gitignore (
260
+ & dir,
261
+ & git_dir,
262
+ & [ "info/exclude" ] ,
263
+ self . 0 . opts . ignore_case_insensitive ,
264
+ ) ;
265
+ errs. maybe_push ( err) ;
266
+ m
267
+ }
268
+ Err ( err) => {
269
+ errs. maybe_push ( err) ;
270
+ Gitignore :: empty ( )
271
+ }
272
+ }
261
273
} ;
262
274
let has_git = if self . 0 . opts . git_ignore {
263
275
dir. join ( ".git" ) . exists ( )
@@ -675,20 +687,23 @@ impl IgnoreBuilder {
675
687
676
688
/// Creates a new gitignore matcher for the directory given.
677
689
///
678
- /// Ignore globs are extracted from each of the file names in `dir` in the
679
- /// order given (earlier names have lower precedence than later names).
690
+ /// The matcher is meant to match files below `dir`.
691
+ /// Ignore globs are extracted from each of the file names relative to
692
+ /// `dir_for_ignorefile` in the order given (earlier names have lower
693
+ /// precedence than later names).
680
694
///
681
695
/// I/O errors are ignored.
682
696
pub fn create_gitignore < T : AsRef < OsStr > > (
683
697
dir : & Path ,
698
+ dir_for_ignorefile : & Path ,
684
699
names : & [ T ] ,
685
700
case_insensitive : bool ,
686
701
) -> ( Gitignore , Option < Error > ) {
687
702
let mut builder = GitignoreBuilder :: new ( dir) ;
688
703
let mut errs = PartialErrorBuilder :: default ( ) ;
689
704
builder. case_insensitive ( case_insensitive) . unwrap ( ) ;
690
705
for name in names {
691
- let gipath = dir . join ( name. as_ref ( ) ) ;
706
+ let gipath = dir_for_ignorefile . join ( name. as_ref ( ) ) ;
692
707
errs. maybe_push_ignore_io ( builder. add ( gipath) ) ;
693
708
}
694
709
let gi = match builder. build ( ) {
@@ -701,10 +716,55 @@ pub fn create_gitignore<T: AsRef<OsStr>>(
701
716
( gi, errs. into_error_option ( ) )
702
717
}
703
718
719
+ /// Find the GIT_COMMON_DIR for the given git worktree.
720
+ ///
721
+ /// This is the directory that may contain a private ignore file "info/exclude".
722
+ /// Unlike git, this function does *not* read environment variables
723
+ /// GIT_DIR and GIT_COMMON_DIR, because it is not clear how to use them when
724
+ /// multiple repositories are searched.
725
+ ///
726
+ /// Some I/O errors are ignored.
727
+ fn resolve_git_commondir ( dir : & Path ) -> Result < PathBuf , Option < Error > > {
728
+ let git_dir_path = || dir. join ( ".git" ) ;
729
+ let git_dir = git_dir_path ( ) ;
730
+ if !git_dir. is_file ( ) {
731
+ return Ok ( git_dir) ;
732
+ }
733
+ let file = match File :: open ( git_dir) {
734
+ Ok ( file) => io:: BufReader :: new ( file) ,
735
+ Err ( err) => return Err ( Some ( Error :: Io ( err) . with_path ( git_dir_path ( ) ) ) ) ,
736
+ } ;
737
+ let dot_git_line = match file. lines ( ) . next ( ) {
738
+ Some ( Ok ( line) ) => line,
739
+ Some ( Err ( err) ) => return Err ( Some ( Error :: Io ( err) . with_path ( git_dir_path ( ) ) ) ) ,
740
+ None => return Err ( None ) ,
741
+ } ;
742
+ if !dot_git_line. starts_with ( "gitdir: " ) {
743
+ return Err ( None ) ;
744
+ }
745
+ let real_git_dir = PathBuf :: from ( & dot_git_line[ "gitdir: " . len ( ) ..] ) ;
746
+ let git_commondir_file = || real_git_dir. join ( "commondir" ) ;
747
+ let file = match File :: open ( git_commondir_file ( ) ) {
748
+ Ok ( file) => io:: BufReader :: new ( file) ,
749
+ Err ( err) => return Err ( Some ( Error :: Io ( err) . with_path ( git_commondir_file ( ) ) ) ) ,
750
+ } ;
751
+ let commondir_line = match file. lines ( ) . next ( ) {
752
+ Some ( Ok ( line) ) => line,
753
+ Some ( Err ( err) ) => return Err ( Some ( Error :: Io ( err) . with_path ( git_commondir_file ( ) ) ) ) ,
754
+ None => return Err ( None ) ,
755
+ } ;
756
+ let commondir_abs = if commondir_line. starts_with ( "." ) {
757
+ real_git_dir. join ( commondir_line) // relative commondir
758
+ } else {
759
+ PathBuf :: from ( commondir_line)
760
+ } ;
761
+ Ok ( commondir_abs)
762
+ }
763
+
704
764
#[ cfg( test) ]
705
765
mod tests {
706
766
use std:: fs:: { self , File } ;
707
- use std:: io:: Write ;
767
+ use std:: io:: { self , Write } ;
708
768
use std:: path:: Path ;
709
769
710
770
use dir:: IgnoreBuilder ;
@@ -991,4 +1051,47 @@ mod tests {
991
1051
assert ! ( ig2. matched( "foo" , false ) . is_ignore( ) ) ;
992
1052
assert ! ( ig2. matched( "src/foo" , false ) . is_ignore( ) ) ;
993
1053
}
1054
+
1055
+ #[ test]
1056
+ fn git_info_exclude_in_linked_worktree ( ) {
1057
+ let td = tmpdir ( ) ;
1058
+ let git_dir = td. path ( ) . join ( ".git" ) ;
1059
+ mkdirp ( git_dir. join ( "info" ) ) ;
1060
+ wfile ( git_dir. join ( "info/exclude" ) , "ignore_me" ) ;
1061
+ mkdirp ( git_dir. join ( "worktrees/linked-worktree" ) ) ;
1062
+ let commondir_path = || git_dir. join ( "worktrees/linked-worktree/commondir" ) ;
1063
+ mkdirp ( td. path ( ) . join ( "linked-worktree" ) ) ;
1064
+ let worktree_git_dir_abs = format ! ( "gitdir: {}" , git_dir. join( "worktrees/linked-worktree" ) . to_str( ) . unwrap( ) ) ;
1065
+ wfile ( td. path ( ) . join ( "linked-worktree/.git" ) , & worktree_git_dir_abs) ;
1066
+
1067
+ wfile ( commondir_path ( ) , "../.." ) ; // relative commondir
1068
+ let ib = IgnoreBuilder :: new ( ) . build ( ) ;
1069
+ let ( ignore, err) = ib. add_child ( td. path ( ) . join ( "linked-worktree" ) ) ;
1070
+ assert ! ( err. is_none( ) ) ;
1071
+ assert ! ( ignore. matched( "ignore_me" , false ) . is_ignore( ) ) ;
1072
+
1073
+ wfile ( commondir_path ( ) , git_dir. to_str ( ) . unwrap ( ) ) ; // absolute commondir
1074
+ let ( ignore, err) = ib. add_child ( td. path ( ) . join ( "linked-worktree" ) ) ;
1075
+ assert ! ( err. is_none( ) ) ;
1076
+ assert ! ( ignore. matched( "ignore_me" , false ) . is_ignore( ) ) ;
1077
+
1078
+ assert ! ( fs:: remove_file( commondir_path( ) ) . is_ok( ) ) ; // missing commondir file
1079
+ let ( _, err) = ib. add_child ( td. path ( ) . join ( "linked-worktree" ) ) ;
1080
+ assert ! ( err. is_some( ) ) ;
1081
+ assert ! ( match err {
1082
+ Some ( Error :: WithPath { path, err } ) => path == commondir_path( ) && match * err {
1083
+ Error :: Io ( ioerr) => ioerr. kind( ) == io:: ErrorKind :: NotFound ,
1084
+ _ => false ,
1085
+ } ,
1086
+ _ => false ,
1087
+ } ) ;
1088
+
1089
+ wfile ( td. path ( ) . join ( "linked-worktree/.git" ) , "garbage" ) ;
1090
+ let ( _, err) = ib. add_child ( td. path ( ) . join ( "linked-worktree" ) ) ;
1091
+ assert ! ( err. is_none( ) ) ;
1092
+
1093
+ wfile ( td. path ( ) . join ( "linked-worktree/.git" ) , "gitdir: garbage" ) ;
1094
+ let ( _, err) = ib. add_child ( td. path ( ) . join ( "linked-worktree" ) ) ;
1095
+ assert ! ( err. is_some( ) ) ;
1096
+ }
994
1097
}
0 commit comments