##// END OF EJS Templates
rust: introduce SIZE_FROM_OTHER_PARENT constant...
Raphaël Gomès -
r44003:8210c3f4 default
parent child Browse files
Show More
@@ -1,77 +1,82 b''
1 1 // dirstate module
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 use crate::{utils::hg_path::HgPathBuf, DirstateParseError};
9 9 use std::collections::hash_map;
10 10 use std::collections::HashMap;
11 11 use std::convert::TryFrom;
12 12
13 13 pub mod dirs_multiset;
14 14 pub mod dirstate_map;
15 15 pub mod parsers;
16 16 pub mod status;
17 17
18 18 #[derive(Debug, PartialEq, Clone)]
19 19 pub struct DirstateParents {
20 20 pub p1: [u8; 20],
21 21 pub p2: [u8; 20],
22 22 }
23 23
24 24 /// The C implementation uses all signed types. This will be an issue
25 25 /// either when 4GB+ source files are commonplace or in 2038, whichever
26 26 /// comes first.
27 27 #[derive(Debug, PartialEq, Copy, Clone)]
28 28 pub struct DirstateEntry {
29 29 pub state: EntryState,
30 30 pub mode: i32,
31 31 pub mtime: i32,
32 32 pub size: i32,
33 33 }
34 34
35 /// A `DirstateEntry` with a size of `-2` means that it was merged from the
36 /// other parent. This allows revert to pick the right status back during a
37 /// merge.
38 pub const SIZE_FROM_OTHER_PARENT: i32 = -2;
39
35 40 pub type StateMap = HashMap<HgPathBuf, DirstateEntry>;
36 41 pub type StateMapIter<'a> = hash_map::Iter<'a, HgPathBuf, DirstateEntry>;
37 42 pub type CopyMap = HashMap<HgPathBuf, HgPathBuf>;
38 43 pub type CopyMapIter<'a> = hash_map::Iter<'a, HgPathBuf, HgPathBuf>;
39 44
40 45 #[derive(Copy, Clone, Debug, Eq, PartialEq)]
41 46 pub enum EntryState {
42 47 Normal,
43 48 Added,
44 49 Removed,
45 50 Merged,
46 51 Unknown,
47 52 }
48 53
49 54 impl TryFrom<u8> for EntryState {
50 55 type Error = DirstateParseError;
51 56
52 57 fn try_from(value: u8) -> Result<Self, Self::Error> {
53 58 match value {
54 59 b'n' => Ok(EntryState::Normal),
55 60 b'a' => Ok(EntryState::Added),
56 61 b'r' => Ok(EntryState::Removed),
57 62 b'm' => Ok(EntryState::Merged),
58 63 b'?' => Ok(EntryState::Unknown),
59 64 _ => Err(DirstateParseError::CorruptedEntry(format!(
60 65 "Incorrect entry state {}",
61 66 value
62 67 ))),
63 68 }
64 69 }
65 70 }
66 71
67 72 impl Into<u8> for EntryState {
68 73 fn into(self) -> u8 {
69 74 match self {
70 75 EntryState::Normal => b'n',
71 76 EntryState::Added => b'a',
72 77 EntryState::Removed => b'r',
73 78 EntryState::Merged => b'm',
74 79 EntryState::Unknown => b'?',
75 80 }
76 81 }
77 82 }
@@ -1,424 +1,426 b''
1 1 // dirstate_map.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 use crate::utils::hg_path::{HgPath, HgPathBuf};
9 8 use crate::{
10 dirstate::{parsers::PARENT_SIZE, EntryState},
9 dirstate::{parsers::PARENT_SIZE, EntryState, SIZE_FROM_OTHER_PARENT},
11 10 pack_dirstate, parse_dirstate,
12 utils::files::normalize_case,
11 utils::{
12 files::normalize_case,
13 hg_path::{HgPath, HgPathBuf},
14 },
13 15 CopyMap, DirsMultiset, DirstateEntry, DirstateError, DirstateMapError,
14 16 DirstateParents, DirstateParseError, StateMap,
15 17 };
16 18 use core::borrow::Borrow;
17 19 use std::collections::{HashMap, HashSet};
18 20 use std::convert::TryInto;
19 21 use std::iter::FromIterator;
20 22 use std::ops::Deref;
21 23 use std::time::Duration;
22 24
23 25 pub type FileFoldMap = HashMap<HgPathBuf, HgPathBuf>;
24 26
25 27 const NULL_ID: [u8; 20] = [0; 20];
26 28 const MTIME_UNSET: i32 = -1;
27 const SIZE_DIRTY: i32 = -2;
28 29
29 30 #[derive(Default)]
30 31 pub struct DirstateMap {
31 32 state_map: StateMap,
32 33 pub copy_map: CopyMap,
33 34 file_fold_map: Option<FileFoldMap>,
34 35 pub dirs: Option<DirsMultiset>,
35 36 pub all_dirs: Option<DirsMultiset>,
36 37 non_normal_set: HashSet<HgPathBuf>,
37 38 other_parent_set: HashSet<HgPathBuf>,
38 39 parents: Option<DirstateParents>,
39 40 dirty_parents: bool,
40 41 }
41 42
42 43 /// Should only really be used in python interface code, for clarity
43 44 impl Deref for DirstateMap {
44 45 type Target = StateMap;
45 46
46 47 fn deref(&self) -> &Self::Target {
47 48 &self.state_map
48 49 }
49 50 }
50 51
51 52 impl FromIterator<(HgPathBuf, DirstateEntry)> for DirstateMap {
52 53 fn from_iter<I: IntoIterator<Item = (HgPathBuf, DirstateEntry)>>(
53 54 iter: I,
54 55 ) -> Self {
55 56 Self {
56 57 state_map: iter.into_iter().collect(),
57 58 ..Self::default()
58 59 }
59 60 }
60 61 }
61 62
62 63 impl DirstateMap {
63 64 pub fn new() -> Self {
64 65 Self::default()
65 66 }
66 67
67 68 pub fn clear(&mut self) {
68 69 self.state_map.clear();
69 70 self.copy_map.clear();
70 71 self.file_fold_map = None;
71 72 self.non_normal_set.clear();
72 73 self.other_parent_set.clear();
73 74 self.set_parents(&DirstateParents {
74 75 p1: NULL_ID,
75 76 p2: NULL_ID,
76 77 })
77 78 }
78 79
79 80 /// Add a tracked file to the dirstate
80 81 pub fn add_file(
81 82 &mut self,
82 83 filename: &HgPath,
83 84 old_state: EntryState,
84 85 entry: DirstateEntry,
85 86 ) {
86 87 if old_state == EntryState::Unknown || old_state == EntryState::Removed
87 88 {
88 89 if let Some(ref mut dirs) = self.dirs {
89 90 dirs.add_path(filename)
90 91 }
91 92 }
92 93 if old_state == EntryState::Unknown {
93 94 if let Some(ref mut all_dirs) = self.all_dirs {
94 95 all_dirs.add_path(filename)
95 96 }
96 97 }
97 98 self.state_map.insert(filename.to_owned(), entry.to_owned());
98 99
99 100 if entry.state != EntryState::Normal || entry.mtime == MTIME_UNSET {
100 101 self.non_normal_set.insert(filename.to_owned());
101 102 }
102 103
103 if entry.size == SIZE_DIRTY {
104 if entry.size == SIZE_FROM_OTHER_PARENT {
104 105 self.other_parent_set.insert(filename.to_owned());
105 106 }
106 107 }
107 108
108 109 /// Mark a file as removed in the dirstate.
109 110 ///
110 111 /// The `size` parameter is used to store sentinel values that indicate
111 112 /// the file's previous state. In the future, we should refactor this
112 113 /// to be more explicit about what that state is.
113 114 pub fn remove_file(
114 115 &mut self,
115 116 filename: &HgPath,
116 117 old_state: EntryState,
117 118 size: i32,
118 119 ) -> Result<(), DirstateMapError> {
119 120 if old_state != EntryState::Unknown && old_state != EntryState::Removed
120 121 {
121 122 if let Some(ref mut dirs) = self.dirs {
122 123 dirs.delete_path(filename)?;
123 124 }
124 125 }
125 126 if old_state == EntryState::Unknown {
126 127 if let Some(ref mut all_dirs) = self.all_dirs {
127 128 all_dirs.add_path(filename);
128 129 }
129 130 }
130 131
131 132 if let Some(ref mut file_fold_map) = self.file_fold_map {
132 133 file_fold_map.remove(&normalize_case(filename));
133 134 }
134 135 self.state_map.insert(
135 136 filename.to_owned(),
136 137 DirstateEntry {
137 138 state: EntryState::Removed,
138 139 mode: 0,
139 140 size,
140 141 mtime: 0,
141 142 },
142 143 );
143 144 self.non_normal_set.insert(filename.to_owned());
144 145 Ok(())
145 146 }
146 147
147 148 /// Remove a file from the dirstate.
148 149 /// Returns `true` if the file was previously recorded.
149 150 pub fn drop_file(
150 151 &mut self,
151 152 filename: &HgPath,
152 153 old_state: EntryState,
153 154 ) -> Result<bool, DirstateMapError> {
154 155 let exists = self.state_map.remove(filename).is_some();
155 156
156 157 if exists {
157 158 if old_state != EntryState::Removed {
158 159 if let Some(ref mut dirs) = self.dirs {
159 160 dirs.delete_path(filename)?;
160 161 }
161 162 }
162 163 if let Some(ref mut all_dirs) = self.all_dirs {
163 164 all_dirs.delete_path(filename)?;
164 165 }
165 166 }
166 167 if let Some(ref mut file_fold_map) = self.file_fold_map {
167 168 file_fold_map.remove(&normalize_case(filename));
168 169 }
169 170 self.non_normal_set.remove(filename);
170 171
171 172 Ok(exists)
172 173 }
173 174
174 175 pub fn clear_ambiguous_times(
175 176 &mut self,
176 177 filenames: Vec<HgPathBuf>,
177 178 now: i32,
178 179 ) {
179 180 for filename in filenames {
180 181 let mut changed = false;
181 182 self.state_map
182 183 .entry(filename.to_owned())
183 184 .and_modify(|entry| {
184 185 if entry.state == EntryState::Normal && entry.mtime == now
185 186 {
186 187 changed = true;
187 188 *entry = DirstateEntry {
188 189 mtime: MTIME_UNSET,
189 190 ..*entry
190 191 };
191 192 }
192 193 });
193 194 if changed {
194 195 self.non_normal_set.insert(filename.to_owned());
195 196 }
196 197 }
197 198 }
198 199
199 200 pub fn non_normal_other_parent_entries(
200 201 &self,
201 202 ) -> (HashSet<HgPathBuf>, HashSet<HgPathBuf>) {
202 203 let mut non_normal = HashSet::new();
203 204 let mut other_parent = HashSet::new();
204 205
205 206 for (
206 207 filename,
207 208 DirstateEntry {
208 209 state, size, mtime, ..
209 210 },
210 211 ) in self.state_map.iter()
211 212 {
212 213 if *state != EntryState::Normal || *mtime == MTIME_UNSET {
213 214 non_normal.insert(filename.to_owned());
214 215 }
215 if *state == EntryState::Normal && *size == SIZE_DIRTY {
216 if *state == EntryState::Normal && *size == SIZE_FROM_OTHER_PARENT
217 {
216 218 other_parent.insert(filename.to_owned());
217 219 }
218 220 }
219 221
220 222 (non_normal, other_parent)
221 223 }
222 224
223 225 /// Both of these setters and their uses appear to be the simplest way to
224 226 /// emulate a Python lazy property, but it is ugly and unidiomatic.
225 227 /// TODO One day, rewriting this struct using the typestate might be a
226 228 /// good idea.
227 229 pub fn set_all_dirs(&mut self) {
228 230 if self.all_dirs.is_none() {
229 231 self.all_dirs =
230 232 Some(DirsMultiset::from_dirstate(&self.state_map, None));
231 233 }
232 234 }
233 235
234 236 pub fn set_dirs(&mut self) {
235 237 if self.dirs.is_none() {
236 238 self.dirs = Some(DirsMultiset::from_dirstate(
237 239 &self.state_map,
238 240 Some(EntryState::Removed),
239 241 ));
240 242 }
241 243 }
242 244
243 245 pub fn has_tracked_dir(&mut self, directory: &HgPath) -> bool {
244 246 self.set_dirs();
245 247 self.dirs.as_ref().unwrap().contains(directory)
246 248 }
247 249
248 250 pub fn has_dir(&mut self, directory: &HgPath) -> bool {
249 251 self.set_all_dirs();
250 252 self.all_dirs.as_ref().unwrap().contains(directory)
251 253 }
252 254
253 255 pub fn parents(
254 256 &mut self,
255 257 file_contents: &[u8],
256 258 ) -> Result<&DirstateParents, DirstateError> {
257 259 if let Some(ref parents) = self.parents {
258 260 return Ok(parents);
259 261 }
260 262 let parents;
261 263 if file_contents.len() == PARENT_SIZE * 2 {
262 264 parents = DirstateParents {
263 265 p1: file_contents[..PARENT_SIZE].try_into().unwrap(),
264 266 p2: file_contents[PARENT_SIZE..PARENT_SIZE * 2]
265 267 .try_into()
266 268 .unwrap(),
267 269 };
268 270 } else if file_contents.is_empty() {
269 271 parents = DirstateParents {
270 272 p1: NULL_ID,
271 273 p2: NULL_ID,
272 274 };
273 275 } else {
274 276 return Err(DirstateError::Parse(DirstateParseError::Damaged));
275 277 }
276 278
277 279 self.parents = Some(parents);
278 280 Ok(self.parents.as_ref().unwrap())
279 281 }
280 282
281 283 pub fn set_parents(&mut self, parents: &DirstateParents) {
282 284 self.parents = Some(parents.clone());
283 285 self.dirty_parents = true;
284 286 }
285 287
286 288 pub fn read(
287 289 &mut self,
288 290 file_contents: &[u8],
289 291 ) -> Result<Option<DirstateParents>, DirstateError> {
290 292 if file_contents.is_empty() {
291 293 return Ok(None);
292 294 }
293 295
294 296 let parents = parse_dirstate(
295 297 &mut self.state_map,
296 298 &mut self.copy_map,
297 299 file_contents,
298 300 )?;
299 301
300 302 if !self.dirty_parents {
301 303 self.set_parents(&parents);
302 304 }
303 305
304 306 Ok(Some(parents))
305 307 }
306 308
307 309 pub fn pack(
308 310 &mut self,
309 311 parents: DirstateParents,
310 312 now: Duration,
311 313 ) -> Result<Vec<u8>, DirstateError> {
312 314 let packed =
313 315 pack_dirstate(&mut self.state_map, &self.copy_map, parents, now)?;
314 316
315 317 self.dirty_parents = false;
316 318
317 319 let result = self.non_normal_other_parent_entries();
318 320 self.non_normal_set = result.0;
319 321 self.other_parent_set = result.1;
320 322 Ok(packed)
321 323 }
322 324
323 325 pub fn build_file_fold_map(&mut self) -> &FileFoldMap {
324 326 if let Some(ref file_fold_map) = self.file_fold_map {
325 327 return file_fold_map;
326 328 }
327 329 let mut new_file_fold_map = FileFoldMap::new();
328 330 for (filename, DirstateEntry { state, .. }) in self.state_map.borrow()
329 331 {
330 332 if *state == EntryState::Removed {
331 333 new_file_fold_map
332 334 .insert(normalize_case(filename), filename.to_owned());
333 335 }
334 336 }
335 337 self.file_fold_map = Some(new_file_fold_map);
336 338 self.file_fold_map.as_ref().unwrap()
337 339 }
338 340 }
339 341
340 342 #[cfg(test)]
341 343 mod tests {
342 344 use super::*;
343 345
344 346 #[test]
345 347 fn test_dirs_multiset() {
346 348 let mut map = DirstateMap::new();
347 349 assert!(map.dirs.is_none());
348 350 assert!(map.all_dirs.is_none());
349 351
350 352 assert_eq!(false, map.has_dir(HgPath::new(b"nope")));
351 353 assert!(map.all_dirs.is_some());
352 354 assert!(map.dirs.is_none());
353 355
354 356 assert_eq!(false, map.has_tracked_dir(HgPath::new(b"nope")));
355 357 assert!(map.dirs.is_some());
356 358 }
357 359
358 360 #[test]
359 361 fn test_add_file() {
360 362 let mut map = DirstateMap::new();
361 363
362 364 assert_eq!(0, map.len());
363 365
364 366 map.add_file(
365 367 HgPath::new(b"meh"),
366 368 EntryState::Normal,
367 369 DirstateEntry {
368 370 state: EntryState::Normal,
369 371 mode: 1337,
370 372 mtime: 1337,
371 373 size: 1337,
372 374 },
373 375 );
374 376
375 377 assert_eq!(1, map.len());
376 378 assert_eq!(0, map.non_normal_set.len());
377 379 assert_eq!(0, map.other_parent_set.len());
378 380 }
379 381
380 382 #[test]
381 383 fn test_non_normal_other_parent_entries() {
382 384 let map: DirstateMap = [
383 385 (b"f1", (EntryState::Removed, 1337, 1337, 1337)),
384 386 (b"f2", (EntryState::Normal, 1337, 1337, -1)),
385 387 (b"f3", (EntryState::Normal, 1337, 1337, 1337)),
386 388 (b"f4", (EntryState::Normal, 1337, -2, 1337)),
387 389 (b"f5", (EntryState::Added, 1337, 1337, 1337)),
388 390 (b"f6", (EntryState::Added, 1337, 1337, -1)),
389 391 (b"f7", (EntryState::Merged, 1337, 1337, -1)),
390 392 (b"f8", (EntryState::Merged, 1337, 1337, 1337)),
391 393 (b"f9", (EntryState::Merged, 1337, -2, 1337)),
392 394 (b"fa", (EntryState::Added, 1337, -2, 1337)),
393 395 (b"fb", (EntryState::Removed, 1337, -2, 1337)),
394 396 ]
395 397 .iter()
396 398 .map(|(fname, (state, mode, size, mtime))| {
397 399 (
398 400 HgPathBuf::from_bytes(fname.as_ref()),
399 401 DirstateEntry {
400 402 state: *state,
401 403 mode: *mode,
402 404 size: *size,
403 405 mtime: *mtime,
404 406 },
405 407 )
406 408 })
407 409 .collect();
408 410
409 411 let non_normal = [
410 412 b"f1", b"f2", b"f5", b"f6", b"f7", b"f8", b"f9", b"fa", b"fb",
411 413 ]
412 414 .iter()
413 415 .map(|x| HgPathBuf::from_bytes(x.as_ref()))
414 416 .collect();
415 417
416 418 let mut other_parent = HashSet::new();
417 419 other_parent.insert(HgPathBuf::from_bytes(b"f4"));
418 420
419 421 assert_eq!(
420 422 (non_normal, other_parent),
421 423 map.non_normal_other_parent_entries()
422 424 );
423 425 }
424 426 }
@@ -1,223 +1,228 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 use crate::utils::files::HgMetadata;
13 use crate::utils::hg_path::{hg_path_to_path_buf, HgPath};
14 use crate::{CopyMap, DirstateEntry, DirstateMap, EntryState};
12 use crate::{
13 dirstate::SIZE_FROM_OTHER_PARENT,
14 utils::{
15 files::HgMetadata,
16 hg_path::{hg_path_to_path_buf, HgPath},
17 },
18 CopyMap, DirstateEntry, DirstateMap, EntryState,
19 };
15 20 use rayon::prelude::*;
16 21 use std::path::Path;
17 22
18 23 /// Marker enum used to dispatch new status entries into the right collections.
19 24 /// Is similar to `crate::EntryState`, but represents the transient state of
20 25 /// entries during the lifetime of a command.
21 26 enum Dispatch {
22 27 Unsure,
23 28 Modified,
24 29 Added,
25 30 Removed,
26 31 Deleted,
27 32 Clean,
28 33 Unknown,
29 34 }
30 35
31 36 /// Dates and times that are outside the 31-bit signed range are compared
32 37 /// modulo 2^31. This should prevent hg from behaving badly with very large
33 38 /// files or corrupt dates while still having a high probability of detecting
34 39 /// changes. (issue2608)
35 40 /// TODO I haven't found a way of having `b` be `Into<i32>`, since `From<u64>`
36 41 /// is not defined for `i32`, and there is no `As` trait. This forces the
37 42 /// caller to cast `b` as `i32`.
38 43 fn mod_compare(a: i32, b: i32) -> bool {
39 44 a & i32::max_value() != b & i32::max_value()
40 45 }
41 46
42 47 /// The file corresponding to the dirstate entry was found on the filesystem.
43 48 fn dispatch_found(
44 49 filename: impl AsRef<HgPath>,
45 50 entry: DirstateEntry,
46 51 metadata: HgMetadata,
47 52 copy_map: &CopyMap,
48 53 check_exec: bool,
49 54 list_clean: bool,
50 55 last_normal_time: i64,
51 56 ) -> Dispatch {
52 57 let DirstateEntry {
53 58 state,
54 59 mode,
55 60 mtime,
56 61 size,
57 62 } = entry;
58 63
59 64 let HgMetadata {
60 65 st_mode,
61 66 st_size,
62 67 st_mtime,
63 68 ..
64 69 } = metadata;
65 70
66 71 match state {
67 72 EntryState::Normal => {
68 73 let size_changed = mod_compare(size, st_size as i32);
69 74 let mode_changed =
70 75 (mode ^ st_mode as i32) & 0o100 != 0o000 && check_exec;
71 76 let metadata_changed = size >= 0 && (size_changed || mode_changed);
72 let other_parent = size == -2;
77 let other_parent = size == SIZE_FROM_OTHER_PARENT;
73 78 if metadata_changed
74 79 || other_parent
75 80 || copy_map.contains_key(filename.as_ref())
76 81 {
77 82 Dispatch::Modified
78 83 } else if mod_compare(mtime, st_mtime as i32) {
79 84 Dispatch::Unsure
80 85 } else if st_mtime == last_normal_time {
81 86 // the file may have just been marked as normal and
82 87 // it may have changed in the same second without
83 88 // changing its size. This can happen if we quickly
84 89 // do multiple commits. Force lookup, so we don't
85 90 // miss such a racy file change.
86 91 Dispatch::Unsure
87 92 } else if list_clean {
88 93 Dispatch::Clean
89 94 } else {
90 95 Dispatch::Unknown
91 96 }
92 97 }
93 98 EntryState::Merged => Dispatch::Modified,
94 99 EntryState::Added => Dispatch::Added,
95 100 EntryState::Removed => Dispatch::Removed,
96 101 EntryState::Unknown => Dispatch::Unknown,
97 102 }
98 103 }
99 104
100 105 /// The file corresponding to this Dirstate entry is missing.
101 106 fn dispatch_missing(state: EntryState) -> Dispatch {
102 107 match state {
103 108 // File was removed from the filesystem during commands
104 109 EntryState::Normal | EntryState::Merged | EntryState::Added => {
105 110 Dispatch::Deleted
106 111 }
107 112 // File was removed, everything is normal
108 113 EntryState::Removed => Dispatch::Removed,
109 114 // File is unknown to Mercurial, everything is normal
110 115 EntryState::Unknown => Dispatch::Unknown,
111 116 }
112 117 }
113 118
114 119 /// Stat all entries in the `DirstateMap` and mark them for dispatch into
115 120 /// the relevant collections.
116 121 fn stat_dmap_entries(
117 122 dmap: &DirstateMap,
118 123 root_dir: impl AsRef<Path> + Sync + Send,
119 124 check_exec: bool,
120 125 list_clean: bool,
121 126 last_normal_time: i64,
122 127 ) -> impl ParallelIterator<Item = std::io::Result<(&HgPath, Dispatch)>> {
123 128 dmap.par_iter().map(move |(filename, entry)| {
124 129 let filename: &HgPath = filename;
125 130 let filename_as_path = hg_path_to_path_buf(filename)?;
126 131 let meta = root_dir.as_ref().join(filename_as_path).symlink_metadata();
127 132
128 133 match meta {
129 134 Ok(ref m)
130 135 if !(m.file_type().is_file()
131 136 || m.file_type().is_symlink()) =>
132 137 {
133 138 Ok((filename, dispatch_missing(entry.state)))
134 139 }
135 140 Ok(m) => Ok((
136 141 filename,
137 142 dispatch_found(
138 143 filename,
139 144 *entry,
140 145 HgMetadata::from_metadata(m),
141 146 &dmap.copy_map,
142 147 check_exec,
143 148 list_clean,
144 149 last_normal_time,
145 150 ),
146 151 )),
147 152 Err(ref e)
148 153 if e.kind() == std::io::ErrorKind::NotFound
149 154 || e.raw_os_error() == Some(20) =>
150 155 {
151 156 // Rust does not yet have an `ErrorKind` for
152 157 // `NotADirectory` (errno 20)
153 158 // It happens if the dirstate contains `foo/bar` and
154 159 // foo is not a directory
155 160 Ok((filename, dispatch_missing(entry.state)))
156 161 }
157 162 Err(e) => Err(e),
158 163 }
159 164 })
160 165 }
161 166
162 167 pub struct StatusResult<'a> {
163 168 pub modified: Vec<&'a HgPath>,
164 169 pub added: Vec<&'a HgPath>,
165 170 pub removed: Vec<&'a HgPath>,
166 171 pub deleted: Vec<&'a HgPath>,
167 172 pub clean: Vec<&'a HgPath>,
168 173 // TODO ignored
169 174 // TODO unknown
170 175 }
171 176
172 177 fn build_response(
173 178 results: Vec<(&HgPath, Dispatch)>,
174 179 ) -> (Vec<&HgPath>, StatusResult) {
175 180 let mut lookup = vec![];
176 181 let mut modified = vec![];
177 182 let mut added = vec![];
178 183 let mut removed = vec![];
179 184 let mut deleted = vec![];
180 185 let mut clean = vec![];
181 186
182 187 for (filename, dispatch) in results.into_iter() {
183 188 match dispatch {
184 189 Dispatch::Unknown => {}
185 190 Dispatch::Unsure => lookup.push(filename),
186 191 Dispatch::Modified => modified.push(filename),
187 192 Dispatch::Added => added.push(filename),
188 193 Dispatch::Removed => removed.push(filename),
189 194 Dispatch::Deleted => deleted.push(filename),
190 195 Dispatch::Clean => clean.push(filename),
191 196 }
192 197 }
193 198
194 199 (
195 200 lookup,
196 201 StatusResult {
197 202 modified,
198 203 added,
199 204 removed,
200 205 deleted,
201 206 clean,
202 207 },
203 208 )
204 209 }
205 210
206 211 pub fn status(
207 212 dmap: &DirstateMap,
208 213 root_dir: impl AsRef<Path> + Sync + Send + Copy,
209 214 list_clean: bool,
210 215 last_normal_time: i64,
211 216 check_exec: bool,
212 217 ) -> std::io::Result<(Vec<&HgPath>, StatusResult)> {
213 218 let results: std::io::Result<_> = stat_dmap_entries(
214 219 &dmap,
215 220 root_dir,
216 221 check_exec,
217 222 list_clean,
218 223 last_normal_time,
219 224 )
220 225 .collect();
221 226
222 227 Ok(build_response(results?))
223 228 }
General Comments 0
You need to be logged in to leave comments. Login now