summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorBenjamin Sago <ogham@bsago.me>2017-08-28 23:45:15 +0100
committerBenjamin Sago <ogham@bsago.me>2017-08-28 23:52:21 +0100
commit3d4ddf8af6bd746b5d07b9ee62126fb404aa2c29 (patch)
tree4b0f22de4d93147eb28755195f2d4b32cf2bbf9c
parent62075fe984a78acd052ea8f2cb2b5293d17786d7 (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.rs102
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 {