Browse Source

Group Git repositories by their workdir

This uses the Git module’s newfound powers of getting actual GitRepo values from a factory to cache repositories a bit more. Now, when querying two directories under the same repository, it’ll open both, see that they have the same workdir, and only use the first one.
Benjamin Sago 8 năm trước cách đây
mục cha
commit
3d4ddf8af6
1 tập tin đã thay đổi với 83 bổ sung19 xóa
  1. 83 19
      src/fs/feature/git.rs

+ 83 - 19
src/fs/feature/git.rs

@@ -1,4 +1,5 @@
-use std::collections::HashMap;
+//! Getting the Git status of files and directories.
+
 use std::path::{Path, PathBuf};
 
 use git2;
@@ -6,53 +7,116 @@ use git2;
 use fs::fields as f;
 
 
+/// A **Git cache** is assembled based on the user’s input arguments.
+///
+/// This uses vectors to avoid the overhead of hashing: it’s not worth it when the
+/// expected number of Git repositories per exa invocation is 0 or 1...
 pub struct GitCache {
-    repos: HashMap<PathBuf, Option<GitRepo>>,
+
+    /// A list of discovered Git repositories and their paths.
+    repos: Vec<GitRepo>,
+
+    /// Paths that we’ve confirmed do not have Git repositories underneath them.
+    misses: Vec<PathBuf>,
 }
 
+
+/// A **Git repository** is one we’ve discovered somewhere on the filesystem.
 pub struct GitRepo {
+
+    /// Most of the interesting Git stuff goes through this.
     repo: git2::Repository,
+
+    /// The working directory of this repository.
+    /// This is used to check whether two repositories are the same.
     workdir: PathBuf,
+
+    /// The path that was originally checked to discover this repository.
+    /// This is as important as the extra_paths (it gets checked first), but
+    /// is separate to avoid having to deal with a non-empty Vec.
+    original_path: PathBuf,
+
+    /// Any other paths that were checked only to result in this same
+    /// repository.
+    extra_paths: Vec<PathBuf>,
+}
+
+impl GitRepo {
+    fn has_workdir(&self, path: &Path) -> bool {
+        self.workdir == path
+    }
+
+    fn has_path(&self, path: &Path) -> bool {
+        self.original_path == path || self.extra_paths.iter().any(|e| e == path)
+    }
 }
 
 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);
+        let mut git = GitCache {
+            repos: Vec::with_capacity(iter.size_hint().0),
+            misses: Vec::new(),
+        };
 
         for path in iter {
-            if repos.contains_key(&path) {
+            if git.misses.contains(&path) {
+                debug!("Skipping {:?} because it already came back Gitless", path);
+            }
+            else if git.repos.iter().any(|e| e.has_path(&path)) {
                 debug!("Skipping {:?} because we already queried it", path);
             }
             else {
-                let repo = GitRepo::discover(&path);
-                let _ = repos.insert(path, repo);
+                match GitRepo::discover(path) {
+                    Ok(r) => {
+                        if let Some(mut r2) = git.repos.iter_mut().find(|e| e.has_workdir(&r.workdir)) {
+                            debug!("Adding to existing repo (workdir matches with {:?})", r2.workdir);
+                            r2.extra_paths.push(r.original_path);
+                            continue;
+                        }
+
+                        debug!("Creating new repo in cache");
+                        git.repos.push(r);
+                    },
+                    Err(miss) => git.misses.push(miss),
+                }
             }
         }
 
-        GitCache { repos }
+        git
     }
 }
 
 impl GitRepo {
-    fn discover(path: &Path) -> Option<GitRepo> {
+    fn discover(path: PathBuf) -> Result<GitRepo, PathBuf> {
         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 });
+
+        let repo = match git2::Repository::discover(&path) {
+            Ok(r) => r,
+            Err(e) => {
+                error!("Error discovering Git repositories: {:?}", e);
+                return Err(path);
             }
-        }
+        };
 
-        None
+        match repo.workdir().map(|wd| wd.to_path_buf()) {
+            Some(workdir) => Ok(GitRepo { repo, workdir, original_path: path, extra_paths: Vec::new() }),
+            None => {
+                warn!("Repository has no workdir?");
+                Err(path)
+            }
+        }
     }
 }
 
 impl GitCache {
+
+    /// Gets a repository from the cache and scans it to get all its files’ statuses.
     pub fn get(&self, index: &Path) -> Option<Git> {
-        let repo = match self.repos[index] {
-            Some(ref r) => r,
-            None        => return None,
+        let repo = match self.repos.iter().find(|e| e.has_path(index)) {
+            Some(r) => r,
+            None    => return None,
         };
 
         info!("Getting Git statuses for repo with workdir {:?}", &repo.workdir);
@@ -77,7 +141,7 @@ impl GitCache {
 }
 
 
-/// Container of Git statuses for all the files in this folder's Git repository.
+/// Container of Git statuses for all the files in this folders Git repository.
 pub struct Git {
     statuses: Vec<(PathBuf, git2::Status)>,
 }
@@ -96,7 +160,7 @@ impl Git {
 
     /// Get the combined status for all the files whose paths begin with the
     /// path that gets passed in. This is used for getting the status of
-    /// directories, which don't really have an 'official' status.
+    /// directories, which don’t really have an ‘official’ status.
     pub fn dir_status(&self, dir: &Path) -> f::Git {
         let s = self.statuses.iter()
                              .filter(|p| p.0.starts_with(dir))
@@ -118,7 +182,7 @@ fn working_tree_status(status: git2::Status) -> f::GitStatus {
     }
 }
 
-/// The character to display if the file has been modified, and the change
+/// The character to display if the file has been modified and the change
 /// has been staged.
 fn index_status(status: git2::Status) -> f::GitStatus {
     match status {