##// END OF EJS Templates
dirstate-tree: Paralellize the status algorithm with Rayon...
Simon Sapin -
r47887:60d852ae default
parent child Browse files
Show More
@@ -13,10 +13,12 b' use crate::HgPathBuf;'
13 13 use crate::PatternFileWarning;
14 14 use crate::StatusError;
15 15 use crate::StatusOptions;
16 use rayon::prelude::*;
16 17 use std::borrow::Cow;
17 18 use std::io;
18 19 use std::path::Path;
19 20 use std::path::PathBuf;
21 use std::sync::Mutex;
20 22
21 23 /// Returns the status of the working directory compared to its parent
22 24 /// changeset.
@@ -41,11 +43,11 b" pub fn status<'tree>("
41 43 (Box::new(|&_| true), vec![])
42 44 };
43 45
44 let mut common = StatusCommon {
46 let common = StatusCommon {
45 47 options,
46 48 matcher,
47 49 ignore_fn,
48 outcome: DirstateStatus::default(),
50 outcome: Mutex::new(DirstateStatus::default()),
49 51 };
50 52 let is_at_repo_root = true;
51 53 let hg_path = HgPath::new("");
@@ -57,7 +59,7 b" pub fn status<'tree>("
57 59 &root_dir,
58 60 is_at_repo_root,
59 61 );
60 Ok((common.outcome, warnings))
62 Ok((common.outcome.into_inner().unwrap(), warnings))
61 63 }
62 64
63 65 /// Bag of random things needed by various parts of the algorithm. Reduces the
@@ -66,12 +68,12 b" struct StatusCommon<'tree, 'a> {"
66 68 options: StatusOptions,
67 69 matcher: &'a (dyn Matcher + Sync),
68 70 ignore_fn: IgnoreFnType<'a>,
69 outcome: DirstateStatus<'tree>,
71 outcome: Mutex<DirstateStatus<'tree>>,
70 72 }
71 73
72 74 impl<'tree, 'a> StatusCommon<'tree, 'a> {
73 75 fn read_dir(
74 &mut self,
76 &self,
75 77 hg_path: &HgPath,
76 78 fs_path: &Path,
77 79 is_at_repo_root: bool,
@@ -79,13 +81,15 b" impl<'tree, 'a> StatusCommon<'tree, 'a> "
79 81 DirEntry::read_dir(fs_path, is_at_repo_root).map_err(|error| {
80 82 let errno = error.raw_os_error().expect("expected real OS error");
81 83 self.outcome
84 .lock()
85 .unwrap()
82 86 .bad
83 87 .push((hg_path.to_owned().into(), BadMatch::OsError(errno)))
84 88 })
85 89 }
86 90
87 91 fn traverse_fs_directory_and_dirstate(
88 &mut self,
92 &self,
89 93 has_ignored_ancestor: bool,
90 94 dirstate_nodes: &'tree mut ChildNodes,
91 95 directory_hg_path: &'tree HgPath,
@@ -104,19 +108,22 b" impl<'tree, 'a> StatusCommon<'tree, 'a> "
104 108
105 109 // `merge_join_by` requires both its input iterators to be sorted:
106 110
111 //
107 112 // * `BTreeMap` iterates according to keys’ ordering by definition
108 113
109 114 // `sort_unstable_by_key` doesn’t allow keys borrowing from the value:
110 115 // https://github.com/rust-lang/rust/issues/34162
111 116 fs_entries.sort_unstable_by(|e1, e2| e1.base_name.cmp(&e2.base_name));
112 117
113 for pair in itertools::merge_join_by(
118 itertools::merge_join_by(
114 119 dirstate_nodes,
115 120 &fs_entries,
116 121 |(full_path, _node), fs_entry| {
117 122 full_path.base_name().cmp(&fs_entry.base_name)
118 123 },
119 ) {
124 )
125 .par_bridge()
126 .for_each(|pair| {
120 127 use itertools::EitherOrBoth::*;
121 128 match pair {
122 129 Both((hg_path, dirstate_node), fs_entry) => {
@@ -137,11 +144,11 b" impl<'tree, 'a> StatusCommon<'tree, 'a> "
137 144 fs_entry,
138 145 ),
139 146 }
140 }
147 })
141 148 }
142 149
143 150 fn traverse_fs_and_dirstate(
144 &mut self,
151 &self,
145 152 fs_entry: &DirEntry,
146 153 hg_path: &'tree HgPath,
147 154 dirstate_node: &'tree mut Node,
@@ -160,7 +167,7 b" impl<'tree, 'a> StatusCommon<'tree, 'a> "
160 167 }
161 168 if file_type.is_dir() {
162 169 if self.options.collect_traversed_dirs {
163 self.outcome.traversed.push(hg_path.into())
170 self.outcome.lock().unwrap().traversed.push(hg_path.into())
164 171 }
165 172 let is_ignored = has_ignored_ancestor || (self.ignore_fn)(hg_path);
166 173 let is_at_repo_root = false;
@@ -177,14 +184,20 b" impl<'tree, 'a> StatusCommon<'tree, 'a> "
177 184 if let Some(entry) = &dirstate_node.entry {
178 185 match entry.state {
179 186 EntryState::Added => {
180 self.outcome.added.push(full_path)
187 self.outcome.lock().unwrap().added.push(full_path)
181 188 }
182 EntryState::Removed => {
183 self.outcome.removed.push(full_path)
184 }
185 EntryState::Merged => {
186 self.outcome.modified.push(full_path)
187 }
189 EntryState::Removed => self
190 .outcome
191 .lock()
192 .unwrap()
193 .removed
194 .push(full_path),
195 EntryState::Merged => self
196 .outcome
197 .lock()
198 .unwrap()
199 .modified
200 .push(full_path),
188 201 EntryState::Normal => {
189 202 self.handle_normal_file(
190 203 full_path,
@@ -219,7 +232,7 b" impl<'tree, 'a> StatusCommon<'tree, 'a> "
219 232 /// A file with `EntryState::Normal` in the dirstate was found in the
220 233 /// filesystem
221 234 fn handle_normal_file(
222 &mut self,
235 &self,
223 236 full_path: Cow<'tree, HgPath>,
224 237 dirstate_node: &Node,
225 238 entry: &crate::DirstateEntry,
@@ -243,34 +256,39 b" impl<'tree, 'a> StatusCommon<'tree, 'a> "
243 256 {
244 257 // issue6456: Size returned may be longer due to encryption
245 258 // on EXT-4 fscrypt. TODO maybe only do it on EXT4?
246 self.outcome.unsure.push(full_path)
259 self.outcome.lock().unwrap().unsure.push(full_path)
247 260 } else if dirstate_node.copy_source.is_some()
248 261 || entry.is_from_other_parent()
249 262 || (entry.size >= 0 && (size_changed || mode_changed()))
250 263 {
251 self.outcome.modified.push(full_path)
264 self.outcome.lock().unwrap().modified.push(full_path)
252 265 } else {
253 266 let mtime = mtime_seconds(&fs_entry.metadata);
254 267 if truncate_i64(mtime) != entry.mtime
255 268 || mtime == self.options.last_normal_time
256 269 {
257 self.outcome.unsure.push(full_path)
270 self.outcome.lock().unwrap().unsure.push(full_path)
258 271 } else if self.options.list_clean {
259 self.outcome.clean.push(full_path)
272 self.outcome.lock().unwrap().clean.push(full_path)
260 273 }
261 274 }
262 275 }
263 276
264 277 /// A node in the dirstate tree has no corresponding filesystem entry
265 278 fn traverse_dirstate_only(
266 &mut self,
279 &self,
267 280 hg_path: &'tree HgPath,
268 281 dirstate_node: &'tree mut Node,
269 282 ) {
270 283 self.mark_removed_or_deleted_if_file(hg_path, dirstate_node.state());
271 for (child_hg_path, child_node) in &mut dirstate_node.children {
272 self.traverse_dirstate_only(child_hg_path.full_path(), child_node)
273 }
284 dirstate_node.children.par_iter_mut().for_each(
285 |(child_hg_path, child_node)| {
286 self.traverse_dirstate_only(
287 child_hg_path.full_path(),
288 child_node,
289 )
290 },
291 )
274 292 }
275 293
276 294 /// A node in the dirstate tree has no corresponding *file* on the
@@ -278,16 +296,16 b" impl<'tree, 'a> StatusCommon<'tree, 'a> "
278 296 ///
279 297 /// Does nothing on a "directory" node
280 298 fn mark_removed_or_deleted_if_file(
281 &mut self,
299 &self,
282 300 hg_path: &'tree HgPath,
283 301 dirstate_node_state: Option<EntryState>,
284 302 ) {
285 303 if let Some(state) = dirstate_node_state {
286 304 if self.matcher.matches(hg_path) {
287 305 if let EntryState::Removed = state {
288 self.outcome.removed.push(hg_path.into())
306 self.outcome.lock().unwrap().removed.push(hg_path.into())
289 307 } else {
290 self.outcome.deleted.push(hg_path.into())
308 self.outcome.lock().unwrap().deleted.push(hg_path.into())
291 309 }
292 310 }
293 311 }
@@ -295,7 +313,7 b" impl<'tree, 'a> StatusCommon<'tree, 'a> "
295 313
296 314 /// Something in the filesystem has no corresponding dirstate node
297 315 fn traverse_fs_only(
298 &mut self,
316 &self,
299 317 has_ignored_ancestor: bool,
300 318 directory_hg_path: &HgPath,
301 319 fs_entry: &DirEntry,
@@ -321,17 +339,17 b" impl<'tree, 'a> StatusCommon<'tree, 'a> "
321 339 &fs_entry.full_path,
322 340 is_at_repo_root,
323 341 ) {
324 for child_fs_entry in children_fs_entries {
342 children_fs_entries.par_iter().for_each(|child_fs_entry| {
325 343 self.traverse_fs_only(
326 344 is_ignored,
327 345 &hg_path,
328 &child_fs_entry,
346 child_fs_entry,
329 347 )
330 }
348 })
331 349 }
332 350 }
333 351 if self.options.collect_traversed_dirs {
334 self.outcome.traversed.push(hg_path.into())
352 self.outcome.lock().unwrap().traversed.push(hg_path.into())
335 353 }
336 354 } else if file_or_symlink && self.matcher.matches(&hg_path) {
337 355 self.mark_unknown_or_ignored(has_ignored_ancestor, hg_path.into())
@@ -339,18 +357,18 b" impl<'tree, 'a> StatusCommon<'tree, 'a> "
339 357 }
340 358
341 359 fn mark_unknown_or_ignored(
342 &mut self,
360 &self,
343 361 has_ignored_ancestor: bool,
344 362 hg_path: Cow<'tree, HgPath>,
345 363 ) {
346 364 let is_ignored = has_ignored_ancestor || (self.ignore_fn)(&hg_path);
347 365 if is_ignored {
348 366 if self.options.list_ignored {
349 self.outcome.ignored.push(hg_path)
367 self.outcome.lock().unwrap().ignored.push(hg_path)
350 368 }
351 369 } else {
352 370 if self.options.list_unknown {
353 self.outcome.unknown.push(hg_path)
371 self.outcome.lock().unwrap().unknown.push(hg_path)
354 372 }
355 373 }
356 374 }
General Comments 0
You need to be logged in to leave comments. Login now