changeset 44597:e62052d0f377

rust-status: only involve ignore mechanism when needed This prevents unnecessary fallbacks to Python, improving performance for `hg update` for instance. On Mozilla-Central a noop update goes from 1.6s down to 700ms. Differential Revision: https://phab.mercurial-scm.org/D8315
author Raphaël Gomès <rgomes@octobus.net>
date Fri, 20 Mar 2020 15:21:34 +0100
parents 7333e8bb9781
children f451a347d21a
files rust/hg-core/src/dirstate/status.rs rust/hg-core/src/matchers.rs rust/hg-cpython/src/dirstate/status.rs
diffstat 3 files changed, 51 insertions(+), 32 deletions(-) [+]
line wrap: on
line diff
--- a/rust/hg-core/src/dirstate/status.rs	Thu Mar 26 00:07:12 2020 +0900
+++ b/rust/hg-core/src/dirstate/status.rs	Fri Mar 20 15:21:34 2020 +0100
@@ -33,7 +33,7 @@
     fs::{read_dir, DirEntry},
     io::ErrorKind,
     ops::Deref,
-    path::Path,
+    path::{Path, PathBuf},
 };
 
 /// Wrong type of file from a `BadMatch`
@@ -94,6 +94,9 @@
 }
 
 type IoResult<T> = std::io::Result<T>;
+/// `Box<dyn Trait>` is syntactic sugar for `Box<dyn Trait, 'static>`, so add
+/// an explicit lifetime here to not fight `'static` bounds "out of nowhere".
+type IgnoreFnType<'a> = Box<dyn for<'r> Fn(&'r HgPath) -> bool + Sync + 'a>;
 
 /// Dates and times that are outside the 31-bit signed range are compared
 /// modulo 2^31. This should prevent hg from behaving badly with very large
@@ -312,8 +315,8 @@
     root_dir: impl AsRef<Path> + Sync + Send + Copy + 'a,
     dmap: &'a DirstateMap,
     old_results: &'a FastHashMap<Cow<HgPath>, Dispatch>,
-    ignore_fn: &'a (impl for<'r> Fn(&'r HgPath) -> bool + Sync),
-    dir_ignore_fn: &'a (impl for<'r> Fn(&'r HgPath) -> bool + Sync),
+    ignore_fn: &'a IgnoreFnType,
+    dir_ignore_fn: &'a IgnoreFnType,
     options: StatusOptions,
     filename: HgPathBuf,
     dir_entry: DirEntry,
@@ -393,8 +396,8 @@
     root_dir: impl AsRef<Path> + Sync + Send + Copy + 'a,
     dmap: &'a DirstateMap,
     old_results: &'a FastHashMap<Cow<HgPath>, Dispatch>,
-    ignore_fn: &'a (impl for<'r> Fn(&'r HgPath) -> bool + Sync),
-    dir_ignore_fn: &'a (impl for<'r> Fn(&'r HgPath) -> bool + Sync),
+    ignore_fn: &'a IgnoreFnType,
+    dir_ignore_fn: &'a IgnoreFnType,
     options: StatusOptions,
     entry_option: Option<&'a DirstateEntry>,
     directory: HgPathBuf,
@@ -439,8 +442,8 @@
     dmap: &'a DirstateMap,
     directory: impl AsRef<HgPath>,
     old_results: &FastHashMap<Cow<'a, HgPath>, Dispatch>,
-    ignore_fn: &(impl for<'r> Fn(&'r HgPath) -> bool + Sync),
-    dir_ignore_fn: &(impl for<'r> Fn(&'r HgPath) -> bool + Sync),
+    ignore_fn: &IgnoreFnType,
+    dir_ignore_fn: &IgnoreFnType,
     options: StatusOptions,
 ) -> IoResult<()> {
     let directory = directory.as_ref();
@@ -522,8 +525,8 @@
     dmap: &'a DirstateMap,
     path: impl AsRef<HgPath>,
     old_results: &FastHashMap<Cow<'a, HgPath>, Dispatch>,
-    ignore_fn: &(impl for<'r> Fn(&'r HgPath) -> bool + Sync),
-    dir_ignore_fn: &(impl for<'r> Fn(&'r HgPath) -> bool + Sync),
+    ignore_fn: &IgnoreFnType,
+    dir_ignore_fn: &IgnoreFnType,
     options: StatusOptions,
     results: &mut Vec<(Cow<'a, HgPath>, Dispatch)>,
 ) -> IoResult<()> {
@@ -805,26 +808,39 @@
     dmap: &'a DirstateMap,
     matcher: &'b (impl Matcher + Sync),
     root_dir: impl AsRef<Path> + Sync + Send + Copy + 'c,
-    ignore_files: &[impl AsRef<Path> + 'c],
+    ignore_files: Vec<PathBuf>,
     options: StatusOptions,
 ) -> StatusResult<(
     (Vec<Cow<'c, HgPath>>, DirstateStatus<'c>),
     Vec<PatternFileWarning>,
 )> {
-    let (ignore_fn, warnings) = get_ignore_function(&ignore_files, root_dir)?;
+    // Needs to outlive `dir_ignore_fn` since it's captured.
+    let mut ignore_fn: IgnoreFnType;
+
+    // Only involve real ignore mechanism if we're listing unknowns or ignored.
+    let (dir_ignore_fn, warnings): (IgnoreFnType, _) = if options.list_ignored
+        || options.list_unknown
+    {
+        let (ignore, warnings) = get_ignore_function(ignore_files, root_dir)?;
 
-    // Is the path or one of its ancestors ignored?
-    let dir_ignore_fn = |dir: &_| {
-        if ignore_fn(dir) {
-            true
-        } else {
-            for p in find_dirs(dir) {
-                if ignore_fn(p) {
-                    return true;
+        ignore_fn = ignore;
+        let dir_ignore_fn = Box::new(|dir: &_| {
+            // Is the path or one of its ancestors ignored?
+            if ignore_fn(dir) {
+                true
+            } else {
+                for p in find_dirs(dir) {
+                    if ignore_fn(p) {
+                        return true;
+                    }
                 }
+                false
             }
-            false
-        }
+        });
+        (dir_ignore_fn, warnings)
+    } else {
+        ignore_fn = Box::new(|&_| true);
+        (Box::new(|&_| true), vec![])
     };
 
     let files = matcher.file_set();
--- a/rust/hg-core/src/matchers.rs	Thu Mar 26 00:07:12 2020 +0900
+++ b/rust/hg-core/src/matchers.rs	Fri Mar 20 15:21:34 2020 +0100
@@ -24,12 +24,12 @@
     PatternSyntax,
 };
 
-use micro_timer::timed;
+use std::borrow::ToOwned;
 use std::collections::HashSet;
 use std::fmt::{Display, Error, Formatter};
 use std::iter::FromIterator;
 use std::ops::Deref;
-use std::path::Path;
+use std::path::{Path, PathBuf};
 
 #[derive(Debug, PartialEq)]
 pub enum VisitChildrenSet<'a> {
@@ -507,7 +507,8 @@
         let mut prefixes = vec![];
 
         for SubInclude { prefix, root, path } in subincludes.into_iter() {
-            let (match_fn, warnings) = get_ignore_function(&[path], root)?;
+            let (match_fn, warnings) =
+                get_ignore_function(vec![path.to_path_buf()], root)?;
             all_warnings.extend(warnings);
             prefixes.push(prefix.to_owned());
             submatchers.insert(prefix.to_owned(), match_fn);
@@ -578,12 +579,11 @@
 /// Parses all "ignore" files with their recursive includes and returns a
 /// function that checks whether a given file (in the general sense) should be
 /// ignored.
-#[timed]
 pub fn get_ignore_function<'a>(
-    all_pattern_files: &[impl AsRef<Path>],
+    all_pattern_files: Vec<PathBuf>,
     root_dir: impl AsRef<Path>,
 ) -> PatternResult<(
-    impl for<'r> Fn(&'r HgPath) -> bool + Sync,
+    Box<dyn for<'r> Fn(&'r HgPath) -> bool + Sync + 'a>,
     Vec<PatternFileWarning>,
 )> {
     let mut all_patterns = vec![];
@@ -593,12 +593,15 @@
         let (patterns, warnings) =
             get_patterns_from_file(pattern_file, &root_dir)?;
 
-        all_patterns.extend(patterns);
+        all_patterns.extend(patterns.to_owned());
         all_warnings.extend(warnings);
     }
     let (matcher, warnings) = IncludeMatcher::new(all_patterns, root_dir)?;
     all_warnings.extend(warnings);
-    Ok((move |path: &HgPath| matcher.matches(path), all_warnings))
+    Ok((
+        Box::new(move |path: &HgPath| matcher.matches(path)),
+        all_warnings,
+    ))
 }
 
 impl<'a> IncludeMatcher<'a> {
--- a/rust/hg-cpython/src/dirstate/status.rs	Thu Mar 26 00:07:12 2020 +0900
+++ b/rust/hg-cpython/src/dirstate/status.rs	Fri Mar 20 15:21:34 2020 +0100
@@ -127,7 +127,7 @@
                 &dmap,
                 &matcher,
                 &root_dir,
-                &ignore_files,
+                ignore_files,
                 StatusOptions {
                     check_exec,
                     last_normal_time,
@@ -163,7 +163,7 @@
                 &dmap,
                 &matcher,
                 &root_dir,
-                &ignore_files,
+                ignore_files,
                 StatusOptions {
                     check_exec,
                     last_normal_time,
@@ -217,7 +217,7 @@
                 &dmap,
                 &matcher,
                 &root_dir,
-                &ignore_files,
+                ignore_files,
                 StatusOptions {
                     check_exec,
                     last_normal_time,