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.
This commit is contained in:
Benjamin Sago 2017-08-28 23:45:15 +01:00
parent 62075fe984
commit 3d4ddf8af6
1 changed files with 83 additions and 19 deletions

View File

@ -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 users input arguments.
///
/// This uses vectors to avoid the overhead of hashing: its 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 weve confirmed do not have Git repositories underneath them.
misses: Vec<PathBuf>,
}
/// A **Git repository** is one weve 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);
}
};
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)
}
}
None
}
}
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 dont 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 {