Skip to content

Commit

Permalink
Merge branch 'late-git-discovery'
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
ogham committed Sep 2, 2017
2 parents d86fc42 + c60ea36 commit 265f93f
Show file tree
Hide file tree
Showing 32 changed files with 610 additions and 179 deletions.
40 changes: 34 additions & 6 deletions Vagrantfile
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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.
Expand Down
42 changes: 31 additions & 11 deletions src/exa.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand All @@ -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.
Expand All @@ -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 }
})
}

Expand All @@ -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) => {
Expand All @@ -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)?,
}
Expand Down Expand Up @@ -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)?,
}
Expand Down Expand Up @@ -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)
}
}
}
Expand Down
30 changes: 5 additions & 25 deletions src/fs/dir.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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 {
Expand All @@ -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
Expand All @@ -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()
}
}
}


Expand Down
Loading

0 comments on commit 265f93f

Please sign in to comment.