diff options
author | Benjamin Sago <ogham@bsago.me> | 2017-08-28 23:45:15 +0100 |
---|---|---|
committer | Benjamin Sago <ogham@bsago.me> | 2017-08-28 23:52:21 +0100 |
commit | 3d4ddf8af6bd746b5d07b9ee62126fb404aa2c29 (patch) | |
tree | 4b0f22de4d93147eb28755195f2d4b32cf2bbf9c | |
parent | 62075fe984a78acd052ea8f2cb2b5293d17786d7 (diff) |
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.
-rw-r--r-- | src/fs/feature/git.rs | 102 |
1 files changed, 83 insertions, 19 deletions
diff --git a/src/fs/feature/git.rs b/src/fs/feature/git.rs index 768ddc8..75e2ef7 100644 --- a/src/fs/feature/git.rs +++ b/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 folder’s 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 { |