summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorBenjamin Sago <ogham@bsago.me>2017-09-02 21:22:24 +0100
committerBenjamin Sago <ogham@bsago.me>2017-09-02 21:59:15 +0100
commit265f93f7cd9b4560af64b606075a623b3fdf2105 (patch)
tree55e50dffe82059afad15f114b7fdde42afbaced2
parentd86fc4286be613aab5a0a8ca70682c005eb71fa1 (diff)
parentc60ea36a315860cf41bb8f43cab514c746216f67 (diff)
Merge branch 'late-git-discovery'
This merges in the new Git code, which now uses a global cache rather than being per-repository. This lets exa keep the Git column when listing files outside of a directory and when in recursive or tree views. Fixes #24 and #183.
-rw-r--r--Vagrantfile40
-rw-r--r--src/exa.rs42
-rw-r--r--src/fs/dir.rs30
-rw-r--r--src/fs/feature/git.rs273
-rw-r--r--src/fs/feature/mod.rs41
-rw-r--r--src/fs/fields.rs6
-rw-r--r--src/fs/file.rs71
-rw-r--r--src/options/mod.rs2
-rw-r--r--src/output/details.rs15
-rw-r--r--src/output/grid_details.rs29
-rw-r--r--src/output/table.rs61
-rw-r--r--xtests/git_129
-rw-r--r--xtests/git_121216
-rw-r--r--xtests/git_1_additions (renamed from xtests/git_additions)0
-rw-r--r--xtests/git_1_both9
-rw-r--r--xtests/git_1_edits (renamed from xtests/git_edits)0
-rw-r--r--xtests/git_1_file1
-rw-r--r--xtests/git_1_files4
-rw-r--r--xtests/git_1_long3
-rw-r--r--xtests/git_1_nogit3
-rw-r--r--xtests/git_1_recurse16
-rw-r--r--xtests/git_1_tree11
-rw-r--r--xtests/git_2122119
-rw-r--r--xtests/git_2_all9
-rw-r--r--xtests/git_2_ignoreds2
-rw-r--r--xtests/git_2_long3
-rw-r--r--xtests/git_2_nogit3
-rw-r--r--xtests/git_2_recurse24
-rw-r--r--xtests/git_2_repository1
-rw-r--r--xtests/git_2_target1
-rw-r--r--xtests/git_2_tree13
-rwxr-xr-xxtests/run.sh32
32 files changed, 610 insertions, 179 deletions
diff --git a/Vagrantfile b/Vagrantfile
index 7335be9..f6107da 100644
--- a/Vagrantfile
+++ b/Vagrantfile
@@ -480,10 +480,7 @@ Vagrant.configure(2) do |config|
touch $dir/that-file
done
- touch -t #{some_date} "#{test_dir}/attributes" # there's probably
- touch -t #{some_date} "#{test_dir}/attributes"/* # a better
- touch -t #{some_date} "#{test_dir}/attributes"/*/* # way to
- touch -t #{some_date} "#{test_dir}/attributes"/*/*/* # do this
+ find "#{test_dir}/attributes" -exec touch {} -t #{some_date} \\;
# I want to use the following to test,
# but it only works on macos:
@@ -519,12 +516,43 @@ Vagrant.configure(2) do |config|
echo "more modifications!" | tee edits/unstaged edits/both additions/edited
touch additions/unstaged
-
- touch -t #{some_date} "#{test_dir}/git/"*/*
+ find "#{test_dir}/git" -exec touch {} -t #{some_date} \\;
sudo chown #{user}:#{user} -R "#{test_dir}/git"
EOF
+ # A second Git repository
+ # for testing two at once
+ config.vm.provision :shell, privileged: false, inline: <<-EOF
+ set -xe
+ mkdir -p "#{test_dir}/git2/deeply/nested/directory"
+ cd "#{test_dir}/git2"
+ git init
+
+ touch "deeply/nested/directory/upd8d"
+ git add "deeply/nested/directory/upd8d"
+ git commit -m "Automated test commit"
+
+ echo "Now with contents" > "deeply/nested/directory/upd8d"
+ touch "deeply/nested/directory/l8st"
+
+ echo -e "target\n*.mp3" > ".gitignore"
+ mkdir "ignoreds"
+ touch "ignoreds/music.mp3"
+ touch "ignoreds/music.m4a"
+
+ mkdir "target"
+ touch "target/another ignored file"
+
+ mkdir "deeply/nested/repository"
+ cd "deeply/nested/repository"
+ git init
+ touch subfile
+
+ find "#{test_dir}/git2" -exec touch {} -t #{some_date} \\;
+ sudo chown #{user}:#{user} -R "#{test_dir}/git2"
+ EOF
+
# Hidden and dot file testcases.
# We need to set the permissions of `.` and `..` because they actually
# get displayed in the output here, so this has to come last.
diff --git a/src/exa.rs b/src/exa.rs
index a87fefd..f40b4b0 100644
--- a/src/exa.rs
+++ b/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};
@@ -55,6 +56,11 @@ pub struct Exa<'args, 'w, W: Write + 'w> {
/// List of the free command-line arguments that should correspond to file
/// names (anything that isn’t an option).
pub args: Vec<&'args OsStr>,
+
+ /// A global Git cache, if the option was passed in.
+ /// This has to last the lifetime of the program, because the user might
+ /// want to list several directories in the same repository.
+ pub git: Option<GitCache>,
}
/// The “real” environment variables type.
@@ -67,14 +73,33 @@ impl Vars for LiveVars {
}
}
+/// Create a Git cache populated with the arguments that are going to be
+/// listed before they’re actually listed, if the options demand it.
+fn git_options(options: &Options, args: &[&OsStr]) -> Option<GitCache> {
+ if options.should_scan_for_git() {
+ Some(args.iter().map(|os| PathBuf::from(os)).collect())
+ }
+ else {
+ None
+ }
+}
+
impl<'args, 'w, W: Write + 'w> Exa<'args, 'w, W> {
pub fn new<I>(args: I, writer: &'w mut W) -> Result<Exa<'args, 'w, W>, Misfire>
where I: Iterator<Item=&'args OsString> {
- Options::parse(args, &LiveVars).map(move |(options, args)| {
+ Options::parse(args, &LiveVars).map(move |(options, mut args)| {
debug!("Dir action from arguments: {:#?}", options.dir_action);
debug!("Filter from arguments: {:#?}", options.filter);
debug!("View from arguments: {:#?}", options.view.mode);
- Exa { options, writer, args }
+
+ // List the current directory by default, like ls.
+ // This has to be done here, otherwise git_options won’t see it.
+ if args.is_empty() {
+ args = vec![ OsStr::new(".") ];
+ }
+
+ let git = git_options(&options, &args);
+ Exa { options, writer, args, git }
})
}
@@ -83,11 +108,6 @@ impl<'args, 'w, W: Write + 'w> Exa<'args, 'w, W> {
let mut dirs = Vec::new();
let mut exit_status = 0;
- // List the current directory by default, like ls.
- if self.args.is_empty() {
- self.args = vec![ OsStr::new(".") ];
- }
-
for file_path in &self.args {
match File::new(PathBuf::from(file_path), None, None) {
Err(e) => {
@@ -96,7 +116,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() {
Ok(d) => dirs.push(d),
Err(e) => writeln!(stderr(), "{:?}: {}", file_path, e)?,
}
@@ -156,7 +176,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() {
Ok(d) => child_dirs.push(d),
Err(e) => writeln!(stderr(), "{}: {}", child_dir.path.display(), e)?,
}
@@ -192,10 +212,10 @@ impl<'args, 'w, W: Write + 'w> Exa<'args, 'w, W> {
grid::Render { files, colours, style, opts }.render(self.writer)
}
Mode::Details(ref opts) => {
- details::Render { dir, files, colours, style, opts, filter: &self.options.filter, recurse: self.options.dir_action.recurse_options() }.render(self.writer)
+ details::Render { dir, files, colours, style, opts, filter: &self.options.filter, recurse: self.options.dir_action.recurse_options() }.render(self.git.as_ref(), self.writer)
}
Mode::GridDetails(ref opts) => {
- grid_details::Render { dir, files, colours, style, grid: &opts.grid, details: &opts.details, filter: &self.options.filter, row_threshold: opts.row_threshold }.render(self.writer)
+ grid_details::Render { dir, files, colours, style, grid: &opts.grid, details: &opts.details, filter: &self.options.filter, row_threshold: opts.row_threshold }.render(self.git.as_ref(), self.writer)
}
}
}
diff --git a/src/fs/dir.rs b/src/fs/dir.rs
index 77345d9..c0ba0ea 100644
--- a/src/fs/dir.rs
+++ b/src/fs/dir.rs
@@ -3,8 +3,7 @@ use std::fs;
use std::path::{Path, PathBuf};
use std::slice::Iter as SliceIter;
-use fs::feature::Git;
-use fs::{File, fields};
+use fs::File;
/// A **Dir** provides a cached list of the file paths in a directory that's
@@ -20,10 +19,6 @@ pub struct Dir {
/// The path that was read.
pub path: PathBuf,
-
- /// Holds a `Git` object if scanning for Git repositories is switched on,
- /// and this directory happens to contain one.
- git: Option<Git>,
}
impl Dir {
@@ -36,15 +31,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) -> IOResult<Dir> {
info!("Reading directory {:?}", &path);
let contents: Vec<PathBuf> = try!(fs::read_dir(&path)?
- .map(|result| result.map(|entry| entry.path()))
- .collect());
+ .map(|result| result.map(|entry| entry.path()))
+ .collect());
- let git = if git { Git::scan(&path).ok() } else { None };
- Ok(Dir { contents, path, git })
+ Ok(Dir { contents, path })
}
/// Produce an iterator of IO results of trying to read all the files in
@@ -67,20 +61,6 @@ impl Dir {
pub fn join(&self, child: &Path) -> PathBuf {
self.path.join(child)
}
-
- /// Return whether there's a Git repository on or above this directory.
- pub fn has_git_repo(&self) -> bool {
- self.git.is_some()
- }
-
- /// Get a string describing the Git status of the given file.
- pub fn git_status(&self, path: &Path, prefix_lookup: bool) -> fields::Git {
- match (&self.git, prefix_lookup) {
- (&Some(ref git), false) => git.status(path),
- (&Some(ref git), true) => git.dir_status(path),
- (&None, _) => fields::Git::empty()
- }
- }
}
diff --git a/src/fs/feature/git.rs b/src/fs/feature/git.rs
index 0bfc24e..6ecf8e9 100644
--- a/src/fs/feature/git.rs
+++ b/src/fs/feature/git.rs
@@ -1,57 +1,276 @@
+//! Getting the Git status of files and directories.
+
use std::path::{Path, PathBuf};
+use std::sync::Mutex;
use git2;
use fs::fields as f;
-/// Container of Git statuses for all the files in this folder's Git repository.
-pub struct Git {
- statuses: Vec<(PathBuf, git2::Status)>,
+/// 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 {
+
+ /// 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>,
}
-impl Git {
+impl GitCache {
+ pub fn has_anything_for(&self, index: &Path) -> bool {
+ self.repos.iter().any(|e| e.has_path(index))
+ }
- /// 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);
+ pub fn get(&self, index: &Path, prefix_lookup: bool) -> f::Git {
+ self.repos.iter()
+ .find(|e| e.has_path(index))
+ .map(|repo| repo.search(index, prefix_lookup))
+ .unwrap_or_default()
+ }
+}
- let repo = git2::Repository::discover(path)?;
- let workdir = match repo.workdir() {
- Some(w) => w,
- None => return Ok(Git { statuses: vec![] }), // bare repo
+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 git = GitCache {
+ repos: Vec::with_capacity(iter.size_hint().0),
+ misses: Vec::new(),
};
- let statuses = repo.statuses(None)?.iter()
- .map(|e| (workdir.join(Path::new(e.path().unwrap())), e.status()))
- .collect();
+ for path in iter {
+ 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 {
+ 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;
+ }
- Ok(Git { statuses: statuses })
+ debug!("Discovered new Git repo");
+ git.repos.push(r);
+ },
+ Err(miss) => git.misses.push(miss),
+ }
+ }
+ }
+
+ git
}
+}
+
+
+
+
+/// A **Git repository** is one we’ve discovered somewhere on the filesystem.
+pub struct GitRepo {
+
+ /// The queryable contents of the repository: either a `git2` repo, or the
+ /// cached results from when we queried it last time.
+ contents: Mutex<GitContents>,
- /// 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()
- .find(|p| p.0.as_path() == path);
- match status {
- Some(&(_, s)) => f::Git { staged: index_status(s), unstaged: working_tree_status(s) },
- None => f::Git { staged: f::GitStatus::NotModified, unstaged: f::GitStatus::NotModified }
+ /// 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>,
+}
+
+/// A repository’s queried state.
+enum GitContents {
+
+ /// All the interesting Git stuff goes through this.
+ Before { repo: git2::Repository },
+
+ /// Temporary value used in `repo_to_statuses` so we can move the
+ /// repository out of the `Before` variant.
+ Processing,
+
+ /// The data we’ve extracted from the repository, but only after we’ve
+ /// actually done so.
+ After { statuses: Git }
+}
+
+impl GitRepo {
+
+ /// Searches through this repository for a path (to a file or directory,
+ /// depending on the prefix-lookup flag) and returns its Git status.
+ ///
+ /// Actually querying the `git2` repository for the mapping of paths to
+ /// Git statuses is only done once, and gets cached so we don't need to
+ /// re-query the entire repository the times after that.
+ ///
+ /// The temporary `Processing` enum variant is used after the `git2`
+ /// repository is moved out, but before the results have been moved in!
+ /// See https://stackoverflow.com/q/45985827/3484614
+ fn search(&self, index: &Path, prefix_lookup: bool) -> f::Git {
+ use self::GitContents::*;
+ use std::mem::replace;
+
+ let mut contents = self.contents.lock().unwrap();
+ if let After { ref statuses } = *contents {
+ debug!("Git repo {:?} has been found in cache", &self.workdir);
+ return statuses.status(index, prefix_lookup);
}
+
+ debug!("Querying Git repo {:?} for the first time", &self.workdir);
+ let repo = replace(&mut *contents, Processing).inner_repo();
+ let statuses = repo_to_statuses(repo, &self.workdir);
+ let result = statuses.status(index, prefix_lookup);
+ let _processing = replace(&mut *contents, After { statuses });
+ result
+ }
+
+ /// Whether this repository has the given working directory.
+ fn has_workdir(&self, path: &Path) -> bool {
+ self.workdir == path
+ }
+
+ /// Whether this repository cares about the given path at all.
+ fn has_path(&self, path: &Path) -> bool {
+ path.starts_with(&self.original_path) || self.extra_paths.iter().any(|e| path.starts_with(e))
+ }
+
+ /// Searches for a Git repository at any point above the given path.
+ /// Returns the original buffer if none is found.
+ fn discover(path: PathBuf) -> Result<GitRepo, PathBuf> {
+ info!("Searching for Git repository above {:?}", path);
+ 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) => {
+ let contents = Mutex::new(GitContents::Before { repo });
+ Ok(GitRepo { contents, workdir, original_path: path, extra_paths: Vec::new() })
+ },
+ None => {
+ warn!("Repository has no workdir?");
+ Err(path)
+ }
+ }
+ }
+}
+
+
+impl GitContents {
+ /// Assumes that the repository hasn’t been queried, and extracts it
+ /// (consuming the value) if it has. This is needed because the entire
+ /// enum variant gets replaced when a repo is queried (see above).
+ fn inner_repo(self) -> git2::Repository {
+ if let GitContents::Before { repo } = self {
+ repo
+ }
+ else {
+ unreachable!("Tried to extract a non-Repository")
+ }
+ }
+}
+
+/// Iterates through a repository’s statuses, consuming it and returning the
+/// mapping of files to their Git status.
+/// We will have already used the working directory at this point, so it gets
+/// passed in rather than deriving it from the `Repository` again.
+fn repo_to_statuses(repo: git2::Repository, workdir: &Path) -> Git {
+ let mut statuses = Vec::new();
+
+ info!("Getting Git statuses for repo with workdir {:?}", workdir);
+ match repo.statuses(None) {
+ Ok(es) => {
+ for e in es.iter() {
+ let path = workdir.join(Path::new(e.path().unwrap()));
+ let elem = (path, e.status());
+ statuses.push(elem);
+ }
+ },
+ Err(e) => error!("Error looking up Git statuses: {:?}", e),
+ }
+
+ Git { statuses }
+}
+
+// The `repo.statuses` call above takes a long time. exa debug output:
+//
+// 20.311276 INFO:exa::fs::feature::git: Getting Git statuses for repo with workdir "/vagrant/"
+// 20.799610 DEBUG:exa::output::table: Getting Git status for file "./Cargo.toml"
+//
+// Even inserting another logging line immediately afterwards doesn't make it
+// look any faster.
+
+
+/// Container of Git statuses for all the files in this folder’s Git repository.
+struct Git {
+ statuses: Vec<(PathBuf, git2::Status)>,
+}
+
+impl Git {
+
+ /// Get either the file or directory status for the given path.
+ /// “Prefix lookup” means that it should report an aggregate status of all
+ /// paths starting with the given prefix (in other words, a directory).
+ fn status(&self, index: &Path, prefix_lookup: bool) -> f::Git {
+ if prefix_lookup { self.dir_status(index) }
+ else { self.file_status(index) }
+ }
+
+ /// Get the status for the file at the given path.
+ fn file_status(&self, file: &Path) -> f::Git {
+ let path = reorient(file);
+ self.statuses.iter()
+ .find(|p| p.0.as_path() == path)
+ .map(|&(_, s)| f::Git { staged: index_status(s), unstaged: working_tree_status(s) })
+ .unwrap_or_default()
}
/// 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.
- pub fn dir_status(&self, dir: &Path) -> f::Git {
+ /// directories, which don’t really have an ‘official’ status.
+ fn dir_status(&self, dir: &Path) -> f::Git {
+ let path = reorient(dir);
let s = self.statuses.iter()
- .filter(|p| p.0.starts_with(dir))
+ .filter(|p| p.0.starts_with(&path))
.fold(git2::Status::empty(), |a, b| a | b.1);
f::Git { staged: index_status(s), unstaged: working_tree_status(s) }
}
}
+/// Converts a path to an absolute path based on the current directory.
+/// Paths need to be absolute for them to be compared properly, otherwise
+/// you’d ask a repo about “./README.md” but it only knows about
+/// “/vagrant/REAMDE.md”, prefixed by the workdir.
+fn reorient(path: &Path) -> PathBuf {
+ use std::env::current_dir;
+ // I’m not 100% on this func tbh
+ match current_dir() {
+ Err(_) => Path::new(".").join(&path),
+ Ok(dir) => dir.join(&path),
+ }
+}
+
/// The character to display if the file has been modified, but not staged.
fn working_tree_status(status: git2::Status) -> f::GitStatus {
match status {
@@ -64,7 +283,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 {
diff --git a/src/fs/feature/mod.rs b/src/fs/feature/mod.rs
index 72db8e1..e2f5d0c 100644
--- a/src/fs/feature/mod.rs
+++ b/src/fs/feature/mod.rs
@@ -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
+ }
}
- pub fn status(&self, _: &Path) -> fields::Git {
- panic!("Tried to access a Git repo without Git support!");
+ impl GitCache {
+ pub fn get(&self, _index: &Path) -> Option<Git> {
+ panic!("Tried to query a Git cache, but Git support is disabled")
+ }
}
- pub fn dir_status(&self, path: &Path) -> fields::Git {
- self.status(path)
+ pub struct Git;
+
+ impl Git {
+ pub fn status(&self, _: &Path) -> fields::Git {
+ panic!("Tried to get a Git status, but Git support is disabled")
+ }
+
+ pub fn dir_status(&self, path: &Path) -> fields::Git {
+ self.status(path)
+ }
}
}
diff --git a/src/fs/fields.rs b/src/fs/fields.rs
index 75301a6..26d8939 100644
--- a/src/fs/fields.rs
+++ b/src/fs/fields.rs
@@ -1,3 +1,4 @@
+
//! Wrapper types for the values returned from `File`s.
//!
//! The methods of `File` that return information about the entry on the
@@ -206,10 +207,11 @@ pub struct Git {
pub unstaged: GitStatus,
}
-impl Git {
+use std::default::Default;
+impl Default for Git {
/// Create a Git status for a file with nothing done to it.
- pub fn empty() -> Git {
+ fn default() -> Git {
Git { staged: GitStatus::NotModified, unstaged: GitStatus::NotModified }
}
}
diff --git a/src/fs/file.rs b/src/fs/file.rs
index c07f879..97b3cd6 100644
--- a/src/fs/file.rs
+++ b/src/fs/file.rs
@@ -19,7 +19,7 @@ use fs::fields as f;
/// start and hold on to all the information.
pub struct File<'dir> {
- /// The filename portion of this file's path, including the extension.
+ /// The filename portion of this file’s path, including the extension.
///
/// This is used to compare against certain filenames (such as checking if
/// it’s “Makefile” or something) and to highlight only the filename in
@@ -33,26 +33,27 @@ pub struct File<'dir> {
/// The path that begat this file.
///
- /// Even though the file's name is extracted, the path needs to be kept
- /// around, as certain operations involve looking up the file's absolute
- /// location (such as the Git status, or searching for compiled files).
+ /// Even though the file’s name is extracted, the path needs to be kept
+ /// around, as certain operations involve looking up the file’s absolute
+ /// location (such as searching for compiled files) or using its original
+ /// path (following a symlink).
pub path: PathBuf,
- /// A cached `metadata` call for this file.
+ /// A cached `metadata` (`stat`) call for this file.
///
/// This too is queried multiple times, and is *not* cached by the OS, as
- /// it could easily change between invocations - but exa is so short-lived
+ /// it could easily change between invocations — but exa is so short-lived
/// it's better to just cache it.
pub metadata: fs::Metadata,
- /// A reference to the directory that contains this file, if present.
+ /// A reference to the directory that contains this file, if any.
///
/// Filenames that get passed in on the command-line directly will have no
- /// parent directory reference - although they technically have one on the
- /// filesystem, we'll never need to look at it, so it'll be `None`.
+ /// parent directory reference — although they technically have one on the
+ /// filesystem, we’ll never need to look at it, so it’ll be `None`.
/// However, *directories* that get passed in will produce files that
/// contain a reference to it, which is used in certain operations (such
- /// as looking up a file's Git status).
+ /// as looking up compiled files).
pub parent_dir: Option<&'dir Dir>,
}
@@ -88,11 +89,11 @@ impl<'dir> File<'dir> {
/// Extract an extension from a file path, if one is present, in lowercase.
///
/// The extension is the series of characters after the last dot. This
- /// deliberately counts dotfiles, so the ".git" folder has the extension "git".
+ /// deliberately counts dotfiles, so the “.git” folder has the extension “git”.
///
/// ASCII lowercasing is used because these extensions are only compared
/// against a pre-compiled list of extensions which are known to only exist
- /// within ASCII, so it's alright.
+ /// within ASCII, so it’s alright.
fn ext(path: &Path) -> Option<String> {
use std::ascii::AsciiExt;
@@ -110,24 +111,24 @@ impl<'dir> File<'dir> {
}
/// If this file is a directory on the filesystem, then clone its
- /// `PathBuf` for use in one of our own `Dir` objects, and read a list of
+ /// `PathBuf` for use in one of our own `Dir` values, and read a list of
/// its contents.
///
- /// Returns an IO error upon failure, but this shouldn't be used to check
+ /// 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) -> IOResult<Dir> {
+ Dir::read_dir(self.path.clone())
}
- /// Whether this file is a regular file on the filesystem - that is, not a
+ /// Whether this file is a regular file on the filesystem — that is, not a
/// directory, a link, or anything else treated specially.
pub fn is_file(&self) -> bool {
self.metadata.is_file()
}
/// Whether this file is both a regular file *and* executable for the
- /// current user. Executable files have different semantics than
- /// executable directories, and so should be highlighted differently.
+ /// current user. An executable file has a different purpose from an
+ /// executable directory, so they should be highlighted differently.
pub fn is_executable_file(&self) -> bool {
let bit