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