##// END OF EJS Templates
rust-status: refactor dispatch case for normal files...
Raphaël Gomès -
r44002:51cd8673 default
parent child Browse files
Show More
@@ -1,221 +1,223 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::utils::files::HgMetadata;
12 use crate::utils::files::HgMetadata;
13 use crate::utils::hg_path::{hg_path_to_path_buf, HgPath};
13 use crate::utils::hg_path::{hg_path_to_path_buf, HgPath};
14 use crate::{CopyMap, DirstateEntry, DirstateMap, EntryState};
14 use crate::{CopyMap, DirstateEntry, DirstateMap, EntryState};
15 use rayon::prelude::*;
15 use rayon::prelude::*;
16 use std::path::Path;
16 use std::path::Path;
17
17
18 /// Marker enum used to dispatch new status entries into the right collections.
18 /// Marker enum used to dispatch new status entries into the right collections.
19 /// Is similar to `crate::EntryState`, but represents the transient state of
19 /// Is similar to `crate::EntryState`, but represents the transient state of
20 /// entries during the lifetime of a command.
20 /// entries during the lifetime of a command.
21 enum Dispatch {
21 enum Dispatch {
22 Unsure,
22 Unsure,
23 Modified,
23 Modified,
24 Added,
24 Added,
25 Removed,
25 Removed,
26 Deleted,
26 Deleted,
27 Clean,
27 Clean,
28 Unknown,
28 Unknown,
29 }
29 }
30
30
31 /// Dates and times that are outside the 31-bit signed range are compared
32 /// modulo 2^31. This should prevent hg from behaving badly with very large
33 /// files or corrupt dates while still having a high probability of detecting
34 /// changes. (issue2608)
35 /// TODO I haven't found a way of having `b` be `Into<i32>`, since `From<u64>`
36 /// is not defined for `i32`, and there is no `As` trait. This forces the
37 /// caller to cast `b` as `i32`.
38 fn mod_compare(a: i32, b: i32) -> bool {
39 a & i32::max_value() != b & i32::max_value()
40 }
41
31 /// The file corresponding to the dirstate entry was found on the filesystem.
42 /// The file corresponding to the dirstate entry was found on the filesystem.
32 fn dispatch_found(
43 fn dispatch_found(
33 filename: impl AsRef<HgPath>,
44 filename: impl AsRef<HgPath>,
34 entry: DirstateEntry,
45 entry: DirstateEntry,
35 metadata: HgMetadata,
46 metadata: HgMetadata,
36 copy_map: &CopyMap,
47 copy_map: &CopyMap,
37 check_exec: bool,
48 check_exec: bool,
38 list_clean: bool,
49 list_clean: bool,
39 last_normal_time: i64,
50 last_normal_time: i64,
40 ) -> Dispatch {
51 ) -> Dispatch {
41 let DirstateEntry {
52 let DirstateEntry {
42 state,
53 state,
43 mode,
54 mode,
44 mtime,
55 mtime,
45 size,
56 size,
46 } = entry;
57 } = entry;
47
58
48 let HgMetadata {
59 let HgMetadata {
49 st_mode,
60 st_mode,
50 st_size,
61 st_size,
51 st_mtime,
62 st_mtime,
52 ..
63 ..
53 } = metadata;
64 } = metadata;
54
65
55 match state {
66 match state {
56 EntryState::Normal => {
67 EntryState::Normal => {
57 // Dates and times that are outside the 31-bit signed
68 let size_changed = mod_compare(size, st_size as i32);
58 // range are compared modulo 2^31. This should prevent
59 // it from behaving badly with very large files or
60 // corrupt dates while still having a high probability
61 // of detecting changes. (issue2608)
62 let range_mask = 0x7fffffff;
63
64 let size_changed = (size != st_size as i32)
65 && size != (st_size as i32 & range_mask);
66 let mode_changed =
69 let mode_changed =
67 (mode ^ st_mode as i32) & 0o100 != 0o000 && check_exec;
70 (mode ^ st_mode as i32) & 0o100 != 0o000 && check_exec;
68 if size >= 0
71 let metadata_changed = size >= 0 && (size_changed || mode_changed);
69 && (size_changed || mode_changed)
72 let other_parent = size == -2;
70 || size == -2 // other parent
73 if metadata_changed
71 || copy_map.contains_key(filename.as_ref())
74 || other_parent
75 || copy_map.contains_key(filename.as_ref())
72 {
76 {
73 Dispatch::Modified
77 Dispatch::Modified
74 } else if mtime != st_mtime as i32
78 } else if mod_compare(mtime, st_mtime as i32) {
75 && mtime != (st_mtime as i32 & range_mask)
76 {
77 Dispatch::Unsure
79 Dispatch::Unsure
78 } else if st_mtime == last_normal_time {
80 } else if st_mtime == last_normal_time {
79 // the file may have just been marked as normal and
81 // the file may have just been marked as normal and
80 // it may have changed in the same second without
82 // it may have changed in the same second without
81 // changing its size. This can happen if we quickly
83 // changing its size. This can happen if we quickly
82 // do multiple commits. Force lookup, so we don't
84 // do multiple commits. Force lookup, so we don't
83 // miss such a racy file change.
85 // miss such a racy file change.
84 Dispatch::Unsure
86 Dispatch::Unsure
85 } else if list_clean {
87 } else if list_clean {
86 Dispatch::Clean
88 Dispatch::Clean
87 } else {
89 } else {
88 Dispatch::Unknown
90 Dispatch::Unknown
89 }
91 }
90 }
92 }
91 EntryState::Merged => Dispatch::Modified,
93 EntryState::Merged => Dispatch::Modified,
92 EntryState::Added => Dispatch::Added,
94 EntryState::Added => Dispatch::Added,
93 EntryState::Removed => Dispatch::Removed,
95 EntryState::Removed => Dispatch::Removed,
94 EntryState::Unknown => Dispatch::Unknown,
96 EntryState::Unknown => Dispatch::Unknown,
95 }
97 }
96 }
98 }
97
99
98 /// The file corresponding to this Dirstate entry is missing.
100 /// The file corresponding to this Dirstate entry is missing.
99 fn dispatch_missing(state: EntryState) -> Dispatch {
101 fn dispatch_missing(state: EntryState) -> Dispatch {
100 match state {
102 match state {
101 // File was removed from the filesystem during commands
103 // File was removed from the filesystem during commands
102 EntryState::Normal | EntryState::Merged | EntryState::Added => {
104 EntryState::Normal | EntryState::Merged | EntryState::Added => {
103 Dispatch::Deleted
105 Dispatch::Deleted
104 }
106 }
105 // File was removed, everything is normal
107 // File was removed, everything is normal
106 EntryState::Removed => Dispatch::Removed,
108 EntryState::Removed => Dispatch::Removed,
107 // File is unknown to Mercurial, everything is normal
109 // File is unknown to Mercurial, everything is normal
108 EntryState::Unknown => Dispatch::Unknown,
110 EntryState::Unknown => Dispatch::Unknown,
109 }
111 }
110 }
112 }
111
113
112 /// Stat all entries in the `DirstateMap` and mark them for dispatch into
114 /// Stat all entries in the `DirstateMap` and mark them for dispatch into
113 /// the relevant collections.
115 /// the relevant collections.
114 fn stat_dmap_entries(
116 fn stat_dmap_entries(
115 dmap: &DirstateMap,
117 dmap: &DirstateMap,
116 root_dir: impl AsRef<Path> + Sync + Send,
118 root_dir: impl AsRef<Path> + Sync + Send,
117 check_exec: bool,
119 check_exec: bool,
118 list_clean: bool,
120 list_clean: bool,
119 last_normal_time: i64,
121 last_normal_time: i64,
120 ) -> impl ParallelIterator<Item = std::io::Result<(&HgPath, Dispatch)>> {
122 ) -> impl ParallelIterator<Item = std::io::Result<(&HgPath, Dispatch)>> {
121 dmap.par_iter().map(move |(filename, entry)| {
123 dmap.par_iter().map(move |(filename, entry)| {
122 let filename: &HgPath = filename;
124 let filename: &HgPath = filename;
123 let filename_as_path = hg_path_to_path_buf(filename)?;
125 let filename_as_path = hg_path_to_path_buf(filename)?;
124 let meta = root_dir.as_ref().join(filename_as_path).symlink_metadata();
126 let meta = root_dir.as_ref().join(filename_as_path).symlink_metadata();
125
127
126 match meta {
128 match meta {
127 Ok(ref m)
129 Ok(ref m)
128 if !(m.file_type().is_file()
130 if !(m.file_type().is_file()
129 || m.file_type().is_symlink()) =>
131 || m.file_type().is_symlink()) =>
130 {
132 {
131 Ok((filename, dispatch_missing(entry.state)))
133 Ok((filename, dispatch_missing(entry.state)))
132 }
134 }
133 Ok(m) => Ok((
135 Ok(m) => Ok((
134 filename,
136 filename,
135 dispatch_found(
137 dispatch_found(
136 filename,
138 filename,
137 *entry,
139 *entry,
138 HgMetadata::from_metadata(m),
140 HgMetadata::from_metadata(m),
139 &dmap.copy_map,
141 &dmap.copy_map,
140 check_exec,
142 check_exec,
141 list_clean,
143 list_clean,
142 last_normal_time,
144 last_normal_time,
143 ),
145 ),
144 )),
146 )),
145 Err(ref e)
147 Err(ref e)
146 if e.kind() == std::io::ErrorKind::NotFound
148 if e.kind() == std::io::ErrorKind::NotFound
147 || e.raw_os_error() == Some(20) =>
149 || e.raw_os_error() == Some(20) =>
148 {
150 {
149 // Rust does not yet have an `ErrorKind` for
151 // Rust does not yet have an `ErrorKind` for
150 // `NotADirectory` (errno 20)
152 // `NotADirectory` (errno 20)
151 // It happens if the dirstate contains `foo/bar` and
153 // It happens if the dirstate contains `foo/bar` and
152 // foo is not a directory
154 // foo is not a directory
153 Ok((filename, dispatch_missing(entry.state)))
155 Ok((filename, dispatch_missing(entry.state)))
154 }
156 }
155 Err(e) => Err(e),
157 Err(e) => Err(e),
156 }
158 }
157 })
159 })
158 }
160 }
159
161
160 pub struct StatusResult<'a> {
162 pub struct StatusResult<'a> {
161 pub modified: Vec<&'a HgPath>,
163 pub modified: Vec<&'a HgPath>,
162 pub added: Vec<&'a HgPath>,
164 pub added: Vec<&'a HgPath>,
163 pub removed: Vec<&'a HgPath>,
165 pub removed: Vec<&'a HgPath>,
164 pub deleted: Vec<&'a HgPath>,
166 pub deleted: Vec<&'a HgPath>,
165 pub clean: Vec<&'a HgPath>,
167 pub clean: Vec<&'a HgPath>,
166 // TODO ignored
168 // TODO ignored
167 // TODO unknown
169 // TODO unknown
168 }
170 }
169
171
170 fn build_response(
172 fn build_response(
171 results: Vec<(&HgPath, Dispatch)>,
173 results: Vec<(&HgPath, Dispatch)>,
172 ) -> (Vec<&HgPath>, StatusResult) {
174 ) -> (Vec<&HgPath>, StatusResult) {
173 let mut lookup = vec![];
175 let mut lookup = vec![];
174 let mut modified = vec![];
176 let mut modified = vec![];
175 let mut added = vec![];
177 let mut added = vec![];
176 let mut removed = vec![];
178 let mut removed = vec![];
177 let mut deleted = vec![];
179 let mut deleted = vec![];
178 let mut clean = vec![];
180 let mut clean = vec![];
179
181
180 for (filename, dispatch) in results.into_iter() {
182 for (filename, dispatch) in results.into_iter() {
181 match dispatch {
183 match dispatch {
182 Dispatch::Unknown => {}
184 Dispatch::Unknown => {}
183 Dispatch::Unsure => lookup.push(filename),
185 Dispatch::Unsure => lookup.push(filename),
184 Dispatch::Modified => modified.push(filename),
186 Dispatch::Modified => modified.push(filename),
185 Dispatch::Added => added.push(filename),
187 Dispatch::Added => added.push(filename),
186 Dispatch::Removed => removed.push(filename),
188 Dispatch::Removed => removed.push(filename),
187 Dispatch::Deleted => deleted.push(filename),
189 Dispatch::Deleted => deleted.push(filename),
188 Dispatch::Clean => clean.push(filename),
190 Dispatch::Clean => clean.push(filename),
189 }
191 }
190 }
192 }
191
193
192 (
194 (
193 lookup,
195 lookup,
194 StatusResult {
196 StatusResult {
195 modified,
197 modified,
196 added,
198 added,
197 removed,
199 removed,
198 deleted,
200 deleted,
199 clean,
201 clean,
200 },
202 },
201 )
203 )
202 }
204 }
203
205
204 pub fn status(
206 pub fn status(
205 dmap: &DirstateMap,
207 dmap: &DirstateMap,
206 root_dir: impl AsRef<Path> + Sync + Send + Copy,
208 root_dir: impl AsRef<Path> + Sync + Send + Copy,
207 list_clean: bool,
209 list_clean: bool,
208 last_normal_time: i64,
210 last_normal_time: i64,
209 check_exec: bool,
211 check_exec: bool,
210 ) -> std::io::Result<(Vec<&HgPath>, StatusResult)> {
212 ) -> std::io::Result<(Vec<&HgPath>, StatusResult)> {
211 let results: std::io::Result<_> = stat_dmap_entries(
213 let results: std::io::Result<_> = stat_dmap_entries(
212 &dmap,
214 &dmap,
213 root_dir,
215 root_dir,
214 check_exec,
216 check_exec,
215 list_clean,
217 list_clean,
216 last_normal_time,
218 last_normal_time,
217 )
219 )
218 .collect();
220 .collect();
219
221
220 Ok(build_response(results?))
222 Ok(build_response(results?))
221 }
223 }
General Comments 0
You need to be logged in to leave comments. Login now