mirror of
https://github.com/Llewellynvdm/exa.git
synced 2024-11-26 13:56:27 +00:00
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.
This commit is contained in:
parent
55aaecb74d
commit
040dbb2414
20
src/exa.rs
20
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)?,
|
||||
}
|
||||
|
@ -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 })
|
||||
}
|
||||
|
||||
|
@ -1,3 +1,4 @@
|
||||
use std::collections::HashMap;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use git2;
|
||||
@ -5,6 +6,73 @@ use git2;
|
||||
use fs::fields as f;
|
||||
|
||||
|
||||
pub struct GitCache {
|
||||
repos: HashMap<PathBuf, Option<GitRepo>>,
|
||||
}
|
||||
|
||||
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 }
|
||||
}
|
||||
}
|
||||
|
||||
impl GitCache {
|
||||
pub fn get(&self, index: &Path) -> Option<Git> {
|
||||
let repo = match self.repos[index] {
|
||||
Some(ref r) => r,
|
||||
None => return None,
|
||||
};
|
||||
|
||||
let iter = match repo.repo.statuses(None) {
|
||||
Ok(es) => es,
|
||||
Err(_) => return None,
|
||||
};
|
||||
|
||||
let mut statuses = Vec::new();
|
||||
|
||||
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)>,
|
||||
@ -12,24 +80,6 @@ pub struct Git {
|
||||
|
||||
impl Git {
|
||||
|
||||
/// 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);
|
||||
|
||||
let repo = git2::Repository::discover(path)?;
|
||||
let workdir = match repo.workdir() {
|
||||
Some(w) => w,
|
||||
None => return Ok(Git { statuses: vec![] }), // bare repo
|
||||
};
|
||||
|
||||
let statuses = repo.statuses(None)?.iter()
|
||||
.map(|e| (workdir.join(Path::new(e.path().unwrap())), e.status()))
|
||||
.collect();
|
||||
|
||||
Ok(Git { statuses: statuses })
|
||||
}
|
||||
|
||||
/// Get the status for the file at the given path, if present.
|
||||
pub fn status(&self, path: &Path) -> f::Git {
|
||||
let status = self.statuses.iter()
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
impl GitCache {
|
||||
pub fn get(&self, _index: &Path) -> Option<Git> {
|
||||
panic!("Tried to query a Git cache, but Git support is disabled")
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Git;
|
||||
|
||||
impl Git {
|
||||
pub fn status(&self, _: &Path) -> fields::Git {
|
||||
panic!("Tried to access a Git repo without Git support!");
|
||||
panic!("Tried to get a Git status, but Git support is disabled")
|
||||
}
|
||||
|
||||
pub fn dir_status(&self, path: &Path) -> fields::Git {
|
||||
self.status(path)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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,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};
|
||||
|
@ -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)) },
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user