comparison rust/hg-core/src/dirstate_tree/status.rs @ 47353:73ddcedeaadf

dirstate-tree: Change status() results to not borrow DirstateMap The `status` function takes a `&'tree mut DirstateMap<'on_disk>` parameter. `'on_disk` borrows a read-only byte buffer with the contents of the `.hg/dirstate` file. `DirstateMap` internally uses represents file paths as `std::borrow::Cow<'on_disk, HgPath>`, which borrows the byte buffer when possible and allocates an owned string if not, such as for files added to the dirstate after it was loaded from disk. Previously the return type of of `status` has a `'tree`?lifetime, meaning it could borrow all paths from the `DirstateMap`. With this changeset, that lifetime is changed to `'on_disk` meaning that only paths from the byte buffer can be borrowed, and paths allocated by `DirstateMap` must be copied. Usually most paths are in the byte buffer, and most paths are not part of the return value of `status`, so the number of extra copies should be small. This change will enable `status` to mutate the `DirstateMap` after it has finished constructing its return value. Previously such mutation would be prevented by possible on-going borrows. Differential Revision: https://phab.mercurial-scm.org/D10824
author Simon Sapin <simon.sapin@octobus.net>
date Fri, 28 May 2021 20:07:27 +0200
parents 5e12b6bfdd3e
children 7138c863d0a1
comparison
equal deleted inserted replaced
47352:5e12b6bfdd3e 47353:73ddcedeaadf
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::ChildNodesRef; 3 use crate::dirstate_tree::dirstate_map::ChildNodesRef;
3 use crate::dirstate_tree::dirstate_map::DirstateMap; 4 use crate::dirstate_tree::dirstate_map::DirstateMap;
4 use crate::dirstate_tree::dirstate_map::NodeRef; 5 use crate::dirstate_tree::dirstate_map::NodeRef;
5 use crate::dirstate_tree::on_disk::DirstateV2ParseError; 6 use crate::dirstate_tree::on_disk::DirstateV2ParseError;
6 use crate::matchers::get_ignore_function; 7 use crate::matchers::get_ignore_function;
15 use crate::PatternFileWarning; 16 use crate::PatternFileWarning;
16 use crate::StatusError; 17 use crate::StatusError;
17 use crate::StatusOptions; 18 use crate::StatusOptions;
18 use micro_timer::timed; 19 use micro_timer::timed;
19 use rayon::prelude::*; 20 use rayon::prelude::*;
20 use std::borrow::Cow;
21 use std::io; 21 use std::io;
22 use std::path::Path; 22 use std::path::Path;
23 use std::path::PathBuf; 23 use std::path::PathBuf;
24 use std::sync::Mutex; 24 use std::sync::Mutex;
25 25
37 dmap: &'tree mut DirstateMap<'on_disk>, 37 dmap: &'tree mut DirstateMap<'on_disk>,
38 matcher: &(dyn Matcher + Sync), 38 matcher: &(dyn Matcher + Sync),
39 root_dir: PathBuf, 39 root_dir: PathBuf,
40 ignore_files: Vec<PathBuf>, 40 ignore_files: Vec<PathBuf>,
41 options: StatusOptions, 41 options: StatusOptions,
42 ) -> Result<(DirstateStatus<'tree>, Vec<PatternFileWarning>), StatusError> { 42 ) -> Result<(DirstateStatus<'on_disk>, Vec<PatternFileWarning>), StatusError> {
43 let (ignore_fn, warnings): (IgnoreFnType, _) = 43 let (ignore_fn, warnings): (IgnoreFnType, _) =
44 if options.list_ignored || options.list_unknown { 44 if options.list_ignored || options.list_unknown {
45 get_ignore_function(ignore_files, &root_dir)? 45 get_ignore_function(ignore_files, &root_dir)?
46 } else { 46 } else {
47 (Box::new(|&_| true), vec![]) 47 (Box::new(|&_| true), vec![])
53 matcher, 53 matcher,
54 ignore_fn, 54 ignore_fn,
55 outcome: Mutex::new(DirstateStatus::default()), 55 outcome: Mutex::new(DirstateStatus::default()),
56 }; 56 };
57 let is_at_repo_root = true; 57 let is_at_repo_root = true;
58 let hg_path = HgPath::new(""); 58 let hg_path = &BorrowedPath::OnDisk(HgPath::new(""));
59 let has_ignored_ancestor = false; 59 let has_ignored_ancestor = false;
60 common.traverse_fs_directory_and_dirstate( 60 common.traverse_fs_directory_and_dirstate(
61 has_ignored_ancestor, 61 has_ignored_ancestor,
62 dmap.root.as_ref(), 62 dmap.root.as_ref(),
63 hg_path, 63 hg_path,
67 Ok((common.outcome.into_inner().unwrap(), warnings)) 67 Ok((common.outcome.into_inner().unwrap(), warnings))
68 } 68 }
69 69
70 /// Bag of random things needed by various parts of the algorithm. Reduces the 70 /// Bag of random things needed by various parts of the algorithm. Reduces the
71 /// number of parameters passed to functions. 71 /// number of parameters passed to functions.
72 struct StatusCommon<'tree, 'a, 'on_disk: 'tree> { 72 struct StatusCommon<'a, 'tree, 'on_disk: 'tree> {
73 dmap: &'tree DirstateMap<'on_disk>, 73 dmap: &'tree DirstateMap<'on_disk>,
74 options: StatusOptions, 74 options: StatusOptions,
75 matcher: &'a (dyn Matcher + Sync), 75 matcher: &'a (dyn Matcher + Sync),
76 ignore_fn: IgnoreFnType<'a>, 76 ignore_fn: IgnoreFnType<'a>,
77 outcome: Mutex<DirstateStatus<'tree>>, 77 outcome: Mutex<DirstateStatus<'on_disk>>,
78 } 78 }
79 79
80 impl<'tree, 'a> StatusCommon<'tree, 'a, '_> { 80 impl<'a, 'tree, 'on_disk> StatusCommon<'a, 'tree, 'on_disk> {
81 fn read_dir( 81 fn read_dir(
82 &self, 82 &self,
83 hg_path: &HgPath, 83 hg_path: &HgPath,
84 fs_path: &Path, 84 fs_path: &Path,
85 is_at_repo_root: bool, 85 is_at_repo_root: bool,
98 } 98 }
99 99
100 fn traverse_fs_directory_and_dirstate( 100 fn traverse_fs_directory_and_dirstate(
101 &self, 101 &self,
102 has_ignored_ancestor: bool, 102 has_ignored_ancestor: bool,
103 dirstate_nodes: ChildNodesRef<'tree, '_>, 103 dirstate_nodes: ChildNodesRef<'tree, 'on_disk>,
104 directory_hg_path: &'tree HgPath, 104 directory_hg_path: &BorrowedPath<'tree, 'on_disk>,
105 directory_fs_path: &Path, 105 directory_fs_path: &Path,
106 is_at_repo_root: bool, 106 is_at_repo_root: bool,
107 ) -> Result<(), DirstateV2ParseError> { 107 ) -> Result<(), DirstateV2ParseError> {
108 if !self.options.list_unknown && !self.options.list_ignored { 108 if !self.options.list_unknown && !self.options.list_ignored {
109 // We only care about files in the dirstate, so we can skip listing 109 // We only care about files in the dirstate, so we can skip listing
197 197
198 fn traverse_fs_and_dirstate( 198 fn traverse_fs_and_dirstate(
199 &self, 199 &self,
200 fs_path: &Path, 200 fs_path: &Path,
201 fs_metadata: &std::fs::Metadata, 201 fs_metadata: &std::fs::Metadata,
202 dirstate_node: NodeRef<'tree, '_>, 202 dirstate_node: NodeRef<'tree, 'on_disk>,
203 has_ignored_ancestor: bool, 203 has_ignored_ancestor: bool,
204 ) -> Result<(), DirstateV2ParseError> { 204 ) -> Result<(), DirstateV2ParseError> {
205 let hg_path = dirstate_node.full_path(self.dmap.on_disk)?; 205 let hg_path = &dirstate_node.full_path_borrowed(self.dmap.on_disk)?;
206 let file_type = fs_metadata.file_type(); 206 let file_type = fs_metadata.file_type();
207 let file_or_symlink = file_type.is_file() || file_type.is_symlink(); 207 let file_or_symlink = file_type.is_file() || file_type.is_symlink();
208 if !file_or_symlink { 208 if !file_or_symlink {
209 // If we previously had a file here, it was removed (with 209 // If we previously had a file here, it was removed (with
210 // `hg rm` or similar) or deleted before it could be 210 // `hg rm` or similar) or deleted before it could be
211 // replaced by a directory or something else. 211 // replaced by a directory or something else.
212 self.mark_removed_or_deleted_if_file( 212 self.mark_removed_or_deleted_if_file(
213 hg_path, 213 &hg_path,
214 dirstate_node.state()?, 214 dirstate_node.state()?,
215 ); 215 );
216 } 216 }
217 if file_type.is_dir() { 217 if file_type.is_dir() {
218 if self.options.collect_traversed_dirs { 218 if self.options.collect_traversed_dirs {
219 self.outcome.lock().unwrap().traversed.push(hg_path.into()) 219 self.outcome
220 .lock()
221 .unwrap()
222 .traversed
223 .push(hg_path.detach_from_tree())
220 } 224 }
221 let is_ignored = has_ignored_ancestor || (self.ignore_fn)(hg_path); 225 let is_ignored = has_ignored_ancestor || (self.ignore_fn)(hg_path);
222 let is_at_repo_root = false; 226 let is_at_repo_root = false;
223 self.traverse_fs_directory_and_dirstate( 227 self.traverse_fs_directory_and_dirstate(
224 is_ignored, 228 is_ignored,
227 fs_path, 231 fs_path,
228 is_at_repo_root, 232 is_at_repo_root,
229 )? 233 )?
230 } else { 234 } else {
231 if file_or_symlink && self.matcher.matches(hg_path) { 235 if file_or_symlink && self.matcher.matches(hg_path) {
232 let full_path = Cow::from(hg_path);
233 if let Some(state) = dirstate_node.state()? { 236 if let Some(state) = dirstate_node.state()? {
234 match state { 237 match state {
235 EntryState::Added => { 238 EntryState::Added => self
236 self.outcome.lock().unwrap().added.push(full_path) 239 .outcome
237 } 240 .lock()
241 .unwrap()
242 .added
243 .push(hg_path.detach_from_tree()),
238 EntryState::Removed => self 244 EntryState::Removed => self
239 .outcome 245 .outcome
240 .lock() 246 .lock()
241 .unwrap() 247 .unwrap()
242 .removed 248 .removed
243 .push(full_path), 249 .push(hg_path.detach_from_tree()),
244 EntryState::Merged => self 250 EntryState::Merged => self
245 .outcome 251 .outcome
246 .lock() 252 .lock()
247 .unwrap() 253 .unwrap()
248 .modified 254 .modified
249 .push(full_path), 255 .push(hg_path.detach_from_tree()),
250 EntryState::Normal => self 256 EntryState::Normal => self
251 .handle_normal_file(&dirstate_node, fs_metadata)?, 257 .handle_normal_file(&dirstate_node, fs_metadata)?,
252 // This variant is not used in DirstateMap 258 // This variant is not used in DirstateMap
253 // nodes 259 // nodes
254 EntryState::Unknown => unreachable!(), 260 EntryState::Unknown => unreachable!(),
255 } 261 }
256 } else { 262 } else {
257 // `node.entry.is_none()` indicates a "directory" 263 // `node.entry.is_none()` indicates a "directory"
258 // node, but the filesystem has a file 264 // node, but the filesystem has a file
259 self.mark_unknown_or_ignored( 265 self.mark_unknown_or_ignored(has_ignored_ancestor, hg_path)
260 has_ignored_ancestor,
261 full_path,
262 )
263 } 266 }
264 } 267 }
265 268
266 for child_node in dirstate_node.children(self.dmap.on_disk)?.iter() 269 for child_node in dirstate_node.children(self.dmap.on_disk)?.iter()
267 { 270 {
273 276
274 /// A file with `EntryState::Normal` in the dirstate was found in the 277 /// A file with `EntryState::Normal` in the dirstate was found in the
275 /// filesystem 278 /// filesystem
276 fn handle_normal_file( 279 fn handle_normal_file(
277 &self, 280 &self,
278 dirstate_node: &NodeRef<'tree, '_>, 281 dirstate_node: &NodeRef<'tree, 'on_disk>,
279 fs_metadata: &std::fs::Metadata, 282 fs_metadata: &std::fs::Metadata,
280 ) -> Result<(), DirstateV2ParseError> { 283 ) -> Result<(), DirstateV2ParseError> {
281 // Keep the low 31 bits 284 // Keep the low 31 bits
282 fn truncate_u64(value: u64) -> i32 { 285 fn truncate_u64(value: u64) -> i32 {
283 (value & 0x7FFF_FFFF) as i32 286 (value & 0x7FFF_FFFF) as i32
287 } 290 }
288 291
289 let entry = dirstate_node 292 let entry = dirstate_node
290 .entry()? 293 .entry()?
291 .expect("handle_normal_file called with entry-less node"); 294 .expect("handle_normal_file called with entry-less node");
292 let full_path = Cow::from(dirstate_node.full_path(self.dmap.on_disk)?); 295 let hg_path = &dirstate_node.full_path_borrowed(self.dmap.on_disk)?;
293 let mode_changed = 296 let mode_changed =
294 || self.options.check_exec && entry.mode_changed(fs_metadata); 297 || self.options.check_exec && entry.mode_changed(fs_metadata);
295 let size_changed = entry.size != truncate_u64(fs_metadata.len()); 298 let size_changed = entry.size != truncate_u64(fs_metadata.len());
296 if entry.size >= 0 299 if entry.size >= 0
297 && size_changed 300 && size_changed
298 && fs_metadata.file_type().is_symlink() 301 && fs_metadata.file_type().is_symlink()
299 { 302 {
300 // issue6456: Size returned may be longer due to encryption 303 // issue6456: Size returned may be longer due to encryption
301 // on EXT-4 fscrypt. TODO maybe only do it on EXT4? 304 // on EXT-4 fscrypt. TODO maybe only do it on EXT4?
302 self.outcome.lock().unwrap().unsure.push(full_path) 305 self.outcome
306 .lock()
307 .unwrap()
308 .unsure
309 .push(hg_path.detach_from_tree())
303 } else if dirstate_node.has_copy_source() 310 } else if dirstate_node.has_copy_source()
304 || entry.is_from_other_parent() 311 || entry.is_from_other_parent()
305 || (entry.size >= 0 && (size_changed || mode_changed())) 312 || (entry.size >= 0 && (size_changed || mode_changed()))
306 { 313 {
307 self.outcome.lock().unwrap().modified.push(full_path) 314 self.outcome
315 .lock()
316 .unwrap()
317 .modified
318 .push(hg_path.detach_from_tree())
308 } else { 319 } else {
309 let mtime = mtime_seconds(fs_metadata); 320 let mtime = mtime_seconds(fs_metadata);
310 if truncate_i64(mtime) != entry.mtime 321 if truncate_i64(mtime) != entry.mtime
311 || mtime == self.options.last_normal_time 322 || mtime == self.options.last_normal_time
312 { 323 {
313 self.outcome.lock().unwrap().unsure.push(full_path) 324 self.outcome
325 .lock()
326 .unwrap()
327 .unsure
328 .push(hg_path.detach_from_tree())
314 } else if self.options.list_clean { 329 } else if self.options.list_clean {
315 self.outcome.lock().unwrap().clean.push(full_path) 330 self.outcome
331 .lock()
332 .unwrap()
333 .clean
334 .push(hg_path.detach_from_tree())
316 } 335 }
317 } 336 }
318 Ok(()) 337 Ok(())
319 } 338 }
320 339
321 /// A node in the dirstate tree has no corresponding filesystem entry 340 /// A node in the dirstate tree has no corresponding filesystem entry
322 fn traverse_dirstate_only( 341 fn traverse_dirstate_only(
323 &self, 342 &self,
324 dirstate_node: NodeRef<'tree, '_>, 343 dirstate_node: NodeRef<'tree, 'on_disk>,
325 ) -> Result<(), DirstateV2ParseError> { 344 ) -> Result<(), DirstateV2ParseError> {
326 self.mark_removed_or_deleted_if_file( 345 self.mark_removed_or_deleted_if_file(
327 dirstate_node.full_path(self.dmap.on_disk)?, 346 &dirstate_node.full_path_borrowed(self.dmap.on_disk)?,
328 dirstate_node.state()?, 347 dirstate_node.state()?,
329 ); 348 );
330 dirstate_node 349 dirstate_node
331 .children(self.dmap.on_disk)? 350 .children(self.dmap.on_disk)?
332 .par_iter() 351 .par_iter()
338 /// filesystem 357 /// filesystem
339 /// 358 ///
340 /// Does nothing on a "directory" node 359 /// Does nothing on a "directory" node
341 fn mark_removed_or_deleted_if_file( 360 fn mark_removed_or_deleted_if_file(
342 &self, 361 &self,
343 hg_path: &'tree HgPath, 362 hg_path: &BorrowedPath<'tree, 'on_disk>,
344 dirstate_node_state: Option<EntryState>, 363 dirstate_node_state: Option<EntryState>,
345 ) { 364 ) {
346 if let Some(state) = dirstate_node_state { 365 if let Some(state) = dirstate_node_state {
347 if self.matcher.matches(hg_path) { 366 if self.matcher.matches(hg_path) {
348 if let EntryState::Removed = state { 367 if let EntryState::Removed = state {
349 self.outcome.lock().unwrap().removed.push(hg_path.into()) 368 self.outcome
369 .lock()
370 .unwrap()
371 .removed
372 .push(hg_path.detach_from_tree())
350 } else { 373 } else {
351 self.outcome.lock().unwrap().deleted.push(hg_path.into()) 374 self.outcome
375 .lock()
376 .unwrap()
377 .deleted
378 .push(hg_path.detach_from_tree())
352 } 379 }
353 } 380 }
354 } 381 }
355 } 382 }
356 383
393 } 420 }
394 if self.options.collect_traversed_dirs { 421 if self.options.collect_traversed_dirs {
395 self.outcome.lock().unwrap().traversed.push(hg_path.into()) 422 self.outcome.lock().unwrap().traversed.push(hg_path.into())
396 } 423 }
397 } else if file_or_symlink && self.matcher.matches(&hg_path) { 424 } else if file_or_symlink && self.matcher.matches(&hg_path) {
398 self.mark_unknown_or_ignored(has_ignored_ancestor, hg_path.into()) 425 self.mark_unknown_or_ignored(
426 has_ignored_ancestor,
427 &BorrowedPath::InMemory(&hg_path),
428 )
399 } 429 }
400 } 430 }
401 431
402 fn mark_unknown_or_ignored( 432 fn mark_unknown_or_ignored(
403 &self, 433 &self,
404 has_ignored_ancestor: bool, 434 has_ignored_ancestor: bool,
405 hg_path: Cow<'tree, HgPath>, 435 hg_path: &BorrowedPath<'_, 'on_disk>,
406 ) { 436 ) {
407 let is_ignored = has_ignored_ancestor || (self.ignore_fn)(&hg_path); 437 let is_ignored = has_ignored_ancestor || (self.ignore_fn)(&hg_path);
408 if is_ignored { 438 if is_ignored {
409 if self.options.list_ignored { 439 if self.options.list_ignored {
410 self.outcome.lock().unwrap().ignored.push(hg_path) 440 self.outcome
441 .lock()
442 .unwrap()
443 .ignored
444 .push(hg_path.detach_from_tree())
411 } 445 }
412 } else { 446 } else {
413 if self.options.list_unknown { 447 if self.options.list_unknown {
414 self.outcome.lock().unwrap().unknown.push(hg_path) 448 self.outcome
449 .lock()
450 .unwrap()
451 .unknown
452 .push(hg_path.detach_from_tree())
415 } 453 }
416 } 454 }
417 } 455 }
418 } 456 }
419 457