##// END OF EJS Templates
rust-status: add missing variants to `Dispatch` enum...
Raphaël Gomès -
r45013:61709b84 default
parent child Browse files
Show More
@@ -1,321 +1,361
1 1 // status.rs
2 2 //
3 3 // Copyright 2019 Raphaël Gomès <rgomes@octobus.net>
4 4 //
5 5 // This software may be used and distributed according to the terms of the
6 6 // GNU General Public License version 2 or any later version.
7 7
8 8 //! Rust implementation of dirstate.status (dirstate.py).
9 9 //! It is currently missing a lot of functionality compared to the Python one
10 10 //! and will only be triggered in narrow cases.
11 11
12 12 use crate::{
13 13 dirstate::SIZE_FROM_OTHER_PARENT,
14 14 matchers::Matcher,
15 15 utils::{
16 16 files::HgMetadata,
17 17 hg_path::{
18 18 hg_path_to_path_buf, os_string_to_hg_path_buf, HgPath, HgPathBuf,
19 19 },
20 20 },
21 21 CopyMap, DirstateEntry, DirstateMap, EntryState,
22 22 };
23 23 use rayon::prelude::*;
24 24 use std::collections::HashSet;
25 25 use std::fs::{read_dir, DirEntry};
26 26 use std::path::Path;
27 27
28 /// Wrong type of file from a `BadMatch`
29 /// Note: a lot of those don't exist on all platforms.
30 #[derive(Debug)]
31 pub enum BadType {
32 CharacterDevice,
33 BlockDevice,
34 FIFO,
35 Socket,
36 Directory,
37 Unknown,
38 }
39
40 /// Was explicitly matched but cannot be found/accessed
41 #[derive(Debug)]
42 pub enum BadMatch {
43 OsError(i32),
44 BadType(BadType),
45 }
46
28 47 /// Marker enum used to dispatch new status entries into the right collections.
29 48 /// Is similar to `crate::EntryState`, but represents the transient state of
30 49 /// entries during the lifetime of a command.
31 50 enum Dispatch {
32 51 Unsure,
33 52 Modified,
34 53 Added,
35 54 Removed,
36 55 Deleted,
37 56 Clean,
38 57 Unknown,
58 Ignored,
59 /// Empty dispatch, the file is not worth listing
60 None,
61 /// Was explicitly matched but cannot be found/accessed
62 Bad(BadMatch),
63 Directory {
64 /// True if the directory used to be a file in the dmap so we can say
65 /// that it's been removed.
66 was_file: bool,
67 },
39 68 }
40 69
41 70 type IoResult<T> = std::io::Result<T>;
42 71
43 72 /// Dates and times that are outside the 31-bit signed range are compared
44 73 /// modulo 2^31. This should prevent hg from behaving badly with very large
45 74 /// files or corrupt dates while still having a high probability of detecting
46 75 /// changes. (issue2608)
47 76 /// TODO I haven't found a way of having `b` be `Into<i32>`, since `From<u64>`
48 77 /// is not defined for `i32`, and there is no `As` trait. This forces the
49 78 /// caller to cast `b` as `i32`.
50 79 fn mod_compare(a: i32, b: i32) -> bool {
51 80 a & i32::max_value() != b & i32::max_value()
52 81 }
53 82
54 83 /// Return a sorted list containing information about the entries
55 84 /// in the directory.
56 85 ///
57 86 /// * `skip_dot_hg` - Return an empty vec if `path` contains a `.hg` directory
58 87 fn list_directory(
59 88 path: impl AsRef<Path>,
60 89 skip_dot_hg: bool,
61 90 ) -> std::io::Result<Vec<(HgPathBuf, DirEntry)>> {
62 91 let mut results = vec![];
63 92 let entries = read_dir(path.as_ref())?;
64 93
65 94 for entry in entries {
66 95 let entry = entry?;
67 96 let filename = os_string_to_hg_path_buf(entry.file_name())?;
68 97 let file_type = entry.file_type()?;
69 98 if skip_dot_hg && filename.as_bytes() == b".hg" && file_type.is_dir() {
70 99 return Ok(vec![]);
71 100 } else {
72 101 results.push((HgPathBuf::from(filename), entry))
73 102 }
74 103 }
75 104
76 105 results.sort_unstable_by_key(|e| e.0.clone());
77 106 Ok(results)
78 107 }
79 108
80 109 /// The file corresponding to the dirstate entry was found on the filesystem.
81 110 fn dispatch_found(
82 111 filename: impl AsRef<HgPath>,
83 112 entry: DirstateEntry,
84 113 metadata: HgMetadata,
85 114 copy_map: &CopyMap,
86 115 options: StatusOptions,
87 116 ) -> Dispatch {
88 117 let DirstateEntry {
89 118 state,
90 119 mode,
91 120 mtime,
92 121 size,
93 122 } = entry;
94 123
95 124 let HgMetadata {
96 125 st_mode,
97 126 st_size,
98 127 st_mtime,
99 128 ..
100 129 } = metadata;
101 130
102 131 match state {
103 132 EntryState::Normal => {
104 133 let size_changed = mod_compare(size, st_size as i32);
105 134 let mode_changed =
106 135 (mode ^ st_mode as i32) & 0o100 != 0o000 && options.check_exec;
107 136 let metadata_changed = size >= 0 && (size_changed || mode_changed);
108 137 let other_parent = size == SIZE_FROM_OTHER_PARENT;
109 138 if metadata_changed
110 139 || other_parent
111 140 || copy_map.contains_key(filename.as_ref())
112 141 {
113 142 Dispatch::Modified
114 143 } else if mod_compare(mtime, st_mtime as i32) {
115 144 Dispatch::Unsure
116 145 } else if st_mtime == options.last_normal_time {
117 146 // the file may have just been marked as normal and
118 147 // it may have changed in the same second without
119 148 // changing its size. This can happen if we quickly
120 149 // do multiple commits. Force lookup, so we don't
121 150 // miss such a racy file change.
122 151 Dispatch::Unsure
123 152 } else if options.list_clean {
124 153 Dispatch::Clean
125 154 } else {
126 155 Dispatch::Unknown
127 156 }
128 157 }
129 158 EntryState::Merged => Dispatch::Modified,
130 159 EntryState::Added => Dispatch::Added,
131 160 EntryState::Removed => Dispatch::Removed,
132 161 EntryState::Unknown => Dispatch::Unknown,
133 162 }
134 163 }
135 164
136 165 /// The file corresponding to this Dirstate entry is missing.
137 166 fn dispatch_missing(state: EntryState) -> Dispatch {
138 167 match state {
139 168 // File was removed from the filesystem during commands
140 169 EntryState::Normal | EntryState::Merged | EntryState::Added => {
141 170 Dispatch::Deleted
142 171 }
143 172 // File was removed, everything is normal
144 173 EntryState::Removed => Dispatch::Removed,
145 174 // File is unknown to Mercurial, everything is normal
146 175 EntryState::Unknown => Dispatch::Unknown,
147 176 }
148 177 }
149 178
150 179 /// Get stat data about the files explicitly specified by match.
151 180 /// TODO subrepos
152 181 fn walk_explicit<'a>(
153 182 files: &'a HashSet<&HgPath>,
154 183 dmap: &'a DirstateMap,
155 184 root_dir: impl AsRef<Path> + Sync + Send,
156 185 options: StatusOptions,
157 186 ) -> impl ParallelIterator<Item = IoResult<(&'a HgPath, Dispatch)>> {
158 187 files.par_iter().filter_map(move |filename| {
159 188 // TODO normalization
160 189 let normalized = filename.as_ref();
161 190
162 191 let buf = match hg_path_to_path_buf(normalized) {
163 192 Ok(x) => x,
164 193 Err(e) => return Some(Err(e.into())),
165 194 };
166 195 let target = root_dir.as_ref().join(buf);
167 196 let st = target.symlink_metadata();
168 197 match st {
169 198 Ok(meta) => {
170 199 let file_type = meta.file_type();
171 200 if file_type.is_file() || file_type.is_symlink() {
172 201 if let Some(entry) = dmap.get(normalized) {
173 202 return Some(Ok((
174 203 normalized,
175 204 dispatch_found(
176 205 &normalized,
177 206 *entry,
178 207 HgMetadata::from_metadata(meta),
179 208 &dmap.copy_map,
180 209 options,
181 210 ),
182 211 )));
183 212 }
184 213 } else {
185 214 if dmap.contains_key(normalized) {
186 215 return Some(Ok((normalized, Dispatch::Removed)));
187 216 }
188 217 }
189 218 }
190 219 Err(_) => {
191 220 if let Some(entry) = dmap.get(normalized) {
192 221 return Some(Ok((
193 222 normalized,
194 223 dispatch_missing(entry.state),
195 224 )));
196 225 }
197 226 }
198 227 };
199 228 None
200 229 })
201 230 }
202 231
203 232 #[derive(Debug, Copy, Clone)]
204 233 pub struct StatusOptions {
205 234 /// Remember the most recent modification timeslot for status, to make
206 235 /// sure we won't miss future size-preserving file content modifications
207 236 /// that happen within the same timeslot.
208 237 pub last_normal_time: i64,
209 238 /// Whether we are on a filesystem with UNIX-like exec flags
210 239 pub check_exec: bool,
211 240 pub list_clean: bool,
212 241 }
213 242
214 243 /// Stat all entries in the `DirstateMap` and mark them for dispatch into
215 244 /// the relevant collections.
216 245 fn stat_dmap_entries(
217 246 dmap: &DirstateMap,
218 247 root_dir: impl AsRef<Path> + Sync + Send,
219 248 options: StatusOptions,
220 249 ) -> impl ParallelIterator<Item = IoResult<(&HgPath, Dispatch)>> {
221 250 dmap.par_iter().map(move |(filename, entry)| {
222 251 let filename: &HgPath = filename;
223 252 let filename_as_path = hg_path_to_path_buf(filename)?;
224 253 let meta = root_dir.as_ref().join(filename_as_path).symlink_metadata();
225 254
226 255 match meta {
227 256 Ok(ref m)
228 257 if !(m.file_type().is_file()
229 258 || m.file_type().is_symlink()) =>
230 259 {
231 260 Ok((filename, dispatch_missing(entry.state)))
232 261 }
233 262 Ok(m) => Ok((
234 263 filename,
235 264 dispatch_found(
236 265 filename,
237 266 *entry,
238 267 HgMetadata::from_metadata(m),
239 268 &dmap.copy_map,
240 269 options,
241 270 ),
242 271 )),
243 272 Err(ref e)
244 273 if e.kind() == std::io::ErrorKind::NotFound
245 274 || e.raw_os_error() == Some(20) =>
246 275 {
247 276 // Rust does not yet have an `ErrorKind` for
248 277 // `NotADirectory` (errno 20)
249 278 // It happens if the dirstate contains `foo/bar` and
250 279 // foo is not a directory
251 280 Ok((filename, dispatch_missing(entry.state)))
252 281 }
253 282 Err(e) => Err(e),
254 283 }
255 284 })
256 285 }
257 286
258 287 pub struct DirstateStatus<'a> {
259 288 pub modified: Vec<&'a HgPath>,
260 289 pub added: Vec<&'a HgPath>,
261 290 pub removed: Vec<&'a HgPath>,
262 291 pub deleted: Vec<&'a HgPath>,
263 292 pub clean: Vec<&'a HgPath>,
264 /* TODO ignored
265 * TODO unknown */
293 pub ignored: Vec<&'a HgPath>,
294 pub unknown: Vec<&'a HgPath>,
295 pub bad: Vec<(&'a HgPath, BadMatch)>,
266 296 }
267 297
268 298 fn build_response<'a>(
269 299 results: impl IntoIterator<Item = IoResult<(&'a HgPath, Dispatch)>>,
270 300 ) -> IoResult<(Vec<&'a HgPath>, DirstateStatus<'a>)> {
271 301 let mut lookup = vec![];
272 302 let mut modified = vec![];
273 303 let mut added = vec![];
274 304 let mut removed = vec![];
275 305 let mut deleted = vec![];
276 306 let mut clean = vec![];
307 let mut ignored = vec![];
308 let mut unknown = vec![];
309 let mut bad = vec![];
277 310
278 311 for res in results.into_iter() {
279 312 let (filename, dispatch) = res?;
280 313 match dispatch {
281 Dispatch::Unknown => {}
314 Dispatch::Unknown => unknown.push(filename),
282 315 Dispatch::Unsure => lookup.push(filename),
283 316 Dispatch::Modified => modified.push(filename),
284 317 Dispatch::Added => added.push(filename),
285 318 Dispatch::Removed => removed.push(filename),
286 319 Dispatch::Deleted => deleted.push(filename),
287 320 Dispatch::Clean => clean.push(filename),
321 Dispatch::Ignored => ignored.push(filename),
322 Dispatch::None => {}
323 Dispatch::Bad(reason) => bad.push((filename, reason)),
324 Dispatch::Directory { .. } => {}
288 325 }
289 326 }
290 327
291 328 Ok((
292 329 lookup,
293 330 DirstateStatus {
294 331 modified,
295 332 added,
296 333 removed,
297 334 deleted,
298 335 clean,
336 ignored,
337 unknown,
338 bad,
299 339 },
300 340 ))
301 341 }
302 342
303 343 pub fn status<'a: 'c, 'b: 'c, 'c>(
304 344 dmap: &'a DirstateMap,
305 345 matcher: &'b impl Matcher,
306 346 root_dir: impl AsRef<Path> + Sync + Send + Copy,
307 347 options: StatusOptions,
308 348 ) -> IoResult<(Vec<&'c HgPath>, DirstateStatus<'c>)> {
309 349 let files = matcher.file_set();
310 350 let mut results = vec![];
311 351 if let Some(files) = files {
312 352 results.par_extend(walk_explicit(&files, &dmap, root_dir, options));
313 353 }
314 354
315 355 if !matcher.is_exact() {
316 356 let stat_results = stat_dmap_entries(&dmap, root_dir, options);
317 357 results.par_extend(stat_results);
318 358 }
319 359
320 360 build_response(results)
321 361 }
General Comments 0
You need to be logged in to leave comments. Login now