@@ -10,6 +10,8 @@ pub enum Error {
10
10
NoGitRepository { path : PathBuf } ,
11
11
#[ error( "Could find a git repository in '{}' or in any of its parents within ceiling height of {}" , . path. display( ) , . ceiling_height) ]
12
12
NoGitRepositoryWithinCeiling { path : PathBuf , ceiling_height : usize } ,
13
+ #[ error( "Could find a git repository in '{}' or in any of its parents within device limits below '{}'" , . path. display( ) , . limit. display( ) ) ]
14
+ NoGitRepositoryWithinFs { path : PathBuf , limit : PathBuf } ,
13
15
#[ error( "None of the passed ceiling directories prefixed the git-dir candidate, making them ineffective." ) ]
14
16
NoMatchingCeilingDir ,
15
17
#[ error( "Could find a trusted git repository in '{}' or in any of its parents, candidate at '{}' discarded" , . path. display( ) , . candidate. display( ) ) ]
@@ -38,6 +40,11 @@ pub struct Options<'a> {
38
40
pub ceiling_dirs : & ' a [ PathBuf ] ,
39
41
/// If true, and `ceiling_dirs` is not empty, we expect at least one ceiling directory to match or else there will be an error.
40
42
pub match_ceiling_dir_or_error : bool ,
43
+ /// if `true` avoid crossing filesystem boundaries.
44
+ /// Only supported on Unix-like systems.
45
+ // TODO: test on Linux
46
+ // TODO: Handle WASI once https://github.com/rust-lang/rust/issues/71213 is resolved
47
+ pub cross_fs : bool ,
41
48
}
42
49
43
50
impl Default for Options < ' _ > {
@@ -46,11 +53,14 @@ impl Default for Options<'_> {
46
53
required_trust : git_sec:: Trust :: Reduced ,
47
54
ceiling_dirs : & [ ] ,
48
55
match_ceiling_dir_or_error : true ,
56
+ cross_fs : false ,
49
57
}
50
58
}
51
59
}
52
60
53
61
pub ( crate ) mod function {
62
+ #[ cfg( unix) ]
63
+ use std:: fs;
54
64
use std:: path:: { Path , PathBuf } ;
55
65
56
66
use git_sec:: Trust ;
@@ -63,12 +73,14 @@ pub(crate) mod function {
63
73
///
64
74
/// Fail if no valid-looking git repository could be found.
65
75
// TODO: tests for trust-based discovery
76
+ #[ cfg_attr( not( unix) , allow( unused_variables) ) ]
66
77
pub fn discover_opts (
67
78
directory : impl AsRef < Path > ,
68
79
Options {
69
80
required_trust,
70
81
ceiling_dirs,
71
82
match_ceiling_dir_or_error,
83
+ cross_fs,
72
84
} : Options < ' _ > ,
73
85
) -> Result < ( crate :: repository:: Path , Trust ) , Error > {
74
86
// Absolutize the path so that `Path::parent()` _actually_ gives
@@ -77,7 +89,11 @@ pub(crate) mod function {
77
89
// working with paths paths that contain '..'.)
78
90
let cwd = std:: env:: current_dir ( ) . ok ( ) ;
79
91
let dir = git_path:: absolutize ( directory. as_ref ( ) , cwd. as_deref ( ) ) ;
80
- if !dir. is_dir ( ) {
92
+ let dir_metadata = dir. metadata ( ) . map_err ( |_| Error :: InaccessibleDirectory {
93
+ path : dir. to_path_buf ( ) ,
94
+ } ) ?;
95
+
96
+ if !dir_metadata. is_dir ( ) {
81
97
return Err ( Error :: InaccessibleDirectory { path : dir. into_owned ( ) } ) ;
82
98
}
83
99
let mut dir_made_absolute = cwd. as_deref ( ) . map_or ( false , |cwd| {
@@ -101,6 +117,9 @@ pub(crate) mod function {
101
117
None
102
118
} ;
103
119
120
+ #[ cfg( unix) ]
121
+ let initial_device = device_id ( & dir_metadata) ;
122
+
104
123
let mut cursor = dir. clone ( ) . into_owned ( ) ;
105
124
let mut current_height = 0 ;
106
125
' outer: loop {
@@ -112,6 +131,20 @@ pub(crate) mod function {
112
131
}
113
132
current_height += 1 ;
114
133
134
+ #[ cfg( unix) ]
135
+ if current_height != 0 && !cross_fs {
136
+ let metadata = cursor. metadata ( ) . map_err ( |_| Error :: InaccessibleDirectory {
137
+ path : cursor. to_path_buf ( ) ,
138
+ } ) ?;
139
+
140
+ if device_id ( & metadata) != initial_device {
141
+ return Err ( Error :: NoGitRepositoryWithinFs {
142
+ path : dir. into_owned ( ) ,
143
+ limit : cursor. to_path_buf ( ) ,
144
+ } ) ;
145
+ }
146
+ }
147
+
115
148
for append_dot_git in & [ false , true ] {
116
149
if * append_dot_git {
117
150
cursor. push ( ".git" ) ;
@@ -217,6 +250,20 @@ pub(crate) mod function {
217
250
. min ( )
218
251
}
219
252
253
+ #[ cfg( target_os = "linux" ) ]
254
+ /// Returns the device ID of the directory.
255
+ fn device_id ( m : & fs:: Metadata ) -> u64 {
256
+ use std:: os:: linux:: fs:: MetadataExt ;
257
+ m. st_dev ( )
258
+ }
259
+
260
+ #[ cfg( all( unix, not( target_os = "linux" ) ) ) ]
261
+ /// Returns the device ID of the directory.
262
+ fn device_id ( m : & fs:: Metadata ) -> u64 {
263
+ use std:: os:: unix:: fs:: MetadataExt ;
264
+ m. dev ( )
265
+ }
266
+
220
267
/// Find the location of the git repository directly in `directory` or in any of its parent directories, and provide
221
268
/// the trust level derived from Path ownership.
222
269
///
0 commit comments