@@ -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,24 @@ 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 = if cursor. as_os_str ( ) . is_empty ( ) {
137
+ Path :: new ( "." )
138
+ } else {
139
+ cursor. as_ref ( )
140
+ }
141
+ . metadata ( )
142
+ . map_err ( |_| Error :: InaccessibleDirectory { path : cursor. clone ( ) } ) ?;
143
+
144
+ if device_id ( & metadata) != initial_device {
145
+ return Err ( Error :: NoGitRepositoryWithinFs {
146
+ path : dir. into_owned ( ) ,
147
+ limit : cursor. clone ( ) ,
148
+ } ) ;
149
+ }
150
+ }
151
+
115
152
for append_dot_git in & [ false , true ] {
116
153
if * append_dot_git {
117
154
cursor. push ( ".git" ) ;
@@ -217,6 +254,20 @@ pub(crate) mod function {
217
254
. min ( )
218
255
}
219
256
257
+ #[ cfg( target_os = "linux" ) ]
258
+ /// Returns the device ID of the directory.
259
+ fn device_id ( m : & fs:: Metadata ) -> u64 {
260
+ use std:: os:: linux:: fs:: MetadataExt ;
261
+ m. st_dev ( )
262
+ }
263
+
264
+ #[ cfg( all( unix, not( target_os = "linux" ) ) ) ]
265
+ /// Returns the device ID of the directory.
266
+ fn device_id ( m : & fs:: Metadata ) -> u64 {
267
+ use std:: os:: unix:: fs:: MetadataExt ;
268
+ m. dev ( )
269
+ }
270
+
220
271
/// Find the location of the git repository directly in `directory` or in any of its parent directories, and provide
221
272
/// the trust level derived from Path ownership.
222
273
///
0 commit comments