##// END OF EJS Templates
rust: remove an unnecessary set of parentheses...
Martin von Zweigbergk -
r44654:3c265cef default
parent child Browse files
Show More
@@ -1,306 +1,306 b''
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::{hg_path_to_path_buf, HgPath},
17 hg_path::{hg_path_to_path_buf, HgPath},
18 },
18 },
19 CopyMap, DirstateEntry, DirstateMap, EntryState,
19 CopyMap, DirstateEntry, DirstateMap, EntryState,
20 };
20 };
21 use rayon::prelude::*;
21 use rayon::prelude::*;
22 use std::collections::HashSet;
22 use std::collections::HashSet;
23 use std::path::Path;
23 use std::path::Path;
24
24
25 /// Marker enum used to dispatch new status entries into the right collections.
25 /// Marker enum used to dispatch new status entries into the right collections.
26 /// Is similar to `crate::EntryState`, but represents the transient state of
26 /// Is similar to `crate::EntryState`, but represents the transient state of
27 /// entries during the lifetime of a command.
27 /// entries during the lifetime of a command.
28 enum Dispatch {
28 enum Dispatch {
29 Unsure,
29 Unsure,
30 Modified,
30 Modified,
31 Added,
31 Added,
32 Removed,
32 Removed,
33 Deleted,
33 Deleted,
34 Clean,
34 Clean,
35 Unknown,
35 Unknown,
36 }
36 }
37
37
38 type IoResult<T> = std::io::Result<T>;
38 type IoResult<T> = std::io::Result<T>;
39
39
40 /// Dates and times that are outside the 31-bit signed range are compared
40 /// Dates and times that are outside the 31-bit signed range are compared
41 /// modulo 2^31. This should prevent hg from behaving badly with very large
41 /// modulo 2^31. This should prevent hg from behaving badly with very large
42 /// files or corrupt dates while still having a high probability of detecting
42 /// files or corrupt dates while still having a high probability of detecting
43 /// changes. (issue2608)
43 /// changes. (issue2608)
44 /// TODO I haven't found a way of having `b` be `Into<i32>`, since `From<u64>`
44 /// TODO I haven't found a way of having `b` be `Into<i32>`, since `From<u64>`
45 /// is not defined for `i32`, and there is no `As` trait. This forces the
45 /// is not defined for `i32`, and there is no `As` trait. This forces the
46 /// caller to cast `b` as `i32`.
46 /// caller to cast `b` as `i32`.
47 fn mod_compare(a: i32, b: i32) -> bool {
47 fn mod_compare(a: i32, b: i32) -> bool {
48 a & i32::max_value() != b & i32::max_value()
48 a & i32::max_value() != b & i32::max_value()
49 }
49 }
50
50
51 /// The file corresponding to the dirstate entry was found on the filesystem.
51 /// The file corresponding to the dirstate entry was found on the filesystem.
52 fn dispatch_found(
52 fn dispatch_found(
53 filename: impl AsRef<HgPath>,
53 filename: impl AsRef<HgPath>,
54 entry: DirstateEntry,
54 entry: DirstateEntry,
55 metadata: HgMetadata,
55 metadata: HgMetadata,
56 copy_map: &CopyMap,
56 copy_map: &CopyMap,
57 check_exec: bool,
57 check_exec: bool,
58 list_clean: bool,
58 list_clean: bool,
59 last_normal_time: i64,
59 last_normal_time: i64,
60 ) -> Dispatch {
60 ) -> Dispatch {
61 let DirstateEntry {
61 let DirstateEntry {
62 state,
62 state,
63 mode,
63 mode,
64 mtime,
64 mtime,
65 size,
65 size,
66 } = entry;
66 } = entry;
67
67
68 let HgMetadata {
68 let HgMetadata {
69 st_mode,
69 st_mode,
70 st_size,
70 st_size,
71 st_mtime,
71 st_mtime,
72 ..
72 ..
73 } = metadata;
73 } = metadata;
74
74
75 match state {
75 match state {
76 EntryState::Normal => {
76 EntryState::Normal => {
77 let size_changed = mod_compare(size, st_size as i32);
77 let size_changed = mod_compare(size, st_size as i32);
78 let mode_changed =
78 let mode_changed =
79 (mode ^ st_mode as i32) & 0o100 != 0o000 && check_exec;
79 (mode ^ st_mode as i32) & 0o100 != 0o000 && check_exec;
80 let metadata_changed = size >= 0 && (size_changed || mode_changed);
80 let metadata_changed = size >= 0 && (size_changed || mode_changed);
81 let other_parent = size == SIZE_FROM_OTHER_PARENT;
81 let other_parent = size == SIZE_FROM_OTHER_PARENT;
82 if metadata_changed
82 if metadata_changed
83 || other_parent
83 || other_parent
84 || copy_map.contains_key(filename.as_ref())
84 || copy_map.contains_key(filename.as_ref())
85 {
85 {
86 Dispatch::Modified
86 Dispatch::Modified
87 } else if mod_compare(mtime, st_mtime as i32) {
87 } else if mod_compare(mtime, st_mtime as i32) {
88 Dispatch::Unsure
88 Dispatch::Unsure
89 } else if st_mtime == last_normal_time {
89 } else if st_mtime == last_normal_time {
90 // the file may have just been marked as normal and
90 // the file may have just been marked as normal and
91 // it may have changed in the same second without
91 // it may have changed in the same second without
92 // changing its size. This can happen if we quickly
92 // changing its size. This can happen if we quickly
93 // do multiple commits. Force lookup, so we don't
93 // do multiple commits. Force lookup, so we don't
94 // miss such a racy file change.
94 // miss such a racy file change.
95 Dispatch::Unsure
95 Dispatch::Unsure
96 } else if list_clean {
96 } else if list_clean {
97 Dispatch::Clean
97 Dispatch::Clean
98 } else {
98 } else {
99 Dispatch::Unknown
99 Dispatch::Unknown
100 }
100 }
101 }
101 }
102 EntryState::Merged => Dispatch::Modified,
102 EntryState::Merged => Dispatch::Modified,
103 EntryState::Added => Dispatch::Added,
103 EntryState::Added => Dispatch::Added,
104 EntryState::Removed => Dispatch::Removed,
104 EntryState::Removed => Dispatch::Removed,
105 EntryState::Unknown => Dispatch::Unknown,
105 EntryState::Unknown => Dispatch::Unknown,
106 }
106 }
107 }
107 }
108
108
109 /// The file corresponding to this Dirstate entry is missing.
109 /// The file corresponding to this Dirstate entry is missing.
110 fn dispatch_missing(state: EntryState) -> Dispatch {
110 fn dispatch_missing(state: EntryState) -> Dispatch {
111 match state {
111 match state {
112 // File was removed from the filesystem during commands
112 // File was removed from the filesystem during commands
113 EntryState::Normal | EntryState::Merged | EntryState::Added => {
113 EntryState::Normal | EntryState::Merged | EntryState::Added => {
114 Dispatch::Deleted
114 Dispatch::Deleted
115 }
115 }
116 // File was removed, everything is normal
116 // File was removed, everything is normal
117 EntryState::Removed => Dispatch::Removed,
117 EntryState::Removed => Dispatch::Removed,
118 // File is unknown to Mercurial, everything is normal
118 // File is unknown to Mercurial, everything is normal
119 EntryState::Unknown => Dispatch::Unknown,
119 EntryState::Unknown => Dispatch::Unknown,
120 }
120 }
121 }
121 }
122
122
123 /// Get stat data about the files explicitly specified by match.
123 /// Get stat data about the files explicitly specified by match.
124 /// TODO subrepos
124 /// TODO subrepos
125 fn walk_explicit<'a>(
125 fn walk_explicit<'a>(
126 files: &'a HashSet<&HgPath>,
126 files: &'a HashSet<&HgPath>,
127 dmap: &'a DirstateMap,
127 dmap: &'a DirstateMap,
128 root_dir: impl AsRef<Path> + Sync + Send,
128 root_dir: impl AsRef<Path> + Sync + Send,
129 check_exec: bool,
129 check_exec: bool,
130 list_clean: bool,
130 list_clean: bool,
131 last_normal_time: i64,
131 last_normal_time: i64,
132 ) -> impl ParallelIterator<Item = IoResult<(&'a HgPath, Dispatch)>> {
132 ) -> impl ParallelIterator<Item = IoResult<(&'a HgPath, Dispatch)>> {
133 files.par_iter().filter_map(move |filename| {
133 files.par_iter().filter_map(move |filename| {
134 // TODO normalization
134 // TODO normalization
135 let normalized = filename.as_ref();
135 let normalized = filename.as_ref();
136
136
137 let buf = match hg_path_to_path_buf(normalized) {
137 let buf = match hg_path_to_path_buf(normalized) {
138 Ok(x) => x,
138 Ok(x) => x,
139 Err(e) => return Some(Err(e.into())),
139 Err(e) => return Some(Err(e.into())),
140 };
140 };
141 let target = root_dir.as_ref().join(buf);
141 let target = root_dir.as_ref().join(buf);
142 let st = target.symlink_metadata();
142 let st = target.symlink_metadata();
143 match st {
143 match st {
144 Ok(meta) => {
144 Ok(meta) => {
145 let file_type = meta.file_type();
145 let file_type = meta.file_type();
146 if file_type.is_file() || file_type.is_symlink() {
146 if file_type.is_file() || file_type.is_symlink() {
147 if let Some(entry) = dmap.get(normalized) {
147 if let Some(entry) = dmap.get(normalized) {
148 return Some(Ok((
148 return Some(Ok((
149 normalized,
149 normalized,
150 dispatch_found(
150 dispatch_found(
151 &normalized,
151 &normalized,
152 *entry,
152 *entry,
153 HgMetadata::from_metadata(meta),
153 HgMetadata::from_metadata(meta),
154 &dmap.copy_map,
154 &dmap.copy_map,
155 check_exec,
155 check_exec,
156 list_clean,
156 list_clean,
157 last_normal_time,
157 last_normal_time,
158 ),
158 ),
159 )));
159 )));
160 }
160 }
161 } else {
161 } else {
162 if dmap.contains_key(normalized) {
162 if dmap.contains_key(normalized) {
163 return Some(Ok((normalized, Dispatch::Removed)));
163 return Some(Ok((normalized, Dispatch::Removed)));
164 }
164 }
165 }
165 }
166 }
166 }
167 Err(_) => {
167 Err(_) => {
168 if let Some(entry) = dmap.get(normalized) {
168 if let Some(entry) = dmap.get(normalized) {
169 return Some(Ok((
169 return Some(Ok((
170 normalized,
170 normalized,
171 dispatch_missing(entry.state),
171 dispatch_missing(entry.state),
172 )));
172 )));
173 }
173 }
174 }
174 }
175 };
175 };
176 None
176 None
177 })
177 })
178 }
178 }
179
179
180 /// Stat all entries in the `DirstateMap` and mark them for dispatch into
180 /// Stat all entries in the `DirstateMap` and mark them for dispatch into
181 /// the relevant collections.
181 /// the relevant collections.
182 fn stat_dmap_entries(
182 fn stat_dmap_entries(
183 dmap: &DirstateMap,
183 dmap: &DirstateMap,
184 root_dir: impl AsRef<Path> + Sync + Send,
184 root_dir: impl AsRef<Path> + Sync + Send,
185 check_exec: bool,
185 check_exec: bool,
186 list_clean: bool,
186 list_clean: bool,
187 last_normal_time: i64,
187 last_normal_time: i64,
188 ) -> impl ParallelIterator<Item = IoResult<(&HgPath, Dispatch)>> {
188 ) -> impl ParallelIterator<Item = IoResult<(&HgPath, Dispatch)>> {
189 dmap.par_iter().map(move |(filename, entry)| {
189 dmap.par_iter().map(move |(filename, entry)| {
190 let filename: &HgPath = filename;
190 let filename: &HgPath = filename;
191 let filename_as_path = hg_path_to_path_buf(filename)?;
191 let filename_as_path = hg_path_to_path_buf(filename)?;
192 let meta = root_dir.as_ref().join(filename_as_path).symlink_metadata();
192 let meta = root_dir.as_ref().join(filename_as_path).symlink_metadata();
193
193
194 match meta {
194 match meta {
195 Ok(ref m)
195 Ok(ref m)
196 if !(m.file_type().is_file()
196 if !(m.file_type().is_file()
197 || m.file_type().is_symlink()) =>
197 || m.file_type().is_symlink()) =>
198 {
198 {
199 Ok((filename, dispatch_missing(entry.state)))
199 Ok((filename, dispatch_missing(entry.state)))
200 }
200 }
201 Ok(m) => Ok((
201 Ok(m) => Ok((
202 filename,
202 filename,
203 dispatch_found(
203 dispatch_found(
204 filename,
204 filename,
205 *entry,
205 *entry,
206 HgMetadata::from_metadata(m),
206 HgMetadata::from_metadata(m),
207 &dmap.copy_map,
207 &dmap.copy_map,
208 check_exec,
208 check_exec,
209 list_clean,
209 list_clean,
210 last_normal_time,
210 last_normal_time,
211 ),
211 ),
212 )),
212 )),
213 Err(ref e)
213 Err(ref e)
214 if e.kind() == std::io::ErrorKind::NotFound
214 if e.kind() == std::io::ErrorKind::NotFound
215 || e.raw_os_error() == Some(20) =>
215 || e.raw_os_error() == Some(20) =>
216 {
216 {
217 // Rust does not yet have an `ErrorKind` for
217 // Rust does not yet have an `ErrorKind` for
218 // `NotADirectory` (errno 20)
218 // `NotADirectory` (errno 20)
219 // It happens if the dirstate contains `foo/bar` and
219 // It happens if the dirstate contains `foo/bar` and
220 // foo is not a directory
220 // foo is not a directory
221 Ok((filename, dispatch_missing(entry.state)))
221 Ok((filename, dispatch_missing(entry.state)))
222 }
222 }
223 Err(e) => Err(e),
223 Err(e) => Err(e),
224 }
224 }
225 })
225 })
226 }
226 }
227
227
228 pub struct StatusResult<'a> {
228 pub struct StatusResult<'a> {
229 pub modified: Vec<&'a HgPath>,
229 pub modified: Vec<&'a HgPath>,
230 pub added: Vec<&'a HgPath>,
230 pub added: Vec<&'a HgPath>,
231 pub removed: Vec<&'a HgPath>,
231 pub removed: Vec<&'a HgPath>,
232 pub deleted: Vec<&'a HgPath>,
232 pub deleted: Vec<&'a HgPath>,
233 pub clean: Vec<&'a HgPath>,
233 pub clean: Vec<&'a HgPath>,
234 /* TODO ignored
234 /* TODO ignored
235 * TODO unknown */
235 * TODO unknown */
236 }
236 }
237
237
238 fn build_response<'a>(
238 fn build_response<'a>(
239 results: impl IntoIterator<Item = IoResult<(&'a HgPath, Dispatch)>>,
239 results: impl IntoIterator<Item = IoResult<(&'a HgPath, Dispatch)>>,
240 ) -> IoResult<(Vec<&'a HgPath>, StatusResult<'a>)> {
240 ) -> IoResult<(Vec<&'a HgPath>, StatusResult<'a>)> {
241 let mut lookup = vec![];
241 let mut lookup = vec![];
242 let mut modified = vec![];
242 let mut modified = vec![];
243 let mut added = vec![];
243 let mut added = vec![];
244 let mut removed = vec![];
244 let mut removed = vec![];
245 let mut deleted = vec![];
245 let mut deleted = vec![];
246 let mut clean = vec![];
246 let mut clean = vec![];
247
247
248 for res in results.into_iter() {
248 for res in results.into_iter() {
249 let (filename, dispatch) = res?;
249 let (filename, dispatch) = res?;
250 match dispatch {
250 match dispatch {
251 Dispatch::Unknown => {}
251 Dispatch::Unknown => {}
252 Dispatch::Unsure => lookup.push(filename),
252 Dispatch::Unsure => lookup.push(filename),
253 Dispatch::Modified => modified.push(filename),
253 Dispatch::Modified => modified.push(filename),
254 Dispatch::Added => added.push(filename),
254 Dispatch::Added => added.push(filename),
255 Dispatch::Removed => removed.push(filename),
255 Dispatch::Removed => removed.push(filename),
256 Dispatch::Deleted => deleted.push(filename),
256 Dispatch::Deleted => deleted.push(filename),
257 Dispatch::Clean => clean.push(filename),
257 Dispatch::Clean => clean.push(filename),
258 }
258 }
259 }
259 }
260
260
261 Ok((
261 Ok((
262 lookup,
262 lookup,
263 StatusResult {
263 StatusResult {
264 modified,
264 modified,
265 added,
265 added,
266 removed,
266 removed,
267 deleted,
267 deleted,
268 clean,
268 clean,
269 },
269 },
270 ))
270 ))
271 }
271 }
272
272
273 pub fn status<'a: 'c, 'b: 'c, 'c>(
273 pub fn status<'a: 'c, 'b: 'c, 'c>(
274 dmap: &'a DirstateMap,
274 dmap: &'a DirstateMap,
275 matcher: &'b (impl Matcher),
275 matcher: &'b impl Matcher,
276 root_dir: impl AsRef<Path> + Sync + Send + Copy,
276 root_dir: impl AsRef<Path> + Sync + Send + Copy,
277 list_clean: bool,
277 list_clean: bool,
278 last_normal_time: i64,
278 last_normal_time: i64,
279 check_exec: bool,
279 check_exec: bool,
280 ) -> IoResult<(Vec<&'c HgPath>, StatusResult<'c>)> {
280 ) -> IoResult<(Vec<&'c HgPath>, StatusResult<'c>)> {
281 let files = matcher.file_set();
281 let files = matcher.file_set();
282 let mut results = vec![];
282 let mut results = vec![];
283 if let Some(files) = files {
283 if let Some(files) = files {
284 results.par_extend(walk_explicit(
284 results.par_extend(walk_explicit(
285 &files,
285 &files,
286 &dmap,
286 &dmap,
287 root_dir,
287 root_dir,
288 check_exec,
288 check_exec,
289 list_clean,
289 list_clean,
290 last_normal_time,
290 last_normal_time,
291 ));
291 ));
292 }
292 }
293
293
294 if !matcher.is_exact() {
294 if !matcher.is_exact() {
295 let stat_results = stat_dmap_entries(
295 let stat_results = stat_dmap_entries(
296 &dmap,
296 &dmap,
297 root_dir,
297 root_dir,
298 check_exec,
298 check_exec,
299 list_clean,
299 list_clean,
300 last_normal_time,
300 last_normal_time,
301 );
301 );
302 results.par_extend(stat_results);
302 results.par_extend(stat_results);
303 }
303 }
304
304
305 build_response(results)
305 build_response(results)
306 }
306 }
General Comments 0
You need to be logged in to leave comments. Login now