فهرست منبع

Use a global Git cache

This commit adds a cache for Git repositories based on the path being queried.

Its only immediate effect is that when you query the same directory twice (such as /testcases/git /testcases/git), it won’t need to check that the second one is a Git directory the second time. So, a minuscule optimisation for something you’d never do anyway? Wrong! It’s going to let us combine multiple entries over the same repository later, letting us use --tree and --recurse, because now Git scanning is behind a factory.
Benjamin Sago 8 سال پیش
والد
کامیت
040dbb2414
7فایلهای تغییر یافته به همراه125 افزوده شده و 39 حذف شده
  1. 18 2
      src/exa.rs
  2. 7 3
      src/fs/dir.rs
  3. 66 16
      src/fs/feature/git.rs
  4. 28 13
      src/fs/feature/mod.rs
  5. 3 3
      src/fs/file.rs
  6. 1 1
      src/fs/mod.rs
  7. 2 1
      src/output/details.rs

+ 18 - 2
src/exa.rs

@@ -30,6 +30,7 @@ use std::path::{Component, PathBuf};
 use ansi_term::{ANSIStrings, Style};
 
 use fs::{Dir, File};
+use fs::feature::git::GitCache;
 use options::{Options, Vars};
 pub use options::Misfire;
 use output::{escape, lines, grid, grid_details, details, View, Mode};
@@ -79,6 +80,8 @@ impl<'args, 'w, W: Write + 'w> Exa<'args, 'w, W> {
     }
 
     pub fn run(&mut self) -> IOResult<i32> {
+        use fs::DirOptions;
+
         let mut files = Vec::new();
         let mut dirs = Vec::new();
         let mut exit_status = 0;
@@ -88,6 +91,8 @@ impl<'args, 'w, W: Write + 'w> Exa<'args, 'w, W> {
             self.args = vec![ OsStr::new(".") ];
         }
 
+        let git = self.git_options(&*self.args);
+
         for file_path in &self.args {
             match File::new(PathBuf::from(file_path), None, None) {
                 Err(e) => {
@@ -96,7 +101,7 @@ impl<'args, 'w, W: Write + 'w> Exa<'args, 'w, W> {
                 },
                 Ok(f) => {
                     if f.is_directory() && !self.options.dir_action.treat_dirs_as_files() {
-                        match f.to_dir(self.options.should_scan_for_git()) {
+                        match f.to_dir(DirOptions { git: git.as_ref() }) {
                             Ok(d) => dirs.push(d),
                             Err(e) => writeln!(stderr(), "{:?}: {}", file_path, e)?,
                         }
@@ -121,7 +126,18 @@ impl<'args, 'w, W: Write + 'w> Exa<'args, 'w, W> {
         self.print_dirs(dirs, no_files, is_only_dir, exit_status)
     }
 
+    fn git_options(&self, args: &[&OsStr]) -> Option<GitCache> {
+        if self.options.should_scan_for_git() {
+            Some(args.iter().map(|os| PathBuf::from(os)).collect())
+        }
+        else {
+            None
+        }
+    }
+
     fn print_dirs(&mut self, dir_files: Vec<Dir>, mut first: bool, is_only_dir: bool, exit_status: i32) -> IOResult<i32> {
+        use fs::DirOptions;
+
         for dir in dir_files {
 
             // Put a gap between directories, or between the list of files and
@@ -156,7 +172,7 @@ impl<'args, 'w, W: Write + 'w> Exa<'args, 'w, W> {
 
                     let mut child_dirs = Vec::new();
                     for child_dir in children.iter().filter(|f| f.is_directory()) {
-                        match child_dir.to_dir(false) {
+                        match child_dir.to_dir(DirOptions { git: None }) {
                             Ok(d)  => child_dirs.push(d),
                             Err(e) => writeln!(stderr(), "{}: {}", child_dir.path.display(), e)?,
                         }

+ 7 - 3
src/fs/dir.rs

@@ -3,7 +3,7 @@ use std::fs;
 use std::path::{Path, PathBuf};
 use std::slice::Iter as SliceIter;
 
-use fs::feature::Git;
+use fs::feature::git::{Git, GitCache};
 use fs::{File, fields};
 
 
@@ -26,6 +26,10 @@ pub struct Dir {
     git: Option<Git>,
 }
 
+pub struct DirOptions<'exa> {
+    pub git: Option<&'exa GitCache>
+}
+
 impl Dir {
 
     /// Create a new Dir object filled with all the files in the directory
@@ -36,14 +40,14 @@ impl Dir {
     /// The `read_dir` iterator doesn’t actually yield the `.` and `..`
     /// entries, so if the user wants to see them, we’ll have to add them
     /// ourselves after the files have been read.
-    pub fn read_dir(path: PathBuf, git: bool) -> IOResult<Dir> {
+    pub fn read_dir(path: PathBuf, options: DirOptions) -> IOResult<Dir> {
         info!("Reading directory {:?}", &path);
 
         let contents: Vec<PathBuf> = try!(fs::read_dir(&path)?
                                                  .map(|result| result.map(|entry| entry.path()))
                                                  .collect());
 
-        let git = if git { Git::scan(&path).ok() } else { None };
+        let git = options.git.and_then(|cache| cache.get(&path));
         Ok(Dir { contents, path, git })
     }
 

+ 66 - 16
src/fs/feature/git.rs

@@ -1,3 +1,4 @@
+use std::collections::HashMap;
 use std::path::{Path, PathBuf};
 
 use git2;
@@ -5,30 +6,79 @@ use git2;
 use fs::fields as f;
 
 
-/// Container of Git statuses for all the files in this folder's Git repository.
-pub struct Git {
-    statuses: Vec<(PathBuf, git2::Status)>,
+pub struct GitCache {
+    repos: HashMap<PathBuf, Option<GitRepo>>,
 }
 
-impl Git {
+pub struct GitRepo {
+    repo: git2::Repository,
+    workdir: PathBuf,
+}
+
+impl GitRepo {
+    fn discover(path: &Path) -> Option<GitRepo> {
+    	info!("Searching for Git repository above {:?}", path);
+        if let Ok(repo) = git2::Repository::discover(&path) {
+            if let Some(workdir) = repo.workdir().map(|wd| wd.to_path_buf()) {
+                return Some(GitRepo { repo, workdir });
+            }
+        }
+
+        None
+    }
+}
+
+use std::iter::FromIterator;
+impl FromIterator<PathBuf> for GitCache {
+    fn from_iter<I: IntoIterator<Item=PathBuf>>(iter: I) -> Self {
+        let iter = iter.into_iter();
+        let mut repos = HashMap::with_capacity(iter.size_hint().0);
+
+        for path in iter {
+            if repos.contains_key(&path) {
+            	debug!("Skipping {:?} because we already queried it", path);
+            }
+            else {
+                let repo = GitRepo::discover(&path);
+                let _ = repos.insert(path, repo);
+            }
+        }
+
+        GitCache { repos }
+    }
+}
 
-    /// Discover a Git repository on or above this directory, scanning it for
-    /// the files' statuses if one is found.
-    pub fn scan(path: &Path) -> Result<Git, git2::Error> {
-        info!("Scanning for Git repository under {:?}", path);
+impl GitCache {
+    pub fn get(&self, index: &Path) -> Option<Git> {
+        let repo = match self.repos[index] {
+            Some(ref r) => r,
+            None => return None,
+        };
 
-        let repo = git2::Repository::discover(path)?;
-        let workdir = match repo.workdir() {
-            Some(w) => w,
-            None => return Ok(Git { statuses: vec![] }),  // bare repo
+        let iter = match repo.repo.statuses(None) {
+            Ok(es) => es,
+            Err(_) => return None,
         };
 
-        let statuses = repo.statuses(None)?.iter()
-										   .map(|e| (workdir.join(Path::new(e.path().unwrap())), e.status()))
-										   .collect();
+        let mut statuses = Vec::new();
 
-        Ok(Git { statuses: statuses })
+        for e in iter.iter() {
+            let path = repo.workdir.join(Path::new(e.path().unwrap()));
+            let elem = (path, e.status());
+            statuses.push(elem);
+        }
+
+        Some(Git { statuses })
     }
+}
+
+
+/// Container of Git statuses for all the files in this folder's Git repository.
+pub struct Git {
+    statuses: Vec<(PathBuf, git2::Status)>,
+}
+
+impl Git {
 
     /// Get the status for the file at the given path, if present.
     pub fn status(&self, path: &Path) -> f::Git {

+ 28 - 13
src/fs/feature/mod.rs

@@ -3,24 +3,39 @@ pub mod xattr;
 
 // Git support
 
-#[cfg(feature="git")] mod git;
-#[cfg(feature="git")] pub use self::git::Git;
-
-#[cfg(not(feature="git"))] pub struct Git;
-#[cfg(not(feature="git"))] use std::path::Path;
-#[cfg(not(feature="git"))] use fs::fields;
+#[cfg(feature="git")] pub mod git;
 
 #[cfg(not(feature="git"))]
-impl Git {
-    pub fn scan(_: &Path) -> Result<Git, ()> {
-        Err(())
+pub mod git {
+    use std::iter::FromIterator;
+    use std::path::{Path, PathBuf};
+
+    use fs::fields;
+
+
+    pub struct GitCache;
+
+    impl FromIterator<PathBuf> for GitCache {
+        fn from_iter<I: IntoIterator<Item=PathBuf>>(_iter: I) -> Self {
+            GitCache
+        }
     }
 
-    pub fn status(&self, _: &Path) -> fields::Git {
-        panic!("Tried to access a Git repo without Git support!");
+    impl GitCache {
+        pub fn get(&self, _index: &Path) -> Option<Git> {
+            panic!("Tried to query a Git cache, but Git support is disabled")
+        }
     }
 
-    pub fn dir_status(&self, path: &Path) -> fields::Git {
-        self.status(path)
+    pub struct Git;
+
+    impl Git {
+        pub fn status(&self, _: &Path) -> fields::Git {
+            panic!("Tried to get a Git status, but Git support is disabled")
+        }
+
+        pub fn dir_status(&self, path: &Path) -> fields::Git {
+            self.status(path)
+        }
     }
 }

+ 3 - 3
src/fs/file.rs

@@ -6,7 +6,7 @@ use std::io::Result as IOResult;
 use std::os::unix::fs::{MetadataExt, PermissionsExt, FileTypeExt};
 use std::path::{Path, PathBuf};
 
-use fs::dir::Dir;
+use fs::dir::{Dir, DirOptions};
 use fs::fields as f;
 
 
@@ -115,8 +115,8 @@ impl<'dir> File<'dir> {
     ///
     /// Returns an IO error upon failure, but this shouldn't be used to check
     /// if a `File` is a directory or not! For that, just use `is_directory()`.
-    pub fn to_dir(&self, scan_for_git: bool) -> IOResult<Dir> {
-        Dir::read_dir(self.path.clone(), scan_for_git)
+    pub fn to_dir(&self, options: DirOptions) -> IOResult<Dir> {
+        Dir::read_dir(self.path.clone(), options)
     }
 
     /// Whether this file is a regular file on the filesystem - that is, not a

+ 1 - 1
src/fs/mod.rs

@@ -1,5 +1,5 @@
 mod dir;
-pub use self::dir::{Dir, DotFilter};
+pub use self::dir::{Dir, DirOptions, DotFilter};
 
 mod file;
 pub use self::file::{File, FileTarget};

+ 2 - 1
src/output/details.rs

@@ -178,6 +178,7 @@ impl<'a> Render<'a> {
         use scoped_threadpool::Pool;
         use std::sync::{Arc, Mutex};
         use fs::feature::xattr;
+        use fs::DirOptions;
 
         let mut pool = Pool::new(num_cpus::get() as u32);
         let mut file_eggs = Vec::new();
@@ -240,7 +241,7 @@ impl<'a> Render<'a> {
 
                     if let Some(r) = self.recurse {
                         if file.is_directory() && r.tree && !r.is_too_deep(depth.0) {
-                            match file.to_dir(false) {
+                            match file.to_dir(DirOptions { git: None }) {
                                 Ok(d)  => { dir = Some(d); },
                                 Err(e) => { errors.push((e, None)) },
                             }