11use gix_dir:: { walk, EntryRef } ;
22use pretty_assertions:: assert_eq;
3+ use std:: sync:: atomic:: AtomicBool ;
34
45use crate :: walk_utils:: {
56 collect, collect_filtered, collect_filtered_with_cwd, entry, entry_dirstat, entry_nokind, entry_nomatch, entryps,
@@ -16,6 +17,81 @@ use gix_dir::walk::EmissionMode::*;
1617use gix_dir:: walk:: ForDeletionMode ;
1718use gix_ignore:: Kind :: * ;
1819
20+ #[ test]
21+ #[ cfg_attr( windows, ignore = "symlinks the way they are organized don't yet work on windows" ) ]
22+ fn symlink_to_dir_can_be_excluded ( ) -> crate :: Result {
23+ let root = fixture_in ( "many-symlinks" , "excluded-symlinks-to-dir" ) ;
24+ let ( ( out, _root) , entries) = collect ( & root, None , |keep, ctx| {
25+ walk (
26+ & root,
27+ ctx,
28+ gix_dir:: walk:: Options {
29+ emit_ignored : Some ( Matching ) ,
30+ ..options ( )
31+ } ,
32+ keep,
33+ )
34+ } ) ;
35+ assert_eq ! (
36+ out,
37+ walk:: Outcome {
38+ read_dir_calls: 2 ,
39+ returned_entries: entries. len( ) ,
40+ seen_entries: 9 ,
41+ }
42+ ) ;
43+
44+ assert_eq ! (
45+ entries,
46+ & [
47+ entry( "file1" , Ignored ( Expendable ) , Symlink ) ,
48+ entry( "file2" , Untracked , Symlink ) ,
49+ entry( "ignored" , Ignored ( Expendable ) , Directory ) ,
50+ entry( "ignored-must-be-dir" , Ignored ( Expendable ) , Directory ) ,
51+ entry( "src/file" , Untracked , File ) ,
52+ entry( "src1" , Ignored ( Expendable ) , Symlink ) ,
53+ entry( "src2" , Untracked , Symlink ) , /* marked as src2/ in .gitignore */
54+ ] ,
55+ "by default, symlinks are counted as files only, even if they point to a directory, when handled by the exclude machinery"
56+ ) ;
57+
58+ let ( ( out, _root) , entries) = collect ( & root, None , |keep, ctx| {
59+ walk (
60+ & root,
61+ ctx,
62+ gix_dir:: walk:: Options {
63+ emit_ignored : Some ( Matching ) ,
64+ symlinks_to_directories_are_ignored_like_directories : true ,
65+ ..options ( )
66+ } ,
67+ keep,
68+ )
69+ } ) ;
70+ assert_eq ! (
71+ out,
72+ walk:: Outcome {
73+ read_dir_calls: 2 ,
74+ returned_entries: entries. len( ) ,
75+ seen_entries: 9 ,
76+ }
77+ ) ;
78+
79+ assert_eq ! (
80+ entries,
81+ & [
82+ entry( "file1" , Ignored ( Expendable ) , Symlink ) ,
83+ entry( "file2" , Untracked , Symlink ) ,
84+ entry( "ignored" , Ignored ( Expendable ) , Directory ) ,
85+ entry( "ignored-must-be-dir" , Ignored ( Expendable ) , Directory ) ,
86+ entry( "src/file" , Untracked , File ) ,
87+ entry( "src1" , Ignored ( Expendable ) , Symlink ) ,
88+ entry( "src2" , Ignored ( Expendable ) , Symlink ) , /* marked as src2/ in .gitignore */
89+ ] ,
90+ "with libgit2 compatibility enabled, symlinks to directories are treated like a directory, not symlink"
91+ ) ;
92+ Ok ( ( ) )
93+ }
94+
1995#[ test]
2096#[ cfg_attr( windows, ignore = "symlinks the way they are organized don't yet work on windows" ) ]
2197fn root_may_not_lead_through_symlinks ( ) -> crate :: Result {
@@ -43,6 +119,57 @@ fn root_may_not_lead_through_symlinks() -> crate::Result {
43119 Ok ( ( ) )
44120}
45121
122+ #[ test]
123+ #[ cfg_attr( windows, ignore = "symlinks the way they are organized don't yet work on windows" ) ]
124+ fn root_may_be_a_symlink_if_it_is_the_worktree ( ) -> crate :: Result {
125+ let root = fixture_in ( "many-symlinks" , "worktree-root-is-symlink" ) ;
126+ let ( ( _out, _root) , entries) = collect ( & root, None , |keep, ctx| {
127+ walk (
128+ & root,
129+ ctx,
130+ gix_dir:: walk:: Options {
131+ emit_ignored : Some ( Matching ) ,
132+ symlinks_to_directories_are_ignored_like_directories : true ,
133+ ..options ( )
134+ } ,
135+ keep,
136+ )
137+ } ) ;
138+
139+ assert_eq ! (
140+ entries,
141+ & [
142+ entry( "file1" , Ignored ( Expendable ) , Symlink ) ,
143+ entry( "file2" , Untracked , Symlink ) ,
144+ entry( "ignored" , Ignored ( Expendable ) , Directory ) ,
145+ entry( "ignored-must-be-dir" , Ignored ( Expendable ) , Directory ) ,
146+ entry( "src/file" , Untracked , File ) ,
147+ entry( "src1" , Ignored ( Expendable ) , Symlink ) ,
148+ entry( "src2" , Ignored ( Expendable ) , Symlink ) , /* marked as src2/ in .gitignore */
149+ ] ,
150+ "it traversed the directory normally - without this capability, symlinked repos can't be traversed"
151+ ) ;
152+ Ok ( ( ) )
153+ }
154+
155+ #[ test]
156+ fn should_interrupt_works_even_in_empty_directories ( ) {
157+ let root = fixture ( "empty" ) ;
158+ let should_interrupt = AtomicBool :: new ( true ) ;
159+ let err = try_collect_filtered_opts_collect (
160+ & root,
161+ None ,
162+ |keep, ctx| walk ( & root, ctx, gix_dir:: walk:: Options { ..options ( ) } , keep) ,
163+ None :: < & str > ,
164+ Options {
165+ should_interrupt : Some ( & should_interrupt) ,
166+ ..Default :: default ( )
167+ } ,
168+ )
169+ . unwrap_err ( ) ;
170+ assert ! ( matches!( err, gix_dir:: walk:: Error :: Interrupted ) ) ;
171+ }
172+
46173#[ test]
47174fn empty_root ( ) -> crate :: Result {
48175 let root = fixture ( "empty" ) ;
0 commit comments