1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
//! The working-tree adapter for [`PathProbe`](crate::select::policy::PathProbe).
//!
//! Depends on `std::fs` and `camino`. It is the only place in the selection
//! tree that inspects the filesystem, and it reads metadata alone: selection
//! never opens a file, so a path this module reports as a regular file is not
//! thereby readable, and the writer still decides what to do about that.
//!
//! Absence is a classification, because the selection has a rule for a
//! candidate that is gone. Every other failure to read one is returned as a
//! [`ProbeFailure`]: a permission failure or a path through a file is not a
//! verdict about the file, and a run that could not classify a candidate must
//! say so rather than format the ones it could. See
//! [`select_files`](crate::select::policy::select_files).
//!
//! Which of the two a failure is cannot be read off the failure alone: Windows
//! reports a path through a file with the same `NOT_FOUND` as a path that is
//! gone, so the question is put to the path's ancestors rather than to the leaf.
//! [`unreachable`] is that walk, and [`absent`] is where the answer is given.
//!
//! The reads are the ambient filesystem's rather than a capability's, and
//! deliberately: confinement is what this module decides, and a capability
//! rooted at the working directory reports a candidate that escapes and one
//! that cannot be read as the same `PermissionDenied`, which would leave
//! [`PathKind::OutsideRoot`] unstatable. ADR 0010 states the rule; the Git
//! directory the conflict guard opens is its one exception.
//!
//! Classification uses `symlink_metadata`, not `metadata`, because a link's
//! extension says nothing about its target and rewriting through one escapes
//! the selection. For the same reason the canonical path is confined to the
//! tree: `symlink_metadata` does not follow a candidate's *final* component,
//! but it does see through a symlinked ancestor, so `docs/guide.md` is reported
//! as a regular file even when `docs` is a link to a directory outside the
//! working tree. See [`PathKind::OutsideRoot`].
use ;
use ;
use ;
/// Probes the real working tree using `std::fs::symlink_metadata`.
///
/// `symlink_metadata`, not `metadata`: see [`PathKind::Symlink`].
/// Names a canonical path, unless it lies outside the tree `root` names.
///
/// The last decision the probe makes, and the one that keeps a selection from
/// reaching through a symlinked ancestor: a candidate is what its real path
/// says it is, and a real path that leaves `root` is not a file this selection
/// may name.
/// Whether `canonical` lies within the tree `root` names.
///
/// Both sides are canonicalized. A root reached through a link — `/tmp` on a
/// system where it is one — would otherwise disagree with every path beneath
/// it, and refuse the whole tree. A root that does not exist confines nothing:
/// containment cannot be established, and assuming it held is exactly the escape
/// this rule exists to stop.
///
/// # Errors
///
/// Returns an I/O error if the root exists but cannot be read. A root that
/// cannot be resolved leaves every candidate's classification unwarranted, and
/// the selection does not report a confinement it could not establish.
/// The classification of a candidate whose read failed.
///
/// A `symlink_metadata` that failed outright and a `canonicalize` that failed
/// after the metadata was read are the same question — is the file gone, or
/// could it not be read? — and they must answer it the same way, because the
/// difference is a candidate silently skipped or a run told its selection was
/// incomplete. [`absent`] is where the answer is given; this is only the shape
/// the probe wants it in.
///
/// The reason this is a function of the failure rather than a pair of match arms
/// in the probe above: a path `symlink_metadata` has already accepted can reach
/// the second arm only by losing a race with the filesystem, so no fixture
/// stages it there. Here, every arm is a test's to cover.
/// Whether a failed read of `path` is the absence the selection has a rule for.
///
/// `Ok(())` is the absence a candidate staged for deletion is skipped under, and
/// it is the one cause the caller can act on: a file removed since Git listed
/// it, or one removed between the metadata read in [`PathProbe::probe`] and the
/// canonicalization that follows. `Err` is the failure to report instead.
///
/// [`ErrorKind::NotFound`] is not by itself evidence of absence, and the
/// decision is therefore about the whole path rather than the leaf that failed;
/// see [`unreachable`]. Every other kind is a path that is present but
/// unreadable, and is reported as it arrived.
///
/// # Errors
///
/// Returns `error` unchanged when its kind is not [`ErrorKind::NotFound`], and
/// what [`unreachable`] found when it is.
/// The nearest ancestor of `path` that exists, when it is not a directory.
///
/// A `NotFound` does not say which part of a path is not there. On Windows a
/// path beneath a regular file fails with `NOT_FOUND` — `ERROR_PATH_NOT_FOUND`
/// — exactly as one that is not there at all does, where Unix reports
/// `ENOTDIR`; a leaf's own failure cannot tell the two apart, and reading the
/// first as the second silently skips a candidate the run was asked to
/// consider. The nearest ancestor that exists decides, and a directory reached
/// through a link is a directory, which is why this walk follows links where
/// the probe itself does not.
///
/// `None` is the absence the selection has a rule for: no part of the path is
/// there. `Some(error)` is the failure to report instead — a path that stops at
/// something other than a directory is [`ErrorKind::NotADirectory`], the kind
/// Unix produces for it, and a walk that fails for any other reason reports
/// whatever stopped it.
/// What the walk needs to know about one ancestor.
///
/// The walk is stated over this rather than over the metadata itself because a
/// `Metadata` cannot be fabricated, and the reading it exists for — an ancestor
/// whose own read failed with something other than absence — is one no fixture
/// stages: a candidate reaches the walk only through a `NotFound` on its own
/// leaf, by which time the filesystem has answered for every ancestor above it.
/// Reads one ancestor, the way the walk sees it.
/// The walk, over whatever reads an ancestor.
///
/// The reader is a parameter so that the arm no fixture reaches — an ancestor
/// whose read failed with something other than absence — is a test's to cover,
/// which is also what stops the guard from being replaced by one that walks
/// past it and reports a candidate as absent.