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