##// END OF EJS Templates
rust: Remove EntryState::Unknown...
Simon Sapin -
r48838:1b2ee68e default
parent child Browse files
Show More
@@ -1,481 +1,470 b''
1 // dirstate_map.rs
1 // dirstate_map.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 use crate::dirstate::parsers::Timestamp;
8 use crate::dirstate::parsers::Timestamp;
9 use crate::{
9 use crate::{
10 dirstate::EntryState,
10 dirstate::EntryState,
11 dirstate::MTIME_UNSET,
11 dirstate::MTIME_UNSET,
12 dirstate::SIZE_FROM_OTHER_PARENT,
12 dirstate::SIZE_FROM_OTHER_PARENT,
13 dirstate::SIZE_NON_NORMAL,
13 dirstate::SIZE_NON_NORMAL,
14 dirstate::V1_RANGEMASK,
14 dirstate::V1_RANGEMASK,
15 pack_dirstate, parse_dirstate,
15 pack_dirstate, parse_dirstate,
16 utils::hg_path::{HgPath, HgPathBuf},
16 utils::hg_path::{HgPath, HgPathBuf},
17 CopyMap, DirsMultiset, DirstateEntry, DirstateError, DirstateParents,
17 CopyMap, DirsMultiset, DirstateEntry, DirstateError, DirstateParents,
18 StateMap,
18 StateMap,
19 };
19 };
20 use micro_timer::timed;
20 use micro_timer::timed;
21 use std::collections::HashSet;
21 use std::collections::HashSet;
22 use std::iter::FromIterator;
22 use std::iter::FromIterator;
23 use std::ops::Deref;
23 use std::ops::Deref;
24
24
25 #[derive(Default)]
25 #[derive(Default)]
26 pub struct DirstateMap {
26 pub struct DirstateMap {
27 state_map: StateMap,
27 state_map: StateMap,
28 pub copy_map: CopyMap,
28 pub copy_map: CopyMap,
29 pub dirs: Option<DirsMultiset>,
29 pub dirs: Option<DirsMultiset>,
30 pub all_dirs: Option<DirsMultiset>,
30 pub all_dirs: Option<DirsMultiset>,
31 non_normal_set: Option<HashSet<HgPathBuf>>,
31 non_normal_set: Option<HashSet<HgPathBuf>>,
32 other_parent_set: Option<HashSet<HgPathBuf>>,
32 other_parent_set: Option<HashSet<HgPathBuf>>,
33 }
33 }
34
34
35 /// Should only really be used in python interface code, for clarity
35 /// Should only really be used in python interface code, for clarity
36 impl Deref for DirstateMap {
36 impl Deref for DirstateMap {
37 type Target = StateMap;
37 type Target = StateMap;
38
38
39 fn deref(&self) -> &Self::Target {
39 fn deref(&self) -> &Self::Target {
40 &self.state_map
40 &self.state_map
41 }
41 }
42 }
42 }
43
43
44 impl FromIterator<(HgPathBuf, DirstateEntry)> for DirstateMap {
44 impl FromIterator<(HgPathBuf, DirstateEntry)> for DirstateMap {
45 fn from_iter<I: IntoIterator<Item = (HgPathBuf, DirstateEntry)>>(
45 fn from_iter<I: IntoIterator<Item = (HgPathBuf, DirstateEntry)>>(
46 iter: I,
46 iter: I,
47 ) -> Self {
47 ) -> Self {
48 Self {
48 Self {
49 state_map: iter.into_iter().collect(),
49 state_map: iter.into_iter().collect(),
50 ..Self::default()
50 ..Self::default()
51 }
51 }
52 }
52 }
53 }
53 }
54
54
55 impl DirstateMap {
55 impl DirstateMap {
56 pub fn new() -> Self {
56 pub fn new() -> Self {
57 Self::default()
57 Self::default()
58 }
58 }
59
59
60 pub fn clear(&mut self) {
60 pub fn clear(&mut self) {
61 self.state_map = StateMap::default();
61 self.state_map = StateMap::default();
62 self.copy_map.clear();
62 self.copy_map.clear();
63 self.non_normal_set = None;
63 self.non_normal_set = None;
64 self.other_parent_set = None;
64 self.other_parent_set = None;
65 }
65 }
66
66
67 pub fn set_v1_inner(&mut self, filename: &HgPath, entry: DirstateEntry) {
67 pub fn set_v1_inner(&mut self, filename: &HgPath, entry: DirstateEntry) {
68 self.state_map.insert(filename.to_owned(), entry);
68 self.state_map.insert(filename.to_owned(), entry);
69 }
69 }
70
70
71 /// Add a tracked file to the dirstate
71 /// Add a tracked file to the dirstate
72 pub fn add_file(
72 pub fn add_file(
73 &mut self,
73 &mut self,
74 filename: &HgPath,
74 filename: &HgPath,
75 entry: DirstateEntry,
75 entry: DirstateEntry,
76 // XXX once the dust settle this should probably become an enum
76 // XXX once the dust settle this should probably become an enum
77 added: bool,
77 added: bool,
78 merged: bool,
78 merged: bool,
79 from_p2: bool,
79 from_p2: bool,
80 possibly_dirty: bool,
80 possibly_dirty: bool,
81 ) -> Result<(), DirstateError> {
81 ) -> Result<(), DirstateError> {
82 let state;
82 let state;
83 let size;
83 let size;
84 let mtime;
84 let mtime;
85 if added {
85 if added {
86 assert!(!possibly_dirty);
86 assert!(!possibly_dirty);
87 assert!(!from_p2);
87 assert!(!from_p2);
88 state = EntryState::Added;
88 state = EntryState::Added;
89 size = SIZE_NON_NORMAL;
89 size = SIZE_NON_NORMAL;
90 mtime = MTIME_UNSET;
90 mtime = MTIME_UNSET;
91 } else if merged {
91 } else if merged {
92 assert!(!possibly_dirty);
92 assert!(!possibly_dirty);
93 assert!(!from_p2);
93 assert!(!from_p2);
94 state = EntryState::Merged;
94 state = EntryState::Merged;
95 size = SIZE_FROM_OTHER_PARENT;
95 size = SIZE_FROM_OTHER_PARENT;
96 mtime = MTIME_UNSET;
96 mtime = MTIME_UNSET;
97 } else if from_p2 {
97 } else if from_p2 {
98 assert!(!possibly_dirty);
98 assert!(!possibly_dirty);
99 state = EntryState::Normal;
99 state = EntryState::Normal;
100 size = SIZE_FROM_OTHER_PARENT;
100 size = SIZE_FROM_OTHER_PARENT;
101 mtime = MTIME_UNSET;
101 mtime = MTIME_UNSET;
102 } else if possibly_dirty {
102 } else if possibly_dirty {
103 state = EntryState::Normal;
103 state = EntryState::Normal;
104 size = SIZE_NON_NORMAL;
104 size = SIZE_NON_NORMAL;
105 mtime = MTIME_UNSET;
105 mtime = MTIME_UNSET;
106 } else {
106 } else {
107 state = EntryState::Normal;
107 state = EntryState::Normal;
108 size = entry.size() & V1_RANGEMASK;
108 size = entry.size() & V1_RANGEMASK;
109 mtime = entry.mtime() & V1_RANGEMASK;
109 mtime = entry.mtime() & V1_RANGEMASK;
110 }
110 }
111 let mode = entry.mode();
111 let mode = entry.mode();
112 let entry = DirstateEntry::from_v1_data(state, mode, size, mtime);
112 let entry = DirstateEntry::from_v1_data(state, mode, size, mtime);
113
113
114 let old_state = match self.get(filename) {
114 let old_state = self.get(filename).map(|e| e.state());
115 Some(e) => e.state(),
115 if old_state.is_none() || old_state == Some(EntryState::Removed) {
116 None => EntryState::Unknown,
117 };
118 if old_state == EntryState::Unknown || old_state == EntryState::Removed
119 {
120 if let Some(ref mut dirs) = self.dirs {
116 if let Some(ref mut dirs) = self.dirs {
121 dirs.add_path(filename)?;
117 dirs.add_path(filename)?;
122 }
118 }
123 }
119 }
124 if old_state == EntryState::Unknown {
120 if old_state.is_none() {
125 if let Some(ref mut all_dirs) = self.all_dirs {
121 if let Some(ref mut all_dirs) = self.all_dirs {
126 all_dirs.add_path(filename)?;
122 all_dirs.add_path(filename)?;
127 }
123 }
128 }
124 }
129 self.state_map.insert(filename.to_owned(), entry.to_owned());
125 self.state_map.insert(filename.to_owned(), entry.to_owned());
130
126
131 if entry.is_non_normal() {
127 if entry.is_non_normal() {
132 self.get_non_normal_other_parent_entries()
128 self.get_non_normal_other_parent_entries()
133 .0
129 .0
134 .insert(filename.to_owned());
130 .insert(filename.to_owned());
135 }
131 }
136
132
137 if entry.is_from_other_parent() {
133 if entry.is_from_other_parent() {
138 self.get_non_normal_other_parent_entries()
134 self.get_non_normal_other_parent_entries()
139 .1
135 .1
140 .insert(filename.to_owned());
136 .insert(filename.to_owned());
141 }
137 }
142 Ok(())
138 Ok(())
143 }
139 }
144
140
145 /// Mark a file as removed in the dirstate.
141 /// Mark a file as removed in the dirstate.
146 ///
142 ///
147 /// The `size` parameter is used to store sentinel values that indicate
143 /// The `size` parameter is used to store sentinel values that indicate
148 /// the file's previous state. In the future, we should refactor this
144 /// the file's previous state. In the future, we should refactor this
149 /// to be more explicit about what that state is.
145 /// to be more explicit about what that state is.
150 pub fn remove_file(
146 pub fn remove_file(
151 &mut self,
147 &mut self,
152 filename: &HgPath,
148 filename: &HgPath,
153 in_merge: bool,
149 in_merge: bool,
154 ) -> Result<(), DirstateError> {
150 ) -> Result<(), DirstateError> {
155 let old_entry_opt = self.get(filename);
151 let old_entry_opt = self.get(filename);
156 let old_state = match old_entry_opt {
152 let old_state = old_entry_opt.map(|e| e.state());
157 Some(e) => e.state(),
158 None => EntryState::Unknown,
159 };
160 let mut size = 0;
153 let mut size = 0;
161 if in_merge {
154 if in_merge {
162 // XXX we should not be able to have 'm' state and 'FROM_P2' if not
155 // XXX we should not be able to have 'm' state and 'FROM_P2' if not
163 // during a merge. So I (marmoute) am not sure we need the
156 // during a merge. So I (marmoute) am not sure we need the
164 // conditionnal at all. Adding double checking this with assert
157 // conditionnal at all. Adding double checking this with assert
165 // would be nice.
158 // would be nice.
166 if let Some(old_entry) = old_entry_opt {
159 if let Some(old_entry) = old_entry_opt {
167 // backup the previous state
160 // backup the previous state
168 if old_entry.state() == EntryState::Merged {
161 if old_entry.state() == EntryState::Merged {
169 size = SIZE_NON_NORMAL;
162 size = SIZE_NON_NORMAL;
170 } else if old_entry.state() == EntryState::Normal
163 } else if old_entry.state() == EntryState::Normal
171 && old_entry.size() == SIZE_FROM_OTHER_PARENT
164 && old_entry.size() == SIZE_FROM_OTHER_PARENT
172 {
165 {
173 // other parent
166 // other parent
174 size = SIZE_FROM_OTHER_PARENT;
167 size = SIZE_FROM_OTHER_PARENT;
175 self.get_non_normal_other_parent_entries()
168 self.get_non_normal_other_parent_entries()
176 .1
169 .1
177 .insert(filename.to_owned());
170 .insert(filename.to_owned());
178 }
171 }
179 }
172 }
180 }
173 }
181 if old_state != EntryState::Unknown && old_state != EntryState::Removed
174 if old_state.is_some() && old_state != Some(EntryState::Removed) {
182 {
183 if let Some(ref mut dirs) = self.dirs {
175 if let Some(ref mut dirs) = self.dirs {
184 dirs.delete_path(filename)?;
176 dirs.delete_path(filename)?;
185 }
177 }
186 }
178 }
187 if old_state == EntryState::Unknown {
179 if old_state.is_none() {
188 if let Some(ref mut all_dirs) = self.all_dirs {
180 if let Some(ref mut all_dirs) = self.all_dirs {
189 all_dirs.add_path(filename)?;
181 all_dirs.add_path(filename)?;
190 }
182 }
191 }
183 }
192 if size == 0 {
184 if size == 0 {
193 self.copy_map.remove(filename);
185 self.copy_map.remove(filename);
194 }
186 }
195
187
196 self.state_map
188 self.state_map
197 .insert(filename.to_owned(), DirstateEntry::new_removed(size));
189 .insert(filename.to_owned(), DirstateEntry::new_removed(size));
198 self.get_non_normal_other_parent_entries()
190 self.get_non_normal_other_parent_entries()
199 .0
191 .0
200 .insert(filename.to_owned());
192 .insert(filename.to_owned());
201 Ok(())
193 Ok(())
202 }
194 }
203
195
204 /// Remove a file from the dirstate.
196 /// Remove a file from the dirstate.
205 /// Returns `true` if the file was previously recorded.
197 /// Returns `true` if the file was previously recorded.
206 pub fn drop_file(
198 pub fn drop_file(
207 &mut self,
199 &mut self,
208 filename: &HgPath,
200 filename: &HgPath,
209 ) -> Result<bool, DirstateError> {
201 ) -> Result<bool, DirstateError> {
210 let old_state = match self.get(filename) {
202 let old_state = self.get(filename).map(|e| e.state());
211 Some(e) => e.state(),
212 None => EntryState::Unknown,
213 };
214 let exists = self.state_map.remove(filename).is_some();
203 let exists = self.state_map.remove(filename).is_some();
215
204
216 if exists {
205 if exists {
217 if old_state != EntryState::Removed {
206 if old_state != Some(EntryState::Removed) {
218 if let Some(ref mut dirs) = self.dirs {
207 if let Some(ref mut dirs) = self.dirs {
219 dirs.delete_path(filename)?;
208 dirs.delete_path(filename)?;
220 }
209 }
221 }
210 }
222 if let Some(ref mut all_dirs) = self.all_dirs {
211 if let Some(ref mut all_dirs) = self.all_dirs {
223 all_dirs.delete_path(filename)?;
212 all_dirs.delete_path(filename)?;
224 }
213 }
225 }
214 }
226 self.get_non_normal_other_parent_entries()
215 self.get_non_normal_other_parent_entries()
227 .0
216 .0
228 .remove(filename);
217 .remove(filename);
229
218
230 Ok(exists)
219 Ok(exists)
231 }
220 }
232
221
233 pub fn clear_ambiguous_times(
222 pub fn clear_ambiguous_times(
234 &mut self,
223 &mut self,
235 filenames: Vec<HgPathBuf>,
224 filenames: Vec<HgPathBuf>,
236 now: i32,
225 now: i32,
237 ) {
226 ) {
238 for filename in filenames {
227 for filename in filenames {
239 if let Some(entry) = self.state_map.get_mut(&filename) {
228 if let Some(entry) = self.state_map.get_mut(&filename) {
240 if entry.clear_ambiguous_mtime(now) {
229 if entry.clear_ambiguous_mtime(now) {
241 self.get_non_normal_other_parent_entries()
230 self.get_non_normal_other_parent_entries()
242 .0
231 .0
243 .insert(filename.to_owned());
232 .insert(filename.to_owned());
244 }
233 }
245 }
234 }
246 }
235 }
247 }
236 }
248
237
249 pub fn non_normal_entries_remove(
238 pub fn non_normal_entries_remove(
250 &mut self,
239 &mut self,
251 key: impl AsRef<HgPath>,
240 key: impl AsRef<HgPath>,
252 ) -> bool {
241 ) -> bool {
253 self.get_non_normal_other_parent_entries()
242 self.get_non_normal_other_parent_entries()
254 .0
243 .0
255 .remove(key.as_ref())
244 .remove(key.as_ref())
256 }
245 }
257
246
258 pub fn non_normal_entries_add(&mut self, key: impl AsRef<HgPath>) {
247 pub fn non_normal_entries_add(&mut self, key: impl AsRef<HgPath>) {
259 self.get_non_normal_other_parent_entries()
248 self.get_non_normal_other_parent_entries()
260 .0
249 .0
261 .insert(key.as_ref().into());
250 .insert(key.as_ref().into());
262 }
251 }
263
252
264 pub fn non_normal_entries_union(
253 pub fn non_normal_entries_union(
265 &mut self,
254 &mut self,
266 other: HashSet<HgPathBuf>,
255 other: HashSet<HgPathBuf>,
267 ) -> Vec<HgPathBuf> {
256 ) -> Vec<HgPathBuf> {
268 self.get_non_normal_other_parent_entries()
257 self.get_non_normal_other_parent_entries()
269 .0
258 .0
270 .union(&other)
259 .union(&other)
271 .map(ToOwned::to_owned)
260 .map(ToOwned::to_owned)
272 .collect()
261 .collect()
273 }
262 }
274
263
275 pub fn get_non_normal_other_parent_entries(
264 pub fn get_non_normal_other_parent_entries(
276 &mut self,
265 &mut self,
277 ) -> (&mut HashSet<HgPathBuf>, &mut HashSet<HgPathBuf>) {
266 ) -> (&mut HashSet<HgPathBuf>, &mut HashSet<HgPathBuf>) {
278 self.set_non_normal_other_parent_entries(false);
267 self.set_non_normal_other_parent_entries(false);
279 (
268 (
280 self.non_normal_set.as_mut().unwrap(),
269 self.non_normal_set.as_mut().unwrap(),
281 self.other_parent_set.as_mut().unwrap(),
270 self.other_parent_set.as_mut().unwrap(),
282 )
271 )
283 }
272 }
284
273
285 /// Useful to get immutable references to those sets in contexts where
274 /// Useful to get immutable references to those sets in contexts where
286 /// you only have an immutable reference to the `DirstateMap`, like when
275 /// you only have an immutable reference to the `DirstateMap`, like when
287 /// sharing references with Python.
276 /// sharing references with Python.
288 ///
277 ///
289 /// TODO, get rid of this along with the other "setter/getter" stuff when
278 /// TODO, get rid of this along with the other "setter/getter" stuff when
290 /// a nice typestate plan is defined.
279 /// a nice typestate plan is defined.
291 ///
280 ///
292 /// # Panics
281 /// # Panics
293 ///
282 ///
294 /// Will panic if either set is `None`.
283 /// Will panic if either set is `None`.
295 pub fn get_non_normal_other_parent_entries_panic(
284 pub fn get_non_normal_other_parent_entries_panic(
296 &self,
285 &self,
297 ) -> (&HashSet<HgPathBuf>, &HashSet<HgPathBuf>) {
286 ) -> (&HashSet<HgPathBuf>, &HashSet<HgPathBuf>) {
298 (
287 (
299 self.non_normal_set.as_ref().unwrap(),
288 self.non_normal_set.as_ref().unwrap(),
300 self.other_parent_set.as_ref().unwrap(),
289 self.other_parent_set.as_ref().unwrap(),
301 )
290 )
302 }
291 }
303
292
304 pub fn set_non_normal_other_parent_entries(&mut self, force: bool) {
293 pub fn set_non_normal_other_parent_entries(&mut self, force: bool) {
305 if !force
294 if !force
306 && self.non_normal_set.is_some()
295 && self.non_normal_set.is_some()
307 && self.other_parent_set.is_some()
296 && self.other_parent_set.is_some()
308 {
297 {
309 return;
298 return;
310 }
299 }
311 let mut non_normal = HashSet::new();
300 let mut non_normal = HashSet::new();
312 let mut other_parent = HashSet::new();
301 let mut other_parent = HashSet::new();
313
302
314 for (filename, entry) in self.state_map.iter() {
303 for (filename, entry) in self.state_map.iter() {
315 if entry.is_non_normal() {
304 if entry.is_non_normal() {
316 non_normal.insert(filename.to_owned());
305 non_normal.insert(filename.to_owned());
317 }
306 }
318 if entry.is_from_other_parent() {
307 if entry.is_from_other_parent() {
319 other_parent.insert(filename.to_owned());
308 other_parent.insert(filename.to_owned());
320 }
309 }
321 }
310 }
322 self.non_normal_set = Some(non_normal);
311 self.non_normal_set = Some(non_normal);
323 self.other_parent_set = Some(other_parent);
312 self.other_parent_set = Some(other_parent);
324 }
313 }
325
314
326 /// Both of these setters and their uses appear to be the simplest way to
315 /// Both of these setters and their uses appear to be the simplest way to
327 /// emulate a Python lazy property, but it is ugly and unidiomatic.
316 /// emulate a Python lazy property, but it is ugly and unidiomatic.
328 /// TODO One day, rewriting this struct using the typestate might be a
317 /// TODO One day, rewriting this struct using the typestate might be a
329 /// good idea.
318 /// good idea.
330 pub fn set_all_dirs(&mut self) -> Result<(), DirstateError> {
319 pub fn set_all_dirs(&mut self) -> Result<(), DirstateError> {
331 if self.all_dirs.is_none() {
320 if self.all_dirs.is_none() {
332 self.all_dirs = Some(DirsMultiset::from_dirstate(
321 self.all_dirs = Some(DirsMultiset::from_dirstate(
333 self.state_map.iter().map(|(k, v)| Ok((k, *v))),
322 self.state_map.iter().map(|(k, v)| Ok((k, *v))),
334 false,
323 false,
335 )?);
324 )?);
336 }
325 }
337 Ok(())
326 Ok(())
338 }
327 }
339
328
340 pub fn set_dirs(&mut self) -> Result<(), DirstateError> {
329 pub fn set_dirs(&mut self) -> Result<(), DirstateError> {
341 if self.dirs.is_none() {
330 if self.dirs.is_none() {
342 self.dirs = Some(DirsMultiset::from_dirstate(
331 self.dirs = Some(DirsMultiset::from_dirstate(
343 self.state_map.iter().map(|(k, v)| Ok((k, *v))),
332 self.state_map.iter().map(|(k, v)| Ok((k, *v))),
344 true,
333 true,
345 )?);
334 )?);
346 }
335 }
347 Ok(())
336 Ok(())
348 }
337 }
349
338
350 pub fn has_tracked_dir(
339 pub fn has_tracked_dir(
351 &mut self,
340 &mut self,
352 directory: &HgPath,
341 directory: &HgPath,
353 ) -> Result<bool, DirstateError> {
342 ) -> Result<bool, DirstateError> {
354 self.set_dirs()?;
343 self.set_dirs()?;
355 Ok(self.dirs.as_ref().unwrap().contains(directory))
344 Ok(self.dirs.as_ref().unwrap().contains(directory))
356 }
345 }
357
346
358 pub fn has_dir(
347 pub fn has_dir(
359 &mut self,
348 &mut self,
360 directory: &HgPath,
349 directory: &HgPath,
361 ) -> Result<bool, DirstateError> {
350 ) -> Result<bool, DirstateError> {
362 self.set_all_dirs()?;
351 self.set_all_dirs()?;
363 Ok(self.all_dirs.as_ref().unwrap().contains(directory))
352 Ok(self.all_dirs.as_ref().unwrap().contains(directory))
364 }
353 }
365
354
366 #[timed]
355 #[timed]
367 pub fn read(
356 pub fn read(
368 &mut self,
357 &mut self,
369 file_contents: &[u8],
358 file_contents: &[u8],
370 ) -> Result<Option<DirstateParents>, DirstateError> {
359 ) -> Result<Option<DirstateParents>, DirstateError> {
371 if file_contents.is_empty() {
360 if file_contents.is_empty() {
372 return Ok(None);
361 return Ok(None);
373 }
362 }
374
363
375 let (parents, entries, copies) = parse_dirstate(file_contents)?;
364 let (parents, entries, copies) = parse_dirstate(file_contents)?;
376 self.state_map.extend(
365 self.state_map.extend(
377 entries
366 entries
378 .into_iter()
367 .into_iter()
379 .map(|(path, entry)| (path.to_owned(), entry)),
368 .map(|(path, entry)| (path.to_owned(), entry)),
380 );
369 );
381 self.copy_map.extend(
370 self.copy_map.extend(
382 copies
371 copies
383 .into_iter()
372 .into_iter()
384 .map(|(path, copy)| (path.to_owned(), copy.to_owned())),
373 .map(|(path, copy)| (path.to_owned(), copy.to_owned())),
385 );
374 );
386 Ok(Some(parents.clone()))
375 Ok(Some(parents.clone()))
387 }
376 }
388
377
389 pub fn pack(
378 pub fn pack(
390 &mut self,
379 &mut self,
391 parents: DirstateParents,
380 parents: DirstateParents,
392 now: Timestamp,
381 now: Timestamp,
393 ) -> Result<Vec<u8>, DirstateError> {
382 ) -> Result<Vec<u8>, DirstateError> {
394 let packed =
383 let packed =
395 pack_dirstate(&mut self.state_map, &self.copy_map, parents, now)?;
384 pack_dirstate(&mut self.state_map, &self.copy_map, parents, now)?;
396
385
397 self.set_non_normal_other_parent_entries(true);
386 self.set_non_normal_other_parent_entries(true);
398 Ok(packed)
387 Ok(packed)
399 }
388 }
400 }
389 }
401
390
402 #[cfg(test)]
391 #[cfg(test)]
403 mod tests {
392 mod tests {
404 use super::*;
393 use super::*;
405
394
406 #[test]
395 #[test]
407 fn test_dirs_multiset() {
396 fn test_dirs_multiset() {
408 let mut map = DirstateMap::new();
397 let mut map = DirstateMap::new();
409 assert!(map.dirs.is_none());
398 assert!(map.dirs.is_none());
410 assert!(map.all_dirs.is_none());
399 assert!(map.all_dirs.is_none());
411
400
412 assert_eq!(map.has_dir(HgPath::new(b"nope")).unwrap(), false);
401 assert_eq!(map.has_dir(HgPath::new(b"nope")).unwrap(), false);
413 assert!(map.all_dirs.is_some());
402 assert!(map.all_dirs.is_some());
414 assert!(map.dirs.is_none());
403 assert!(map.dirs.is_none());
415
404
416 assert_eq!(map.has_tracked_dir(HgPath::new(b"nope")).unwrap(), false);
405 assert_eq!(map.has_tracked_dir(HgPath::new(b"nope")).unwrap(), false);
417 assert!(map.dirs.is_some());
406 assert!(map.dirs.is_some());
418 }
407 }
419
408
420 #[test]
409 #[test]
421 fn test_add_file() {
410 fn test_add_file() {
422 let mut map = DirstateMap::new();
411 let mut map = DirstateMap::new();
423
412
424 assert_eq!(0, map.len());
413 assert_eq!(0, map.len());
425
414
426 map.add_file(
415 map.add_file(
427 HgPath::new(b"meh"),
416 HgPath::new(b"meh"),
428 DirstateEntry::from_v1_data(EntryState::Normal, 1337, 1337, 1337),
417 DirstateEntry::from_v1_data(EntryState::Normal, 1337, 1337, 1337),
429 false,
418 false,
430 false,
419 false,
431 false,
420 false,
432 false,
421 false,
433 )
422 )
434 .unwrap();
423 .unwrap();
435
424
436 assert_eq!(1, map.len());
425 assert_eq!(1, map.len());
437 assert_eq!(0, map.get_non_normal_other_parent_entries().0.len());
426 assert_eq!(0, map.get_non_normal_other_parent_entries().0.len());
438 assert_eq!(0, map.get_non_normal_other_parent_entries().1.len());
427 assert_eq!(0, map.get_non_normal_other_parent_entries().1.len());
439 }
428 }
440
429
441 #[test]
430 #[test]
442 fn test_non_normal_other_parent_entries() {
431 fn test_non_normal_other_parent_entries() {
443 let mut map: DirstateMap = [
432 let mut map: DirstateMap = [
444 (b"f1", (EntryState::Removed, 1337, 1337, 1337)),
433 (b"f1", (EntryState::Removed, 1337, 1337, 1337)),
445 (b"f2", (EntryState::Normal, 1337, 1337, -1)),
434 (b"f2", (EntryState::Normal, 1337, 1337, -1)),
446 (b"f3", (EntryState::Normal, 1337, 1337, 1337)),
435 (b"f3", (EntryState::Normal, 1337, 1337, 1337)),
447 (b"f4", (EntryState::Normal, 1337, -2, 1337)),
436 (b"f4", (EntryState::Normal, 1337, -2, 1337)),
448 (b"f5", (EntryState::Added, 1337, 1337, 1337)),
437 (b"f5", (EntryState::Added, 1337, 1337, 1337)),
449 (b"f6", (EntryState::Added, 1337, 1337, -1)),
438 (b"f6", (EntryState::Added, 1337, 1337, -1)),
450 (b"f7", (EntryState::Merged, 1337, 1337, -1)),
439 (b"f7", (EntryState::Merged, 1337, 1337, -1)),
451 (b"f8", (EntryState::Merged, 1337, 1337, 1337)),
440 (b"f8", (EntryState::Merged, 1337, 1337, 1337)),
452 (b"f9", (EntryState::Merged, 1337, -2, 1337)),
441 (b"f9", (EntryState::Merged, 1337, -2, 1337)),
453 (b"fa", (EntryState::Added, 1337, -2, 1337)),
442 (b"fa", (EntryState::Added, 1337, -2, 1337)),
454 (b"fb", (EntryState::Removed, 1337, -2, 1337)),
443 (b"fb", (EntryState::Removed, 1337, -2, 1337)),
455 ]
444 ]
456 .iter()
445 .iter()
457 .map(|(fname, (state, mode, size, mtime))| {
446 .map(|(fname, (state, mode, size, mtime))| {
458 (
447 (
459 HgPathBuf::from_bytes(fname.as_ref()),
448 HgPathBuf::from_bytes(fname.as_ref()),
460 DirstateEntry::from_v1_data(*state, *mode, *size, *mtime),
449 DirstateEntry::from_v1_data(*state, *mode, *size, *mtime),
461 )
450 )
462 })
451 })
463 .collect();
452 .collect();
464
453
465 let mut non_normal = [
454 let mut non_normal = [
466 b"f1", b"f2", b"f5", b"f6", b"f7", b"f8", b"f9", b"fa", b"fb",
455 b"f1", b"f2", b"f5", b"f6", b"f7", b"f8", b"f9", b"fa", b"fb",
467 ]
456 ]
468 .iter()
457 .iter()
469 .map(|x| HgPathBuf::from_bytes(x.as_ref()))
458 .map(|x| HgPathBuf::from_bytes(x.as_ref()))
470 .collect();
459 .collect();
471
460
472 let mut other_parent = HashSet::new();
461 let mut other_parent = HashSet::new();
473 other_parent.insert(HgPathBuf::from_bytes(b"f4"));
462 other_parent.insert(HgPathBuf::from_bytes(b"f4"));
474 let entries = map.get_non_normal_other_parent_entries();
463 let entries = map.get_non_normal_other_parent_entries();
475
464
476 assert_eq!(
465 assert_eq!(
477 (&mut non_normal, &mut other_parent),
466 (&mut non_normal, &mut other_parent),
478 (entries.0, entries.1)
467 (entries.0, entries.1)
479 );
468 );
480 }
469 }
481 }
470 }
@@ -1,193 +1,190 b''
1 use crate::errors::HgError;
1 use crate::errors::HgError;
2 use std::convert::TryFrom;
2 use std::convert::TryFrom;
3
3
4 #[derive(Copy, Clone, Debug, Eq, PartialEq)]
4 #[derive(Copy, Clone, Debug, Eq, PartialEq)]
5 pub enum EntryState {
5 pub enum EntryState {
6 Normal,
6 Normal,
7 Added,
7 Added,
8 Removed,
8 Removed,
9 Merged,
9 Merged,
10 Unknown,
11 }
10 }
12
11
13 /// The C implementation uses all signed types. This will be an issue
12 /// The C implementation uses all signed types. This will be an issue
14 /// either when 4GB+ source files are commonplace or in 2038, whichever
13 /// either when 4GB+ source files are commonplace or in 2038, whichever
15 /// comes first.
14 /// comes first.
16 #[derive(Debug, PartialEq, Copy, Clone)]
15 #[derive(Debug, PartialEq, Copy, Clone)]
17 pub struct DirstateEntry {
16 pub struct DirstateEntry {
18 state: EntryState,
17 state: EntryState,
19 mode: i32,
18 mode: i32,
20 size: i32,
19 size: i32,
21 mtime: i32,
20 mtime: i32,
22 }
21 }
23
22
24 pub const V1_RANGEMASK: i32 = 0x7FFFFFFF;
23 pub const V1_RANGEMASK: i32 = 0x7FFFFFFF;
25
24
26 pub const MTIME_UNSET: i32 = -1;
25 pub const MTIME_UNSET: i32 = -1;
27
26
28 /// A `DirstateEntry` with a size of `-2` means that it was merged from the
27 /// A `DirstateEntry` with a size of `-2` means that it was merged from the
29 /// other parent. This allows revert to pick the right status back during a
28 /// other parent. This allows revert to pick the right status back during a
30 /// merge.
29 /// merge.
31 pub const SIZE_FROM_OTHER_PARENT: i32 = -2;
30 pub const SIZE_FROM_OTHER_PARENT: i32 = -2;
32 /// A special value used for internal representation of special case in
31 /// A special value used for internal representation of special case in
33 /// dirstate v1 format.
32 /// dirstate v1 format.
34 pub const SIZE_NON_NORMAL: i32 = -1;
33 pub const SIZE_NON_NORMAL: i32 = -1;
35
34
36 impl DirstateEntry {
35 impl DirstateEntry {
37 pub fn from_v1_data(
36 pub fn from_v1_data(
38 state: EntryState,
37 state: EntryState,
39 mode: i32,
38 mode: i32,
40 size: i32,
39 size: i32,
41 mtime: i32,
40 mtime: i32,
42 ) -> Self {
41 ) -> Self {
43 Self {
42 Self {
44 state,
43 state,
45 mode,
44 mode,
46 size,
45 size,
47 mtime,
46 mtime,
48 }
47 }
49 }
48 }
50
49
51 /// Creates a new entry in "removed" state.
50 /// Creates a new entry in "removed" state.
52 ///
51 ///
53 /// `size` is expected to be zero, `SIZE_NON_NORMAL`, or
52 /// `size` is expected to be zero, `SIZE_NON_NORMAL`, or
54 /// `SIZE_FROM_OTHER_PARENT`
53 /// `SIZE_FROM_OTHER_PARENT`
55 pub fn new_removed(size: i32) -> Self {
54 pub fn new_removed(size: i32) -> Self {
56 Self {
55 Self {
57 state: EntryState::Removed,
56 state: EntryState::Removed,
58 mode: 0,
57 mode: 0,
59 size,
58 size,
60 mtime: 0,
59 mtime: 0,
61 }
60 }
62 }
61 }
63
62
64 /// TODO: refactor `DirstateMap::add_file` to not take a `DirstateEntry`
63 /// TODO: refactor `DirstateMap::add_file` to not take a `DirstateEntry`
65 /// parameter and remove this constructor
64 /// parameter and remove this constructor
66 pub fn new_for_add_file(mode: i32, size: i32, mtime: i32) -> Self {
65 pub fn new_for_add_file(mode: i32, size: i32, mtime: i32) -> Self {
67 Self {
66 Self {
68 // XXX Arbitrary default value since the value is determined later
67 // XXX Arbitrary default value since the value is determined later
69 state: EntryState::Normal,
68 state: EntryState::Normal,
70 mode,
69 mode,
71 size,
70 size,
72 mtime,
71 mtime,
73 }
72 }
74 }
73 }
75
74
76 pub fn state(&self) -> EntryState {
75 pub fn state(&self) -> EntryState {
77 self.state
76 self.state
78 }
77 }
79
78
80 pub fn mode(&self) -> i32 {
79 pub fn mode(&self) -> i32 {
81 self.mode
80 self.mode
82 }
81 }
83
82
84 pub fn size(&self) -> i32 {
83 pub fn size(&self) -> i32 {
85 self.size
84 self.size
86 }
85 }
87
86
88 pub fn mtime(&self) -> i32 {
87 pub fn mtime(&self) -> i32 {
89 self.mtime
88 self.mtime
90 }
89 }
91
90
92 /// Returns `(state, mode, size, mtime)` for the puprose of serialization
91 /// Returns `(state, mode, size, mtime)` for the puprose of serialization
93 /// in the dirstate-v1 format.
92 /// in the dirstate-v1 format.
94 ///
93 ///
95 /// This includes marker values such as `mtime == -1`. In the future we may
94 /// This includes marker values such as `mtime == -1`. In the future we may
96 /// want to not represent these cases that way in memory, but serialization
95 /// want to not represent these cases that way in memory, but serialization
97 /// will need to keep the same format.
96 /// will need to keep the same format.
98 pub fn v1_data(&self) -> (u8, i32, i32, i32) {
97 pub fn v1_data(&self) -> (u8, i32, i32, i32) {
99 (self.state.into(), self.mode, self.size, self.mtime)
98 (self.state.into(), self.mode, self.size, self.mtime)
100 }
99 }
101
100
102 pub fn is_non_normal(&self) -> bool {
101 pub fn is_non_normal(&self) -> bool {
103 self.state != EntryState::Normal || self.mtime == MTIME_UNSET
102 self.state != EntryState::Normal || self.mtime == MTIME_UNSET
104 }
103 }
105
104
106 pub fn is_from_other_parent(&self) -> bool {
105 pub fn is_from_other_parent(&self) -> bool {
107 self.state == EntryState::Normal && self.size == SIZE_FROM_OTHER_PARENT
106 self.state == EntryState::Normal && self.size == SIZE_FROM_OTHER_PARENT
108 }
107 }
109
108
110 // TODO: other platforms
109 // TODO: other platforms
111 #[cfg(unix)]
110 #[cfg(unix)]
112 pub fn mode_changed(
111 pub fn mode_changed(
113 &self,
112 &self,
114 filesystem_metadata: &std::fs::Metadata,
113 filesystem_metadata: &std::fs::Metadata,
115 ) -> bool {
114 ) -> bool {
116 use std::os::unix::fs::MetadataExt;
115 use std::os::unix::fs::MetadataExt;
117 const EXEC_BIT_MASK: u32 = 0o100;
116 const EXEC_BIT_MASK: u32 = 0o100;
118 let dirstate_exec_bit = (self.mode as u32) & EXEC_BIT_MASK;
117 let dirstate_exec_bit = (self.mode as u32) & EXEC_BIT_MASK;
119 let fs_exec_bit = filesystem_metadata.mode() & EXEC_BIT_MASK;
118 let fs_exec_bit = filesystem_metadata.mode() & EXEC_BIT_MASK;
120 dirstate_exec_bit != fs_exec_bit
119 dirstate_exec_bit != fs_exec_bit
121 }
120 }
122
121
123 /// Returns a `(state, mode, size, mtime)` tuple as for
122 /// Returns a `(state, mode, size, mtime)` tuple as for
124 /// `DirstateMapMethods::debug_iter`.
123 /// `DirstateMapMethods::debug_iter`.
125 pub fn debug_tuple(&self) -> (u8, i32, i32, i32) {
124 pub fn debug_tuple(&self) -> (u8, i32, i32, i32) {
126 (self.state.into(), self.mode, self.size, self.mtime)
125 (self.state.into(), self.mode, self.size, self.mtime)
127 }
126 }
128
127
129 pub fn mtime_is_ambiguous(&self, now: i32) -> bool {
128 pub fn mtime_is_ambiguous(&self, now: i32) -> bool {
130 self.state == EntryState::Normal && self.mtime == now
129 self.state == EntryState::Normal && self.mtime == now
131 }
130 }
132
131
133 pub fn clear_ambiguous_mtime(&mut self, now: i32) -> bool {
132 pub fn clear_ambiguous_mtime(&mut self, now: i32) -> bool {
134 let ambiguous = self.mtime_is_ambiguous(now);
133 let ambiguous = self.mtime_is_ambiguous(now);
135 if ambiguous {
134 if ambiguous {
136 // The file was last modified "simultaneously" with the current
135 // The file was last modified "simultaneously" with the current
137 // write to dirstate (i.e. within the same second for file-
136 // write to dirstate (i.e. within the same second for file-
138 // systems with a granularity of 1 sec). This commonly happens
137 // systems with a granularity of 1 sec). This commonly happens
139 // for at least a couple of files on 'update'.
138 // for at least a couple of files on 'update'.
140 // The user could change the file without changing its size
139 // The user could change the file without changing its size
141 // within the same second. Invalidate the file's mtime in
140 // within the same second. Invalidate the file's mtime in
142 // dirstate, forcing future 'status' calls to compare the
141 // dirstate, forcing future 'status' calls to compare the
143 // contents of the file if the size is the same. This prevents
142 // contents of the file if the size is the same. This prevents
144 // mistakenly treating such files as clean.
143 // mistakenly treating such files as clean.
145 self.clear_mtime()
144 self.clear_mtime()
146 }
145 }
147 ambiguous
146 ambiguous
148 }
147 }
149
148
150 pub fn clear_mtime(&mut self) {
149 pub fn clear_mtime(&mut self) {
151 self.mtime = -1;
150 self.mtime = -1;
152 }
151 }
153 }
152 }
154
153
155 impl EntryState {
154 impl EntryState {
156 pub fn is_tracked(self) -> bool {
155 pub fn is_tracked(self) -> bool {
157 use EntryState::*;
156 use EntryState::*;
158 match self {
157 match self {
159 Normal | Added | Merged => true,
158 Normal | Added | Merged => true,
160 Removed | Unknown => false,
159 Removed => false,
161 }
160 }
162 }
161 }
163 }
162 }
164
163
165 impl TryFrom<u8> for EntryState {
164 impl TryFrom<u8> for EntryState {
166 type Error = HgError;
165 type Error = HgError;
167
166
168 fn try_from(value: u8) -> Result<Self, Self::Error> {
167 fn try_from(value: u8) -> Result<Self, Self::Error> {
169 match value {
168 match value {
170 b'n' => Ok(EntryState::Normal),
169 b'n' => Ok(EntryState::Normal),
171 b'a' => Ok(EntryState::Added),
170 b'a' => Ok(EntryState::Added),
172 b'r' => Ok(EntryState::Removed),
171 b'r' => Ok(EntryState::Removed),
173 b'm' => Ok(EntryState::Merged),
172 b'm' => Ok(EntryState::Merged),
174 b'?' => Ok(EntryState::Unknown),
175 _ => Err(HgError::CorruptedRepository(format!(
173 _ => Err(HgError::CorruptedRepository(format!(
176 "Incorrect dirstate entry state {}",
174 "Incorrect dirstate entry state {}",
177 value
175 value
178 ))),
176 ))),
179 }
177 }
180 }
178 }
181 }
179 }
182
180
183 impl Into<u8> for EntryState {
181 impl Into<u8> for EntryState {
184 fn into(self) -> u8 {
182 fn into(self) -> u8 {
185 match self {
183 match self {
186 EntryState::Normal => b'n',
184 EntryState::Normal => b'n',
187 EntryState::Added => b'a',
185 EntryState::Added => b'a',
188 EntryState::Removed => b'r',
186 EntryState::Removed => b'r',
189 EntryState::Merged => b'm',
187 EntryState::Merged => b'm',
190 EntryState::Unknown => b'?',
191 }
188 }
192 }
189 }
193 }
190 }
@@ -1,947 +1,944 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::dirstate_tree::on_disk::DirstateV2ParseError;
12 use crate::dirstate_tree::on_disk::DirstateV2ParseError;
13 use crate::utils::path_auditor::PathAuditor;
13 use crate::utils::path_auditor::PathAuditor;
14 use crate::{
14 use crate::{
15 dirstate::SIZE_FROM_OTHER_PARENT,
15 dirstate::SIZE_FROM_OTHER_PARENT,
16 filepatterns::PatternFileWarning,
16 filepatterns::PatternFileWarning,
17 matchers::{get_ignore_function, Matcher, VisitChildrenSet},
17 matchers::{get_ignore_function, Matcher, VisitChildrenSet},
18 utils::{
18 utils::{
19 files::{find_dirs, HgMetadata},
19 files::{find_dirs, HgMetadata},
20 hg_path::{
20 hg_path::{
21 hg_path_to_path_buf, os_string_to_hg_path_buf, HgPath, HgPathBuf,
21 hg_path_to_path_buf, os_string_to_hg_path_buf, HgPath, HgPathBuf,
22 HgPathError,
22 HgPathError,
23 },
23 },
24 },
24 },
25 CopyMap, DirstateEntry, DirstateMap, EntryState, FastHashMap,
25 CopyMap, DirstateEntry, DirstateMap, EntryState, FastHashMap,
26 PatternError,
26 PatternError,
27 };
27 };
28 use lazy_static::lazy_static;
28 use lazy_static::lazy_static;
29 use micro_timer::timed;
29 use micro_timer::timed;
30 use rayon::prelude::*;
30 use rayon::prelude::*;
31 use std::{
31 use std::{
32 borrow::Cow,
32 borrow::Cow,
33 collections::HashSet,
33 collections::HashSet,
34 fmt,
34 fmt,
35 fs::{read_dir, DirEntry},
35 fs::{read_dir, DirEntry},
36 io::ErrorKind,
36 io::ErrorKind,
37 ops::Deref,
37 ops::Deref,
38 path::{Path, PathBuf},
38 path::{Path, PathBuf},
39 };
39 };
40
40
41 /// Wrong type of file from a `BadMatch`
41 /// Wrong type of file from a `BadMatch`
42 /// Note: a lot of those don't exist on all platforms.
42 /// Note: a lot of those don't exist on all platforms.
43 #[derive(Debug, Copy, Clone)]
43 #[derive(Debug, Copy, Clone)]
44 pub enum BadType {
44 pub enum BadType {
45 CharacterDevice,
45 CharacterDevice,
46 BlockDevice,
46 BlockDevice,
47 FIFO,
47 FIFO,
48 Socket,
48 Socket,
49 Directory,
49 Directory,
50 Unknown,
50 Unknown,
51 }
51 }
52
52
53 impl fmt::Display for BadType {
53 impl fmt::Display for BadType {
54 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
54 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
55 f.write_str(match self {
55 f.write_str(match self {
56 BadType::CharacterDevice => "character device",
56 BadType::CharacterDevice => "character device",
57 BadType::BlockDevice => "block device",
57 BadType::BlockDevice => "block device",
58 BadType::FIFO => "fifo",
58 BadType::FIFO => "fifo",
59 BadType::Socket => "socket",
59 BadType::Socket => "socket",
60 BadType::Directory => "directory",
60 BadType::Directory => "directory",
61 BadType::Unknown => "unknown",
61 BadType::Unknown => "unknown",
62 })
62 })
63 }
63 }
64 }
64 }
65
65
66 /// Was explicitly matched but cannot be found/accessed
66 /// Was explicitly matched but cannot be found/accessed
67 #[derive(Debug, Copy, Clone)]
67 #[derive(Debug, Copy, Clone)]
68 pub enum BadMatch {
68 pub enum BadMatch {
69 OsError(i32),
69 OsError(i32),
70 BadType(BadType),
70 BadType(BadType),
71 }
71 }
72
72
73 /// Enum used to dispatch new status entries into the right collections.
73 /// Enum used to dispatch new status entries into the right collections.
74 /// Is similar to `crate::EntryState`, but represents the transient state of
74 /// Is similar to `crate::EntryState`, but represents the transient state of
75 /// entries during the lifetime of a command.
75 /// entries during the lifetime of a command.
76 #[derive(Debug, Copy, Clone)]
76 #[derive(Debug, Copy, Clone)]
77 pub enum Dispatch {
77 pub enum Dispatch {
78 Unsure,
78 Unsure,
79 Modified,
79 Modified,
80 Added,
80 Added,
81 Removed,
81 Removed,
82 Deleted,
82 Deleted,
83 Clean,
83 Clean,
84 Unknown,
84 Unknown,
85 Ignored,
85 Ignored,
86 /// Empty dispatch, the file is not worth listing
86 /// Empty dispatch, the file is not worth listing
87 None,
87 None,
88 /// Was explicitly matched but cannot be found/accessed
88 /// Was explicitly matched but cannot be found/accessed
89 Bad(BadMatch),
89 Bad(BadMatch),
90 Directory {
90 Directory {
91 /// True if the directory used to be a file in the dmap so we can say
91 /// True if the directory used to be a file in the dmap so we can say
92 /// that it's been removed.
92 /// that it's been removed.
93 was_file: bool,
93 was_file: bool,
94 },
94 },
95 }
95 }
96
96
97 type IoResult<T> = std::io::Result<T>;
97 type IoResult<T> = std::io::Result<T>;
98
98
99 /// `Box<dyn Trait>` is syntactic sugar for `Box<dyn Trait + 'static>`, so add
99 /// `Box<dyn Trait>` is syntactic sugar for `Box<dyn Trait + 'static>`, so add
100 /// an explicit lifetime here to not fight `'static` bounds "out of nowhere".
100 /// an explicit lifetime here to not fight `'static` bounds "out of nowhere".
101 pub type IgnoreFnType<'a> =
101 pub type IgnoreFnType<'a> =
102 Box<dyn for<'r> Fn(&'r HgPath) -> bool + Sync + 'a>;
102 Box<dyn for<'r> Fn(&'r HgPath) -> bool + Sync + 'a>;
103
103
104 /// We have a good mix of owned (from directory traversal) and borrowed (from
104 /// We have a good mix of owned (from directory traversal) and borrowed (from
105 /// the dirstate/explicit) paths, this comes up a lot.
105 /// the dirstate/explicit) paths, this comes up a lot.
106 pub type HgPathCow<'a> = Cow<'a, HgPath>;
106 pub type HgPathCow<'a> = Cow<'a, HgPath>;
107
107
108 /// A path with its computed ``Dispatch`` information
108 /// A path with its computed ``Dispatch`` information
109 type DispatchedPath<'a> = (HgPathCow<'a>, Dispatch);
109 type DispatchedPath<'a> = (HgPathCow<'a>, Dispatch);
110
110
111 /// The conversion from `HgPath` to a real fs path failed.
111 /// The conversion from `HgPath` to a real fs path failed.
112 /// `22` is the error code for "Invalid argument"
112 /// `22` is the error code for "Invalid argument"
113 const INVALID_PATH_DISPATCH: Dispatch = Dispatch::Bad(BadMatch::OsError(22));
113 const INVALID_PATH_DISPATCH: Dispatch = Dispatch::Bad(BadMatch::OsError(22));
114
114
115 /// Dates and times that are outside the 31-bit signed range are compared
115 /// Dates and times that are outside the 31-bit signed range are compared
116 /// modulo 2^31. This should prevent hg from behaving badly with very large
116 /// modulo 2^31. This should prevent hg from behaving badly with very large
117 /// files or corrupt dates while still having a high probability of detecting
117 /// files or corrupt dates while still having a high probability of detecting
118 /// changes. (issue2608)
118 /// changes. (issue2608)
119 /// TODO I haven't found a way of having `b` be `Into<i32>`, since `From<u64>`
119 /// TODO I haven't found a way of having `b` be `Into<i32>`, since `From<u64>`
120 /// is not defined for `i32`, and there is no `As` trait. This forces the
120 /// is not defined for `i32`, and there is no `As` trait. This forces the
121 /// caller to cast `b` as `i32`.
121 /// caller to cast `b` as `i32`.
122 fn mod_compare(a: i32, b: i32) -> bool {
122 fn mod_compare(a: i32, b: i32) -> bool {
123 a & i32::max_value() != b & i32::max_value()
123 a & i32::max_value() != b & i32::max_value()
124 }
124 }
125
125
126 /// Return a sorted list containing information about the entries
126 /// Return a sorted list containing information about the entries
127 /// in the directory.
127 /// in the directory.
128 ///
128 ///
129 /// * `skip_dot_hg` - Return an empty vec if `path` contains a `.hg` directory
129 /// * `skip_dot_hg` - Return an empty vec if `path` contains a `.hg` directory
130 fn list_directory(
130 fn list_directory(
131 path: impl AsRef<Path>,
131 path: impl AsRef<Path>,
132 skip_dot_hg: bool,
132 skip_dot_hg: bool,
133 ) -> std::io::Result<Vec<(HgPathBuf, DirEntry)>> {
133 ) -> std::io::Result<Vec<(HgPathBuf, DirEntry)>> {
134 let mut results = vec![];
134 let mut results = vec![];
135 let entries = read_dir(path.as_ref())?;
135 let entries = read_dir(path.as_ref())?;
136
136
137 for entry in entries {
137 for entry in entries {
138 let entry = entry?;
138 let entry = entry?;
139 let filename = os_string_to_hg_path_buf(entry.file_name())?;
139 let filename = os_string_to_hg_path_buf(entry.file_name())?;
140 let file_type = entry.file_type()?;
140 let file_type = entry.file_type()?;
141 if skip_dot_hg && filename.as_bytes() == b".hg" && file_type.is_dir() {
141 if skip_dot_hg && filename.as_bytes() == b".hg" && file_type.is_dir() {
142 return Ok(vec![]);
142 return Ok(vec![]);
143 } else {
143 } else {
144 results.push((filename, entry))
144 results.push((filename, entry))
145 }
145 }
146 }
146 }
147
147
148 results.sort_unstable_by_key(|e| e.0.clone());
148 results.sort_unstable_by_key(|e| e.0.clone());
149 Ok(results)
149 Ok(results)
150 }
150 }
151
151
152 /// The file corresponding to the dirstate entry was found on the filesystem.
152 /// The file corresponding to the dirstate entry was found on the filesystem.
153 fn dispatch_found(
153 fn dispatch_found(
154 filename: impl AsRef<HgPath>,
154 filename: impl AsRef<HgPath>,
155 entry: DirstateEntry,
155 entry: DirstateEntry,
156 metadata: HgMetadata,
156 metadata: HgMetadata,
157 copy_map: &CopyMap,
157 copy_map: &CopyMap,
158 options: StatusOptions,
158 options: StatusOptions,
159 ) -> Dispatch {
159 ) -> Dispatch {
160 match entry.state() {
160 match entry.state() {
161 EntryState::Normal => {
161 EntryState::Normal => {
162 let mode = entry.mode();
162 let mode = entry.mode();
163 let size = entry.size();
163 let size = entry.size();
164 let mtime = entry.mtime();
164 let mtime = entry.mtime();
165
165
166 let HgMetadata {
166 let HgMetadata {
167 st_mode,
167 st_mode,
168 st_size,
168 st_size,
169 st_mtime,
169 st_mtime,
170 ..
170 ..
171 } = metadata;
171 } = metadata;
172
172
173 let size_changed = mod_compare(size, st_size as i32);
173 let size_changed = mod_compare(size, st_size as i32);
174 let mode_changed =
174 let mode_changed =
175 (mode ^ st_mode as i32) & 0o100 != 0o000 && options.check_exec;
175 (mode ^ st_mode as i32) & 0o100 != 0o000 && options.check_exec;
176 let metadata_changed = size >= 0 && (size_changed || mode_changed);
176 let metadata_changed = size >= 0 && (size_changed || mode_changed);
177 let other_parent = size == SIZE_FROM_OTHER_PARENT;
177 let other_parent = size == SIZE_FROM_OTHER_PARENT;
178
178
179 if metadata_changed
179 if metadata_changed
180 || other_parent
180 || other_parent
181 || copy_map.contains_key(filename.as_ref())
181 || copy_map.contains_key(filename.as_ref())
182 {
182 {
183 if metadata.is_symlink() && size_changed {
183 if metadata.is_symlink() && size_changed {
184 // issue6456: Size returned may be longer due to encryption
184 // issue6456: Size returned may be longer due to encryption
185 // on EXT-4 fscrypt. TODO maybe only do it on EXT4?
185 // on EXT-4 fscrypt. TODO maybe only do it on EXT4?
186 Dispatch::Unsure
186 Dispatch::Unsure
187 } else {
187 } else {
188 Dispatch::Modified
188 Dispatch::Modified
189 }
189 }
190 } else if mod_compare(mtime, st_mtime as i32)
190 } else if mod_compare(mtime, st_mtime as i32)
191 || st_mtime == options.last_normal_time
191 || st_mtime == options.last_normal_time
192 {
192 {
193 // the file may have just been marked as normal and
193 // the file may have just been marked as normal and
194 // it may have changed in the same second without
194 // it may have changed in the same second without
195 // changing its size. This can happen if we quickly
195 // changing its size. This can happen if we quickly
196 // do multiple commits. Force lookup, so we don't
196 // do multiple commits. Force lookup, so we don't
197 // miss such a racy file change.
197 // miss such a racy file change.
198 Dispatch::Unsure
198 Dispatch::Unsure
199 } else if options.list_clean {
199 } else if options.list_clean {
200 Dispatch::Clean
200 Dispatch::Clean
201 } else {
201 } else {
202 Dispatch::None
202 Dispatch::None
203 }
203 }
204 }
204 }
205 EntryState::Merged => Dispatch::Modified,
205 EntryState::Merged => Dispatch::Modified,
206 EntryState::Added => Dispatch::Added,
206 EntryState::Added => Dispatch::Added,
207 EntryState::Removed => Dispatch::Removed,
207 EntryState::Removed => Dispatch::Removed,
208 EntryState::Unknown => Dispatch::Unknown,
209 }
208 }
210 }
209 }
211
210
212 /// The file corresponding to this Dirstate entry is missing.
211 /// The file corresponding to this Dirstate entry is missing.
213 fn dispatch_missing(state: EntryState) -> Dispatch {
212 fn dispatch_missing(state: EntryState) -> Dispatch {
214 match state {
213 match state {
215 // File was removed from the filesystem during commands
214 // File was removed from the filesystem during commands
216 EntryState::Normal | EntryState::Merged | EntryState::Added => {
215 EntryState::Normal | EntryState::Merged | EntryState::Added => {
217 Dispatch::Deleted
216 Dispatch::Deleted
218 }
217 }
219 // File was removed, everything is normal
218 // File was removed, everything is normal
220 EntryState::Removed => Dispatch::Removed,
219 EntryState::Removed => Dispatch::Removed,
221 // File is unknown to Mercurial, everything is normal
222 EntryState::Unknown => Dispatch::Unknown,
223 }
220 }
224 }
221 }
225
222
226 fn dispatch_os_error(e: &std::io::Error) -> Dispatch {
223 fn dispatch_os_error(e: &std::io::Error) -> Dispatch {
227 Dispatch::Bad(BadMatch::OsError(
224 Dispatch::Bad(BadMatch::OsError(
228 e.raw_os_error().expect("expected real OS error"),
225 e.raw_os_error().expect("expected real OS error"),
229 ))
226 ))
230 }
227 }
231
228
232 lazy_static! {
229 lazy_static! {
233 static ref DEFAULT_WORK: HashSet<&'static HgPath> = {
230 static ref DEFAULT_WORK: HashSet<&'static HgPath> = {
234 let mut h = HashSet::new();
231 let mut h = HashSet::new();
235 h.insert(HgPath::new(b""));
232 h.insert(HgPath::new(b""));
236 h
233 h
237 };
234 };
238 }
235 }
239
236
240 #[derive(Debug, Copy, Clone)]
237 #[derive(Debug, Copy, Clone)]
241 pub struct StatusOptions {
238 pub struct StatusOptions {
242 /// Remember the most recent modification timeslot for status, to make
239 /// Remember the most recent modification timeslot for status, to make
243 /// sure we won't miss future size-preserving file content modifications
240 /// sure we won't miss future size-preserving file content modifications
244 /// that happen within the same timeslot.
241 /// that happen within the same timeslot.
245 pub last_normal_time: i64,
242 pub last_normal_time: i64,
246 /// Whether we are on a filesystem with UNIX-like exec flags
243 /// Whether we are on a filesystem with UNIX-like exec flags
247 pub check_exec: bool,
244 pub check_exec: bool,
248 pub list_clean: bool,
245 pub list_clean: bool,
249 pub list_unknown: bool,
246 pub list_unknown: bool,
250 pub list_ignored: bool,
247 pub list_ignored: bool,
251 /// Whether to collect traversed dirs for applying a callback later.
248 /// Whether to collect traversed dirs for applying a callback later.
252 /// Used by `hg purge` for example.
249 /// Used by `hg purge` for example.
253 pub collect_traversed_dirs: bool,
250 pub collect_traversed_dirs: bool,
254 }
251 }
255
252
256 #[derive(Debug, Default)]
253 #[derive(Debug, Default)]
257 pub struct DirstateStatus<'a> {
254 pub struct DirstateStatus<'a> {
258 /// Tracked files whose contents have changed since the parent revision
255 /// Tracked files whose contents have changed since the parent revision
259 pub modified: Vec<HgPathCow<'a>>,
256 pub modified: Vec<HgPathCow<'a>>,
260
257
261 /// Newly-tracked files that were not present in the parent
258 /// Newly-tracked files that were not present in the parent
262 pub added: Vec<HgPathCow<'a>>,
259 pub added: Vec<HgPathCow<'a>>,
263
260
264 /// Previously-tracked files that have been (re)moved with an hg command
261 /// Previously-tracked files that have been (re)moved with an hg command
265 pub removed: Vec<HgPathCow<'a>>,
262 pub removed: Vec<HgPathCow<'a>>,
266
263
267 /// (Still) tracked files that are missing, (re)moved with an non-hg
264 /// (Still) tracked files that are missing, (re)moved with an non-hg
268 /// command
265 /// command
269 pub deleted: Vec<HgPathCow<'a>>,
266 pub deleted: Vec<HgPathCow<'a>>,
270
267
271 /// Tracked files that are up to date with the parent.
268 /// Tracked files that are up to date with the parent.
272 /// Only pupulated if `StatusOptions::list_clean` is true.
269 /// Only pupulated if `StatusOptions::list_clean` is true.
273 pub clean: Vec<HgPathCow<'a>>,
270 pub clean: Vec<HgPathCow<'a>>,
274
271
275 /// Files in the working directory that are ignored with `.hgignore`.
272 /// Files in the working directory that are ignored with `.hgignore`.
276 /// Only pupulated if `StatusOptions::list_ignored` is true.
273 /// Only pupulated if `StatusOptions::list_ignored` is true.
277 pub ignored: Vec<HgPathCow<'a>>,
274 pub ignored: Vec<HgPathCow<'a>>,
278
275
279 /// Files in the working directory that are neither tracked nor ignored.
276 /// Files in the working directory that are neither tracked nor ignored.
280 /// Only pupulated if `StatusOptions::list_unknown` is true.
277 /// Only pupulated if `StatusOptions::list_unknown` is true.
281 pub unknown: Vec<HgPathCow<'a>>,
278 pub unknown: Vec<HgPathCow<'a>>,
282
279
283 /// Was explicitly matched but cannot be found/accessed
280 /// Was explicitly matched but cannot be found/accessed
284 pub bad: Vec<(HgPathCow<'a>, BadMatch)>,
281 pub bad: Vec<(HgPathCow<'a>, BadMatch)>,
285
282
286 /// Either clean or modified, but we can’t tell from filesystem metadata
283 /// Either clean or modified, but we can’t tell from filesystem metadata
287 /// alone. The file contents need to be read and compared with that in
284 /// alone. The file contents need to be read and compared with that in
288 /// the parent.
285 /// the parent.
289 pub unsure: Vec<HgPathCow<'a>>,
286 pub unsure: Vec<HgPathCow<'a>>,
290
287
291 /// Only filled if `collect_traversed_dirs` is `true`
288 /// Only filled if `collect_traversed_dirs` is `true`
292 pub traversed: Vec<HgPathCow<'a>>,
289 pub traversed: Vec<HgPathCow<'a>>,
293
290
294 /// Whether `status()` made changed to the `DirstateMap` that should be
291 /// Whether `status()` made changed to the `DirstateMap` that should be
295 /// written back to disk
292 /// written back to disk
296 pub dirty: bool,
293 pub dirty: bool,
297 }
294 }
298
295
299 #[derive(Debug, derive_more::From)]
296 #[derive(Debug, derive_more::From)]
300 pub enum StatusError {
297 pub enum StatusError {
301 /// Generic IO error
298 /// Generic IO error
302 IO(std::io::Error),
299 IO(std::io::Error),
303 /// An invalid path that cannot be represented in Mercurial was found
300 /// An invalid path that cannot be represented in Mercurial was found
304 Path(HgPathError),
301 Path(HgPathError),
305 /// An invalid "ignore" pattern was found
302 /// An invalid "ignore" pattern was found
306 Pattern(PatternError),
303 Pattern(PatternError),
307 /// Corrupted dirstate
304 /// Corrupted dirstate
308 DirstateV2ParseError(DirstateV2ParseError),
305 DirstateV2ParseError(DirstateV2ParseError),
309 }
306 }
310
307
311 pub type StatusResult<T> = Result<T, StatusError>;
308 pub type StatusResult<T> = Result<T, StatusError>;
312
309
313 impl fmt::Display for StatusError {
310 impl fmt::Display for StatusError {
314 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
311 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
315 match self {
312 match self {
316 StatusError::IO(error) => error.fmt(f),
313 StatusError::IO(error) => error.fmt(f),
317 StatusError::Path(error) => error.fmt(f),
314 StatusError::Path(error) => error.fmt(f),
318 StatusError::Pattern(error) => error.fmt(f),
315 StatusError::Pattern(error) => error.fmt(f),
319 StatusError::DirstateV2ParseError(_) => {
316 StatusError::DirstateV2ParseError(_) => {
320 f.write_str("dirstate-v2 parse error")
317 f.write_str("dirstate-v2 parse error")
321 }
318 }
322 }
319 }
323 }
320 }
324 }
321 }
325
322
326 /// Gives information about which files are changed in the working directory
323 /// Gives information about which files are changed in the working directory
327 /// and how, compared to the revision we're based on
324 /// and how, compared to the revision we're based on
328 pub struct Status<'a, M: ?Sized + Matcher + Sync> {
325 pub struct Status<'a, M: ?Sized + Matcher + Sync> {
329 dmap: &'a DirstateMap,
326 dmap: &'a DirstateMap,
330 pub(crate) matcher: &'a M,
327 pub(crate) matcher: &'a M,
331 root_dir: PathBuf,
328 root_dir: PathBuf,
332 pub(crate) options: StatusOptions,
329 pub(crate) options: StatusOptions,
333 ignore_fn: IgnoreFnType<'a>,
330 ignore_fn: IgnoreFnType<'a>,
334 }
331 }
335
332
336 impl<'a, M> Status<'a, M>
333 impl<'a, M> Status<'a, M>
337 where
334 where
338 M: ?Sized + Matcher + Sync,
335 M: ?Sized + Matcher + Sync,
339 {
336 {
340 pub fn new(
337 pub fn new(
341 dmap: &'a DirstateMap,
338 dmap: &'a DirstateMap,
342 matcher: &'a M,
339 matcher: &'a M,
343 root_dir: PathBuf,
340 root_dir: PathBuf,
344 ignore_files: Vec<PathBuf>,
341 ignore_files: Vec<PathBuf>,
345 options: StatusOptions,
342 options: StatusOptions,
346 ) -> StatusResult<(Self, Vec<PatternFileWarning>)> {
343 ) -> StatusResult<(Self, Vec<PatternFileWarning>)> {
347 // Needs to outlive `dir_ignore_fn` since it's captured.
344 // Needs to outlive `dir_ignore_fn` since it's captured.
348
345
349 let (ignore_fn, warnings): (IgnoreFnType, _) =
346 let (ignore_fn, warnings): (IgnoreFnType, _) =
350 if options.list_ignored || options.list_unknown {
347 if options.list_ignored || options.list_unknown {
351 get_ignore_function(ignore_files, &root_dir, &mut |_| {})?
348 get_ignore_function(ignore_files, &root_dir, &mut |_| {})?
352 } else {
349 } else {
353 (Box::new(|&_| true), vec![])
350 (Box::new(|&_| true), vec![])
354 };
351 };
355
352
356 Ok((
353 Ok((
357 Self {
354 Self {
358 dmap,
355 dmap,
359 matcher,
356 matcher,
360 root_dir,
357 root_dir,
361 options,
358 options,
362 ignore_fn,
359 ignore_fn,
363 },
360 },
364 warnings,
361 warnings,
365 ))
362 ))
366 }
363 }
367
364
368 /// Is the path ignored?
365 /// Is the path ignored?
369 pub fn is_ignored(&self, path: impl AsRef<HgPath>) -> bool {
366 pub fn is_ignored(&self, path: impl AsRef<HgPath>) -> bool {
370 (self.ignore_fn)(path.as_ref())
367 (self.ignore_fn)(path.as_ref())
371 }
368 }
372
369
373 /// Is the path or one of its ancestors ignored?
370 /// Is the path or one of its ancestors ignored?
374 pub fn dir_ignore(&self, dir: impl AsRef<HgPath>) -> bool {
371 pub fn dir_ignore(&self, dir: impl AsRef<HgPath>) -> bool {
375 // Only involve ignore mechanism if we're listing unknowns or ignored.
372 // Only involve ignore mechanism if we're listing unknowns or ignored.
376 if self.options.list_ignored || self.options.list_unknown {
373 if self.options.list_ignored || self.options.list_unknown {
377 if self.is_ignored(&dir) {
374 if self.is_ignored(&dir) {
378 true
375 true
379 } else {
376 } else {
380 for p in find_dirs(dir.as_ref()) {
377 for p in find_dirs(dir.as_ref()) {
381 if self.is_ignored(p) {
378 if self.is_ignored(p) {
382 return true;
379 return true;
383 }
380 }
384 }
381 }
385 false
382 false
386 }
383 }
387 } else {
384 } else {
388 true
385 true
389 }
386 }
390 }
387 }
391
388
392 /// Get stat data about the files explicitly specified by the matcher.
389 /// Get stat data about the files explicitly specified by the matcher.
393 /// Returns a tuple of the directories that need to be traversed and the
390 /// Returns a tuple of the directories that need to be traversed and the
394 /// files with their corresponding `Dispatch`.
391 /// files with their corresponding `Dispatch`.
395 /// TODO subrepos
392 /// TODO subrepos
396 #[timed]
393 #[timed]
397 pub fn walk_explicit(
394 pub fn walk_explicit(
398 &self,
395 &self,
399 traversed_sender: crossbeam_channel::Sender<HgPathBuf>,
396 traversed_sender: crossbeam_channel::Sender<HgPathBuf>,
400 ) -> (Vec<DispatchedPath<'a>>, Vec<DispatchedPath<'a>>) {
397 ) -> (Vec<DispatchedPath<'a>>, Vec<DispatchedPath<'a>>) {
401 self.matcher
398 self.matcher
402 .file_set()
399 .file_set()
403 .unwrap_or(&DEFAULT_WORK)
400 .unwrap_or(&DEFAULT_WORK)
404 .par_iter()
401 .par_iter()
405 .flat_map(|&filename| -> Option<_> {
402 .flat_map(|&filename| -> Option<_> {
406 // TODO normalization
403 // TODO normalization
407 let normalized = filename;
404 let normalized = filename;
408
405
409 let buf = match hg_path_to_path_buf(normalized) {
406 let buf = match hg_path_to_path_buf(normalized) {
410 Ok(x) => x,
407 Ok(x) => x,
411 Err(_) => {
408 Err(_) => {
412 return Some((
409 return Some((
413 Cow::Borrowed(normalized),
410 Cow::Borrowed(normalized),
414 INVALID_PATH_DISPATCH,
411 INVALID_PATH_DISPATCH,
415 ))
412 ))
416 }
413 }
417 };
414 };
418 let target = self.root_dir.join(buf);
415 let target = self.root_dir.join(buf);
419 let st = target.symlink_metadata();
416 let st = target.symlink_metadata();
420 let in_dmap = self.dmap.get(normalized);
417 let in_dmap = self.dmap.get(normalized);
421 match st {
418 match st {
422 Ok(meta) => {
419 Ok(meta) => {
423 let file_type = meta.file_type();
420 let file_type = meta.file_type();
424 return if file_type.is_file() || file_type.is_symlink()
421 return if file_type.is_file() || file_type.is_symlink()
425 {
422 {
426 if let Some(entry) = in_dmap {
423 if let Some(entry) = in_dmap {
427 return Some((
424 return Some((
428 Cow::Borrowed(normalized),
425 Cow::Borrowed(normalized),
429 dispatch_found(
426 dispatch_found(
430 &normalized,
427 &normalized,
431 *entry,
428 *entry,
432 HgMetadata::from_metadata(meta),
429 HgMetadata::from_metadata(meta),
433 &self.dmap.copy_map,
430 &self.dmap.copy_map,
434 self.options,
431 self.options,
435 ),
432 ),
436 ));
433 ));
437 }
434 }
438 Some((
435 Some((
439 Cow::Borrowed(normalized),
436 Cow::Borrowed(normalized),
440 Dispatch::Unknown,
437 Dispatch::Unknown,
441 ))
438 ))
442 } else if file_type.is_dir() {
439 } else if file_type.is_dir() {
443 if self.options.collect_traversed_dirs {
440 if self.options.collect_traversed_dirs {
444 traversed_sender
441 traversed_sender
445 .send(normalized.to_owned())
442 .send(normalized.to_owned())
446 .expect("receiver should outlive sender");
443 .expect("receiver should outlive sender");
447 }
444 }
448 Some((
445 Some((
449 Cow::Borrowed(normalized),
446 Cow::Borrowed(normalized),
450 Dispatch::Directory {
447 Dispatch::Directory {
451 was_file: in_dmap.is_some(),
448 was_file: in_dmap.is_some(),
452 },
449 },
453 ))
450 ))
454 } else {
451 } else {
455 Some((
452 Some((
456 Cow::Borrowed(normalized),
453 Cow::Borrowed(normalized),
457 Dispatch::Bad(BadMatch::BadType(
454 Dispatch::Bad(BadMatch::BadType(
458 // TODO do more than unknown
455 // TODO do more than unknown
459 // Support for all `BadType` variant
456 // Support for all `BadType` variant
460 // varies greatly between platforms.
457 // varies greatly between platforms.
461 // So far, no tests check the type and
458 // So far, no tests check the type and
462 // this should be good enough for most
459 // this should be good enough for most
463 // users.
460 // users.
464 BadType::Unknown,
461 BadType::Unknown,
465 )),
462 )),
466 ))
463 ))
467 };
464 };
468 }
465 }
469 Err(_) => {
466 Err(_) => {
470 if let Some(entry) = in_dmap {
467 if let Some(entry) = in_dmap {
471 return Some((
468 return Some((
472 Cow::Borrowed(normalized),
469 Cow::Borrowed(normalized),
473 dispatch_missing(entry.state()),
470 dispatch_missing(entry.state()),
474 ));
471 ));
475 }
472 }
476 }
473 }
477 };
474 };
478 None
475 None
479 })
476 })
480 .partition(|(_, dispatch)| match dispatch {
477 .partition(|(_, dispatch)| match dispatch {
481 Dispatch::Directory { .. } => true,
478 Dispatch::Directory { .. } => true,
482 _ => false,
479 _ => false,
483 })
480 })
484 }
481 }
485
482
486 /// Walk the working directory recursively to look for changes compared to
483 /// Walk the working directory recursively to look for changes compared to
487 /// the current `DirstateMap`.
484 /// the current `DirstateMap`.
488 ///
485 ///
489 /// This takes a mutable reference to the results to account for the
486 /// This takes a mutable reference to the results to account for the
490 /// `extend` in timings
487 /// `extend` in timings
491 #[timed]
488 #[timed]
492 pub fn traverse(
489 pub fn traverse(
493 &self,
490 &self,
494 path: impl AsRef<HgPath>,
491 path: impl AsRef<HgPath>,
495 old_results: &FastHashMap<HgPathCow<'a>, Dispatch>,
492 old_results: &FastHashMap<HgPathCow<'a>, Dispatch>,
496 results: &mut Vec<DispatchedPath<'a>>,
493 results: &mut Vec<DispatchedPath<'a>>,
497 traversed_sender: crossbeam_channel::Sender<HgPathBuf>,
494 traversed_sender: crossbeam_channel::Sender<HgPathBuf>,
498 ) {
495 ) {
499 // The traversal is done in parallel, so use a channel to gather
496 // The traversal is done in parallel, so use a channel to gather
500 // entries. `crossbeam_channel::Sender` is `Sync`, while `mpsc::Sender`
497 // entries. `crossbeam_channel::Sender` is `Sync`, while `mpsc::Sender`
501 // is not.
498 // is not.
502 let (files_transmitter, files_receiver) =
499 let (files_transmitter, files_receiver) =
503 crossbeam_channel::unbounded();
500 crossbeam_channel::unbounded();
504
501
505 self.traverse_dir(
502 self.traverse_dir(
506 &files_transmitter,
503 &files_transmitter,
507 path,
504 path,
508 &old_results,
505 &old_results,
509 traversed_sender,
506 traversed_sender,
510 );
507 );
511
508
512 // Disconnect the channel so the receiver stops waiting
509 // Disconnect the channel so the receiver stops waiting
513 drop(files_transmitter);
510 drop(files_transmitter);
514
511
515 let new_results = files_receiver
512 let new_results = files_receiver
516 .into_iter()
513 .into_iter()
517 .par_bridge()
514 .par_bridge()
518 .map(|(f, d)| (Cow::Owned(f), d));
515 .map(|(f, d)| (Cow::Owned(f), d));
519
516
520 results.par_extend(new_results);
517 results.par_extend(new_results);
521 }
518 }
522
519
523 /// Dispatch a single entry (file, folder, symlink...) found during
520 /// Dispatch a single entry (file, folder, symlink...) found during
524 /// `traverse`. If the entry is a folder that needs to be traversed, it
521 /// `traverse`. If the entry is a folder that needs to be traversed, it
525 /// will be handled in a separate thread.
522 /// will be handled in a separate thread.
526 fn handle_traversed_entry<'b>(
523 fn handle_traversed_entry<'b>(
527 &'a self,
524 &'a self,
528 scope: &rayon::Scope<'b>,
525 scope: &rayon::Scope<'b>,
529 files_sender: &'b crossbeam_channel::Sender<(HgPathBuf, Dispatch)>,
526 files_sender: &'b crossbeam_channel::Sender<(HgPathBuf, Dispatch)>,
530 old_results: &'a FastHashMap<Cow<HgPath>, Dispatch>,
527 old_results: &'a FastHashMap<Cow<HgPath>, Dispatch>,
531 filename: HgPathBuf,
528 filename: HgPathBuf,
532 dir_entry: DirEntry,
529 dir_entry: DirEntry,
533 traversed_sender: crossbeam_channel::Sender<HgPathBuf>,
530 traversed_sender: crossbeam_channel::Sender<HgPathBuf>,
534 ) -> IoResult<()>
531 ) -> IoResult<()>
535 where
532 where
536 'a: 'b,
533 'a: 'b,
537 {
534 {
538 let file_type = dir_entry.file_type()?;
535 let file_type = dir_entry.file_type()?;
539 let entry_option = self.dmap.get(&filename);
536 let entry_option = self.dmap.get(&filename);
540
537
541 if filename.as_bytes() == b".hg" {
538 if filename.as_bytes() == b".hg" {
542 // Could be a directory or a symlink
539 // Could be a directory or a symlink
543 return Ok(());
540 return Ok(());
544 }
541 }
545
542
546 if file_type.is_dir() {
543 if file_type.is_dir() {
547 self.handle_traversed_dir(
544 self.handle_traversed_dir(
548 scope,
545 scope,
549 files_sender,
546 files_sender,
550 old_results,
547 old_results,
551 entry_option,
548 entry_option,
552 filename,
549 filename,
553 traversed_sender,
550 traversed_sender,
554 );
551 );
555 } else if file_type.is_file() || file_type.is_symlink() {
552 } else if file_type.is_file() || file_type.is_symlink() {
556 if let Some(entry) = entry_option {
553 if let Some(entry) = entry_option {
557 if self.matcher.matches_everything()
554 if self.matcher.matches_everything()
558 || self.matcher.matches(&filename)
555 || self.matcher.matches(&filename)
559 {
556 {
560 let metadata = dir_entry.metadata()?;
557 let metadata = dir_entry.metadata()?;
561 files_sender
558 files_sender
562 .send((
559 .send((
563 filename.to_owned(),
560 filename.to_owned(),
564 dispatch_found(
561 dispatch_found(
565 &filename,
562 &filename,
566 *entry,
563 *entry,
567 HgMetadata::from_metadata(metadata),
564 HgMetadata::from_metadata(metadata),
568 &self.dmap.copy_map,
565 &self.dmap.copy_map,
569 self.options,
566 self.options,
570 ),
567 ),
571 ))
568 ))
572 .unwrap();
569 .unwrap();
573 }
570 }
574 } else if (self.matcher.matches_everything()
571 } else if (self.matcher.matches_everything()
575 || self.matcher.matches(&filename))
572 || self.matcher.matches(&filename))
576 && !self.is_ignored(&filename)
573 && !self.is_ignored(&filename)
577 {
574 {
578 if (self.options.list_ignored
575 if (self.options.list_ignored
579 || self.matcher.exact_match(&filename))
576 || self.matcher.exact_match(&filename))
580 && self.dir_ignore(&filename)
577 && self.dir_ignore(&filename)
581 {
578 {
582 if self.options.list_ignored {
579 if self.options.list_ignored {
583 files_sender
580 files_sender
584 .send((filename.to_owned(), Dispatch::Ignored))
581 .send((filename.to_owned(), Dispatch::Ignored))
585 .unwrap();
582 .unwrap();
586 }
583 }
587 } else if self.options.list_unknown {
584 } else if self.options.list_unknown {
588 files_sender
585 files_sender
589 .send((filename.to_owned(), Dispatch::Unknown))
586 .send((filename.to_owned(), Dispatch::Unknown))
590 .unwrap();
587 .unwrap();
591 }
588 }
592 } else if self.is_ignored(&filename) && self.options.list_ignored {
589 } else if self.is_ignored(&filename) && self.options.list_ignored {
593 if self.matcher.matches(&filename) {
590 if self.matcher.matches(&filename) {
594 files_sender
591 files_sender
595 .send((filename.to_owned(), Dispatch::Ignored))
592 .send((filename.to_owned(), Dispatch::Ignored))
596 .unwrap();
593 .unwrap();
597 }
594 }
598 }
595 }
599 } else if let Some(entry) = entry_option {
596 } else if let Some(entry) = entry_option {
600 // Used to be a file or a folder, now something else.
597 // Used to be a file or a folder, now something else.
601 if self.matcher.matches_everything()
598 if self.matcher.matches_everything()
602 || self.matcher.matches(&filename)
599 || self.matcher.matches(&filename)
603 {
600 {
604 files_sender
601 files_sender
605 .send((
602 .send((
606 filename.to_owned(),
603 filename.to_owned(),
607 dispatch_missing(entry.state()),
604 dispatch_missing(entry.state()),
608 ))
605 ))
609 .unwrap();
606 .unwrap();
610 }
607 }
611 }
608 }
612
609
613 Ok(())
610 Ok(())
614 }
611 }
615
612
616 /// A directory was found in the filesystem and needs to be traversed
613 /// A directory was found in the filesystem and needs to be traversed
617 fn handle_traversed_dir<'b>(
614 fn handle_traversed_dir<'b>(
618 &'a self,
615 &'a self,
619 scope: &rayon::Scope<'b>,
616 scope: &rayon::Scope<'b>,
620 files_sender: &'b crossbeam_channel::Sender<(HgPathBuf, Dispatch)>,
617 files_sender: &'b crossbeam_channel::Sender<(HgPathBuf, Dispatch)>,
621 old_results: &'a FastHashMap<Cow<HgPath>, Dispatch>,
618 old_results: &'a FastHashMap<Cow<HgPath>, Dispatch>,
622 entry_option: Option<&'a DirstateEntry>,
619 entry_option: Option<&'a DirstateEntry>,
623 directory: HgPathBuf,
620 directory: HgPathBuf,
624 traversed_sender: crossbeam_channel::Sender<HgPathBuf>,
621 traversed_sender: crossbeam_channel::Sender<HgPathBuf>,
625 ) where
622 ) where
626 'a: 'b,
623 'a: 'b,
627 {
624 {
628 scope.spawn(move |_| {
625 scope.spawn(move |_| {
629 // Nested `if` until `rust-lang/rust#53668` is stable
626 // Nested `if` until `rust-lang/rust#53668` is stable
630 if let Some(entry) = entry_option {
627 if let Some(entry) = entry_option {
631 // Used to be a file, is now a folder
628 // Used to be a file, is now a folder
632 if self.matcher.matches_everything()
629 if self.matcher.matches_everything()
633 || self.matcher.matches(&directory)
630 || self.matcher.matches(&directory)
634 {
631 {
635 files_sender
632 files_sender
636 .send((
633 .send((
637 directory.to_owned(),
634 directory.to_owned(),
638 dispatch_missing(entry.state()),
635 dispatch_missing(entry.state()),
639 ))
636 ))
640 .unwrap();
637 .unwrap();
641 }
638 }
642 }
639 }
643 // Do we need to traverse it?
640 // Do we need to traverse it?
644 if !self.is_ignored(&directory) || self.options.list_ignored {
641 if !self.is_ignored(&directory) || self.options.list_ignored {
645 self.traverse_dir(
642 self.traverse_dir(
646 files_sender,
643 files_sender,
647 directory,
644 directory,
648 &old_results,
645 &old_results,
649 traversed_sender,
646 traversed_sender,
650 )
647 )
651 }
648 }
652 });
649 });
653 }
650 }
654
651
655 /// Decides whether the directory needs to be listed, and if so handles the
652 /// Decides whether the directory needs to be listed, and if so handles the
656 /// entries in a separate thread.
653 /// entries in a separate thread.
657 fn traverse_dir(
654 fn traverse_dir(
658 &self,
655 &self,
659 files_sender: &crossbeam_channel::Sender<(HgPathBuf, Dispatch)>,
656 files_sender: &crossbeam_channel::Sender<(HgPathBuf, Dispatch)>,
660 directory: impl AsRef<HgPath>,
657 directory: impl AsRef<HgPath>,
661 old_results: &FastHashMap<Cow<HgPath>, Dispatch>,
658 old_results: &FastHashMap<Cow<HgPath>, Dispatch>,
662 traversed_sender: crossbeam_channel::Sender<HgPathBuf>,
659 traversed_sender: crossbeam_channel::Sender<HgPathBuf>,
663 ) {
660 ) {
664 let directory = directory.as_ref();
661 let directory = directory.as_ref();
665
662
666 if self.options.collect_traversed_dirs {
663 if self.options.collect_traversed_dirs {
667 traversed_sender
664 traversed_sender
668 .send(directory.to_owned())
665 .send(directory.to_owned())
669 .expect("receiver should outlive sender");
666 .expect("receiver should outlive sender");
670 }
667 }
671
668
672 let visit_entries = match self.matcher.visit_children_set(directory) {
669 let visit_entries = match self.matcher.visit_children_set(directory) {
673 VisitChildrenSet::Empty => return,
670 VisitChildrenSet::Empty => return,
674 VisitChildrenSet::This | VisitChildrenSet::Recursive => None,
671 VisitChildrenSet::This | VisitChildrenSet::Recursive => None,
675 VisitChildrenSet::Set(set) => Some(set),
672 VisitChildrenSet::Set(set) => Some(set),
676 };
673 };
677 let buf = match hg_path_to_path_buf(directory) {
674 let buf = match hg_path_to_path_buf(directory) {
678 Ok(b) => b,
675 Ok(b) => b,
679 Err(_) => {
676 Err(_) => {
680 files_sender
677 files_sender
681 .send((directory.to_owned(), INVALID_PATH_DISPATCH))
678 .send((directory.to_owned(), INVALID_PATH_DISPATCH))
682 .expect("receiver should outlive sender");
679 .expect("receiver should outlive sender");
683 return;
680 return;
684 }
681 }
685 };
682 };
686 let dir_path = self.root_dir.join(buf);
683 let dir_path = self.root_dir.join(buf);
687
684
688 let skip_dot_hg = !directory.as_bytes().is_empty();
685 let skip_dot_hg = !directory.as_bytes().is_empty();
689 let entries = match list_directory(dir_path, skip_dot_hg) {
686 let entries = match list_directory(dir_path, skip_dot_hg) {
690 Err(e) => {
687 Err(e) => {
691 files_sender
688 files_sender
692 .send((directory.to_owned(), dispatch_os_error(&e)))
689 .send((directory.to_owned(), dispatch_os_error(&e)))
693 .expect("receiver should outlive sender");
690 .expect("receiver should outlive sender");
694 return;
691 return;
695 }
692 }
696 Ok(entries) => entries,
693 Ok(entries) => entries,
697 };
694 };
698
695
699 rayon::scope(|scope| {
696 rayon::scope(|scope| {
700 for (filename, dir_entry) in entries {
697 for (filename, dir_entry) in entries {
701 if let Some(ref set) = visit_entries {
698 if let Some(ref set) = visit_entries {
702 if !set.contains(filename.deref()) {
699 if !set.contains(filename.deref()) {
703 continue;
700 continue;
704 }
701 }
705 }
702 }
706 // TODO normalize
703 // TODO normalize
707 let filename = if directory.is_empty() {
704 let filename = if directory.is_empty() {
708 filename.to_owned()
705 filename.to_owned()
709 } else {
706 } else {
710 directory.join(&filename)
707 directory.join(&filename)
711 };
708 };
712
709
713 if !old_results.contains_key(filename.deref()) {
710 if !old_results.contains_key(filename.deref()) {
714 match self.handle_traversed_entry(
711 match self.handle_traversed_entry(
715 scope,
712 scope,
716 files_sender,
713 files_sender,
717 old_results,
714 old_results,
718 filename,
715 filename,
719 dir_entry,
716 dir_entry,
720 traversed_sender.clone(),
717 traversed_sender.clone(),
721 ) {
718 ) {
722 Err(e) => {
719 Err(e) => {
723 files_sender
720 files_sender
724 .send((
721 .send((
725 directory.to_owned(),
722 directory.to_owned(),
726 dispatch_os_error(&e),
723 dispatch_os_error(&e),
727 ))
724 ))
728 .expect("receiver should outlive sender");
725 .expect("receiver should outlive sender");
729 }
726 }
730 Ok(_) => {}
727 Ok(_) => {}
731 }
728 }
732 }
729 }
733 }
730 }
734 })
731 })
735 }
732 }
736
733
737 /// Add the files in the dirstate to the results.
734 /// Add the files in the dirstate to the results.
738 ///
735 ///
739 /// This takes a mutable reference to the results to account for the
736 /// This takes a mutable reference to the results to account for the
740 /// `extend` in timings
737 /// `extend` in timings
741 #[timed]
738 #[timed]
742 pub fn extend_from_dmap(&self, results: &mut Vec<DispatchedPath<'a>>) {
739 pub fn extend_from_dmap(&self, results: &mut Vec<DispatchedPath<'a>>) {
743 results.par_extend(
740 results.par_extend(
744 self.dmap
741 self.dmap
745 .par_iter()
742 .par_iter()
746 .filter(|(path, _)| self.matcher.matches(path))
743 .filter(|(path, _)| self.matcher.matches(path))
747 .map(move |(filename, entry)| {
744 .map(move |(filename, entry)| {
748 let filename: &HgPath = filename;
745 let filename: &HgPath = filename;
749 let filename_as_path = match hg_path_to_path_buf(filename)
746 let filename_as_path = match hg_path_to_path_buf(filename)
750 {
747 {
751 Ok(f) => f,
748 Ok(f) => f,
752 Err(_) => {
749 Err(_) => {
753 return (
750 return (
754 Cow::Borrowed(filename),
751 Cow::Borrowed(filename),
755 INVALID_PATH_DISPATCH,
752 INVALID_PATH_DISPATCH,
756 )
753 )
757 }
754 }
758 };
755 };
759 let meta = self
756 let meta = self
760 .root_dir
757 .root_dir
761 .join(filename_as_path)
758 .join(filename_as_path)
762 .symlink_metadata();
759 .symlink_metadata();
763 match meta {
760 match meta {
764 Ok(m)
761 Ok(m)
765 if !(m.file_type().is_file()
762 if !(m.file_type().is_file()
766 || m.file_type().is_symlink()) =>
763 || m.file_type().is_symlink()) =>
767 {
764 {
768 (
765 (
769 Cow::Borrowed(filename),
766 Cow::Borrowed(filename),
770 dispatch_missing(entry.state()),
767 dispatch_missing(entry.state()),
771 )
768 )
772 }
769 }
773 Ok(m) => (
770 Ok(m) => (
774 Cow::Borrowed(filename),
771 Cow::Borrowed(filename),
775 dispatch_found(
772 dispatch_found(
776 filename,
773 filename,
777 *entry,
774 *entry,
778 HgMetadata::from_metadata(m),
775 HgMetadata::from_metadata(m),
779 &self.dmap.copy_map,
776 &self.dmap.copy_map,
780 self.options,
777 self.options,
781 ),
778 ),
782 ),
779 ),
783 Err(e)
780 Err(e)
784 if e.kind() == ErrorKind::NotFound
781 if e.kind() == ErrorKind::NotFound
785 || e.raw_os_error() == Some(20) =>
782 || e.raw_os_error() == Some(20) =>
786 {
783 {
787 // Rust does not yet have an `ErrorKind` for
784 // Rust does not yet have an `ErrorKind` for
788 // `NotADirectory` (errno 20)
785 // `NotADirectory` (errno 20)
789 // It happens if the dirstate contains `foo/bar`
786 // It happens if the dirstate contains `foo/bar`
790 // and foo is not a
787 // and foo is not a
791 // directory
788 // directory
792 (
789 (
793 Cow::Borrowed(filename),
790 Cow::Borrowed(filename),
794 dispatch_missing(entry.state()),
791 dispatch_missing(entry.state()),
795 )
792 )
796 }
793 }
797 Err(e) => {
794 Err(e) => {
798 (Cow::Borrowed(filename), dispatch_os_error(&e))
795 (Cow::Borrowed(filename), dispatch_os_error(&e))
799 }
796 }
800 }
797 }
801 }),
798 }),
802 );
799 );
803 }
800 }
804
801
805 /// Checks all files that are in the dirstate but were not found during the
802 /// Checks all files that are in the dirstate but were not found during the
806 /// working directory traversal. This means that the rest must
803 /// working directory traversal. This means that the rest must
807 /// be either ignored, under a symlink or under a new nested repo.
804 /// be either ignored, under a symlink or under a new nested repo.
808 ///
805 ///
809 /// This takes a mutable reference to the results to account for the
806 /// This takes a mutable reference to the results to account for the
810 /// `extend` in timings
807 /// `extend` in timings
811 #[timed]
808 #[timed]
812 pub fn handle_unknowns(&self, results: &mut Vec<DispatchedPath<'a>>) {
809 pub fn handle_unknowns(&self, results: &mut Vec<DispatchedPath<'a>>) {
813 let to_visit: Vec<(&HgPath, &DirstateEntry)> =
810 let to_visit: Vec<(&HgPath, &DirstateEntry)> =
814 if results.is_empty() && self.matcher.matches_everything() {
811 if results.is_empty() && self.matcher.matches_everything() {
815 self.dmap.iter().map(|(f, e)| (f.deref(), e)).collect()
812 self.dmap.iter().map(|(f, e)| (f.deref(), e)).collect()
816 } else {
813 } else {
817 // Only convert to a hashmap if needed.
814 // Only convert to a hashmap if needed.
818 let old_results: FastHashMap<_, _> =
815 let old_results: FastHashMap<_, _> =
819 results.iter().cloned().collect();
816 results.iter().cloned().collect();
820 self.dmap
817 self.dmap
821 .iter()
818 .iter()
822 .filter_map(move |(f, e)| {
819 .filter_map(move |(f, e)| {
823 if !old_results.contains_key(f.deref())
820 if !old_results.contains_key(f.deref())
824 && self.matcher.matches(f)
821 && self.matcher.matches(f)
825 {
822 {
826 Some((f.deref(), e))
823 Some((f.deref(), e))
827 } else {
824 } else {
828 None
825 None
829 }
826 }
830 })
827 })
831 .collect()
828 .collect()
832 };
829 };
833
830
834 let path_auditor = PathAuditor::new(&self.root_dir);
831 let path_auditor = PathAuditor::new(&self.root_dir);
835
832
836 let new_results = to_visit.into_par_iter().filter_map(
833 let new_results = to_visit.into_par_iter().filter_map(
837 |(filename, entry)| -> Option<_> {
834 |(filename, entry)| -> Option<_> {
838 // Report ignored items in the dmap as long as they are not
835 // Report ignored items in the dmap as long as they are not
839 // under a symlink directory.
836 // under a symlink directory.
840 if path_auditor.check(filename) {
837 if path_auditor.check(filename) {
841 // TODO normalize for case-insensitive filesystems
838 // TODO normalize for case-insensitive filesystems
842 let buf = match hg_path_to_path_buf(filename) {
839 let buf = match hg_path_to_path_buf(filename) {
843 Ok(x) => x,
840 Ok(x) => x,
844 Err(_) => {
841 Err(_) => {
845 return Some((
842 return Some((
846 Cow::Owned(filename.to_owned()),
843 Cow::Owned(filename.to_owned()),
847 INVALID_PATH_DISPATCH,
844 INVALID_PATH_DISPATCH,
848 ));
845 ));
849 }
846 }
850 };
847 };
851 Some((
848 Some((
852 Cow::Owned(filename.to_owned()),
849 Cow::Owned(filename.to_owned()),
853 match self.root_dir.join(&buf).symlink_metadata() {
850 match self.root_dir.join(&buf).symlink_metadata() {
854 // File was just ignored, no links, and exists
851 // File was just ignored, no links, and exists
855 Ok(meta) => {
852 Ok(meta) => {
856 let metadata = HgMetadata::from_metadata(meta);
853 let metadata = HgMetadata::from_metadata(meta);
857 dispatch_found(
854 dispatch_found(
858 filename,
855 filename,
859 *entry,
856 *entry,
860 metadata,
857 metadata,
861 &self.dmap.copy_map,
858 &self.dmap.copy_map,
862 self.options,
859 self.options,
863 )
860 )
864 }
861 }
865 // File doesn't exist
862 // File doesn't exist
866 Err(_) => dispatch_missing(entry.state()),
863 Err(_) => dispatch_missing(entry.state()),
867 },
864 },
868 ))
865 ))
869 } else {
866 } else {
870 // It's either missing or under a symlink directory which
867 // It's either missing or under a symlink directory which
871 // we, in this case, report as missing.
868 // we, in this case, report as missing.
872 Some((
869 Some((
873 Cow::Owned(filename.to_owned()),
870 Cow::Owned(filename.to_owned()),
874 dispatch_missing(entry.state()),
871 dispatch_missing(entry.state()),
875 ))
872 ))
876 }
873 }
877 },
874 },
878 );
875 );
879
876
880 results.par_extend(new_results);
877 results.par_extend(new_results);
881 }
878 }
882 }
879 }
883
880
884 #[timed]
881 #[timed]
885 pub fn build_response<'a>(
882 pub fn build_response<'a>(
886 results: impl IntoIterator<Item = DispatchedPath<'a>>,
883 results: impl IntoIterator<Item = DispatchedPath<'a>>,
887 traversed: Vec<HgPathCow<'a>>,
884 traversed: Vec<HgPathCow<'a>>,
888 ) -> DirstateStatus<'a> {
885 ) -> DirstateStatus<'a> {
889 let mut unsure = vec![];
886 let mut unsure = vec![];
890 let mut modified = vec![];
887 let mut modified = vec![];
891 let mut added = vec![];
888 let mut added = vec![];
892 let mut removed = vec![];
889 let mut removed = vec![];
893 let mut deleted = vec![];
890 let mut deleted = vec![];
894 let mut clean = vec![];
891 let mut clean = vec![];
895 let mut ignored = vec![];
892 let mut ignored = vec![];
896 let mut unknown = vec![];
893 let mut unknown = vec![];
897 let mut bad = vec![];
894 let mut bad = vec![];
898
895
899 for (filename, dispatch) in results.into_iter() {
896 for (filename, dispatch) in results.into_iter() {
900 match dispatch {
897 match dispatch {
901 Dispatch::Unknown => unknown.push(filename),
898 Dispatch::Unknown => unknown.push(filename),
902 Dispatch::Unsure => unsure.push(filename),
899 Dispatch::Unsure => unsure.push(filename),
903 Dispatch::Modified => modified.push(filename),
900 Dispatch::Modified => modified.push(filename),
904 Dispatch::Added => added.push(filename),
901 Dispatch::Added => added.push(filename),
905 Dispatch::Removed => removed.push(filename),
902 Dispatch::Removed => removed.push(filename),
906 Dispatch::Deleted => deleted.push(filename),
903 Dispatch::Deleted => deleted.push(filename),
907 Dispatch::Clean => clean.push(filename),
904 Dispatch::Clean => clean.push(filename),
908 Dispatch::Ignored => ignored.push(filename),
905 Dispatch::Ignored => ignored.push(filename),
909 Dispatch::None => {}
906 Dispatch::None => {}
910 Dispatch::Bad(reason) => bad.push((filename, reason)),
907 Dispatch::Bad(reason) => bad.push((filename, reason)),
911 Dispatch::Directory { .. } => {}
908 Dispatch::Directory { .. } => {}
912 }
909 }
913 }
910 }
914
911
915 DirstateStatus {
912 DirstateStatus {
916 modified,
913 modified,
917 added,
914 added,
918 removed,
915 removed,
919 deleted,
916 deleted,
920 clean,
917 clean,
921 ignored,
918 ignored,
922 unknown,
919 unknown,
923 bad,
920 bad,
924 unsure,
921 unsure,
925 traversed,
922 traversed,
926 dirty: false,
923 dirty: false,
927 }
924 }
928 }
925 }
929
926
930 /// Get the status of files in the working directory.
927 /// Get the status of files in the working directory.
931 ///
928 ///
932 /// This is the current entry-point for `hg-core` and is realistically unusable
929 /// This is the current entry-point for `hg-core` and is realistically unusable
933 /// outside of a Python context because its arguments need to provide a lot of
930 /// outside of a Python context because its arguments need to provide a lot of
934 /// information that will not be necessary in the future.
931 /// information that will not be necessary in the future.
935 #[timed]
932 #[timed]
936 pub fn status<'a>(
933 pub fn status<'a>(
937 dmap: &'a DirstateMap,
934 dmap: &'a DirstateMap,
938 matcher: &'a (dyn Matcher + Sync),
935 matcher: &'a (dyn Matcher + Sync),
939 root_dir: PathBuf,
936 root_dir: PathBuf,
940 ignore_files: Vec<PathBuf>,
937 ignore_files: Vec<PathBuf>,
941 options: StatusOptions,
938 options: StatusOptions,
942 ) -> StatusResult<(DirstateStatus<'a>, Vec<PatternFileWarning>)> {
939 ) -> StatusResult<(DirstateStatus<'a>, Vec<PatternFileWarning>)> {
943 let (status, warnings) =
940 let (status, warnings) =
944 Status::new(dmap, matcher, root_dir, ignore_files, options)?;
941 Status::new(dmap, matcher, root_dir, ignore_files, options)?;
945
942
946 Ok((status.run()?, warnings))
943 Ok((status.run()?, warnings))
947 }
944 }
@@ -1,1315 +1,1309 b''
1 use bytes_cast::BytesCast;
1 use bytes_cast::BytesCast;
2 use micro_timer::timed;
2 use micro_timer::timed;
3 use std::borrow::Cow;
3 use std::borrow::Cow;
4 use std::convert::TryInto;
4 use std::convert::TryInto;
5 use std::path::PathBuf;
5 use std::path::PathBuf;
6
6
7 use super::on_disk;
7 use super::on_disk;
8 use super::on_disk::DirstateV2ParseError;
8 use super::on_disk::DirstateV2ParseError;
9 use super::path_with_basename::WithBasename;
9 use super::path_with_basename::WithBasename;
10 use crate::dirstate::parsers::pack_entry;
10 use crate::dirstate::parsers::pack_entry;
11 use crate::dirstate::parsers::packed_entry_size;
11 use crate::dirstate::parsers::packed_entry_size;
12 use crate::dirstate::parsers::parse_dirstate_entries;
12 use crate::dirstate::parsers::parse_dirstate_entries;
13 use crate::dirstate::parsers::Timestamp;
13 use crate::dirstate::parsers::Timestamp;
14 use crate::dirstate::MTIME_UNSET;
14 use crate::dirstate::MTIME_UNSET;
15 use crate::dirstate::SIZE_FROM_OTHER_PARENT;
15 use crate::dirstate::SIZE_FROM_OTHER_PARENT;
16 use crate::dirstate::SIZE_NON_NORMAL;
16 use crate::dirstate::SIZE_NON_NORMAL;
17 use crate::dirstate::V1_RANGEMASK;
17 use crate::dirstate::V1_RANGEMASK;
18 use crate::matchers::Matcher;
18 use crate::matchers::Matcher;
19 use crate::utils::hg_path::{HgPath, HgPathBuf};
19 use crate::utils::hg_path::{HgPath, HgPathBuf};
20 use crate::CopyMapIter;
20 use crate::CopyMapIter;
21 use crate::DirstateEntry;
21 use crate::DirstateEntry;
22 use crate::DirstateError;
22 use crate::DirstateError;
23 use crate::DirstateParents;
23 use crate::DirstateParents;
24 use crate::DirstateStatus;
24 use crate::DirstateStatus;
25 use crate::EntryState;
25 use crate::EntryState;
26 use crate::FastHashMap;
26 use crate::FastHashMap;
27 use crate::PatternFileWarning;
27 use crate::PatternFileWarning;
28 use crate::StateMapIter;
28 use crate::StateMapIter;
29 use crate::StatusError;
29 use crate::StatusError;
30 use crate::StatusOptions;
30 use crate::StatusOptions;
31
31
32 /// Append to an existing data file if the amount of unreachable data (not used
32 /// Append to an existing data file if the amount of unreachable data (not used
33 /// anymore) is less than this fraction of the total amount of existing data.
33 /// anymore) is less than this fraction of the total amount of existing data.
34 const ACCEPTABLE_UNREACHABLE_BYTES_RATIO: f32 = 0.5;
34 const ACCEPTABLE_UNREACHABLE_BYTES_RATIO: f32 = 0.5;
35
35
36 pub struct DirstateMap<'on_disk> {
36 pub struct DirstateMap<'on_disk> {
37 /// Contents of the `.hg/dirstate` file
37 /// Contents of the `.hg/dirstate` file
38 pub(super) on_disk: &'on_disk [u8],
38 pub(super) on_disk: &'on_disk [u8],
39
39
40 pub(super) root: ChildNodes<'on_disk>,
40 pub(super) root: ChildNodes<'on_disk>,
41
41
42 /// Number of nodes anywhere in the tree that have `.entry.is_some()`.
42 /// Number of nodes anywhere in the tree that have `.entry.is_some()`.
43 pub(super) nodes_with_entry_count: u32,
43 pub(super) nodes_with_entry_count: u32,
44
44
45 /// Number of nodes anywhere in the tree that have
45 /// Number of nodes anywhere in the tree that have
46 /// `.copy_source.is_some()`.
46 /// `.copy_source.is_some()`.
47 pub(super) nodes_with_copy_source_count: u32,
47 pub(super) nodes_with_copy_source_count: u32,
48
48
49 /// See on_disk::Header
49 /// See on_disk::Header
50 pub(super) ignore_patterns_hash: on_disk::IgnorePatternsHash,
50 pub(super) ignore_patterns_hash: on_disk::IgnorePatternsHash,
51
51
52 /// How many bytes of `on_disk` are not used anymore
52 /// How many bytes of `on_disk` are not used anymore
53 pub(super) unreachable_bytes: u32,
53 pub(super) unreachable_bytes: u32,
54 }
54 }
55
55
56 /// Using a plain `HgPathBuf` of the full path from the repository root as a
56 /// Using a plain `HgPathBuf` of the full path from the repository root as a
57 /// map key would also work: all paths in a given map have the same parent
57 /// map key would also work: all paths in a given map have the same parent
58 /// path, so comparing full paths gives the same result as comparing base
58 /// path, so comparing full paths gives the same result as comparing base
59 /// names. However `HashMap` would waste time always re-hashing the same
59 /// names. However `HashMap` would waste time always re-hashing the same
60 /// string prefix.
60 /// string prefix.
61 pub(super) type NodeKey<'on_disk> = WithBasename<Cow<'on_disk, HgPath>>;
61 pub(super) type NodeKey<'on_disk> = WithBasename<Cow<'on_disk, HgPath>>;
62
62
63 /// Similar to `&'tree Cow<'on_disk, HgPath>`, but can also be returned
63 /// Similar to `&'tree Cow<'on_disk, HgPath>`, but can also be returned
64 /// for on-disk nodes that don’t actually have a `Cow` to borrow.
64 /// for on-disk nodes that don’t actually have a `Cow` to borrow.
65 pub(super) enum BorrowedPath<'tree, 'on_disk> {
65 pub(super) enum BorrowedPath<'tree, 'on_disk> {
66 InMemory(&'tree HgPathBuf),
66 InMemory(&'tree HgPathBuf),
67 OnDisk(&'on_disk HgPath),
67 OnDisk(&'on_disk HgPath),
68 }
68 }
69
69
70 pub(super) enum ChildNodes<'on_disk> {
70 pub(super) enum ChildNodes<'on_disk> {
71 InMemory(FastHashMap<NodeKey<'on_disk>, Node<'on_disk>>),
71 InMemory(FastHashMap<NodeKey<'on_disk>, Node<'on_disk>>),
72 OnDisk(&'on_disk [on_disk::Node]),
72 OnDisk(&'on_disk [on_disk::Node]),
73 }
73 }
74
74
75 pub(super) enum ChildNodesRef<'tree, 'on_disk> {
75 pub(super) enum ChildNodesRef<'tree, 'on_disk> {
76 InMemory(&'tree FastHashMap<NodeKey<'on_disk>, Node<'on_disk>>),
76 InMemory(&'tree FastHashMap<NodeKey<'on_disk>, Node<'on_disk>>),
77 OnDisk(&'on_disk [on_disk::Node]),
77 OnDisk(&'on_disk [on_disk::Node]),
78 }
78 }
79
79
80 pub(super) enum NodeRef<'tree, 'on_disk> {
80 pub(super) enum NodeRef<'tree, 'on_disk> {
81 InMemory(&'tree NodeKey<'on_disk>, &'tree Node<'on_disk>),
81 InMemory(&'tree NodeKey<'on_disk>, &'tree Node<'on_disk>),
82 OnDisk(&'on_disk on_disk::Node),
82 OnDisk(&'on_disk on_disk::Node),
83 }
83 }
84
84
85 impl<'tree, 'on_disk> BorrowedPath<'tree, 'on_disk> {
85 impl<'tree, 'on_disk> BorrowedPath<'tree, 'on_disk> {
86 pub fn detach_from_tree(&self) -> Cow<'on_disk, HgPath> {
86 pub fn detach_from_tree(&self) -> Cow<'on_disk, HgPath> {
87 match *self {
87 match *self {
88 BorrowedPath::InMemory(in_memory) => Cow::Owned(in_memory.clone()),
88 BorrowedPath::InMemory(in_memory) => Cow::Owned(in_memory.clone()),
89 BorrowedPath::OnDisk(on_disk) => Cow::Borrowed(on_disk),
89 BorrowedPath::OnDisk(on_disk) => Cow::Borrowed(on_disk),
90 }
90 }
91 }
91 }
92 }
92 }
93
93
94 impl<'tree, 'on_disk> std::ops::Deref for BorrowedPath<'tree, 'on_disk> {
94 impl<'tree, 'on_disk> std::ops::Deref for BorrowedPath<'tree, 'on_disk> {
95 type Target = HgPath;
95 type Target = HgPath;
96
96
97 fn deref(&self) -> &HgPath {
97 fn deref(&self) -> &HgPath {
98 match *self {
98 match *self {
99 BorrowedPath::InMemory(in_memory) => in_memory,
99 BorrowedPath::InMemory(in_memory) => in_memory,
100 BorrowedPath::OnDisk(on_disk) => on_disk,
100 BorrowedPath::OnDisk(on_disk) => on_disk,
101 }
101 }
102 }
102 }
103 }
103 }
104
104
105 impl Default for ChildNodes<'_> {
105 impl Default for ChildNodes<'_> {
106 fn default() -> Self {
106 fn default() -> Self {
107 ChildNodes::InMemory(Default::default())
107 ChildNodes::InMemory(Default::default())
108 }
108 }
109 }
109 }
110
110
111 impl<'on_disk> ChildNodes<'on_disk> {
111 impl<'on_disk> ChildNodes<'on_disk> {
112 pub(super) fn as_ref<'tree>(
112 pub(super) fn as_ref<'tree>(
113 &'tree self,
113 &'tree self,
114 ) -> ChildNodesRef<'tree, 'on_disk> {
114 ) -> ChildNodesRef<'tree, 'on_disk> {
115 match self {
115 match self {
116 ChildNodes::InMemory(nodes) => ChildNodesRef::InMemory(nodes),
116 ChildNodes::InMemory(nodes) => ChildNodesRef::InMemory(nodes),
117 ChildNodes::OnDisk(nodes) => ChildNodesRef::OnDisk(nodes),
117 ChildNodes::OnDisk(nodes) => ChildNodesRef::OnDisk(nodes),
118 }
118 }
119 }
119 }
120
120
121 pub(super) fn is_empty(&self) -> bool {
121 pub(super) fn is_empty(&self) -> bool {
122 match self {
122 match self {
123 ChildNodes::InMemory(nodes) => nodes.is_empty(),
123 ChildNodes::InMemory(nodes) => nodes.is_empty(),
124 ChildNodes::OnDisk(nodes) => nodes.is_empty(),
124 ChildNodes::OnDisk(nodes) => nodes.is_empty(),
125 }
125 }
126 }
126 }
127
127
128 fn make_mut(
128 fn make_mut(
129 &mut self,
129 &mut self,
130 on_disk: &'on_disk [u8],
130 on_disk: &'on_disk [u8],
131 unreachable_bytes: &mut u32,
131 unreachable_bytes: &mut u32,
132 ) -> Result<
132 ) -> Result<
133 &mut FastHashMap<NodeKey<'on_disk>, Node<'on_disk>>,
133 &mut FastHashMap<NodeKey<'on_disk>, Node<'on_disk>>,
134 DirstateV2ParseError,
134 DirstateV2ParseError,
135 > {
135 > {
136 match self {
136 match self {
137 ChildNodes::InMemory(nodes) => Ok(nodes),
137 ChildNodes::InMemory(nodes) => Ok(nodes),
138 ChildNodes::OnDisk(nodes) => {
138 ChildNodes::OnDisk(nodes) => {
139 *unreachable_bytes +=
139 *unreachable_bytes +=
140 std::mem::size_of_val::<[on_disk::Node]>(nodes) as u32;
140 std::mem::size_of_val::<[on_disk::Node]>(nodes) as u32;
141 let nodes = nodes
141 let nodes = nodes
142 .iter()
142 .iter()
143 .map(|node| {
143 .map(|node| {
144 Ok((
144 Ok((
145 node.path(on_disk)?,
145 node.path(on_disk)?,
146 node.to_in_memory_node(on_disk)?,
146 node.to_in_memory_node(on_disk)?,
147 ))
147 ))
148 })
148 })
149 .collect::<Result<_, _>>()?;
149 .collect::<Result<_, _>>()?;
150 *self = ChildNodes::InMemory(nodes);
150 *self = ChildNodes::InMemory(nodes);
151 match self {
151 match self {
152 ChildNodes::InMemory(nodes) => Ok(nodes),
152 ChildNodes::InMemory(nodes) => Ok(nodes),
153 ChildNodes::OnDisk(_) => unreachable!(),
153 ChildNodes::OnDisk(_) => unreachable!(),
154 }
154 }
155 }
155 }
156 }
156 }
157 }
157 }
158 }
158 }
159
159
160 impl<'tree, 'on_disk> ChildNodesRef<'tree, 'on_disk> {
160 impl<'tree, 'on_disk> ChildNodesRef<'tree, 'on_disk> {
161 pub(super) fn get(
161 pub(super) fn get(
162 &self,
162 &self,
163 base_name: &HgPath,
163 base_name: &HgPath,
164 on_disk: &'on_disk [u8],
164 on_disk: &'on_disk [u8],
165 ) -> Result<Option<NodeRef<'tree, 'on_disk>>, DirstateV2ParseError> {
165 ) -> Result<Option<NodeRef<'tree, 'on_disk>>, DirstateV2ParseError> {
166 match self {
166 match self {
167 ChildNodesRef::InMemory(nodes) => Ok(nodes
167 ChildNodesRef::InMemory(nodes) => Ok(nodes
168 .get_key_value(base_name)
168 .get_key_value(base_name)
169 .map(|(k, v)| NodeRef::InMemory(k, v))),
169 .map(|(k, v)| NodeRef::InMemory(k, v))),
170 ChildNodesRef::OnDisk(nodes) => {
170 ChildNodesRef::OnDisk(nodes) => {
171 let mut parse_result = Ok(());
171 let mut parse_result = Ok(());
172 let search_result = nodes.binary_search_by(|node| {
172 let search_result = nodes.binary_search_by(|node| {
173 match node.base_name(on_disk) {
173 match node.base_name(on_disk) {
174 Ok(node_base_name) => node_base_name.cmp(base_name),
174 Ok(node_base_name) => node_base_name.cmp(base_name),
175 Err(e) => {
175 Err(e) => {
176 parse_result = Err(e);
176 parse_result = Err(e);
177 // Dummy comparison result, `search_result` won’t
177 // Dummy comparison result, `search_result` won’t
178 // be used since `parse_result` is an error
178 // be used since `parse_result` is an error
179 std::cmp::Ordering::Equal
179 std::cmp::Ordering::Equal
180 }
180 }
181 }
181 }
182 });
182 });
183 parse_result.map(|()| {
183 parse_result.map(|()| {
184 search_result.ok().map(|i| NodeRef::OnDisk(&nodes[i]))
184 search_result.ok().map(|i| NodeRef::OnDisk(&nodes[i]))
185 })
185 })
186 }
186 }
187 }
187 }
188 }
188 }
189
189
190 /// Iterate in undefined order
190 /// Iterate in undefined order
191 pub(super) fn iter(
191 pub(super) fn iter(
192 &self,
192 &self,
193 ) -> impl Iterator<Item = NodeRef<'tree, 'on_disk>> {
193 ) -> impl Iterator<Item = NodeRef<'tree, 'on_disk>> {
194 match self {
194 match self {
195 ChildNodesRef::InMemory(nodes) => itertools::Either::Left(
195 ChildNodesRef::InMemory(nodes) => itertools::Either::Left(
196 nodes.iter().map(|(k, v)| NodeRef::InMemory(k, v)),
196 nodes.iter().map(|(k, v)| NodeRef::InMemory(k, v)),
197 ),
197 ),
198 ChildNodesRef::OnDisk(nodes) => {
198 ChildNodesRef::OnDisk(nodes) => {
199 itertools::Either::Right(nodes.iter().map(NodeRef::OnDisk))
199 itertools::Either::Right(nodes.iter().map(NodeRef::OnDisk))
200 }
200 }
201 }
201 }
202 }
202 }
203
203
204 /// Iterate in parallel in undefined order
204 /// Iterate in parallel in undefined order
205 pub(super) fn par_iter(
205 pub(super) fn par_iter(
206 &self,
206 &self,
207 ) -> impl rayon::iter::ParallelIterator<Item = NodeRef<'tree, 'on_disk>>
207 ) -> impl rayon::iter::ParallelIterator<Item = NodeRef<'tree, 'on_disk>>
208 {
208 {
209 use rayon::prelude::*;
209 use rayon::prelude::*;
210 match self {
210 match self {
211 ChildNodesRef::InMemory(nodes) => rayon::iter::Either::Left(
211 ChildNodesRef::InMemory(nodes) => rayon::iter::Either::Left(
212 nodes.par_iter().map(|(k, v)| NodeRef::InMemory(k, v)),
212 nodes.par_iter().map(|(k, v)| NodeRef::InMemory(k, v)),
213 ),
213 ),
214 ChildNodesRef::OnDisk(nodes) => rayon::iter::Either::Right(
214 ChildNodesRef::OnDisk(nodes) => rayon::iter::Either::Right(
215 nodes.par_iter().map(NodeRef::OnDisk),
215 nodes.par_iter().map(NodeRef::OnDisk),
216 ),
216 ),
217 }
217 }
218 }
218 }
219
219
220 pub(super) fn sorted(&self) -> Vec<NodeRef<'tree, 'on_disk>> {
220 pub(super) fn sorted(&self) -> Vec<NodeRef<'tree, 'on_disk>> {
221 match self {
221 match self {
222 ChildNodesRef::InMemory(nodes) => {
222 ChildNodesRef::InMemory(nodes) => {
223 let mut vec: Vec<_> = nodes
223 let mut vec: Vec<_> = nodes
224 .iter()
224 .iter()
225 .map(|(k, v)| NodeRef::InMemory(k, v))
225 .map(|(k, v)| NodeRef::InMemory(k, v))
226 .collect();
226 .collect();
227 fn sort_key<'a>(node: &'a NodeRef) -> &'a HgPath {
227 fn sort_key<'a>(node: &'a NodeRef) -> &'a HgPath {
228 match node {
228 match node {
229 NodeRef::InMemory(path, _node) => path.base_name(),
229 NodeRef::InMemory(path, _node) => path.base_name(),
230 NodeRef::OnDisk(_) => unreachable!(),
230 NodeRef::OnDisk(_) => unreachable!(),
231 }
231 }
232 }
232 }
233 // `sort_unstable_by_key` doesn’t allow keys borrowing from the
233 // `sort_unstable_by_key` doesn’t allow keys borrowing from the
234 // value: https://github.com/rust-lang/rust/issues/34162
234 // value: https://github.com/rust-lang/rust/issues/34162
235 vec.sort_unstable_by(|a, b| sort_key(a).cmp(sort_key(b)));
235 vec.sort_unstable_by(|a, b| sort_key(a).cmp(sort_key(b)));
236 vec
236 vec
237 }
237 }
238 ChildNodesRef::OnDisk(nodes) => {
238 ChildNodesRef::OnDisk(nodes) => {
239 // Nodes on disk are already sorted
239 // Nodes on disk are already sorted
240 nodes.iter().map(NodeRef::OnDisk).collect()
240 nodes.iter().map(NodeRef::OnDisk).collect()
241 }
241 }
242 }
242 }
243 }
243 }
244 }
244 }
245
245
246 impl<'tree, 'on_disk> NodeRef<'tree, 'on_disk> {
246 impl<'tree, 'on_disk> NodeRef<'tree, 'on_disk> {
247 pub(super) fn full_path(
247 pub(super) fn full_path(
248 &self,
248 &self,
249 on_disk: &'on_disk [u8],
249 on_disk: &'on_disk [u8],
250 ) -> Result<&'tree HgPath, DirstateV2ParseError> {
250 ) -> Result<&'tree HgPath, DirstateV2ParseError> {
251 match self {
251 match self {
252 NodeRef::InMemory(path, _node) => Ok(path.full_path()),
252 NodeRef::InMemory(path, _node) => Ok(path.full_path()),
253 NodeRef::OnDisk(node) => node.full_path(on_disk),
253 NodeRef::OnDisk(node) => node.full_path(on_disk),
254 }
254 }
255 }
255 }
256
256
257 /// Returns a `BorrowedPath`, which can be turned into a `Cow<'on_disk,
257 /// Returns a `BorrowedPath`, which can be turned into a `Cow<'on_disk,
258 /// HgPath>` detached from `'tree`
258 /// HgPath>` detached from `'tree`
259 pub(super) fn full_path_borrowed(
259 pub(super) fn full_path_borrowed(
260 &self,
260 &self,
261 on_disk: &'on_disk [u8],
261 on_disk: &'on_disk [u8],
262 ) -> Result<BorrowedPath<'tree, 'on_disk>, DirstateV2ParseError> {
262 ) -> Result<BorrowedPath<'tree, 'on_disk>, DirstateV2ParseError> {
263 match self {
263 match self {
264 NodeRef::InMemory(path, _node) => match path.full_path() {
264 NodeRef::InMemory(path, _node) => match path.full_path() {
265 Cow::Borrowed(on_disk) => Ok(BorrowedPath::OnDisk(on_disk)),
265 Cow::Borrowed(on_disk) => Ok(BorrowedPath::OnDisk(on_disk)),
266 Cow::Owned(in_memory) => Ok(BorrowedPath::InMemory(in_memory)),
266 Cow::Owned(in_memory) => Ok(BorrowedPath::InMemory(in_memory)),
267 },
267 },
268 NodeRef::OnDisk(node) => {
268 NodeRef::OnDisk(node) => {
269 Ok(BorrowedPath::OnDisk(node.full_path(on_disk)?))
269 Ok(BorrowedPath::OnDisk(node.full_path(on_disk)?))
270 }
270 }
271 }
271 }
272 }
272 }
273
273
274 pub(super) fn base_name(
274 pub(super) fn base_name(
275 &self,
275 &self,
276 on_disk: &'on_disk [u8],
276 on_disk: &'on_disk [u8],
277 ) -> Result<&'tree HgPath, DirstateV2ParseError> {
277 ) -> Result<&'tree HgPath, DirstateV2ParseError> {
278 match self {
278 match self {
279 NodeRef::InMemory(path, _node) => Ok(path.base_name()),
279 NodeRef::InMemory(path, _node) => Ok(path.base_name()),
280 NodeRef::OnDisk(node) => node.base_name(on_disk),
280 NodeRef::OnDisk(node) => node.base_name(on_disk),
281 }
281 }
282 }
282 }
283
283
284 pub(super) fn children(
284 pub(super) fn children(
285 &self,
285 &self,
286 on_disk: &'on_disk [u8],
286 on_disk: &'on_disk [u8],
287 ) -> Result<ChildNodesRef<'tree, 'on_disk>, DirstateV2ParseError> {
287 ) -> Result<ChildNodesRef<'tree, 'on_disk>, DirstateV2ParseError> {
288 match self {
288 match self {
289 NodeRef::InMemory(_path, node) => Ok(node.children.as_ref()),
289 NodeRef::InMemory(_path, node) => Ok(node.children.as_ref()),
290 NodeRef::OnDisk(node) => {
290 NodeRef::OnDisk(node) => {
291 Ok(ChildNodesRef::OnDisk(node.children(on_disk)?))
291 Ok(ChildNodesRef::OnDisk(node.children(on_disk)?))
292 }
292 }
293 }
293 }
294 }
294 }
295
295
296 pub(super) fn has_copy_source(&self) -> bool {
296 pub(super) fn has_copy_source(&self) -> bool {
297 match self {
297 match self {
298 NodeRef::InMemory(_path, node) => node.copy_source.is_some(),
298 NodeRef::InMemory(_path, node) => node.copy_source.is_some(),
299 NodeRef::OnDisk(node) => node.has_copy_source(),
299 NodeRef::OnDisk(node) => node.has_copy_source(),
300 }
300 }
301 }
301 }
302
302
303 pub(super) fn copy_source(
303 pub(super) fn copy_source(
304 &self,
304 &self,
305 on_disk: &'on_disk [u8],
305 on_disk: &'on_disk [u8],
306 ) -> Result<Option<&'tree HgPath>, DirstateV2ParseError> {
306 ) -> Result<Option<&'tree HgPath>, DirstateV2ParseError> {
307 match self {
307 match self {
308 NodeRef::InMemory(_path, node) => {
308 NodeRef::InMemory(_path, node) => {
309 Ok(node.copy_source.as_ref().map(|s| &**s))
309 Ok(node.copy_source.as_ref().map(|s| &**s))
310 }
310 }
311 NodeRef::OnDisk(node) => node.copy_source(on_disk),
311 NodeRef::OnDisk(node) => node.copy_source(on_disk),
312 }
312 }
313 }
313 }
314
314
315 pub(super) fn entry(
315 pub(super) fn entry(
316 &self,
316 &self,
317 ) -> Result<Option<DirstateEntry>, DirstateV2ParseError> {
317 ) -> Result<Option<DirstateEntry>, DirstateV2ParseError> {
318 match self {
318 match self {
319 NodeRef::InMemory(_path, node) => {
319 NodeRef::InMemory(_path, node) => {
320 Ok(node.data.as_entry().copied())
320 Ok(node.data.as_entry().copied())
321 }
321 }
322 NodeRef::OnDisk(node) => node.entry(),
322 NodeRef::OnDisk(node) => node.entry(),
323 }
323 }
324 }
324 }
325
325
326 pub(super) fn state(
326 pub(super) fn state(
327 &self,
327 &self,
328 ) -> Result<Option<EntryState>, DirstateV2ParseError> {
328 ) -> Result<Option<EntryState>, DirstateV2ParseError> {
329 match self {
329 match self {
330 NodeRef::InMemory(_path, node) => {
330 NodeRef::InMemory(_path, node) => {
331 Ok(node.data.as_entry().map(|entry| entry.state()))
331 Ok(node.data.as_entry().map(|entry| entry.state()))
332 }
332 }
333 NodeRef::OnDisk(node) => node.state(),
333 NodeRef::OnDisk(node) => node.state(),
334 }
334 }
335 }
335 }
336
336
337 pub(super) fn cached_directory_mtime(
337 pub(super) fn cached_directory_mtime(
338 &self,
338 &self,
339 ) -> Option<&'tree on_disk::Timestamp> {
339 ) -> Option<&'tree on_disk::Timestamp> {
340 match self {
340 match self {
341 NodeRef::InMemory(_path, node) => match &node.data {
341 NodeRef::InMemory(_path, node) => match &node.data {
342 NodeData::CachedDirectory { mtime } => Some(mtime),
342 NodeData::CachedDirectory { mtime } => Some(mtime),
343 _ => None,
343 _ => None,
344 },
344 },
345 NodeRef::OnDisk(node) => node.cached_directory_mtime(),
345 NodeRef::OnDisk(node) => node.cached_directory_mtime(),
346 }
346 }
347 }
347 }
348
348
349 pub(super) fn descendants_with_entry_count(&self) -> u32 {
349 pub(super) fn descendants_with_entry_count(&self) -> u32 {
350 match self {
350 match self {
351 NodeRef::InMemory(_path, node) => {
351 NodeRef::InMemory(_path, node) => {
352 node.descendants_with_entry_count
352 node.descendants_with_entry_count
353 }
353 }
354 NodeRef::OnDisk(node) => node.descendants_with_entry_count.get(),
354 NodeRef::OnDisk(node) => node.descendants_with_entry_count.get(),
355 }
355 }
356 }
356 }
357
357
358 pub(super) fn tracked_descendants_count(&self) -> u32 {
358 pub(super) fn tracked_descendants_count(&self) -> u32 {
359 match self {
359 match self {
360 NodeRef::InMemory(_path, node) => node.tracked_descendants_count,
360 NodeRef::InMemory(_path, node) => node.tracked_descendants_count,
361 NodeRef::OnDisk(node) => node.tracked_descendants_count.get(),
361 NodeRef::OnDisk(node) => node.tracked_descendants_count.get(),
362 }
362 }
363 }
363 }
364 }
364 }
365
365
366 /// Represents a file or a directory
366 /// Represents a file or a directory
367 #[derive(Default)]
367 #[derive(Default)]
368 pub(super) struct Node<'on_disk> {
368 pub(super) struct Node<'on_disk> {
369 pub(super) data: NodeData,
369 pub(super) data: NodeData,
370
370
371 pub(super) copy_source: Option<Cow<'on_disk, HgPath>>,
371 pub(super) copy_source: Option<Cow<'on_disk, HgPath>>,
372
372
373 pub(super) children: ChildNodes<'on_disk>,
373 pub(super) children: ChildNodes<'on_disk>,
374
374
375 /// How many (non-inclusive) descendants of this node have an entry.
375 /// How many (non-inclusive) descendants of this node have an entry.
376 pub(super) descendants_with_entry_count: u32,
376 pub(super) descendants_with_entry_count: u32,
377
377
378 /// How many (non-inclusive) descendants of this node have an entry whose
378 /// How many (non-inclusive) descendants of this node have an entry whose
379 /// state is "tracked".
379 /// state is "tracked".
380 pub(super) tracked_descendants_count: u32,
380 pub(super) tracked_descendants_count: u32,
381 }
381 }
382
382
383 pub(super) enum NodeData {
383 pub(super) enum NodeData {
384 Entry(DirstateEntry),
384 Entry(DirstateEntry),
385 CachedDirectory { mtime: on_disk::Timestamp },
385 CachedDirectory { mtime: on_disk::Timestamp },
386 None,
386 None,
387 }
387 }
388
388
389 impl Default for NodeData {
389 impl Default for NodeData {
390 fn default() -> Self {
390 fn default() -> Self {
391 NodeData::None
391 NodeData::None
392 }
392 }
393 }
393 }
394
394
395 impl NodeData {
395 impl NodeData {
396 fn has_entry(&self) -> bool {
396 fn has_entry(&self) -> bool {
397 match self {
397 match self {
398 NodeData::Entry(_) => true,
398 NodeData::Entry(_) => true,
399 _ => false,
399 _ => false,
400 }
400 }
401 }
401 }
402
402
403 fn as_entry(&self) -> Option<&DirstateEntry> {
403 fn as_entry(&self) -> Option<&DirstateEntry> {
404 match self {
404 match self {
405 NodeData::Entry(entry) => Some(entry),
405 NodeData::Entry(entry) => Some(entry),
406 _ => None,
406 _ => None,
407 }
407 }
408 }
408 }
409 }
409 }
410
410
411 impl<'on_disk> DirstateMap<'on_disk> {
411 impl<'on_disk> DirstateMap<'on_disk> {
412 pub(super) fn empty(on_disk: &'on_disk [u8]) -> Self {
412 pub(super) fn empty(on_disk: &'on_disk [u8]) -> Self {
413 Self {
413 Self {
414 on_disk,
414 on_disk,
415 root: ChildNodes::default(),
415 root: ChildNodes::default(),
416 nodes_with_entry_count: 0,
416 nodes_with_entry_count: 0,
417 nodes_with_copy_source_count: 0,
417 nodes_with_copy_source_count: 0,
418 ignore_patterns_hash: [0; on_disk::IGNORE_PATTERNS_HASH_LEN],
418 ignore_patterns_hash: [0; on_disk::IGNORE_PATTERNS_HASH_LEN],
419 unreachable_bytes: 0,
419 unreachable_bytes: 0,
420 }
420 }
421 }
421 }
422
422
423 #[timed]
423 #[timed]
424 pub fn new_v2(
424 pub fn new_v2(
425 on_disk: &'on_disk [u8],
425 on_disk: &'on_disk [u8],
426 data_size: usize,
426 data_size: usize,
427 metadata: &[u8],
427 metadata: &[u8],
428 ) -> Result<Self, DirstateError> {
428 ) -> Result<Self, DirstateError> {
429 if let Some(data) = on_disk.get(..data_size) {
429 if let Some(data) = on_disk.get(..data_size) {
430 Ok(on_disk::read(data, metadata)?)
430 Ok(on_disk::read(data, metadata)?)
431 } else {
431 } else {
432 Err(DirstateV2ParseError.into())
432 Err(DirstateV2ParseError.into())
433 }
433 }
434 }
434 }
435
435
436 #[timed]
436 #[timed]
437 pub fn new_v1(
437 pub fn new_v1(
438 on_disk: &'on_disk [u8],
438 on_disk: &'on_disk [u8],
439 ) -> Result<(Self, Option<DirstateParents>), DirstateError> {
439 ) -> Result<(Self, Option<DirstateParents>), DirstateError> {
440 let mut map = Self::empty(on_disk);
440 let mut map = Self::empty(on_disk);
441 if map.on_disk.is_empty() {
441 if map.on_disk.is_empty() {
442 return Ok((map, None));
442 return Ok((map, None));
443 }
443 }
444
444
445 let parents = parse_dirstate_entries(
445 let parents = parse_dirstate_entries(
446 map.on_disk,
446 map.on_disk,
447 |path, entry, copy_source| {
447 |path, entry, copy_source| {
448 let tracked = entry.state().is_tracked();
448 let tracked = entry.state().is_tracked();
449 let node = Self::get_or_insert_node(
449 let node = Self::get_or_insert_node(
450 map.on_disk,
450 map.on_disk,
451 &mut map.unreachable_bytes,
451 &mut map.unreachable_bytes,
452 &mut map.root,
452 &mut map.root,
453 path,
453 path,
454 WithBasename::to_cow_borrowed,
454 WithBasename::to_cow_borrowed,
455 |ancestor| {
455 |ancestor| {
456 if tracked {
456 if tracked {
457 ancestor.tracked_descendants_count += 1
457 ancestor.tracked_descendants_count += 1
458 }
458 }
459 ancestor.descendants_with_entry_count += 1
459 ancestor.descendants_with_entry_count += 1
460 },
460 },
461 )?;
461 )?;
462 assert!(
462 assert!(
463 !node.data.has_entry(),
463 !node.data.has_entry(),
464 "duplicate dirstate entry in read"
464 "duplicate dirstate entry in read"
465 );
465 );
466 assert!(
466 assert!(
467 node.copy_source.is_none(),
467 node.copy_source.is_none(),
468 "duplicate dirstate entry in read"
468 "duplicate dirstate entry in read"
469 );
469 );
470 node.data = NodeData::Entry(*entry);
470 node.data = NodeData::Entry(*entry);
471 node.copy_source = copy_source.map(Cow::Borrowed);
471 node.copy_source = copy_source.map(Cow::Borrowed);
472 map.nodes_with_entry_count += 1;
472 map.nodes_with_entry_count += 1;
473 if copy_source.is_some() {
473 if copy_source.is_some() {
474 map.nodes_with_copy_source_count += 1
474 map.nodes_with_copy_source_count += 1
475 }
475 }
476 Ok(())
476 Ok(())
477 },
477 },
478 )?;
478 )?;
479 let parents = Some(parents.clone());
479 let parents = Some(parents.clone());
480
480
481 Ok((map, parents))
481 Ok((map, parents))
482 }
482 }
483
483
484 /// Assuming dirstate-v2 format, returns whether the next write should
484 /// Assuming dirstate-v2 format, returns whether the next write should
485 /// append to the existing data file that contains `self.on_disk` (true),
485 /// append to the existing data file that contains `self.on_disk` (true),
486 /// or create a new data file from scratch (false).
486 /// or create a new data file from scratch (false).
487 pub(super) fn write_should_append(&self) -> bool {
487 pub(super) fn write_should_append(&self) -> bool {
488 let ratio = self.unreachable_bytes as f32 / self.on_disk.len() as f32;
488 let ratio = self.unreachable_bytes as f32 / self.on_disk.len() as f32;
489 ratio < ACCEPTABLE_UNREACHABLE_BYTES_RATIO
489 ratio < ACCEPTABLE_UNREACHABLE_BYTES_RATIO
490 }
490 }
491
491
492 fn get_node<'tree>(
492 fn get_node<'tree>(
493 &'tree self,
493 &'tree self,
494 path: &HgPath,
494 path: &HgPath,
495 ) -> Result<Option<NodeRef<'tree, 'on_disk>>, DirstateV2ParseError> {
495 ) -> Result<Option<NodeRef<'tree, 'on_disk>>, DirstateV2ParseError> {
496 let mut children = self.root.as_ref();
496 let mut children = self.root.as_ref();
497 let mut components = path.components();
497 let mut components = path.components();
498 let mut component =
498 let mut component =
499 components.next().expect("expected at least one components");
499 components.next().expect("expected at least one components");
500 loop {
500 loop {
501 if let Some(child) = children.get(component, self.on_disk)? {
501 if let Some(child) = children.get(component, self.on_disk)? {
502 if let Some(next_component) = components.next() {
502 if let Some(next_component) = components.next() {
503 component = next_component;
503 component = next_component;
504 children = child.children(self.on_disk)?;
504 children = child.children(self.on_disk)?;
505 } else {
505 } else {
506 return Ok(Some(child));
506 return Ok(Some(child));
507 }
507 }
508 } else {
508 } else {
509 return Ok(None);
509 return Ok(None);
510 }
510 }
511 }
511 }
512 }
512 }
513
513
514 /// Returns a mutable reference to the node at `path` if it exists
514 /// Returns a mutable reference to the node at `path` if it exists
515 ///
515 ///
516 /// This takes `root` instead of `&mut self` so that callers can mutate
516 /// This takes `root` instead of `&mut self` so that callers can mutate
517 /// other fields while the returned borrow is still valid
517 /// other fields while the returned borrow is still valid
518 fn get_node_mut<'tree>(
518 fn get_node_mut<'tree>(
519 on_disk: &'on_disk [u8],
519 on_disk: &'on_disk [u8],
520 unreachable_bytes: &mut u32,
520 unreachable_bytes: &mut u32,
521 root: &'tree mut ChildNodes<'on_disk>,
521 root: &'tree mut ChildNodes<'on_disk>,
522 path: &HgPath,
522 path: &HgPath,
523 ) -> Result<Option<&'tree mut Node<'on_disk>>, DirstateV2ParseError> {
523 ) -> Result<Option<&'tree mut Node<'on_disk>>, DirstateV2ParseError> {
524 let mut children = root;
524 let mut children = root;
525 let mut components = path.components();
525 let mut components = path.components();
526 let mut component =
526 let mut component =
527 components.next().expect("expected at least one components");
527 components.next().expect("expected at least one components");
528 loop {
528 loop {
529 if let Some(child) = children
529 if let Some(child) = children
530 .make_mut(on_disk, unreachable_bytes)?
530 .make_mut(on_disk, unreachable_bytes)?
531 .get_mut(component)
531 .get_mut(component)
532 {
532 {
533 if let Some(next_component) = components.next() {
533 if let Some(next_component) = components.next() {
534 component = next_component;
534 component = next_component;
535 children = &mut child.children;
535 children = &mut child.children;
536 } else {
536 } else {
537 return Ok(Some(child));
537 return Ok(Some(child));
538 }
538 }
539 } else {
539 } else {
540 return Ok(None);
540 return Ok(None);
541 }
541 }
542 }
542 }
543 }
543 }
544
544
545 pub(super) fn get_or_insert<'tree, 'path>(
545 pub(super) fn get_or_insert<'tree, 'path>(
546 &'tree mut self,
546 &'tree mut self,
547 path: &HgPath,
547 path: &HgPath,
548 ) -> Result<&'tree mut Node<'on_disk>, DirstateV2ParseError> {
548 ) -> Result<&'tree mut Node<'on_disk>, DirstateV2ParseError> {
549 Self::get_or_insert_node(
549 Self::get_or_insert_node(
550 self.on_disk,
550 self.on_disk,
551 &mut self.unreachable_bytes,
551 &mut self.unreachable_bytes,
552 &mut self.root,
552 &mut self.root,
553 path,
553 path,
554 WithBasename::to_cow_owned,
554 WithBasename::to_cow_owned,
555 |_| {},
555 |_| {},
556 )
556 )
557 }
557 }
558
558
559 fn get_or_insert_node<'tree, 'path>(
559 fn get_or_insert_node<'tree, 'path>(
560 on_disk: &'on_disk [u8],
560 on_disk: &'on_disk [u8],
561 unreachable_bytes: &mut u32,
561 unreachable_bytes: &mut u32,
562 root: &'tree mut ChildNodes<'on_disk>,
562 root: &'tree mut ChildNodes<'on_disk>,
563 path: &'path HgPath,
563 path: &'path HgPath,
564 to_cow: impl Fn(
564 to_cow: impl Fn(
565 WithBasename<&'path HgPath>,
565 WithBasename<&'path HgPath>,
566 ) -> WithBasename<Cow<'on_disk, HgPath>>,
566 ) -> WithBasename<Cow<'on_disk, HgPath>>,
567 mut each_ancestor: impl FnMut(&mut Node),
567 mut each_ancestor: impl FnMut(&mut Node),
568 ) -> Result<&'tree mut Node<'on_disk>, DirstateV2ParseError> {
568 ) -> Result<&'tree mut Node<'on_disk>, DirstateV2ParseError> {
569 let mut child_nodes = root;
569 let mut child_nodes = root;
570 let mut inclusive_ancestor_paths =
570 let mut inclusive_ancestor_paths =
571 WithBasename::inclusive_ancestors_of(path);
571 WithBasename::inclusive_ancestors_of(path);
572 let mut ancestor_path = inclusive_ancestor_paths
572 let mut ancestor_path = inclusive_ancestor_paths
573 .next()
573 .next()
574 .expect("expected at least one inclusive ancestor");
574 .expect("expected at least one inclusive ancestor");
575 loop {
575 loop {
576 // TODO: can we avoid allocating an owned key in cases where the
576 // TODO: can we avoid allocating an owned key in cases where the
577 // map already contains that key, without introducing double
577 // map already contains that key, without introducing double
578 // lookup?
578 // lookup?
579 let child_node = child_nodes
579 let child_node = child_nodes
580 .make_mut(on_disk, unreachable_bytes)?
580 .make_mut(on_disk, unreachable_bytes)?
581 .entry(to_cow(ancestor_path))
581 .entry(to_cow(ancestor_path))
582 .or_default();
582 .or_default();
583 if let Some(next) = inclusive_ancestor_paths.next() {
583 if let Some(next) = inclusive_ancestor_paths.next() {
584 each_ancestor(child_node);
584 each_ancestor(child_node);
585 ancestor_path = next;
585 ancestor_path = next;
586 child_nodes = &mut child_node.children;
586 child_nodes = &mut child_node.children;
587 } else {
587 } else {
588 return Ok(child_node);
588 return Ok(child_node);
589 }
589 }
590 }
590 }
591 }
591 }
592
592
593 fn add_or_remove_file(
593 fn add_or_remove_file(
594 &mut self,
594 &mut self,
595 path: &HgPath,
595 path: &HgPath,
596 old_state: EntryState,
596 old_state: Option<EntryState>,
597 new_entry: DirstateEntry,
597 new_entry: DirstateEntry,
598 ) -> Result<(), DirstateV2ParseError> {
598 ) -> Result<(), DirstateV2ParseError> {
599 let had_entry = old_state != EntryState::Unknown;
599 let had_entry = old_state.is_some();
600 let was_tracked = old_state.map_or(false, |s| s.is_tracked());
600 let tracked_count_increment =
601 let tracked_count_increment =
601 match (old_state.is_tracked(), new_entry.state().is_tracked()) {
602 match (was_tracked, new_entry.state().is_tracked()) {
602 (false, true) => 1,
603 (false, true) => 1,
603 (true, false) => -1,
604 (true, false) => -1,
604 _ => 0,
605 _ => 0,
605 };
606 };
606
607
607 let node = Self::get_or_insert_node(
608 let node = Self::get_or_insert_node(
608 self.on_disk,
609 self.on_disk,
609 &mut self.unreachable_bytes,
610 &mut self.unreachable_bytes,
610 &mut self.root,
611 &mut self.root,
611 path,
612 path,
612 WithBasename::to_cow_owned,
613 WithBasename::to_cow_owned,
613 |ancestor| {
614 |ancestor| {
614 if !had_entry {
615 if !had_entry {
615 ancestor.descendants_with_entry_count += 1;
616 ancestor.descendants_with_entry_count += 1;
616 }
617 }
617
618
618 // We can’t use `+= increment` because the counter is unsigned,
619 // We can’t use `+= increment` because the counter is unsigned,
619 // and we want debug builds to detect accidental underflow
620 // and we want debug builds to detect accidental underflow
620 // through zero
621 // through zero
621 match tracked_count_increment {
622 match tracked_count_increment {
622 1 => ancestor.tracked_descendants_count += 1,
623 1 => ancestor.tracked_descendants_count += 1,
623 -1 => ancestor.tracked_descendants_count -= 1,
624 -1 => ancestor.tracked_descendants_count -= 1,
624 _ => {}
625 _ => {}
625 }
626 }
626 },
627 },
627 )?;
628 )?;
628 if !had_entry {
629 if !had_entry {
629 self.nodes_with_entry_count += 1
630 self.nodes_with_entry_count += 1
630 }
631 }
631 node.data = NodeData::Entry(new_entry);
632 node.data = NodeData::Entry(new_entry);
632 Ok(())
633 Ok(())
633 }
634 }
634
635
635 fn iter_nodes<'tree>(
636 fn iter_nodes<'tree>(
636 &'tree self,
637 &'tree self,
637 ) -> impl Iterator<
638 ) -> impl Iterator<
638 Item = Result<NodeRef<'tree, 'on_disk>, DirstateV2ParseError>,
639 Item = Result<NodeRef<'tree, 'on_disk>, DirstateV2ParseError>,
639 > + 'tree {
640 > + 'tree {
640 // Depth first tree traversal.
641 // Depth first tree traversal.
641 //
642 //
642 // If we could afford internal iteration and recursion,
643 // If we could afford internal iteration and recursion,
643 // this would look like:
644 // this would look like:
644 //
645 //
645 // ```
646 // ```
646 // fn traverse_children(
647 // fn traverse_children(
647 // children: &ChildNodes,
648 // children: &ChildNodes,
648 // each: &mut impl FnMut(&Node),
649 // each: &mut impl FnMut(&Node),
649 // ) {
650 // ) {
650 // for child in children.values() {
651 // for child in children.values() {
651 // traverse_children(&child.children, each);
652 // traverse_children(&child.children, each);
652 // each(child);
653 // each(child);
653 // }
654 // }
654 // }
655 // }
655 // ```
656 // ```
656 //
657 //
657 // However we want an external iterator and therefore can’t use the
658 // However we want an external iterator and therefore can’t use the
658 // call stack. Use an explicit stack instead:
659 // call stack. Use an explicit stack instead:
659 let mut stack = Vec::new();
660 let mut stack = Vec::new();
660 let mut iter = self.root.as_ref().iter();
661 let mut iter = self.root.as_ref().iter();
661 std::iter::from_fn(move || {
662 std::iter::from_fn(move || {
662 while let Some(child_node) = iter.next() {
663 while let Some(child_node) = iter.next() {
663 let children = match child_node.children(self.on_disk) {
664 let children = match child_node.children(self.on_disk) {
664 Ok(children) => children,
665 Ok(children) => children,
665 Err(error) => return Some(Err(error)),
666 Err(error) => return Some(Err(error)),
666 };
667 };
667 // Pseudo-recursion
668 // Pseudo-recursion
668 let new_iter = children.iter();
669 let new_iter = children.iter();
669 let old_iter = std::mem::replace(&mut iter, new_iter);
670 let old_iter = std::mem::replace(&mut iter, new_iter);
670 stack.push((child_node, old_iter));
671 stack.push((child_node, old_iter));
671 }
672 }
672 // Found the end of a `children.iter()` iterator.
673 // Found the end of a `children.iter()` iterator.
673 if let Some((child_node, next_iter)) = stack.pop() {
674 if let Some((child_node, next_iter)) = stack.pop() {
674 // "Return" from pseudo-recursion by restoring state from the
675 // "Return" from pseudo-recursion by restoring state from the
675 // explicit stack
676 // explicit stack
676 iter = next_iter;
677 iter = next_iter;
677
678
678 Some(Ok(child_node))
679 Some(Ok(child_node))
679 } else {
680 } else {
680 // Reached the bottom of the stack, we’re done
681 // Reached the bottom of the stack, we’re done
681 None
682 None
682 }
683 }
683 })
684 })
684 }
685 }
685
686
686 fn clear_known_ambiguous_mtimes(
687 fn clear_known_ambiguous_mtimes(
687 &mut self,
688 &mut self,
688 paths: &[impl AsRef<HgPath>],
689 paths: &[impl AsRef<HgPath>],
689 ) -> Result<(), DirstateV2ParseError> {
690 ) -> Result<(), DirstateV2ParseError> {
690 for path in paths {
691 for path in paths {
691 if let Some(node) = Self::get_node_mut(
692 if let Some(node) = Self::get_node_mut(
692 self.on_disk,
693 self.on_disk,
693 &mut self.unreachable_bytes,
694 &mut self.unreachable_bytes,
694 &mut self.root,
695 &mut self.root,
695 path.as_ref(),
696 path.as_ref(),
696 )? {
697 )? {
697 if let NodeData::Entry(entry) = &mut node.data {
698 if let NodeData::Entry(entry) = &mut node.data {
698 entry.clear_mtime();
699 entry.clear_mtime();
699 }
700 }
700 }
701 }
701 }
702 }
702 Ok(())
703 Ok(())
703 }
704 }
704
705
705 /// Return a faillilble iterator of full paths of nodes that have an
706 /// Return a faillilble iterator of full paths of nodes that have an
706 /// `entry` for which the given `predicate` returns true.
707 /// `entry` for which the given `predicate` returns true.
707 ///
708 ///
708 /// Fallibility means that each iterator item is a `Result`, which may
709 /// Fallibility means that each iterator item is a `Result`, which may
709 /// indicate a parse error of the on-disk dirstate-v2 format. Such errors
710 /// indicate a parse error of the on-disk dirstate-v2 format. Such errors
710 /// should only happen if Mercurial is buggy or a repository is corrupted.
711 /// should only happen if Mercurial is buggy or a repository is corrupted.
711 fn filter_full_paths<'tree>(
712 fn filter_full_paths<'tree>(
712 &'tree self,
713 &'tree self,
713 predicate: impl Fn(&DirstateEntry) -> bool + 'tree,
714 predicate: impl Fn(&DirstateEntry) -> bool + 'tree,
714 ) -> impl Iterator<Item = Result<&HgPath, DirstateV2ParseError>> + 'tree
715 ) -> impl Iterator<Item = Result<&HgPath, DirstateV2ParseError>> + 'tree
715 {
716 {
716 filter_map_results(self.iter_nodes(), move |node| {
717 filter_map_results(self.iter_nodes(), move |node| {
717 if let Some(entry) = node.entry()? {
718 if let Some(entry) = node.entry()? {
718 if predicate(&entry) {
719 if predicate(&entry) {
719 return Ok(Some(node.full_path(self.on_disk)?));
720 return Ok(Some(node.full_path(self.on_disk)?));
720 }
721 }
721 }
722 }
722 Ok(None)
723 Ok(None)
723 })
724 })
724 }
725 }
725
726
726 fn count_dropped_path(unreachable_bytes: &mut u32, path: &Cow<HgPath>) {
727 fn count_dropped_path(unreachable_bytes: &mut u32, path: &Cow<HgPath>) {
727 if let Cow::Borrowed(path) = path {
728 if let Cow::Borrowed(path) = path {
728 *unreachable_bytes += path.len() as u32
729 *unreachable_bytes += path.len() as u32
729 }
730 }
730 }
731 }
731 }
732 }
732
733
733 /// Like `Iterator::filter_map`, but over a fallible iterator of `Result`s.
734 /// Like `Iterator::filter_map`, but over a fallible iterator of `Result`s.
734 ///
735 ///
735 /// The callback is only called for incoming `Ok` values. Errors are passed
736 /// The callback is only called for incoming `Ok` values. Errors are passed
736 /// through as-is. In order to let it use the `?` operator the callback is
737 /// through as-is. In order to let it use the `?` operator the callback is
737 /// expected to return a `Result` of `Option`, instead of an `Option` of
738 /// expected to return a `Result` of `Option`, instead of an `Option` of
738 /// `Result`.
739 /// `Result`.
739 fn filter_map_results<'a, I, F, A, B, E>(
740 fn filter_map_results<'a, I, F, A, B, E>(
740 iter: I,
741 iter: I,
741 f: F,
742 f: F,
742 ) -> impl Iterator<Item = Result<B, E>> + 'a
743 ) -> impl Iterator<Item = Result<B, E>> + 'a
743 where
744 where
744 I: Iterator<Item = Result<A, E>> + 'a,
745 I: Iterator<Item = Result<A, E>> + 'a,
745 F: Fn(A) -> Result<Option<B>, E> + 'a,
746 F: Fn(A) -> Result<Option<B>, E> + 'a,
746 {
747 {
747 iter.filter_map(move |result| match result {
748 iter.filter_map(move |result| match result {
748 Ok(node) => f(node).transpose(),
749 Ok(node) => f(node).transpose(),
749 Err(e) => Some(Err(e)),
750 Err(e) => Some(Err(e)),
750 })
751 })
751 }
752 }
752
753
753 impl<'on_disk> super::dispatch::DirstateMapMethods for DirstateMap<'on_disk> {
754 impl<'on_disk> super::dispatch::DirstateMapMethods for DirstateMap<'on_disk> {
754 fn clear(&mut self) {
755 fn clear(&mut self) {
755 self.root = Default::default();
756 self.root = Default::default();
756 self.nodes_with_entry_count = 0;
757 self.nodes_with_entry_count = 0;
757 self.nodes_with_copy_source_count = 0;
758 self.nodes_with_copy_source_count = 0;
758 }
759 }
759
760
760 fn set_v1(&mut self, filename: &HgPath, entry: DirstateEntry) {
761 fn set_v1(&mut self, filename: &HgPath, entry: DirstateEntry) {
761 let node =
762 let node =
762 self.get_or_insert(&filename).expect("no parse error in v1");
763 self.get_or_insert(&filename).expect("no parse error in v1");
763 node.data = NodeData::Entry(entry);
764 node.data = NodeData::Entry(entry);
764 node.children = ChildNodes::default();
765 node.children = ChildNodes::default();
765 node.copy_source = None;
766 node.copy_source = None;
766 node.descendants_with_entry_count = 0;
767 node.descendants_with_entry_count = 0;
767 node.tracked_descendants_count = 0;
768 node.tracked_descendants_count = 0;
768 }
769 }
769
770
770 fn add_file(
771 fn add_file(
771 &mut self,
772 &mut self,
772 filename: &HgPath,
773 filename: &HgPath,
773 entry: DirstateEntry,
774 entry: DirstateEntry,
774 added: bool,
775 added: bool,
775 merged: bool,
776 merged: bool,
776 from_p2: bool,
777 from_p2: bool,
777 possibly_dirty: bool,
778 possibly_dirty: bool,
778 ) -> Result<(), DirstateError> {
779 ) -> Result<(), DirstateError> {
779 let state;
780 let state;
780 let size;
781 let size;
781 let mtime;
782 let mtime;
782 if added {
783 if added {
783 assert!(!possibly_dirty);
784 assert!(!possibly_dirty);
784 assert!(!from_p2);
785 assert!(!from_p2);
785 state = EntryState::Added;
786 state = EntryState::Added;
786 size = SIZE_NON_NORMAL;
787 size = SIZE_NON_NORMAL;
787 mtime = MTIME_UNSET;
788 mtime = MTIME_UNSET;
788 } else if merged {
789 } else if merged {
789 assert!(!possibly_dirty);
790 assert!(!possibly_dirty);
790 assert!(!from_p2);
791 assert!(!from_p2);
791 state = EntryState::Merged;
792 state = EntryState::Merged;
792 size = SIZE_FROM_OTHER_PARENT;
793 size = SIZE_FROM_OTHER_PARENT;
793 mtime = MTIME_UNSET;
794 mtime = MTIME_UNSET;
794 } else if from_p2 {
795 } else if from_p2 {
795 assert!(!possibly_dirty);
796 assert!(!possibly_dirty);
796 state = EntryState::Normal;
797 state = EntryState::Normal;
797 size = SIZE_FROM_OTHER_PARENT;
798 size = SIZE_FROM_OTHER_PARENT;
798 mtime = MTIME_UNSET;
799 mtime = MTIME_UNSET;
799 } else if possibly_dirty {
800 } else if possibly_dirty {
800 state = EntryState::Normal;
801 state = EntryState::Normal;
801 size = SIZE_NON_NORMAL;
802 size = SIZE_NON_NORMAL;
802 mtime = MTIME_UNSET;
803 mtime = MTIME_UNSET;
803 } else {
804 } else {
804 state = EntryState::Normal;
805 state = EntryState::Normal;
805 size = entry.size() & V1_RANGEMASK;
806 size = entry.size() & V1_RANGEMASK;
806 mtime = entry.mtime() & V1_RANGEMASK;
807 mtime = entry.mtime() & V1_RANGEMASK;
807 }
808 }
808 let mode = entry.mode();
809 let mode = entry.mode();
809 let entry = DirstateEntry::from_v1_data(state, mode, size, mtime);
810 let entry = DirstateEntry::from_v1_data(state, mode, size, mtime);
810
811
811 let old_state = match self.get(filename)? {
812 let old_state = self.get(filename)?.map(|e| e.state());
812 Some(e) => e.state(),
813 None => EntryState::Unknown,
814 };
815
813
816 Ok(self.add_or_remove_file(filename, old_state, entry)?)
814 Ok(self.add_or_remove_file(filename, old_state, entry)?)
817 }
815 }
818
816
819 fn remove_file(
817 fn remove_file(
820 &mut self,
818 &mut self,
821 filename: &HgPath,
819 filename: &HgPath,
822 in_merge: bool,
820 in_merge: bool,
823 ) -> Result<(), DirstateError> {
821 ) -> Result<(), DirstateError> {
824 let old_entry_opt = self.get(filename)?;
822 let old_entry_opt = self.get(filename)?;
825 let old_state = match old_entry_opt {
823 let old_state = old_entry_opt.map(|e| e.state());
826 Some(e) => e.state(),
827 None => EntryState::Unknown,
828 };
829 let mut size = 0;
824 let mut size = 0;
830 if in_merge {
825 if in_merge {
831 // XXX we should not be able to have 'm' state and 'FROM_P2' if not
826 // XXX we should not be able to have 'm' state and 'FROM_P2' if not
832 // during a merge. So I (marmoute) am not sure we need the
827 // during a merge. So I (marmoute) am not sure we need the
833 // conditionnal at all. Adding double checking this with assert
828 // conditionnal at all. Adding double checking this with assert
834 // would be nice.
829 // would be nice.
835 if let Some(old_entry) = old_entry_opt {
830 if let Some(old_entry) = old_entry_opt {
836 // backup the previous state
831 // backup the previous state
837 if old_entry.state() == EntryState::Merged {
832 if old_entry.state() == EntryState::Merged {
838 size = SIZE_NON_NORMAL;
833 size = SIZE_NON_NORMAL;
839 } else if old_entry.state() == EntryState::Normal
834 } else if old_entry.state() == EntryState::Normal
840 && old_entry.size() == SIZE_FROM_OTHER_PARENT
835 && old_entry.size() == SIZE_FROM_OTHER_PARENT
841 {
836 {
842 // other parent
837 // other parent
843 size = SIZE_FROM_OTHER_PARENT;
838 size = SIZE_FROM_OTHER_PARENT;
844 }
839 }
845 }
840 }
846 }
841 }
847 if size == 0 {
842 if size == 0 {
848 self.copy_map_remove(filename)?;
843 self.copy_map_remove(filename)?;
849 }
844 }
850 let entry = DirstateEntry::new_removed(size);
845 let entry = DirstateEntry::new_removed(size);
851 Ok(self.add_or_remove_file(filename, old_state, entry)?)
846 Ok(self.add_or_remove_file(filename, old_state, entry)?)
852 }
847 }
853
848
854 fn drop_file(&mut self, filename: &HgPath) -> Result<bool, DirstateError> {
849 fn drop_file(&mut self, filename: &HgPath) -> Result<bool, DirstateError> {
855 let old_state = match self.get(filename)? {
850 let was_tracked = self
856 Some(e) => e.state(),
851 .get(filename)?
857 None => EntryState::Unknown,
852 .map_or(false, |e| e.state().is_tracked());
858 };
859 struct Dropped {
853 struct Dropped {
860 was_tracked: bool,
854 was_tracked: bool,
861 had_entry: bool,
855 had_entry: bool,
862 had_copy_source: bool,
856 had_copy_source: bool,
863 }
857 }
864
858
865 /// If this returns `Ok(Some((dropped, removed)))`, then
859 /// If this returns `Ok(Some((dropped, removed)))`, then
866 ///
860 ///
867 /// * `dropped` is about the leaf node that was at `filename`
861 /// * `dropped` is about the leaf node that was at `filename`
868 /// * `removed` is whether this particular level of recursion just
862 /// * `removed` is whether this particular level of recursion just
869 /// removed a node in `nodes`.
863 /// removed a node in `nodes`.
870 fn recur<'on_disk>(
864 fn recur<'on_disk>(
871 on_disk: &'on_disk [u8],
865 on_disk: &'on_disk [u8],
872 unreachable_bytes: &mut u32,
866 unreachable_bytes: &mut u32,
873 nodes: &mut ChildNodes<'on_disk>,
867 nodes: &mut ChildNodes<'on_disk>,
874 path: &HgPath,
868 path: &HgPath,
875 ) -> Result<Option<(Dropped, bool)>, DirstateV2ParseError> {
869 ) -> Result<Option<(Dropped, bool)>, DirstateV2ParseError> {
876 let (first_path_component, rest_of_path) =
870 let (first_path_component, rest_of_path) =
877 path.split_first_component();
871 path.split_first_component();
878 let nodes = nodes.make_mut(on_disk, unreachable_bytes)?;
872 let nodes = nodes.make_mut(on_disk, unreachable_bytes)?;
879 let node = if let Some(node) = nodes.get_mut(first_path_component)
873 let node = if let Some(node) = nodes.get_mut(first_path_component)
880 {
874 {
881 node
875 node
882 } else {
876 } else {
883 return Ok(None);
877 return Ok(None);
884 };
878 };
885 let dropped;
879 let dropped;
886 if let Some(rest) = rest_of_path {
880 if let Some(rest) = rest_of_path {
887 if let Some((d, removed)) = recur(
881 if let Some((d, removed)) = recur(
888 on_disk,
882 on_disk,
889 unreachable_bytes,
883 unreachable_bytes,
890 &mut node.children,
884 &mut node.children,
891 rest,
885 rest,
892 )? {
886 )? {
893 dropped = d;
887 dropped = d;
894 if dropped.had_entry {
888 if dropped.had_entry {
895 node.descendants_with_entry_count -= 1;
889 node.descendants_with_entry_count -= 1;
896 }
890 }
897 if dropped.was_tracked {
891 if dropped.was_tracked {
898 node.tracked_descendants_count -= 1;
892 node.tracked_descendants_count -= 1;
899 }
893 }
900
894
901 // Directory caches must be invalidated when removing a
895 // Directory caches must be invalidated when removing a
902 // child node
896 // child node
903 if removed {
897 if removed {
904 if let NodeData::CachedDirectory { .. } = &node.data {
898 if let NodeData::CachedDirectory { .. } = &node.data {
905 node.data = NodeData::None
899 node.data = NodeData::None
906 }
900 }
907 }
901 }
908 } else {
902 } else {
909 return Ok(None);
903 return Ok(None);
910 }
904 }
911 } else {
905 } else {
912 let had_entry = node.data.has_entry();
906 let had_entry = node.data.has_entry();
913 if had_entry {
907 if had_entry {
914 node.data = NodeData::None
908 node.data = NodeData::None
915 }
909 }
916 if let Some(source) = &node.copy_source {
910 if let Some(source) = &node.copy_source {
917 DirstateMap::count_dropped_path(unreachable_bytes, source)
911 DirstateMap::count_dropped_path(unreachable_bytes, source)
918 }
912 }
919 dropped = Dropped {
913 dropped = Dropped {
920 was_tracked: node
914 was_tracked: node
921 .data
915 .data
922 .as_entry()
916 .as_entry()
923 .map_or(false, |entry| entry.state().is_tracked()),
917 .map_or(false, |entry| entry.state().is_tracked()),
924 had_entry,
918 had_entry,
925 had_copy_source: node.copy_source.take().is_some(),
919 had_copy_source: node.copy_source.take().is_some(),
926 };
920 };
927 }
921 }
928 // After recursion, for both leaf (rest_of_path is None) nodes and
922 // After recursion, for both leaf (rest_of_path is None) nodes and
929 // parent nodes, remove a node if it just became empty.
923 // parent nodes, remove a node if it just became empty.
930 let remove = !node.data.has_entry()
924 let remove = !node.data.has_entry()
931 && node.copy_source.is_none()
925 && node.copy_source.is_none()
932 && node.children.is_empty();
926 && node.children.is_empty();
933 if remove {
927 if remove {
934 let (key, _) =
928 let (key, _) =
935 nodes.remove_entry(first_path_component).unwrap();
929 nodes.remove_entry(first_path_component).unwrap();
936 DirstateMap::count_dropped_path(
930 DirstateMap::count_dropped_path(
937 unreachable_bytes,
931 unreachable_bytes,
938 key.full_path(),
932 key.full_path(),
939 )
933 )
940 }
934 }
941 Ok(Some((dropped, remove)))
935 Ok(Some((dropped, remove)))
942 }
936 }
943
937
944 if let Some((dropped, _removed)) = recur(
938 if let Some((dropped, _removed)) = recur(
945 self.on_disk,
939 self.on_disk,
946 &mut self.unreachable_bytes,
940 &mut self.unreachable_bytes,
947 &mut self.root,
941 &mut self.root,
948 filename,
942 filename,
949 )? {
943 )? {
950 if dropped.had_entry {
944 if dropped.had_entry {
951 self.nodes_with_entry_count -= 1
945 self.nodes_with_entry_count -= 1
952 }
946 }
953 if dropped.had_copy_source {
947 if dropped.had_copy_source {
954 self.nodes_with_copy_source_count -= 1
948 self.nodes_with_copy_source_count -= 1
955 }
949 }
956 Ok(dropped.had_entry)
950 Ok(dropped.had_entry)
957 } else {
951 } else {
958 debug_assert!(!old_state.is_tracked());
952 debug_assert!(!was_tracked);
959 Ok(false)
953 Ok(false)
960 }
954 }
961 }
955 }
962
956
963 fn clear_ambiguous_times(
957 fn clear_ambiguous_times(
964 &mut self,
958 &mut self,
965 filenames: Vec<HgPathBuf>,
959 filenames: Vec<HgPathBuf>,
966 now: i32,
960 now: i32,
967 ) -> Result<(), DirstateV2ParseError> {
961 ) -> Result<(), DirstateV2ParseError> {
968 for filename in filenames {
962 for filename in filenames {
969 if let Some(node) = Self::get_node_mut(
963 if let Some(node) = Self::get_node_mut(
970 self.on_disk,
964 self.on_disk,
971 &mut self.unreachable_bytes,
965 &mut self.unreachable_bytes,
972 &mut self.root,
966 &mut self.root,
973 &filename,
967 &filename,
974 )? {
968 )? {
975 if let NodeData::Entry(entry) = &mut node.data {
969 if let NodeData::Entry(entry) = &mut node.data {
976 entry.clear_ambiguous_mtime(now);
970 entry.clear_ambiguous_mtime(now);
977 }
971 }
978 }
972 }
979 }
973 }
980 Ok(())
974 Ok(())
981 }
975 }
982
976
983 fn non_normal_entries_contains(
977 fn non_normal_entries_contains(
984 &mut self,
978 &mut self,
985 key: &HgPath,
979 key: &HgPath,
986 ) -> Result<bool, DirstateV2ParseError> {
980 ) -> Result<bool, DirstateV2ParseError> {
987 Ok(if let Some(node) = self.get_node(key)? {
981 Ok(if let Some(node) = self.get_node(key)? {
988 node.entry()?.map_or(false, |entry| entry.is_non_normal())
982 node.entry()?.map_or(false, |entry| entry.is_non_normal())
989 } else {
983 } else {
990 false
984 false
991 })
985 })
992 }
986 }
993
987
994 fn non_normal_entries_remove(&mut self, key: &HgPath) -> bool {
988 fn non_normal_entries_remove(&mut self, key: &HgPath) -> bool {
995 // Do nothing, this `DirstateMap` does not have a separate "non normal
989 // Do nothing, this `DirstateMap` does not have a separate "non normal
996 // entries" set that need to be kept up to date.
990 // entries" set that need to be kept up to date.
997 if let Ok(Some(v)) = self.get(key) {
991 if let Ok(Some(v)) = self.get(key) {
998 return v.is_non_normal();
992 return v.is_non_normal();
999 }
993 }
1000 false
994 false
1001 }
995 }
1002
996
1003 fn non_normal_entries_add(&mut self, _key: &HgPath) {
997 fn non_normal_entries_add(&mut self, _key: &HgPath) {
1004 // Do nothing, this `DirstateMap` does not have a separate "non normal
998 // Do nothing, this `DirstateMap` does not have a separate "non normal
1005 // entries" set that need to be kept up to date
999 // entries" set that need to be kept up to date
1006 }
1000 }
1007
1001
1008 fn non_normal_or_other_parent_paths(
1002 fn non_normal_or_other_parent_paths(
1009 &mut self,
1003 &mut self,
1010 ) -> Box<dyn Iterator<Item = Result<&HgPath, DirstateV2ParseError>> + '_>
1004 ) -> Box<dyn Iterator<Item = Result<&HgPath, DirstateV2ParseError>> + '_>
1011 {
1005 {
1012 Box::new(self.filter_full_paths(|entry| {
1006 Box::new(self.filter_full_paths(|entry| {
1013 entry.is_non_normal() || entry.is_from_other_parent()
1007 entry.is_non_normal() || entry.is_from_other_parent()
1014 }))
1008 }))
1015 }
1009 }
1016
1010
1017 fn set_non_normal_other_parent_entries(&mut self, _force: bool) {
1011 fn set_non_normal_other_parent_entries(&mut self, _force: bool) {
1018 // Do nothing, this `DirstateMap` does not have a separate "non normal
1012 // Do nothing, this `DirstateMap` does not have a separate "non normal
1019 // entries" and "from other parent" sets that need to be recomputed
1013 // entries" and "from other parent" sets that need to be recomputed
1020 }
1014 }
1021
1015
1022 fn iter_non_normal_paths(
1016 fn iter_non_normal_paths(
1023 &mut self,
1017 &mut self,
1024 ) -> Box<
1018 ) -> Box<
1025 dyn Iterator<Item = Result<&HgPath, DirstateV2ParseError>> + Send + '_,
1019 dyn Iterator<Item = Result<&HgPath, DirstateV2ParseError>> + Send + '_,
1026 > {
1020 > {
1027 self.iter_non_normal_paths_panic()
1021 self.iter_non_normal_paths_panic()
1028 }
1022 }
1029
1023
1030 fn iter_non_normal_paths_panic(
1024 fn iter_non_normal_paths_panic(
1031 &self,
1025 &self,
1032 ) -> Box<
1026 ) -> Box<
1033 dyn Iterator<Item = Result<&HgPath, DirstateV2ParseError>> + Send + '_,
1027 dyn Iterator<Item = Result<&HgPath, DirstateV2ParseError>> + Send + '_,
1034 > {
1028 > {
1035 Box::new(self.filter_full_paths(|entry| entry.is_non_normal()))
1029 Box::new(self.filter_full_paths(|entry| entry.is_non_normal()))
1036 }
1030 }
1037
1031
1038 fn iter_other_parent_paths(
1032 fn iter_other_parent_paths(
1039 &mut self,
1033 &mut self,
1040 ) -> Box<
1034 ) -> Box<
1041 dyn Iterator<Item = Result<&HgPath, DirstateV2ParseError>> + Send + '_,
1035 dyn Iterator<Item = Result<&HgPath, DirstateV2ParseError>> + Send + '_,
1042 > {
1036 > {
1043 Box::new(self.filter_full_paths(|entry| entry.is_from_other_parent()))
1037 Box::new(self.filter_full_paths(|entry| entry.is_from_other_parent()))
1044 }
1038 }
1045
1039
1046 fn has_tracked_dir(
1040 fn has_tracked_dir(
1047 &mut self,
1041 &mut self,
1048 directory: &HgPath,
1042 directory: &HgPath,
1049 ) -> Result<bool, DirstateError> {
1043 ) -> Result<bool, DirstateError> {
1050 if let Some(node) = self.get_node(directory)? {
1044 if let Some(node) = self.get_node(directory)? {
1051 // A node without a `DirstateEntry` was created to hold child
1045 // A node without a `DirstateEntry` was created to hold child
1052 // nodes, and is therefore a directory.
1046 // nodes, and is therefore a directory.
1053 let state = node.state()?;
1047 let state = node.state()?;
1054 Ok(state.is_none() && node.tracked_descendants_count() > 0)
1048 Ok(state.is_none() && node.tracked_descendants_count() > 0)
1055 } else {
1049 } else {
1056 Ok(false)
1050 Ok(false)
1057 }
1051 }
1058 }
1052 }
1059
1053
1060 fn has_dir(&mut self, directory: &HgPath) -> Result<bool, DirstateError> {
1054 fn has_dir(&mut self, directory: &HgPath) -> Result<bool, DirstateError> {
1061 if let Some(node) = self.get_node(directory)? {
1055 if let Some(node) = self.get_node(directory)? {
1062 // A node without a `DirstateEntry` was created to hold child
1056 // A node without a `DirstateEntry` was created to hold child
1063 // nodes, and is therefore a directory.
1057 // nodes, and is therefore a directory.
1064 let state = node.state()?;
1058 let state = node.state()?;
1065 Ok(state.is_none() && node.descendants_with_entry_count() > 0)
1059 Ok(state.is_none() && node.descendants_with_entry_count() > 0)
1066 } else {
1060 } else {
1067 Ok(false)
1061 Ok(false)
1068 }
1062 }
1069 }
1063 }
1070
1064
1071 #[timed]
1065 #[timed]
1072 fn pack_v1(
1066 fn pack_v1(
1073 &mut self,
1067 &mut self,
1074 parents: DirstateParents,
1068 parents: DirstateParents,
1075 now: Timestamp,
1069 now: Timestamp,
1076 ) -> Result<Vec<u8>, DirstateError> {
1070 ) -> Result<Vec<u8>, DirstateError> {
1077 let now: i32 = now.0.try_into().expect("time overflow");
1071 let now: i32 = now.0.try_into().expect("time overflow");
1078 let mut ambiguous_mtimes = Vec::new();
1072 let mut ambiguous_mtimes = Vec::new();
1079 // Optizimation (to be measured?): pre-compute size to avoid `Vec`
1073 // Optizimation (to be measured?): pre-compute size to avoid `Vec`
1080 // reallocations
1074 // reallocations
1081 let mut size = parents.as_bytes().len();
1075 let mut size = parents.as_bytes().len();
1082 for node in self.iter_nodes() {
1076 for node in self.iter_nodes() {
1083 let node = node?;
1077 let node = node?;
1084 if let Some(entry) = node.entry()? {
1078 if let Some(entry) = node.entry()? {
1085 size += packed_entry_size(
1079 size += packed_entry_size(
1086 node.full_path(self.on_disk)?,
1080 node.full_path(self.on_disk)?,
1087 node.copy_source(self.on_disk)?,
1081 node.copy_source(self.on_disk)?,
1088 );
1082 );
1089 if entry.mtime_is_ambiguous(now) {
1083 if entry.mtime_is_ambiguous(now) {
1090 ambiguous_mtimes.push(
1084 ambiguous_mtimes.push(
1091 node.full_path_borrowed(self.on_disk)?
1085 node.full_path_borrowed(self.on_disk)?
1092 .detach_from_tree(),
1086 .detach_from_tree(),
1093 )
1087 )
1094 }
1088 }
1095 }
1089 }
1096 }
1090 }
1097 self.clear_known_ambiguous_mtimes(&ambiguous_mtimes)?;
1091 self.clear_known_ambiguous_mtimes(&ambiguous_mtimes)?;
1098
1092
1099 let mut packed = Vec::with_capacity(size);
1093 let mut packed = Vec::with_capacity(size);
1100 packed.extend(parents.as_bytes());
1094 packed.extend(parents.as_bytes());
1101
1095
1102 for node in self.iter_nodes() {
1096 for node in self.iter_nodes() {
1103 let node = node?;
1097 let node = node?;
1104 if let Some(entry) = node.entry()? {
1098 if let Some(entry) = node.entry()? {
1105 pack_entry(
1099 pack_entry(
1106 node.full_path(self.on_disk)?,
1100 node.full_path(self.on_disk)?,
1107 &entry,
1101 &entry,
1108 node.copy_source(self.on_disk)?,
1102 node.copy_source(self.on_disk)?,
1109 &mut packed,
1103 &mut packed,
1110 );
1104 );
1111 }
1105 }
1112 }
1106 }
1113 Ok(packed)
1107 Ok(packed)
1114 }
1108 }
1115
1109
1116 /// Returns new data and metadata together with whether that data should be
1110 /// Returns new data and metadata together with whether that data should be
1117 /// appended to the existing data file whose content is at
1111 /// appended to the existing data file whose content is at
1118 /// `self.on_disk` (true), instead of written to a new data file
1112 /// `self.on_disk` (true), instead of written to a new data file
1119 /// (false).
1113 /// (false).
1120 #[timed]
1114 #[timed]
1121 fn pack_v2(
1115 fn pack_v2(
1122 &mut self,
1116 &mut self,
1123 now: Timestamp,
1117 now: Timestamp,
1124 can_append: bool,
1118 can_append: bool,
1125 ) -> Result<(Vec<u8>, Vec<u8>, bool), DirstateError> {
1119 ) -> Result<(Vec<u8>, Vec<u8>, bool), DirstateError> {
1126 // TODO: how do we want to handle this in 2038?
1120 // TODO: how do we want to handle this in 2038?
1127 let now: i32 = now.0.try_into().expect("time overflow");
1121 let now: i32 = now.0.try_into().expect("time overflow");
1128 let mut paths = Vec::new();
1122 let mut paths = Vec::new();
1129 for node in self.iter_nodes() {
1123 for node in self.iter_nodes() {
1130 let node = node?;
1124 let node = node?;
1131 if let Some(entry) = node.entry()? {
1125 if let Some(entry) = node.entry()? {
1132 if entry.mtime_is_ambiguous(now) {
1126 if entry.mtime_is_ambiguous(now) {
1133 paths.push(
1127 paths.push(
1134 node.full_path_borrowed(self.on_disk)?
1128 node.full_path_borrowed(self.on_disk)?
1135 .detach_from_tree(),
1129 .detach_from_tree(),
1136 )
1130 )
1137 }
1131 }
1138 }
1132 }
1139 }
1133 }
1140 // Borrow of `self` ends here since we collect cloned paths
1134 // Borrow of `self` ends here since we collect cloned paths
1141
1135
1142 self.clear_known_ambiguous_mtimes(&paths)?;
1136 self.clear_known_ambiguous_mtimes(&paths)?;
1143
1137
1144 on_disk::write(self, can_append)
1138 on_disk::write(self, can_append)
1145 }
1139 }
1146
1140
1147 fn status<'a>(
1141 fn status<'a>(
1148 &'a mut self,
1142 &'a mut self,
1149 matcher: &'a (dyn Matcher + Sync),
1143 matcher: &'a (dyn Matcher + Sync),
1150 root_dir: PathBuf,
1144 root_dir: PathBuf,
1151 ignore_files: Vec<PathBuf>,
1145 ignore_files: Vec<PathBuf>,
1152 options: StatusOptions,
1146 options: StatusOptions,
1153 ) -> Result<(DirstateStatus<'a>, Vec<PatternFileWarning>), StatusError>
1147 ) -> Result<(DirstateStatus<'a>, Vec<PatternFileWarning>), StatusError>
1154 {
1148 {
1155 super::status::status(self, matcher, root_dir, ignore_files, options)
1149 super::status::status(self, matcher, root_dir, ignore_files, options)
1156 }
1150 }
1157
1151
1158 fn copy_map_len(&self) -> usize {
1152 fn copy_map_len(&self) -> usize {
1159 self.nodes_with_copy_source_count as usize
1153 self.nodes_with_copy_source_count as usize
1160 }
1154 }
1161
1155
1162 fn copy_map_iter(&self) -> CopyMapIter<'_> {
1156 fn copy_map_iter(&self) -> CopyMapIter<'_> {
1163 Box::new(filter_map_results(self.iter_nodes(), move |node| {
1157 Box::new(filter_map_results(self.iter_nodes(), move |node| {
1164 Ok(if let Some(source) = node.copy_source(self.on_disk)? {
1158 Ok(if let Some(source) = node.copy_source(self.on_disk)? {
1165 Some((node.full_path(self.on_disk)?, source))
1159 Some((node.full_path(self.on_disk)?, source))
1166 } else {
1160 } else {
1167 None
1161 None
1168 })
1162 })
1169 }))
1163 }))
1170 }
1164 }
1171
1165
1172 fn copy_map_contains_key(
1166 fn copy_map_contains_key(
1173 &self,
1167 &self,
1174 key: &HgPath,
1168 key: &HgPath,
1175 ) -> Result<bool, DirstateV2ParseError> {
1169 ) -> Result<bool, DirstateV2ParseError> {
1176 Ok(if let Some(node) = self.get_node(key)? {
1170 Ok(if let Some(node) = self.get_node(key)? {
1177 node.has_copy_source()
1171 node.has_copy_source()
1178 } else {
1172 } else {
1179 false
1173 false
1180 })
1174 })
1181 }
1175 }
1182
1176
1183 fn copy_map_get(
1177 fn copy_map_get(
1184 &self,
1178 &self,
1185 key: &HgPath,
1179 key: &HgPath,
1186 ) -> Result<Option<&HgPath>, DirstateV2ParseError> {
1180 ) -> Result<Option<&HgPath>, DirstateV2ParseError> {
1187 if let Some(node) = self.get_node(key)? {
1181 if let Some(node) = self.get_node(key)? {
1188 if let Some(source) = node.copy_source(self.on_disk)? {
1182 if let Some(source) = node.copy_source(self.on_disk)? {
1189 return Ok(Some(source));
1183 return Ok(Some(source));
1190 }
1184 }
1191 }
1185 }
1192 Ok(None)
1186 Ok(None)
1193 }
1187 }
1194
1188
1195 fn copy_map_remove(
1189 fn copy_map_remove(
1196 &mut self,
1190 &mut self,
1197 key: &HgPath,
1191 key: &HgPath,
1198 ) -> Result<Option<HgPathBuf>, DirstateV2ParseError> {
1192 ) -> Result<Option<HgPathBuf>, DirstateV2ParseError> {
1199 let count = &mut self.nodes_with_copy_source_count;
1193 let count = &mut self.nodes_with_copy_source_count;
1200 let unreachable_bytes = &mut self.unreachable_bytes;
1194 let unreachable_bytes = &mut self.unreachable_bytes;
1201 Ok(Self::get_node_mut(
1195 Ok(Self::get_node_mut(
1202 self.on_disk,
1196 self.on_disk,
1203 unreachable_bytes,
1197 unreachable_bytes,
1204 &mut self.root,
1198 &mut self.root,
1205 key,
1199 key,
1206 )?
1200 )?
1207 .and_then(|node| {
1201 .and_then(|node| {
1208 if let Some(source) = &node.copy_source {
1202 if let Some(source) = &node.copy_source {
1209 *count -= 1;
1203 *count -= 1;
1210 Self::count_dropped_path(unreachable_bytes, source);
1204 Self::count_dropped_path(unreachable_bytes, source);
1211 }
1205 }
1212 node.copy_source.take().map(Cow::into_owned)
1206 node.copy_source.take().map(Cow::into_owned)
1213 }))
1207 }))
1214 }
1208 }
1215
1209
1216 fn copy_map_insert(
1210 fn copy_map_insert(
1217 &mut self,
1211 &mut self,
1218 key: HgPathBuf,
1212 key: HgPathBuf,
1219 value: HgPathBuf,
1213 value: HgPathBuf,
1220 ) -> Result<Option<HgPathBuf>, DirstateV2ParseError> {
1214 ) -> Result<Option<HgPathBuf>, DirstateV2ParseError> {
1221 let node = Self::get_or_insert_node(
1215 let node = Self::get_or_insert_node(
1222 self.on_disk,
1216 self.on_disk,
1223 &mut self.unreachable_bytes,
1217 &mut self.unreachable_bytes,
1224 &mut self.root,
1218 &mut self.root,
1225 &key,
1219 &key,
1226 WithBasename::to_cow_owned,
1220 WithBasename::to_cow_owned,
1227 |_ancestor| {},
1221 |_ancestor| {},
1228 )?;
1222 )?;
1229 if node.copy_source.is_none() {
1223 if node.copy_source.is_none() {
1230 self.nodes_with_copy_source_count += 1
1224 self.nodes_with_copy_source_count += 1
1231 }
1225 }
1232 Ok(node.copy_source.replace(value.into()).map(Cow::into_owned))
1226 Ok(node.copy_source.replace(value.into()).map(Cow::into_owned))
1233 }
1227 }
1234
1228
1235 fn len(&self) -> usize {
1229 fn len(&self) -> usize {
1236 self.nodes_with_entry_count as usize
1230 self.nodes_with_entry_count as usize
1237 }
1231 }
1238
1232
1239 fn contains_key(
1233 fn contains_key(
1240 &self,
1234 &self,
1241 key: &HgPath,
1235 key: &HgPath,
1242 ) -> Result<bool, DirstateV2ParseError> {
1236 ) -> Result<bool, DirstateV2ParseError> {
1243 Ok(self.get(key)?.is_some())
1237 Ok(self.get(key)?.is_some())
1244 }
1238 }
1245
1239
1246 fn get(
1240 fn get(
1247 &self,
1241 &self,
1248 key: &HgPath,
1242 key: &HgPath,
1249 ) -> Result<Option<DirstateEntry>, DirstateV2ParseError> {
1243 ) -> Result<Option<DirstateEntry>, DirstateV2ParseError> {
1250 Ok(if let Some(node) = self.get_node(key)? {
1244 Ok(if let Some(node) = self.get_node(key)? {
1251 node.entry()?
1245 node.entry()?
1252 } else {
1246 } else {
1253 None
1247 None
1254 })
1248 })
1255 }
1249 }
1256
1250
1257 fn iter(&self) -> StateMapIter<'_> {
1251 fn iter(&self) -> StateMapIter<'_> {
1258 Box::new(filter_map_results(self.iter_nodes(), move |node| {
1252 Box::new(filter_map_results(self.iter_nodes(), move |node| {
1259 Ok(if let Some(entry) = node.entry()? {
1253 Ok(if let Some(entry) = node.entry()? {
1260 Some((node.full_path(self.on_disk)?, entry))
1254 Some((node.full_path(self.on_disk)?, entry))
1261 } else {
1255 } else {
1262 None
1256 None
1263 })
1257 })
1264 }))
1258 }))
1265 }
1259 }
1266
1260
1267 fn iter_tracked_dirs(
1261 fn iter_tracked_dirs(
1268 &mut self,
1262 &mut self,
1269 ) -> Result<
1263 ) -> Result<
1270 Box<
1264 Box<
1271 dyn Iterator<Item = Result<&HgPath, DirstateV2ParseError>>
1265 dyn Iterator<Item = Result<&HgPath, DirstateV2ParseError>>
1272 + Send
1266 + Send
1273 + '_,
1267 + '_,
1274 >,
1268 >,
1275 DirstateError,
1269 DirstateError,
1276 > {
1270 > {
1277 let on_disk = self.on_disk;
1271 let on_disk = self.on_disk;
1278 Ok(Box::new(filter_map_results(
1272 Ok(Box::new(filter_map_results(
1279 self.iter_nodes(),
1273 self.iter_nodes(),
1280 move |node| {
1274 move |node| {
1281 Ok(if node.tracked_descendants_count() > 0 {
1275 Ok(if node.tracked_descendants_count() > 0 {
1282 Some(node.full_path(on_disk)?)
1276 Some(node.full_path(on_disk)?)
1283 } else {
1277 } else {
1284 None
1278 None
1285 })
1279 })
1286 },
1280 },
1287 )))
1281 )))
1288 }
1282 }
1289
1283
1290 fn debug_iter(
1284 fn debug_iter(
1291 &self,
1285 &self,
1292 all: bool,
1286 all: bool,
1293 ) -> Box<
1287 ) -> Box<
1294 dyn Iterator<
1288 dyn Iterator<
1295 Item = Result<
1289 Item = Result<
1296 (&HgPath, (u8, i32, i32, i32)),
1290 (&HgPath, (u8, i32, i32, i32)),
1297 DirstateV2ParseError,
1291 DirstateV2ParseError,
1298 >,
1292 >,
1299 > + Send
1293 > + Send
1300 + '_,
1294 + '_,
1301 > {
1295 > {
1302 Box::new(filter_map_results(self.iter_nodes(), move |node| {
1296 Box::new(filter_map_results(self.iter_nodes(), move |node| {
1303 let debug_tuple = if let Some(entry) = node.entry()? {
1297 let debug_tuple = if let Some(entry) = node.entry()? {
1304 entry.debug_tuple()
1298 entry.debug_tuple()
1305 } else if !all {
1299 } else if !all {
1306 return Ok(None);
1300 return Ok(None);
1307 } else if let Some(mtime) = node.cached_directory_mtime() {
1301 } else if let Some(mtime) = node.cached_directory_mtime() {
1308 (b' ', 0, -1, mtime.seconds() as i32)
1302 (b' ', 0, -1, mtime.seconds() as i32)
1309 } else {
1303 } else {
1310 (b' ', 0, -1, -1)
1304 (b' ', 0, -1, -1)
1311 };
1305 };
1312 Ok(Some((node.full_path(self.on_disk)?, debug_tuple)))
1306 Ok(Some((node.full_path(self.on_disk)?, debug_tuple)))
1313 }))
1307 }))
1314 }
1308 }
1315 }
1309 }
@@ -1,756 +1,753 b''
1 use crate::dirstate::status::IgnoreFnType;
1 use crate::dirstate::status::IgnoreFnType;
2 use crate::dirstate_tree::dirstate_map::BorrowedPath;
2 use crate::dirstate_tree::dirstate_map::BorrowedPath;
3 use crate::dirstate_tree::dirstate_map::ChildNodesRef;
3 use crate::dirstate_tree::dirstate_map::ChildNodesRef;
4 use crate::dirstate_tree::dirstate_map::DirstateMap;
4 use crate::dirstate_tree::dirstate_map::DirstateMap;
5 use crate::dirstate_tree::dirstate_map::NodeData;
5 use crate::dirstate_tree::dirstate_map::NodeData;
6 use crate::dirstate_tree::dirstate_map::NodeRef;
6 use crate::dirstate_tree::dirstate_map::NodeRef;
7 use crate::dirstate_tree::on_disk::DirstateV2ParseError;
7 use crate::dirstate_tree::on_disk::DirstateV2ParseError;
8 use crate::dirstate_tree::on_disk::Timestamp;
8 use crate::dirstate_tree::on_disk::Timestamp;
9 use crate::matchers::get_ignore_function;
9 use crate::matchers::get_ignore_function;
10 use crate::matchers::Matcher;
10 use crate::matchers::Matcher;
11 use crate::utils::files::get_bytes_from_os_string;
11 use crate::utils::files::get_bytes_from_os_string;
12 use crate::utils::files::get_path_from_bytes;
12 use crate::utils::files::get_path_from_bytes;
13 use crate::utils::hg_path::HgPath;
13 use crate::utils::hg_path::HgPath;
14 use crate::BadMatch;
14 use crate::BadMatch;
15 use crate::DirstateStatus;
15 use crate::DirstateStatus;
16 use crate::EntryState;
16 use crate::EntryState;
17 use crate::HgPathBuf;
17 use crate::HgPathBuf;
18 use crate::PatternFileWarning;
18 use crate::PatternFileWarning;
19 use crate::StatusError;
19 use crate::StatusError;
20 use crate::StatusOptions;
20 use crate::StatusOptions;
21 use micro_timer::timed;
21 use micro_timer::timed;
22 use rayon::prelude::*;
22 use rayon::prelude::*;
23 use sha1::{Digest, Sha1};
23 use sha1::{Digest, Sha1};
24 use std::borrow::Cow;
24 use std::borrow::Cow;
25 use std::io;
25 use std::io;
26 use std::path::Path;
26 use std::path::Path;
27 use std::path::PathBuf;
27 use std::path::PathBuf;
28 use std::sync::Mutex;
28 use std::sync::Mutex;
29 use std::time::SystemTime;
29 use std::time::SystemTime;
30
30
31 /// Returns the status of the working directory compared to its parent
31 /// Returns the status of the working directory compared to its parent
32 /// changeset.
32 /// changeset.
33 ///
33 ///
34 /// This algorithm is based on traversing the filesystem tree (`fs` in function
34 /// This algorithm is based on traversing the filesystem tree (`fs` in function
35 /// and variable names) and dirstate tree at the same time. The core of this
35 /// and variable names) and dirstate tree at the same time. The core of this
36 /// traversal is the recursive `traverse_fs_directory_and_dirstate` function
36 /// traversal is the recursive `traverse_fs_directory_and_dirstate` function
37 /// and its use of `itertools::merge_join_by`. When reaching a path that only
37 /// and its use of `itertools::merge_join_by`. When reaching a path that only
38 /// exists in one of the two trees, depending on information requested by
38 /// exists in one of the two trees, depending on information requested by
39 /// `options` we may need to traverse the remaining subtree.
39 /// `options` we may need to traverse the remaining subtree.
40 #[timed]
40 #[timed]
41 pub fn status<'tree, 'on_disk: 'tree>(
41 pub fn status<'tree, 'on_disk: 'tree>(
42 dmap: &'tree mut DirstateMap<'on_disk>,
42 dmap: &'tree mut DirstateMap<'on_disk>,
43 matcher: &(dyn Matcher + Sync),
43 matcher: &(dyn Matcher + Sync),
44 root_dir: PathBuf,
44 root_dir: PathBuf,
45 ignore_files: Vec<PathBuf>,
45 ignore_files: Vec<PathBuf>,
46 options: StatusOptions,
46 options: StatusOptions,
47 ) -> Result<(DirstateStatus<'on_disk>, Vec<PatternFileWarning>), StatusError> {
47 ) -> Result<(DirstateStatus<'on_disk>, Vec<PatternFileWarning>), StatusError> {
48 let (ignore_fn, warnings, patterns_changed): (IgnoreFnType, _, _) =
48 let (ignore_fn, warnings, patterns_changed): (IgnoreFnType, _, _) =
49 if options.list_ignored || options.list_unknown {
49 if options.list_ignored || options.list_unknown {
50 let mut hasher = Sha1::new();
50 let mut hasher = Sha1::new();
51 let (ignore_fn, warnings) = get_ignore_function(
51 let (ignore_fn, warnings) = get_ignore_function(
52 ignore_files,
52 ignore_files,
53 &root_dir,
53 &root_dir,
54 &mut |pattern_bytes| hasher.update(pattern_bytes),
54 &mut |pattern_bytes| hasher.update(pattern_bytes),
55 )?;
55 )?;
56 let new_hash = *hasher.finalize().as_ref();
56 let new_hash = *hasher.finalize().as_ref();
57 let changed = new_hash != dmap.ignore_patterns_hash;
57 let changed = new_hash != dmap.ignore_patterns_hash;
58 dmap.ignore_patterns_hash = new_hash;
58 dmap.ignore_patterns_hash = new_hash;
59 (ignore_fn, warnings, Some(changed))
59 (ignore_fn, warnings, Some(changed))
60 } else {
60 } else {
61 (Box::new(|&_| true), vec![], None)
61 (Box::new(|&_| true), vec![], None)
62 };
62 };
63
63
64 let common = StatusCommon {
64 let common = StatusCommon {
65 dmap,
65 dmap,
66 options,
66 options,
67 matcher,
67 matcher,
68 ignore_fn,
68 ignore_fn,
69 outcome: Default::default(),
69 outcome: Default::default(),
70 ignore_patterns_have_changed: patterns_changed,
70 ignore_patterns_have_changed: patterns_changed,
71 new_cachable_directories: Default::default(),
71 new_cachable_directories: Default::default(),
72 outated_cached_directories: Default::default(),
72 outated_cached_directories: Default::default(),
73 filesystem_time_at_status_start: filesystem_now(&root_dir).ok(),
73 filesystem_time_at_status_start: filesystem_now(&root_dir).ok(),
74 };
74 };
75 let is_at_repo_root = true;
75 let is_at_repo_root = true;
76 let hg_path = &BorrowedPath::OnDisk(HgPath::new(""));
76 let hg_path = &BorrowedPath::OnDisk(HgPath::new(""));
77 let has_ignored_ancestor = false;
77 let has_ignored_ancestor = false;
78 let root_cached_mtime = None;
78 let root_cached_mtime = None;
79 let root_dir_metadata = None;
79 let root_dir_metadata = None;
80 // If the path we have for the repository root is a symlink, do follow it.
80 // If the path we have for the repository root is a symlink, do follow it.
81 // (As opposed to symlinks within the working directory which are not
81 // (As opposed to symlinks within the working directory which are not
82 // followed, using `std::fs::symlink_metadata`.)
82 // followed, using `std::fs::symlink_metadata`.)
83 common.traverse_fs_directory_and_dirstate(
83 common.traverse_fs_directory_and_dirstate(
84 has_ignored_ancestor,
84 has_ignored_ancestor,
85 dmap.root.as_ref(),
85 dmap.root.as_ref(),
86 hg_path,
86 hg_path,
87 &root_dir,
87 &root_dir,
88 root_dir_metadata,
88 root_dir_metadata,
89 root_cached_mtime,
89 root_cached_mtime,
90 is_at_repo_root,
90 is_at_repo_root,
91 )?;
91 )?;
92 let mut outcome = common.outcome.into_inner().unwrap();
92 let mut outcome = common.outcome.into_inner().unwrap();
93 let new_cachable = common.new_cachable_directories.into_inner().unwrap();
93 let new_cachable = common.new_cachable_directories.into_inner().unwrap();
94 let outdated = common.outated_cached_directories.into_inner().unwrap();
94 let outdated = common.outated_cached_directories.into_inner().unwrap();
95
95
96 outcome.dirty = common.ignore_patterns_have_changed == Some(true)
96 outcome.dirty = common.ignore_patterns_have_changed == Some(true)
97 || !outdated.is_empty()
97 || !outdated.is_empty()
98 || !new_cachable.is_empty();
98 || !new_cachable.is_empty();
99
99
100 // Remove outdated mtimes before adding new mtimes, in case a given
100 // Remove outdated mtimes before adding new mtimes, in case a given
101 // directory is both
101 // directory is both
102 for path in &outdated {
102 for path in &outdated {
103 let node = dmap.get_or_insert(path)?;
103 let node = dmap.get_or_insert(path)?;
104 if let NodeData::CachedDirectory { .. } = &node.data {
104 if let NodeData::CachedDirectory { .. } = &node.data {
105 node.data = NodeData::None
105 node.data = NodeData::None
106 }
106 }
107 }
107 }
108 for (path, mtime) in &new_cachable {
108 for (path, mtime) in &new_cachable {
109 let node = dmap.get_or_insert(path)?;
109 let node = dmap.get_or_insert(path)?;
110 match &node.data {
110 match &node.data {
111 NodeData::Entry(_) => {} // Don’t overwrite an entry
111 NodeData::Entry(_) => {} // Don’t overwrite an entry
112 NodeData::CachedDirectory { .. } | NodeData::None => {
112 NodeData::CachedDirectory { .. } | NodeData::None => {
113 node.data = NodeData::CachedDirectory { mtime: *mtime }
113 node.data = NodeData::CachedDirectory { mtime: *mtime }
114 }
114 }
115 }
115 }
116 }
116 }
117
117
118 Ok((outcome, warnings))
118 Ok((outcome, warnings))
119 }
119 }
120
120
121 /// Bag of random things needed by various parts of the algorithm. Reduces the
121 /// Bag of random things needed by various parts of the algorithm. Reduces the
122 /// number of parameters passed to functions.
122 /// number of parameters passed to functions.
123 struct StatusCommon<'a, 'tree, 'on_disk: 'tree> {
123 struct StatusCommon<'a, 'tree, 'on_disk: 'tree> {
124 dmap: &'tree DirstateMap<'on_disk>,
124 dmap: &'tree DirstateMap<'on_disk>,
125 options: StatusOptions,
125 options: StatusOptions,
126 matcher: &'a (dyn Matcher + Sync),
126 matcher: &'a (dyn Matcher + Sync),
127 ignore_fn: IgnoreFnType<'a>,
127 ignore_fn: IgnoreFnType<'a>,
128 outcome: Mutex<DirstateStatus<'on_disk>>,
128 outcome: Mutex<DirstateStatus<'on_disk>>,
129 new_cachable_directories: Mutex<Vec<(Cow<'on_disk, HgPath>, Timestamp)>>,
129 new_cachable_directories: Mutex<Vec<(Cow<'on_disk, HgPath>, Timestamp)>>,
130 outated_cached_directories: Mutex<Vec<Cow<'on_disk, HgPath>>>,
130 outated_cached_directories: Mutex<Vec<Cow<'on_disk, HgPath>>>,
131
131
132 /// Whether ignore files like `.hgignore` have changed since the previous
132 /// Whether ignore files like `.hgignore` have changed since the previous
133 /// time a `status()` call wrote their hash to the dirstate. `None` means
133 /// time a `status()` call wrote their hash to the dirstate. `None` means
134 /// we don’t know as this run doesn’t list either ignored or uknown files
134 /// we don’t know as this run doesn’t list either ignored or uknown files
135 /// and therefore isn’t reading `.hgignore`.
135 /// and therefore isn’t reading `.hgignore`.
136 ignore_patterns_have_changed: Option<bool>,
136 ignore_patterns_have_changed: Option<bool>,
137
137
138 /// The current time at the start of the `status()` algorithm, as measured
138 /// The current time at the start of the `status()` algorithm, as measured
139 /// and possibly truncated by the filesystem.
139 /// and possibly truncated by the filesystem.
140 filesystem_time_at_status_start: Option<SystemTime>,
140 filesystem_time_at_status_start: Option<SystemTime>,
141 }
141 }
142
142
143 impl<'a, 'tree, 'on_disk> StatusCommon<'a, 'tree, 'on_disk> {
143 impl<'a, 'tree, 'on_disk> StatusCommon<'a, 'tree, 'on_disk> {
144 fn read_dir(
144 fn read_dir(
145 &self,
145 &self,
146 hg_path: &HgPath,
146 hg_path: &HgPath,
147 fs_path: &Path,
147 fs_path: &Path,
148 is_at_repo_root: bool,
148 is_at_repo_root: bool,
149 ) -> Result<Vec<DirEntry>, ()> {
149 ) -> Result<Vec<DirEntry>, ()> {
150 DirEntry::read_dir(fs_path, is_at_repo_root)
150 DirEntry::read_dir(fs_path, is_at_repo_root)
151 .map_err(|error| self.io_error(error, hg_path))
151 .map_err(|error| self.io_error(error, hg_path))
152 }
152 }
153
153
154 fn io_error(&self, error: std::io::Error, hg_path: &HgPath) {
154 fn io_error(&self, error: std::io::Error, hg_path: &HgPath) {
155 let errno = error.raw_os_error().expect("expected real OS error");
155 let errno = error.raw_os_error().expect("expected real OS error");
156 self.outcome
156 self.outcome
157 .lock()
157 .lock()
158 .unwrap()
158 .unwrap()
159 .bad
159 .bad
160 .push((hg_path.to_owned().into(), BadMatch::OsError(errno)))
160 .push((hg_path.to_owned().into(), BadMatch::OsError(errno)))
161 }
161 }
162
162
163 fn check_for_outdated_directory_cache(
163 fn check_for_outdated_directory_cache(
164 &self,
164 &self,
165 dirstate_node: &NodeRef<'tree, 'on_disk>,
165 dirstate_node: &NodeRef<'tree, 'on_disk>,
166 ) -> Result<(), DirstateV2ParseError> {
166 ) -> Result<(), DirstateV2ParseError> {
167 if self.ignore_patterns_have_changed == Some(true)
167 if self.ignore_patterns_have_changed == Some(true)
168 && dirstate_node.cached_directory_mtime().is_some()
168 && dirstate_node.cached_directory_mtime().is_some()
169 {
169 {
170 self.outated_cached_directories.lock().unwrap().push(
170 self.outated_cached_directories.lock().unwrap().push(
171 dirstate_node
171 dirstate_node
172 .full_path_borrowed(self.dmap.on_disk)?
172 .full_path_borrowed(self.dmap.on_disk)?
173 .detach_from_tree(),
173 .detach_from_tree(),
174 )
174 )
175 }
175 }
176 Ok(())
176 Ok(())
177 }
177 }
178
178
179 /// If this returns true, we can get accurate results by only using
179 /// If this returns true, we can get accurate results by only using
180 /// `symlink_metadata` for child nodes that exist in the dirstate and don’t
180 /// `symlink_metadata` for child nodes that exist in the dirstate and don’t
181 /// need to call `read_dir`.
181 /// need to call `read_dir`.
182 fn can_skip_fs_readdir(
182 fn can_skip_fs_readdir(
183 &self,
183 &self,
184 directory_metadata: Option<&std::fs::Metadata>,
184 directory_metadata: Option<&std::fs::Metadata>,
185 cached_directory_mtime: Option<&Timestamp>,
185 cached_directory_mtime: Option<&Timestamp>,
186 ) -> bool {
186 ) -> bool {
187 if !self.options.list_unknown && !self.options.list_ignored {
187 if !self.options.list_unknown && !self.options.list_ignored {
188 // All states that we care about listing have corresponding
188 // All states that we care about listing have corresponding
189 // dirstate entries.
189 // dirstate entries.
190 // This happens for example with `hg status -mard`.
190 // This happens for example with `hg status -mard`.
191 return true;
191 return true;
192 }
192 }
193 if !self.options.list_ignored
193 if !self.options.list_ignored
194 && self.ignore_patterns_have_changed == Some(false)
194 && self.ignore_patterns_have_changed == Some(false)
195 {
195 {
196 if let Some(cached_mtime) = cached_directory_mtime {
196 if let Some(cached_mtime) = cached_directory_mtime {
197 // The dirstate contains a cached mtime for this directory, set
197 // The dirstate contains a cached mtime for this directory, set
198 // by a previous run of the `status` algorithm which found this
198 // by a previous run of the `status` algorithm which found this
199 // directory eligible for `read_dir` caching.
199 // directory eligible for `read_dir` caching.
200 if let Some(meta) = directory_metadata {
200 if let Some(meta) = directory_metadata {
201 if let Ok(current_mtime) = meta.modified() {
201 if let Ok(current_mtime) = meta.modified() {
202 if current_mtime == cached_mtime.into() {
202 if current_mtime == cached_mtime.into() {
203 // The mtime of that directory has not changed
203 // The mtime of that directory has not changed
204 // since then, which means that the results of
204 // since then, which means that the results of
205 // `read_dir` should also be unchanged.
205 // `read_dir` should also be unchanged.
206 return true;
206 return true;
207 }
207 }
208 }
208 }
209 }
209 }
210 }
210 }
211 }
211 }
212 false
212 false
213 }
213 }
214
214
215 /// Returns whether all child entries of the filesystem directory have a
215 /// Returns whether all child entries of the filesystem directory have a
216 /// corresponding dirstate node or are ignored.
216 /// corresponding dirstate node or are ignored.
217 fn traverse_fs_directory_and_dirstate(
217 fn traverse_fs_directory_and_dirstate(
218 &self,
218 &self,
219 has_ignored_ancestor: bool,
219 has_ignored_ancestor: bool,
220 dirstate_nodes: ChildNodesRef<'tree, 'on_disk>,
220 dirstate_nodes: ChildNodesRef<'tree, 'on_disk>,
221 directory_hg_path: &BorrowedPath<'tree, 'on_disk>,
221 directory_hg_path: &BorrowedPath<'tree, 'on_disk>,
222 directory_fs_path: &Path,
222 directory_fs_path: &Path,
223 directory_metadata: Option<&std::fs::Metadata>,
223 directory_metadata: Option<&std::fs::Metadata>,
224 cached_directory_mtime: Option<&Timestamp>,
224 cached_directory_mtime: Option<&Timestamp>,
225 is_at_repo_root: bool,
225 is_at_repo_root: bool,
226 ) -> Result<bool, DirstateV2ParseError> {
226 ) -> Result<bool, DirstateV2ParseError> {
227 if self.can_skip_fs_readdir(directory_metadata, cached_directory_mtime)
227 if self.can_skip_fs_readdir(directory_metadata, cached_directory_mtime)
228 {
228 {
229 dirstate_nodes
229 dirstate_nodes
230 .par_iter()
230 .par_iter()
231 .map(|dirstate_node| {
231 .map(|dirstate_node| {
232 let fs_path = directory_fs_path.join(get_path_from_bytes(
232 let fs_path = directory_fs_path.join(get_path_from_bytes(
233 dirstate_node.base_name(self.dmap.on_disk)?.as_bytes(),
233 dirstate_node.base_name(self.dmap.on_disk)?.as_bytes(),
234 ));
234 ));
235 match std::fs::symlink_metadata(&fs_path) {
235 match std::fs::symlink_metadata(&fs_path) {
236 Ok(fs_metadata) => self.traverse_fs_and_dirstate(
236 Ok(fs_metadata) => self.traverse_fs_and_dirstate(
237 &fs_path,
237 &fs_path,
238 &fs_metadata,
238 &fs_metadata,
239 dirstate_node,
239 dirstate_node,
240 has_ignored_ancestor,
240 has_ignored_ancestor,
241 ),
241 ),
242 Err(e) if e.kind() == std::io::ErrorKind::NotFound => {
242 Err(e) if e.kind() == std::io::ErrorKind::NotFound => {
243 self.traverse_dirstate_only(dirstate_node)
243 self.traverse_dirstate_only(dirstate_node)
244 }
244 }
245 Err(error) => {
245 Err(error) => {
246 let hg_path =
246 let hg_path =
247 dirstate_node.full_path(self.dmap.on_disk)?;
247 dirstate_node.full_path(self.dmap.on_disk)?;
248 Ok(self.io_error(error, hg_path))
248 Ok(self.io_error(error, hg_path))
249 }
249 }
250 }
250 }
251 })
251 })
252 .collect::<Result<_, _>>()?;
252 .collect::<Result<_, _>>()?;
253
253
254 // We don’t know, so conservatively say this isn’t the case
254 // We don’t know, so conservatively say this isn’t the case
255 let children_all_have_dirstate_node_or_are_ignored = false;
255 let children_all_have_dirstate_node_or_are_ignored = false;
256
256
257 return Ok(children_all_have_dirstate_node_or_are_ignored);
257 return Ok(children_all_have_dirstate_node_or_are_ignored);
258 }
258 }
259
259
260 let mut fs_entries = if let Ok(entries) = self.read_dir(
260 let mut fs_entries = if let Ok(entries) = self.read_dir(
261 directory_hg_path,
261 directory_hg_path,
262 directory_fs_path,
262 directory_fs_path,
263 is_at_repo_root,
263 is_at_repo_root,
264 ) {
264 ) {
265 entries
265 entries
266 } else {
266 } else {
267 // Treat an unreadable directory (typically because of insufficient
267 // Treat an unreadable directory (typically because of insufficient
268 // permissions) like an empty directory. `self.read_dir` has
268 // permissions) like an empty directory. `self.read_dir` has
269 // already called `self.io_error` so a warning will be emitted.
269 // already called `self.io_error` so a warning will be emitted.
270 Vec::new()
270 Vec::new()
271 };
271 };
272
272
273 // `merge_join_by` requires both its input iterators to be sorted:
273 // `merge_join_by` requires both its input iterators to be sorted:
274
274
275 let dirstate_nodes = dirstate_nodes.sorted();
275 let dirstate_nodes = dirstate_nodes.sorted();
276 // `sort_unstable_by_key` doesn’t allow keys borrowing from the value:
276 // `sort_unstable_by_key` doesn’t allow keys borrowing from the value:
277 // https://github.com/rust-lang/rust/issues/34162
277 // https://github.com/rust-lang/rust/issues/34162
278 fs_entries.sort_unstable_by(|e1, e2| e1.base_name.cmp(&e2.base_name));
278 fs_entries.sort_unstable_by(|e1, e2| e1.base_name.cmp(&e2.base_name));
279
279
280 // Propagate here any error that would happen inside the comparison
280 // Propagate here any error that would happen inside the comparison
281 // callback below
281 // callback below
282 for dirstate_node in &dirstate_nodes {
282 for dirstate_node in &dirstate_nodes {
283 dirstate_node.base_name(self.dmap.on_disk)?;
283 dirstate_node.base_name(self.dmap.on_disk)?;
284 }
284 }
285 itertools::merge_join_by(
285 itertools::merge_join_by(
286 dirstate_nodes,
286 dirstate_nodes,
287 &fs_entries,
287 &fs_entries,
288 |dirstate_node, fs_entry| {
288 |dirstate_node, fs_entry| {
289 // This `unwrap` never panics because we already propagated
289 // This `unwrap` never panics because we already propagated
290 // those errors above
290 // those errors above
291 dirstate_node
291 dirstate_node
292 .base_name(self.dmap.on_disk)
292 .base_name(self.dmap.on_disk)
293 .unwrap()
293 .unwrap()
294 .cmp(&fs_entry.base_name)
294 .cmp(&fs_entry.base_name)
295 },
295 },
296 )
296 )
297 .par_bridge()
297 .par_bridge()
298 .map(|pair| {
298 .map(|pair| {
299 use itertools::EitherOrBoth::*;
299 use itertools::EitherOrBoth::*;
300 let has_dirstate_node_or_is_ignored;
300 let has_dirstate_node_or_is_ignored;
301 match pair {
301 match pair {
302 Both(dirstate_node, fs_entry) => {
302 Both(dirstate_node, fs_entry) => {
303 self.traverse_fs_and_dirstate(
303 self.traverse_fs_and_dirstate(
304 &fs_entry.full_path,
304 &fs_entry.full_path,
305 &fs_entry.metadata,
305 &fs_entry.metadata,
306 dirstate_node,
306 dirstate_node,
307 has_ignored_ancestor,
307 has_ignored_ancestor,
308 )?;
308 )?;
309 has_dirstate_node_or_is_ignored = true
309 has_dirstate_node_or_is_ignored = true
310 }
310 }
311 Left(dirstate_node) => {
311 Left(dirstate_node) => {
312 self.traverse_dirstate_only(dirstate_node)?;
312 self.traverse_dirstate_only(dirstate_node)?;
313 has_dirstate_node_or_is_ignored = true;
313 has_dirstate_node_or_is_ignored = true;
314 }
314 }
315 Right(fs_entry) => {
315 Right(fs_entry) => {
316 has_dirstate_node_or_is_ignored = self.traverse_fs_only(
316 has_dirstate_node_or_is_ignored = self.traverse_fs_only(
317 has_ignored_ancestor,
317 has_ignored_ancestor,
318 directory_hg_path,
318 directory_hg_path,
319 fs_entry,
319 fs_entry,
320 )
320 )
321 }
321 }
322 }
322 }
323 Ok(has_dirstate_node_or_is_ignored)
323 Ok(has_dirstate_node_or_is_ignored)
324 })
324 })
325 .try_reduce(|| true, |a, b| Ok(a && b))
325 .try_reduce(|| true, |a, b| Ok(a && b))
326 }
326 }
327
327
328 fn traverse_fs_and_dirstate(
328 fn traverse_fs_and_dirstate(
329 &self,
329 &self,
330 fs_path: &Path,
330 fs_path: &Path,
331 fs_metadata: &std::fs::Metadata,
331 fs_metadata: &std::fs::Metadata,
332 dirstate_node: NodeRef<'tree, 'on_disk>,
332 dirstate_node: NodeRef<'tree, 'on_disk>,
333 has_ignored_ancestor: bool,
333 has_ignored_ancestor: bool,
334 ) -> Result<(), DirstateV2ParseError> {
334 ) -> Result<(), DirstateV2ParseError> {
335 self.check_for_outdated_directory_cache(&dirstate_node)?;
335 self.check_for_outdated_directory_cache(&dirstate_node)?;
336 let hg_path = &dirstate_node.full_path_borrowed(self.dmap.on_disk)?;
336 let hg_path = &dirstate_node.full_path_borrowed(self.dmap.on_disk)?;
337 let file_type = fs_metadata.file_type();
337 let file_type = fs_metadata.file_type();
338 let file_or_symlink = file_type.is_file() || file_type.is_symlink();
338 let file_or_symlink = file_type.is_file() || file_type.is_symlink();
339 if !file_or_symlink {
339 if !file_or_symlink {
340 // If we previously had a file here, it was removed (with
340 // If we previously had a file here, it was removed (with
341 // `hg rm` or similar) or deleted before it could be
341 // `hg rm` or similar) or deleted before it could be
342 // replaced by a directory or something else.
342 // replaced by a directory or something else.
343 self.mark_removed_or_deleted_if_file(
343 self.mark_removed_or_deleted_if_file(
344 &hg_path,
344 &hg_path,
345 dirstate_node.state()?,
345 dirstate_node.state()?,
346 );
346 );
347 }
347 }
348 if file_type.is_dir() {
348 if file_type.is_dir() {
349 if self.options.collect_traversed_dirs {
349 if self.options.collect_traversed_dirs {
350 self.outcome
350 self.outcome
351 .lock()
351 .lock()
352 .unwrap()
352 .unwrap()
353 .traversed
353 .traversed
354 .push(hg_path.detach_from_tree())
354 .push(hg_path.detach_from_tree())
355 }
355 }
356 let is_ignored = has_ignored_ancestor || (self.ignore_fn)(hg_path);
356 let is_ignored = has_ignored_ancestor || (self.ignore_fn)(hg_path);
357 let is_at_repo_root = false;
357 let is_at_repo_root = false;
358 let children_all_have_dirstate_node_or_are_ignored = self
358 let children_all_have_dirstate_node_or_are_ignored = self
359 .traverse_fs_directory_and_dirstate(
359 .traverse_fs_directory_and_dirstate(
360 is_ignored,
360 is_ignored,
361 dirstate_node.children(self.dmap.on_disk)?,
361 dirstate_node.children(self.dmap.on_disk)?,
362 hg_path,
362 hg_path,
363 fs_path,
363 fs_path,
364 Some(fs_metadata),
364 Some(fs_metadata),
365 dirstate_node.cached_directory_mtime(),
365 dirstate_node.cached_directory_mtime(),
366 is_at_repo_root,
366 is_at_repo_root,
367 )?;
367 )?;
368 self.maybe_save_directory_mtime(
368 self.maybe_save_directory_mtime(
369 children_all_have_dirstate_node_or_are_ignored,
369 children_all_have_dirstate_node_or_are_ignored,
370 fs_metadata,
370 fs_metadata,
371 dirstate_node,
371 dirstate_node,
372 )?
372 )?
373 } else {
373 } else {
374 if file_or_symlink && self.matcher.matches(hg_path) {
374 if file_or_symlink && self.matcher.matches(hg_path) {
375 if let Some(state) = dirstate_node.state()? {
375 if let Some(state) = dirstate_node.state()? {
376 match state {
376 match state {
377 EntryState::Added => self
377 EntryState::Added => self
378 .outcome
378 .outcome
379 .lock()
379 .lock()
380 .unwrap()
380 .unwrap()
381 .added
381 .added
382 .push(hg_path.detach_from_tree()),
382 .push(hg_path.detach_from_tree()),
383 EntryState::Removed => self
383 EntryState::Removed => self
384 .outcome
384 .outcome
385 .lock()
385 .lock()
386 .unwrap()
386 .unwrap()
387 .removed
387 .removed
388 .push(hg_path.detach_from_tree()),
388 .push(hg_path.detach_from_tree()),
389 EntryState::Merged => self
389 EntryState::Merged => self
390 .outcome
390 .outcome
391 .lock()
391 .lock()
392 .unwrap()
392 .unwrap()
393 .modified
393 .modified
394 .push(hg_path.detach_from_tree()),
394 .push(hg_path.detach_from_tree()),
395 EntryState::Normal => self
395 EntryState::Normal => self
396 .handle_normal_file(&dirstate_node, fs_metadata)?,
396 .handle_normal_file(&dirstate_node, fs_metadata)?,
397 // This variant is not used in DirstateMap
398 // nodes
399 EntryState::Unknown => unreachable!(),
400 }
397 }
401 } else {
398 } else {
402 // `node.entry.is_none()` indicates a "directory"
399 // `node.entry.is_none()` indicates a "directory"
403 // node, but the filesystem has a file
400 // node, but the filesystem has a file
404 self.mark_unknown_or_ignored(
401 self.mark_unknown_or_ignored(
405 has_ignored_ancestor,
402 has_ignored_ancestor,
406 hg_path,
403 hg_path,
407 );
404 );
408 }
405 }
409 }
406 }
410
407
411 for child_node in dirstate_node.children(self.dmap.on_disk)?.iter()
408 for child_node in dirstate_node.children(self.dmap.on_disk)?.iter()
412 {
409 {
413 self.traverse_dirstate_only(child_node)?
410 self.traverse_dirstate_only(child_node)?
414 }
411 }
415 }
412 }
416 Ok(())
413 Ok(())
417 }
414 }
418
415
419 fn maybe_save_directory_mtime(
416 fn maybe_save_directory_mtime(
420 &self,
417 &self,
421 children_all_have_dirstate_node_or_are_ignored: bool,
418 children_all_have_dirstate_node_or_are_ignored: bool,
422 directory_metadata: &std::fs::Metadata,
419 directory_metadata: &std::fs::Metadata,
423 dirstate_node: NodeRef<'tree, 'on_disk>,
420 dirstate_node: NodeRef<'tree, 'on_disk>,
424 ) -> Result<(), DirstateV2ParseError> {
421 ) -> Result<(), DirstateV2ParseError> {
425 if children_all_have_dirstate_node_or_are_ignored {
422 if children_all_have_dirstate_node_or_are_ignored {
426 // All filesystem directory entries from `read_dir` have a
423 // All filesystem directory entries from `read_dir` have a
427 // corresponding node in the dirstate, so we can reconstitute the
424 // corresponding node in the dirstate, so we can reconstitute the
428 // names of those entries without calling `read_dir` again.
425 // names of those entries without calling `read_dir` again.
429 if let (Some(status_start), Ok(directory_mtime)) = (
426 if let (Some(status_start), Ok(directory_mtime)) = (
430 &self.filesystem_time_at_status_start,
427 &self.filesystem_time_at_status_start,
431 directory_metadata.modified(),
428 directory_metadata.modified(),
432 ) {
429 ) {
433 // Although the Rust standard library’s `SystemTime` type
430 // Although the Rust standard library’s `SystemTime` type
434 // has nanosecond precision, the times reported for a
431 // has nanosecond precision, the times reported for a
435 // directory’s (or file’s) modified time may have lower
432 // directory’s (or file’s) modified time may have lower
436 // resolution based on the filesystem (for example ext3
433 // resolution based on the filesystem (for example ext3
437 // only stores integer seconds), kernel (see
434 // only stores integer seconds), kernel (see
438 // https://stackoverflow.com/a/14393315/1162888), etc.
435 // https://stackoverflow.com/a/14393315/1162888), etc.
439 if &directory_mtime >= status_start {
436 if &directory_mtime >= status_start {
440 // The directory was modified too recently, don’t cache its
437 // The directory was modified too recently, don’t cache its
441 // `read_dir` results.
438 // `read_dir` results.
442 //
439 //
443 // A timeline like this is possible:
440 // A timeline like this is possible:
444 //
441 //
445 // 1. A change to this directory (direct child was
442 // 1. A change to this directory (direct child was
446 // added or removed) cause its mtime to be set
443 // added or removed) cause its mtime to be set
447 // (possibly truncated) to `directory_mtime`
444 // (possibly truncated) to `directory_mtime`
448 // 2. This `status` algorithm calls `read_dir`
445 // 2. This `status` algorithm calls `read_dir`
449 // 3. An other change is made to the same directory is
446 // 3. An other change is made to the same directory is
450 // made so that calling `read_dir` agin would give
447 // made so that calling `read_dir` agin would give
451 // different results, but soon enough after 1. that
448 // different results, but soon enough after 1. that
452 // the mtime stays the same
449 // the mtime stays the same
453 //
450 //
454 // On a system where the time resolution poor, this
451 // On a system where the time resolution poor, this
455 // scenario is not unlikely if all three steps are caused
452 // scenario is not unlikely if all three steps are caused
456 // by the same script.
453 // by the same script.
457 } else {
454 } else {
458 // We’ve observed (through `status_start`) that time has
455 // We’ve observed (through `status_start`) that time has
459 // “progressed” since `directory_mtime`, so any further
456 // “progressed” since `directory_mtime`, so any further
460 // change to this directory is extremely likely to cause a
457 // change to this directory is extremely likely to cause a
461 // different mtime.
458 // different mtime.
462 //
459 //
463 // Having the same mtime again is not entirely impossible
460 // Having the same mtime again is not entirely impossible
464 // since the system clock is not monotonous. It could jump
461 // since the system clock is not monotonous. It could jump
465 // backward to some point before `directory_mtime`, then a
462 // backward to some point before `directory_mtime`, then a
466 // directory change could potentially happen during exactly
463 // directory change could potentially happen during exactly
467 // the wrong tick.
464 // the wrong tick.
468 //
465 //
469 // We deem this scenario (unlike the previous one) to be
466 // We deem this scenario (unlike the previous one) to be
470 // unlikely enough in practice.
467 // unlikely enough in practice.
471 let timestamp = directory_mtime.into();
468 let timestamp = directory_mtime.into();
472 let cached = dirstate_node.cached_directory_mtime();
469 let cached = dirstate_node.cached_directory_mtime();
473 if cached != Some(&timestamp) {
470 if cached != Some(&timestamp) {
474 let hg_path = dirstate_node
471 let hg_path = dirstate_node
475 .full_path_borrowed(self.dmap.on_disk)?
472 .full_path_borrowed(self.dmap.on_disk)?
476 .detach_from_tree();
473 .detach_from_tree();
477 self.new_cachable_directories
474 self.new_cachable_directories
478 .lock()
475 .lock()
479 .unwrap()
476 .unwrap()
480 .push((hg_path, timestamp))
477 .push((hg_path, timestamp))
481 }
478 }
482 }
479 }
483 }
480 }
484 }
481 }
485 Ok(())
482 Ok(())
486 }
483 }
487
484
488 /// A file with `EntryState::Normal` in the dirstate was found in the
485 /// A file with `EntryState::Normal` in the dirstate was found in the
489 /// filesystem
486 /// filesystem
490 fn handle_normal_file(
487 fn handle_normal_file(
491 &self,
488 &self,
492 dirstate_node: &NodeRef<'tree, 'on_disk>,
489 dirstate_node: &NodeRef<'tree, 'on_disk>,
493 fs_metadata: &std::fs::Metadata,
490 fs_metadata: &std::fs::Metadata,
494 ) -> Result<(), DirstateV2ParseError> {
491 ) -> Result<(), DirstateV2ParseError> {
495 // Keep the low 31 bits
492 // Keep the low 31 bits
496 fn truncate_u64(value: u64) -> i32 {
493 fn truncate_u64(value: u64) -> i32 {
497 (value & 0x7FFF_FFFF) as i32
494 (value & 0x7FFF_FFFF) as i32
498 }
495 }
499 fn truncate_i64(value: i64) -> i32 {
496 fn truncate_i64(value: i64) -> i32 {
500 (value & 0x7FFF_FFFF) as i32
497 (value & 0x7FFF_FFFF) as i32
501 }
498 }
502
499
503 let entry = dirstate_node
500 let entry = dirstate_node
504 .entry()?
501 .entry()?
505 .expect("handle_normal_file called with entry-less node");
502 .expect("handle_normal_file called with entry-less node");
506 let hg_path = &dirstate_node.full_path_borrowed(self.dmap.on_disk)?;
503 let hg_path = &dirstate_node.full_path_borrowed(self.dmap.on_disk)?;
507 let mode_changed =
504 let mode_changed =
508 || self.options.check_exec && entry.mode_changed(fs_metadata);
505 || self.options.check_exec && entry.mode_changed(fs_metadata);
509 let size = entry.size();
506 let size = entry.size();
510 let size_changed = size != truncate_u64(fs_metadata.len());
507 let size_changed = size != truncate_u64(fs_metadata.len());
511 if size >= 0 && size_changed && fs_metadata.file_type().is_symlink() {
508 if size >= 0 && size_changed && fs_metadata.file_type().is_symlink() {
512 // issue6456: Size returned may be longer due to encryption
509 // issue6456: Size returned may be longer due to encryption
513 // on EXT-4 fscrypt. TODO maybe only do it on EXT4?
510 // on EXT-4 fscrypt. TODO maybe only do it on EXT4?
514 self.outcome
511 self.outcome
515 .lock()
512 .lock()
516 .unwrap()
513 .unwrap()
517 .unsure
514 .unsure
518 .push(hg_path.detach_from_tree())
515 .push(hg_path.detach_from_tree())
519 } else if dirstate_node.has_copy_source()
516 } else if dirstate_node.has_copy_source()
520 || entry.is_from_other_parent()
517 || entry.is_from_other_parent()
521 || (size >= 0 && (size_changed || mode_changed()))
518 || (size >= 0 && (size_changed || mode_changed()))
522 {
519 {
523 self.outcome
520 self.outcome
524 .lock()
521 .lock()
525 .unwrap()
522 .unwrap()
526 .modified
523 .modified
527 .push(hg_path.detach_from_tree())
524 .push(hg_path.detach_from_tree())
528 } else {
525 } else {
529 let mtime = mtime_seconds(fs_metadata);
526 let mtime = mtime_seconds(fs_metadata);
530 if truncate_i64(mtime) != entry.mtime()
527 if truncate_i64(mtime) != entry.mtime()
531 || mtime == self.options.last_normal_time
528 || mtime == self.options.last_normal_time
532 {
529 {
533 self.outcome
530 self.outcome
534 .lock()
531 .lock()
535 .unwrap()
532 .unwrap()
536 .unsure
533 .unsure
537 .push(hg_path.detach_from_tree())
534 .push(hg_path.detach_from_tree())
538 } else if self.options.list_clean {
535 } else if self.options.list_clean {
539 self.outcome
536 self.outcome
540 .lock()
537 .lock()
541 .unwrap()
538 .unwrap()
542 .clean
539 .clean
543 .push(hg_path.detach_from_tree())
540 .push(hg_path.detach_from_tree())
544 }
541 }
545 }
542 }
546 Ok(())
543 Ok(())
547 }
544 }
548
545
549 /// A node in the dirstate tree has no corresponding filesystem entry
546 /// A node in the dirstate tree has no corresponding filesystem entry
550 fn traverse_dirstate_only(
547 fn traverse_dirstate_only(
551 &self,
548 &self,
552 dirstate_node: NodeRef<'tree, 'on_disk>,
549 dirstate_node: NodeRef<'tree, 'on_disk>,
553 ) -> Result<(), DirstateV2ParseError> {
550 ) -> Result<(), DirstateV2ParseError> {
554 self.check_for_outdated_directory_cache(&dirstate_node)?;
551 self.check_for_outdated_directory_cache(&dirstate_node)?;
555 self.mark_removed_or_deleted_if_file(
552 self.mark_removed_or_deleted_if_file(
556 &dirstate_node.full_path_borrowed(self.dmap.on_disk)?,
553 &dirstate_node.full_path_borrowed(self.dmap.on_disk)?,
557 dirstate_node.state()?,
554 dirstate_node.state()?,
558 );
555 );
559 dirstate_node
556 dirstate_node
560 .children(self.dmap.on_disk)?
557 .children(self.dmap.on_disk)?
561 .par_iter()
558 .par_iter()
562 .map(|child_node| self.traverse_dirstate_only(child_node))
559 .map(|child_node| self.traverse_dirstate_only(child_node))
563 .collect()
560 .collect()
564 }
561 }
565
562
566 /// A node in the dirstate tree has no corresponding *file* on the
563 /// A node in the dirstate tree has no corresponding *file* on the
567 /// filesystem
564 /// filesystem
568 ///
565 ///
569 /// Does nothing on a "directory" node
566 /// Does nothing on a "directory" node
570 fn mark_removed_or_deleted_if_file(
567 fn mark_removed_or_deleted_if_file(
571 &self,
568 &self,
572 hg_path: &BorrowedPath<'tree, 'on_disk>,
569 hg_path: &BorrowedPath<'tree, 'on_disk>,
573 dirstate_node_state: Option<EntryState>,
570 dirstate_node_state: Option<EntryState>,
574 ) {
571 ) {
575 if let Some(state) = dirstate_node_state {
572 if let Some(state) = dirstate_node_state {
576 if self.matcher.matches(hg_path) {
573 if self.matcher.matches(hg_path) {
577 if let EntryState::Removed = state {
574 if let EntryState::Removed = state {
578 self.outcome
575 self.outcome
579 .lock()
576 .lock()
580 .unwrap()
577 .unwrap()
581 .removed
578 .removed
582 .push(hg_path.detach_from_tree())
579 .push(hg_path.detach_from_tree())
583 } else {
580 } else {
584 self.outcome
581 self.outcome
585 .lock()
582 .lock()
586 .unwrap()
583 .unwrap()
587 .deleted
584 .deleted
588 .push(hg_path.detach_from_tree())
585 .push(hg_path.detach_from_tree())
589 }
586 }
590 }
587 }
591 }
588 }
592 }
589 }
593
590
594 /// Something in the filesystem has no corresponding dirstate node
591 /// Something in the filesystem has no corresponding dirstate node
595 ///
592 ///
596 /// Returns whether that path is ignored
593 /// Returns whether that path is ignored
597 fn traverse_fs_only(
594 fn traverse_fs_only(
598 &self,
595 &self,
599 has_ignored_ancestor: bool,
596 has_ignored_ancestor: bool,
600 directory_hg_path: &HgPath,
597 directory_hg_path: &HgPath,
601 fs_entry: &DirEntry,
598 fs_entry: &DirEntry,
602 ) -> bool {
599 ) -> bool {
603 let hg_path = directory_hg_path.join(&fs_entry.base_name);
600 let hg_path = directory_hg_path.join(&fs_entry.base_name);
604 let file_type = fs_entry.metadata.file_type();
601 let file_type = fs_entry.metadata.file_type();
605 let file_or_symlink = file_type.is_file() || file_type.is_symlink();
602 let file_or_symlink = file_type.is_file() || file_type.is_symlink();
606 if file_type.is_dir() {
603 if file_type.is_dir() {
607 let is_ignored =
604 let is_ignored =
608 has_ignored_ancestor || (self.ignore_fn)(&hg_path);
605 has_ignored_ancestor || (self.ignore_fn)(&hg_path);
609 let traverse_children = if is_ignored {
606 let traverse_children = if is_ignored {
610 // Descendants of an ignored directory are all ignored
607 // Descendants of an ignored directory are all ignored
611 self.options.list_ignored
608 self.options.list_ignored
612 } else {
609 } else {
613 // Descendants of an unknown directory may be either unknown or
610 // Descendants of an unknown directory may be either unknown or
614 // ignored
611 // ignored
615 self.options.list_unknown || self.options.list_ignored
612 self.options.list_unknown || self.options.list_ignored
616 };
613 };
617 if traverse_children {
614 if traverse_children {
618 let is_at_repo_root = false;
615 let is_at_repo_root = false;
619 if let Ok(children_fs_entries) = self.read_dir(
616 if let Ok(children_fs_entries) = self.read_dir(
620 &hg_path,
617 &hg_path,
621 &fs_entry.full_path,
618 &fs_entry.full_path,
622 is_at_repo_root,
619 is_at_repo_root,
623 ) {
620 ) {
624 children_fs_entries.par_iter().for_each(|child_fs_entry| {
621 children_fs_entries.par_iter().for_each(|child_fs_entry| {
625 self.traverse_fs_only(
622 self.traverse_fs_only(
626 is_ignored,
623 is_ignored,
627 &hg_path,
624 &hg_path,
628 child_fs_entry,
625 child_fs_entry,
629 );
626 );
630 })
627 })
631 }
628 }
632 }
629 }
633 if self.options.collect_traversed_dirs {
630 if self.options.collect_traversed_dirs {
634 self.outcome.lock().unwrap().traversed.push(hg_path.into())
631 self.outcome.lock().unwrap().traversed.push(hg_path.into())
635 }
632 }
636 is_ignored
633 is_ignored
637 } else {
634 } else {
638 if file_or_symlink {
635 if file_or_symlink {
639 if self.matcher.matches(&hg_path) {
636 if self.matcher.matches(&hg_path) {
640 self.mark_unknown_or_ignored(
637 self.mark_unknown_or_ignored(
641 has_ignored_ancestor,
638 has_ignored_ancestor,
642 &BorrowedPath::InMemory(&hg_path),
639 &BorrowedPath::InMemory(&hg_path),
643 )
640 )
644 } else {
641 } else {
645 // We haven’t computed whether this path is ignored. It
642 // We haven’t computed whether this path is ignored. It
646 // might not be, and a future run of status might have a
643 // might not be, and a future run of status might have a
647 // different matcher that matches it. So treat it as not
644 // different matcher that matches it. So treat it as not
648 // ignored. That is, inhibit readdir caching of the parent
645 // ignored. That is, inhibit readdir caching of the parent
649 // directory.
646 // directory.
650 false
647 false
651 }
648 }
652 } else {
649 } else {
653 // This is neither a directory, a plain file, or a symlink.
650 // This is neither a directory, a plain file, or a symlink.
654 // Treat it like an ignored file.
651 // Treat it like an ignored file.
655 true
652 true
656 }
653 }
657 }
654 }
658 }
655 }
659
656
660 /// Returns whether that path is ignored
657 /// Returns whether that path is ignored
661 fn mark_unknown_or_ignored(
658 fn mark_unknown_or_ignored(
662 &self,
659 &self,
663 has_ignored_ancestor: bool,
660 has_ignored_ancestor: bool,
664 hg_path: &BorrowedPath<'_, 'on_disk>,
661 hg_path: &BorrowedPath<'_, 'on_disk>,
665 ) -> bool {
662 ) -> bool {
666 let is_ignored = has_ignored_ancestor || (self.ignore_fn)(&hg_path);
663 let is_ignored = has_ignored_ancestor || (self.ignore_fn)(&hg_path);
667 if is_ignored {
664 if is_ignored {
668 if self.options.list_ignored {
665 if self.options.list_ignored {
669 self.outcome
666 self.outcome
670 .lock()
667 .lock()
671 .unwrap()
668 .unwrap()
672 .ignored
669 .ignored
673 .push(hg_path.detach_from_tree())
670 .push(hg_path.detach_from_tree())
674 }
671 }
675 } else {
672 } else {
676 if self.options.list_unknown {
673 if self.options.list_unknown {
677 self.outcome
674 self.outcome
678 .lock()
675 .lock()
679 .unwrap()
676 .unwrap()
680 .unknown
677 .unknown
681 .push(hg_path.detach_from_tree())
678 .push(hg_path.detach_from_tree())
682 }
679 }
683 }
680 }
684 is_ignored
681 is_ignored
685 }
682 }
686 }
683 }
687
684
688 #[cfg(unix)] // TODO
685 #[cfg(unix)] // TODO
689 fn mtime_seconds(metadata: &std::fs::Metadata) -> i64 {
686 fn mtime_seconds(metadata: &std::fs::Metadata) -> i64 {
690 // Going through `Metadata::modified()` would be portable, but would take
687 // Going through `Metadata::modified()` would be portable, but would take
691 // care to construct a `SystemTime` value with sub-second precision just
688 // care to construct a `SystemTime` value with sub-second precision just
692 // for us to throw that away here.
689 // for us to throw that away here.
693 use std::os::unix::fs::MetadataExt;
690 use std::os::unix::fs::MetadataExt;
694 metadata.mtime()
691 metadata.mtime()
695 }
692 }
696
693
697 struct DirEntry {
694 struct DirEntry {
698 base_name: HgPathBuf,
695 base_name: HgPathBuf,
699 full_path: PathBuf,
696 full_path: PathBuf,
700 metadata: std::fs::Metadata,
697 metadata: std::fs::Metadata,
701 }
698 }
702
699
703 impl DirEntry {
700 impl DirEntry {
704 /// Returns **unsorted** entries in the given directory, with name and
701 /// Returns **unsorted** entries in the given directory, with name and
705 /// metadata.
702 /// metadata.
706 ///
703 ///
707 /// If a `.hg` sub-directory is encountered:
704 /// If a `.hg` sub-directory is encountered:
708 ///
705 ///
709 /// * At the repository root, ignore that sub-directory
706 /// * At the repository root, ignore that sub-directory
710 /// * Elsewhere, we’re listing the content of a sub-repo. Return an empty
707 /// * Elsewhere, we’re listing the content of a sub-repo. Return an empty
711 /// list instead.
708 /// list instead.
712 fn read_dir(path: &Path, is_at_repo_root: bool) -> io::Result<Vec<Self>> {
709 fn read_dir(path: &Path, is_at_repo_root: bool) -> io::Result<Vec<Self>> {
713 let mut results = Vec::new();
710 let mut results = Vec::new();
714 for entry in path.read_dir()? {
711 for entry in path.read_dir()? {
715 let entry = entry?;
712 let entry = entry?;
716 let metadata = entry.metadata()?;
713 let metadata = entry.metadata()?;
717 let name = get_bytes_from_os_string(entry.file_name());
714 let name = get_bytes_from_os_string(entry.file_name());
718 // FIXME don't do this when cached
715 // FIXME don't do this when cached
719 if name == b".hg" {
716 if name == b".hg" {
720 if is_at_repo_root {
717 if is_at_repo_root {
721 // Skip the repo’s own .hg (might be a symlink)
718 // Skip the repo’s own .hg (might be a symlink)
722 continue;
719 continue;
723 } else if metadata.is_dir() {
720 } else if metadata.is_dir() {
724 // A .hg sub-directory at another location means a subrepo,
721 // A .hg sub-directory at another location means a subrepo,
725 // skip it entirely.
722 // skip it entirely.
726 return Ok(Vec::new());
723 return Ok(Vec::new());
727 }
724 }
728 }
725 }
729 results.push(DirEntry {
726 results.push(DirEntry {
730 base_name: name.into(),
727 base_name: name.into(),
731 full_path: entry.path(),
728 full_path: entry.path(),
732 metadata,
729 metadata,
733 })
730 })
734 }
731 }
735 Ok(results)
732 Ok(results)
736 }
733 }
737 }
734 }
738
735
739 /// Return the `mtime` of a temporary file newly-created in the `.hg` directory
736 /// Return the `mtime` of a temporary file newly-created in the `.hg` directory
740 /// of the give repository.
737 /// of the give repository.
741 ///
738 ///
742 /// This is similar to `SystemTime::now()`, with the result truncated to the
739 /// This is similar to `SystemTime::now()`, with the result truncated to the
743 /// same time resolution as other files’ modification times. Using `.hg`
740 /// same time resolution as other files’ modification times. Using `.hg`
744 /// instead of the system’s default temporary directory (such as `/tmp`) makes
741 /// instead of the system’s default temporary directory (such as `/tmp`) makes
745 /// it more likely the temporary file is in the same disk partition as contents
742 /// it more likely the temporary file is in the same disk partition as contents
746 /// of the working directory, which can matter since different filesystems may
743 /// of the working directory, which can matter since different filesystems may
747 /// store timestamps with different resolutions.
744 /// store timestamps with different resolutions.
748 ///
745 ///
749 /// This may fail, typically if we lack write permissions. In that case we
746 /// This may fail, typically if we lack write permissions. In that case we
750 /// should continue the `status()` algoritm anyway and consider the current
747 /// should continue the `status()` algoritm anyway and consider the current
751 /// date/time to be unknown.
748 /// date/time to be unknown.
752 fn filesystem_now(repo_root: &Path) -> Result<SystemTime, io::Error> {
749 fn filesystem_now(repo_root: &Path) -> Result<SystemTime, io::Error> {
753 tempfile::tempfile_in(repo_root.join(".hg"))?
750 tempfile::tempfile_in(repo_root.join(".hg"))?
754 .metadata()?
751 .metadata()?
755 .modified()
752 .modified()
756 }
753 }
General Comments 0
You need to be logged in to leave comments. Login now