Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support gitignore #1071

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,12 @@ icons:
# ignore-globs:
# - .git

# == Use .gitignore ==
# Whether to ignore files in git's .gitignore
# Possible values: false, true
# Do not specify this for the default behavior (false)
# gitignore: false

# == Indicators ==
# Whether to add indicator characters to certain listed files.
# Possible values: false, true
Expand Down
3 changes: 3 additions & 0 deletions doc/lsd.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,9 @@ lsd is a ls command with a lot of pretty colours and some other stuff to enrich
`--git`
: Display git status. Directory git status is a reduction of included file statuses (recursively).

`--gitignore`
: Uses git .gitignore files for filtering files and directories

`--help`
: Prints help information

Expand Down
6 changes: 5 additions & 1 deletion src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -154,11 +154,15 @@ pub struct Cli {
#[arg(short, long)]
pub inode: bool,

/// Show git status on file and directory"
/// Show git status on file and directory
/// Only when used with --long option
#[arg(short, long)]
pub git: bool,

/// Uses git .gitignore files for filtering files and directories
#[arg(long)]
pub gitignore: bool,

/// When showing file information for a symbolic link,
/// show information for the file the link references rather than for the link itself
#[arg(short = 'L', long)]
Expand Down
9 changes: 9 additions & 0 deletions src/config_file.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ pub struct Config {
pub date: Option<String>,
pub dereference: Option<bool>,
pub display: Option<Display>,
pub gitignore: Option<bool>,
pub icons: Option<Icons>,
pub ignore_globs: Option<Vec<String>>,
pub indicators: Option<bool>,
Expand Down Expand Up @@ -114,6 +115,7 @@ impl Config {
date: None,
dereference: None,
display: None,
gitignore: None,
icons: None,
ignore_globs: None,
indicators: None,
Expand Down Expand Up @@ -283,6 +285,12 @@ icons:
# ignore-globs:
# - .git

# == Use .gitignore ==
# Whether to ignore files in git's .gitignore
# Possible values: false, true
# Do not specify this for the default behavior (false)
# gitignore: false

# == Indicators ==
# Whether to add indicator characters to certain listed files.
# Possible values: false, true
Expand Down Expand Up @@ -399,6 +407,7 @@ mod tests {
date: None,
dereference: Some(false),
display: None,
gitignore: None,
icons: Some(config_file::Icons {
when: Some(IconOption::Auto),
theme: Some(IconTheme::Fancy),
Expand Down
4 changes: 3 additions & 1 deletion src/core.rs
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,9 @@ impl Core {
}
};

let cache = if self.flags.blocks.0.contains(&Block::GitStatus) {
let cache = if self.flags.blocks.0.contains(&Block::GitStatus)
|| self.flags.gitignore.use_gitignore()
{
Some(GitCache::new(&path))
} else {
None
Expand Down
5 changes: 5 additions & 0 deletions src/flags.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ pub mod color;
pub mod date;
pub mod dereference;
pub mod display;
pub mod gitignore;
pub mod header;
pub mod hyperlink;
pub mod icons;
Expand Down Expand Up @@ -54,6 +55,8 @@ use clap::Error;
#[cfg(doc)]
use yaml_rust::Yaml;

use self::gitignore::Gitignore;

/// A struct to hold all set configuration flags for the application.
#[derive(Clone, Debug, Default)]
pub struct Flags {
Expand All @@ -63,6 +66,7 @@ pub struct Flags {
pub dereference: Dereference,
pub display: Display,
pub display_indicators: Indicators,
pub gitignore: Gitignore,
pub icons: Icons,
pub ignore_globs: IgnoreGlobs,
pub layout: Layout,
Expand Down Expand Up @@ -97,6 +101,7 @@ impl Flags {
size: SizeFlag::configure_from(cli, config),
permission: PermissionFlag::configure_from(cli, config),
display_indicators: Indicators::configure_from(cli, config),
gitignore: Gitignore::configure_from(cli, config),
icons: Icons::configure_from(cli, config),
ignore_globs: IgnoreGlobs::configure_from(cli, config)?,
no_symlink: NoSymlink::configure_from(cli, config),
Expand Down
116 changes: 116 additions & 0 deletions src/flags/gitignore.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
//! This module defines the [Gitignore]. To set it up from [Cli], a [Config] and its
//! [Default] value, use the [configure_from](Gitignore::configure_from) method.

use crate::app::Cli;
use crate::config_file::Config;

use super::Configurable;
/// The struct holding whether or not to use the gitignore and methods to build it.
/// A value of `true` means to use the gitignore, and filter out gitignored files
#[derive(Clone, Debug, Copy, PartialEq, Eq)]
pub struct Gitignore(pub bool);

impl Gitignore {
pub fn use_gitignore(&self) -> bool {
self.0
}
}

impl Configurable<Self> for Gitignore {
/// Returns a value from either [Cli], a [Config] or a [Default] value. The first value
/// that is not [None] is used. The order of precedence for the value used is:
/// - [from_cli](Gitignore::from_cli)
/// - [from_config](Gitignore::from_config)
/// - [Default::default]
fn configure_from(cli: &Cli, config: &Config) -> Self {
if let Some(value) = Self::from_cli(cli) {
return value;
}

if let Some(value) = Self::from_config(config) {
return value;
}

Default::default()
}

/// Get a potential [Gitignore] from [Cli].
///
/// If the "gitignore" argument has been passed, this returns a [Gitignore] set to `true` in a [Some]
/// If the argument has not been passed, this returns [None].
fn from_cli(cli: &Cli) -> Option<Self> {
cli.gitignore.then_some(Self(true))
}

/// Get a potential [Gitignore] from a [Config].
///
/// If the `Config::gitignore` contains an boolean value,
/// this returns a [Gitignore] set to the value of `Config::gitignore` in a [Some].
/// Otherwise, returns [None].
fn from_config(config: &Config) -> Option<Self> {
config.gitignore.map(Self)
}
}

/// The default value of `Gitignore` is false.
impl Default for Gitignore {
fn default() -> Self {
Self(false)
}
}

#[cfg(test)]
mod test {
use clap::Parser;

use super::Gitignore;

use super::super::Configurable;

use crate::app::Cli;
use crate::config_file::Config;

#[test]
fn test_configuration_from_none() {
let argv = ["lsd"];
let cli = Cli::try_parse_from(argv).unwrap();
assert!(matches!(
Gitignore::configure_from(&cli, &Config::with_none()),
Gitignore(false)
));
}

#[test]
fn test_configuration_from_args() {
let argv = ["lsd", "--gitignore"];
let cli = Cli::try_parse_from(argv).unwrap();
assert!(matches!(
Gitignore::configure_from(&cli, &Config::with_none()),
Gitignore(true)
));
}

#[test]
fn test_configuration_from_config() {
let argv = ["lsd"];
let cli = Cli::try_parse_from(argv).unwrap();
let mut c = Config::with_none();
c.gitignore = Some(true);
assert!(matches!(
Gitignore::configure_from(&cli, &c),
Gitignore(true)
));
}

#[test]
fn test_from_cli_none() {
let argv = ["lsd"];
let cli = Cli::try_parse_from(argv).unwrap();
assert!(Gitignore::from_cli(&cli).is_none());
}

#[test]
fn test_from_config_none() {
assert!(Gitignore::from_config(&Config::with_none()).is_none());
}
}
4 changes: 4 additions & 0 deletions src/meta/git_file_status.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,4 +64,8 @@ impl GitFileStatus {
});
ColoredString::new(Colors::default_style(), res)
}

pub fn is_ignored(&self) -> bool {
self.workdir == GitStatus::Ignored
}
}
25 changes: 21 additions & 4 deletions src/meta/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,13 @@ impl Meta {
return Ok((None, ExitCode::OK));
}

let current_meta_git_status = cache.and_then(|cache| cache.get(&self.path, true));
if flags.gitignore.use_gitignore()
&& current_meta_git_status.is_some_and(|git_status| git_status.is_ignored())
{
return Ok((None, ExitCode::OK));
}

match self.file_type {
FileType::Directory { .. } => (),
FileType::SymLink { is_dir: true } => {
Expand Down Expand Up @@ -107,7 +114,7 @@ impl Meta {
)?;
"..".clone_into(&mut parent_meta.name.name);

current_meta.git_status = cache.and_then(|cache| cache.get(&current_meta.path, true));
current_meta.git_status = current_meta_git_status;
parent_meta.git_status = cache.and_then(|cache| cache.get(&parent_meta.path, true));

content.push(current_meta);
Expand Down Expand Up @@ -165,6 +172,19 @@ impl Meta {
continue;
}

let is_directory = entry.file_type()?.is_dir();
entry_meta.git_status =
cache.and_then(|cache| cache.get(&entry_meta.path, is_directory));

// skip .gitignored files if flags.gitignore is set
if flags.gitignore.use_gitignore()
&& entry_meta
.git_status
.is_some_and(|git_status| git_status.is_ignored())
{
continue;
}

// check dereferencing
if flags.dereference.0 || !matches!(entry_meta.file_type, FileType::SymLink { .. }) {
match entry_meta.recurse_into(depth - 1, flags, cache) {
Expand All @@ -180,9 +200,6 @@ impl Meta {
};
}

let is_directory = entry.file_type()?.is_dir();
entry_meta.git_status =
cache.and_then(|cache| cache.get(&entry_meta.path, is_directory));
content.push(entry_meta);
}

Expand Down