feat(mercurial): Implemented mercurial cache

PLEASE WAIT FOR LATER COMMITS THIS ONLY WORK ON MY MACHINE

feat(mercurial): displaying the info in columns and feature flagging

feat(mercurial): feat finished

this is not prod ready

feat(ignoring): adding the ability to ignore

docs: add documentation for mercurial options

chore: styling
This commit is contained in:
MartinFillon 2023-12-25 22:33:18 +01:00
parent 27edee1e7d
commit 9ae60d1a2b
No known key found for this signature in database
GPG key ID: 16DC898F53F94853
29 changed files with 525 additions and 31 deletions

9
Cargo.lock generated
View file

@ -394,6 +394,7 @@ dependencies = [
"criterion",
"git2",
"glob",
"hgrs",
"libc",
"locale",
"log",
@ -489,6 +490,14 @@ version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "443144c8cdadd93ebf52ddb4056d257f5b52c04d3c804e657d19eb73fc33668b"
[[package]]
name = "hgrs"
version = "0.2.5"
source = "git+https://github.com/MartinFillon/hgrs.git#b28fef5f9801c4705cd524673fa8bc155d66c1a9"
dependencies = [
"log",
]
[[package]]
name = "humantime"
version = "2.1.0"

View file

@ -92,6 +92,11 @@ timeago = { version = "0.4.2", default-features = false }
unicode-width = "0.1"
zoneinfo_compiled = "0.5.1"
[dependencies.hgrs]
git = "https://github.com/MartinFillon/hgrs.git"
version = "0.2.5"
optional = true
[dependencies.git2]
version = "0.18"
optional = true
@ -129,6 +134,7 @@ nix-local = []
# Shouldn't ever be used in CI (slow!)
powertest = []
nix-generated = []
mercurial = ["hgrs"]
# use LTO for smaller binaries (that take longer to build)
[profile.release]

View file

@ -143,6 +143,9 @@ These options are available when running with `--long` (`-l`):
- **--no-user**: suppress the user field
- **--no-time**: suppress the time field
- **--stdin**: read file names from stdin
- **--mercurial**: list each files Mercurial status, if tracked or ignored
- **--mercurial-ignore**: ignore files ignored by Mercurial
- **--no-mercurial**: suppress Mercurial status (always overrides `--mercurial`, `--mercurial-ignore`)
Some of the options accept parameters:

View file

@ -121,3 +121,6 @@ complete -c eza -l git-repos -d "List each git-repos status and branch name"
complete -c eza -l git-repos-no-status -d "List each git-repos branch name (much faster)"
complete -c eza -s '@' -l extended -d "List each file's extended attributes and sizes"
complete -c eza -s Z -l context -d "List each file's security context"
complete -c eza -l mercurial -d "List each file's Mercurial status, if tracked"
complete -c eza -l mercurial-ignore -d "Ignore files that are ignored by Mercurial"
complete -c eza -l no-mercurial -d "Suppress Mercurial status"

View file

@ -59,4 +59,7 @@ export extern "eza" [
--context(-Z) # List each file's security context
--smart-group # Only show group if it has a different name from owner
--stdin # When piping to eza. Read file paths from stdin
--mercurial # List each file's Mercurial status, if tracked
--mercurial-ignore # Ignore files ignored ly Mercurial
--no-mercurial # Suppress Mercurial status
]

View file

@ -69,6 +69,9 @@ __eza() {
'*:filename:_files' \
--smart-group"[Only show group if it has a different name from owner]" \
--stdin"[When piping to eza. Read file names from stdin]"
--mercurial"[List each file's Mercurial status, if tracked]" \
--mercurial-ignore"[Ignore files ignored by Mercurial]" \
--no-mercurial"[Suppress Mercurial status]" \
}
__eza

View file

@ -269,6 +269,17 @@ All Git repository directories will be shown as (themed) `-` without status indi
`--no-git`
: Don't show Git status (always overrides `--git`, `--git-repos`, `--git-repos-no-status`)
`--mercurial` [if eza was built with mercurial support]
: List each files Mercurial status, if tracked.
hg command Needed !!! This adds a character column indicating status. The status character can be `-` for not directories, `M` for a modified file, `A` for a added file, `R` for deleted, `C` for cleaned, `!` for missing, `I` for ignored.
`--mercurial-ignore` [if eza was built with mercurial support]
: Do not list files that are ignored by Mercurial.
hg command Needed !!!
`--no-mercurial` [if eza was built with mercurial support]
: Don't show Mercurial status (always overrides `--mercurial`, `--mercurial-ignore`)
hg command Needed !!!
ENVIRONMENT VARIABLES
=====================

View file

@ -1,10 +1,11 @@
use crate::fs::feature::git::GitCache;
use crate::fs::fields::GitStatus;
use crate::fs::fields::{GitStatus, MercurialStatus};
use std::fs;
use std::io;
use std::path::{Path, PathBuf};
use std::slice::Iter as SliceIter;
use crate::fs::feature::mercurial::MercurialCache;
use log::*;
use crate::fs::File;
@ -52,6 +53,8 @@ impl Dir {
git_ignoring: bool,
deref_links: bool,
total_size: bool,
mercurial: Option<&'ig MercurialCache>,
mercurial_ignore: bool,
) -> Files<'dir, 'ig> {
Files {
inner: self.contents.iter(),
@ -62,6 +65,8 @@ impl Dir {
git_ignoring,
deref_links,
total_size,
mercurial,
mercurial_ignore,
}
}
@ -101,6 +106,10 @@ pub struct Files<'dir, 'ig> {
/// Whether to calculate the directory size recursively
total_size: bool,
mercurial: Option<&'ig MercurialCache>,
mercurial_ignore: bool,
}
impl<'dir, 'ig> Files<'dir, 'ig> {
@ -137,6 +146,13 @@ impl<'dir, 'ig> Files<'dir, 'ig> {
}
}
if self.mercurial_ignore {
let mercurial_status = self.mercurial.map(|g| g.get(path)).unwrap_or_default();
if mercurial_status.status == MercurialStatus::Ignored {
continue;
}
}
let file = File::from_args(
path.clone(),
self.dir,

View file

@ -0,0 +1,80 @@
use hgrs::{FileStatus, MercurialRepository};
use std::path::PathBuf;
use crate::fs::fields as f;
#[derive(Debug, Clone)]
pub struct MercurialCache {
pub repos: Vec<MercurialRepo>,
pub misses: Vec<PathBuf>,
}
impl MercurialCache {
pub fn get(&self, file_path: &PathBuf) -> f::Mercurial {
f::Mercurial {
status: self
.repos
.iter()
.find(|r| r.has_path(file_path))
.map(|r| r.get(file_path))
.unwrap_or_default()
.into(),
}
}
}
impl FromIterator<PathBuf> for MercurialCache {
fn from_iter<T: IntoIterator<Item = PathBuf>>(iter: T) -> Self {
let iter = iter.into_iter();
let mut mercurial = Self {
repos: Vec::with_capacity(iter.size_hint().0),
misses: Vec::new(),
};
for path in iter {
match MercurialRepo::discover(&path) {
Ok(repo) => mercurial.repos.push(repo),
Err(path) => mercurial.misses.push(path),
}
}
mercurial
}
}
#[derive(Debug, Clone)]
pub struct MercurialRepo {
pub repo: MercurialRepository,
pub path: PathBuf,
pub extra_paths: Vec<PathBuf>,
}
impl MercurialRepo {
pub fn get(&self, file_path: &PathBuf) -> FileStatus {
self.repo.get_status(file_path)
}
pub fn has_path(&self, file_path: &PathBuf) -> bool {
let dir = file_path.parent().unwrap();
if dir == self.path {
return true;
}
if self.extra_paths.contains(&dir.into()) {
return true;
}
false
}
pub fn discover(path: &PathBuf) -> Result<Self, PathBuf> {
let r = match hgrs::find_repo_recursively(path, 10) {
Some(r) => r,
None => return Err(path.clone()),
};
Ok(Self {
repo: r,
path: path.into(),
extra_paths: Vec::new(),
})
}
}

View file

@ -3,6 +3,39 @@ pub mod xattr;
#[cfg(feature = "git")]
pub mod git;
#[cfg(feature = "mercurial")]
pub mod mercurial;
#[cfg(not(feature = "mercurial"))]
pub mod mercurial {
use std::iter::FromIterator;
use std::path::{Path, PathBuf};
use crate::fs::fields as f;
pub struct MercurialCache;
impl FromIterator<PathBuf> for MercurialCache {
fn from_iter<I>(_iter: I) -> Self
where
I: IntoIterator<Item = PathBuf>,
{
Self
}
}
impl MercurialCache {
pub fn has_anything_for(&self, _index: &Path) -> bool {
false
}
pub fn get(&self, _index: &Path) -> f::Mercurial {
unreachable!();
}
}
}
#[cfg(not(feature = "git"))]
pub mod git {
use std::iter::FromIterator;

View file

@ -246,6 +246,46 @@ impl Default for Git {
}
}
#[derive(PartialEq, Eq, Copy, Clone)]
pub enum MercurialStatus {
Modified,
Added,
Removed,
Clean,
Missing,
NotTracked,
Ignored,
Directory,
}
#[cfg(feature = "mercurial")]
impl From<hgrs::FileStatus> for MercurialStatus {
fn from(value: hgrs::FileStatus) -> Self {
match value {
hgrs::FileStatus::Modified => MercurialStatus::Modified,
hgrs::FileStatus::Added => MercurialStatus::Added,
hgrs::FileStatus::Removed => MercurialStatus::Removed,
hgrs::FileStatus::Clean => MercurialStatus::Clean,
hgrs::FileStatus::Missing => MercurialStatus::Missing,
hgrs::FileStatus::NotTracked => MercurialStatus::NotTracked,
hgrs::FileStatus::Ignored => MercurialStatus::Ignored,
hgrs::FileStatus::Directory => MercurialStatus::Directory,
}
}
}
pub struct Mercurial {
pub status: MercurialStatus,
}
impl Default for Mercurial {
fn default() -> Self {
Self {
status: MercurialStatus::NotTracked,
}
}
}
pub enum SecurityContextType<'a> {
SELinux(&'a str),
None,

View file

@ -593,7 +593,15 @@ impl<'dir> File<'dir> {
let mut size = 0;
let mut blocks = 0;
for file in dir
.files(super::DotFilter::Dotfiles, None, false, false, true)
.files(
super::DotFilter::Dotfiles,
None,
false,
false,
true,
None,
false,
)
.flatten()
{
match file.recursive_directory_size() {
@ -695,7 +703,15 @@ impl<'dir> File<'dir> {
match Dir::read_dir(self.path.clone()) {
// . & .. are skipped, if the returned iterator has .next(), it's not empty
Ok(has_files) => has_files
.files(super::DotFilter::Dotfiles, None, false, false, false)
.files(
super::DotFilter::Dotfiles,
None,
false,
false,
false,
None,
false,
)
.next()
.is_none(),
Err(_) => false,

View file

@ -68,6 +68,9 @@ pub struct FileFilter {
/// Whether to ignore Git-ignored patterns.
pub git_ignore: GitIgnore,
/// Whether to ignore Mercurial-ignored patterns.
pub mercurial_ignore: MercurialIgnore,
}
impl FileFilter {
@ -354,6 +357,15 @@ pub enum GitIgnore {
Off,
}
#[derive(PartialEq, Eq, Debug, Copy, Clone)]
pub enum MercurialIgnore {
/// Ignore files that Mercurial would ignore.
CheckAndIgnore,
/// Display files, even if Mercurial would ignore them.
Off,
}
#[cfg(test)]
mod test_ignores {
use super::*;

View file

@ -1,3 +1,4 @@
#![allow(clippy::too_many_arguments)]
#[allow(unused)]
pub mod fs;
#[allow(unused)]

View file

@ -20,6 +20,8 @@
#![allow(clippy::unused_self)]
#![allow(clippy::upper_case_acronyms)]
#![allow(clippy::wildcard_imports)]
#![allow(clippy::too_many_arguments)]
#![allow(clippy::fn_params_excessive_bools)]
use std::env;
use std::ffi::{OsStr, OsString};
@ -30,7 +32,8 @@ use std::process::exit;
use ansiterm::{ANSIStrings, Style};
use crate::fs::feature::git::GitCache;
use crate::fs::filter::GitIgnore;
use crate::fs::feature::mercurial::MercurialCache;
use crate::fs::filter::{GitIgnore, MercurialIgnore};
use crate::fs::{Dir, File};
use crate::options::stdin::FilesInput;
use crate::options::{vars, Options, OptionsResult, Vars};
@ -87,6 +90,7 @@ fn main() {
}
let git = git_options(&options, &input_paths);
let mercurial = mercurial_options(&options, &input_paths);
let writer = io::stdout();
let git_repos = git_repos(&options, &input_paths);
@ -100,6 +104,7 @@ fn main() {
console_width,
git,
git_repos,
mercurial,
};
info!("matching on exa.run");
@ -169,6 +174,8 @@ pub struct Exa<'args> {
pub git: Option<GitCache>,
pub git_repos: bool,
pub mercurial: Option<MercurialCache>,
}
/// The “real” environment variables type.
@ -191,6 +198,14 @@ fn git_options(options: &Options, args: &[&OsStr]) -> Option<GitCache> {
}
}
fn mercurial_options(options: &Options, args: &[&OsStr]) -> Option<MercurialCache> {
if options.should_scan_for_mercurial() {
Some(args.iter().map(PathBuf::from).collect())
} else {
None
}
}
#[cfg(not(feature = "git"))]
fn git_repos(_options: &Options, _args: &[&OsStr]) -> bool {
return false;
@ -332,12 +347,16 @@ impl<'args> Exa<'args> {
let mut children = Vec::new();
let git_ignore = self.options.filter.git_ignore == GitIgnore::CheckAndIgnore;
let mercurial_ignore =
self.options.filter.mercurial_ignore == MercurialIgnore::CheckAndIgnore;
for file in dir.files(
self.options.filter.dot_filter,
self.git.as_ref(),
git_ignore,
self.options.view.deref_links,
self.options.view.total_size,
self.mercurial.as_ref(),
mercurial_ignore,
) {
match file {
Ok(file) => children.push(file),
@ -427,7 +446,10 @@ impl<'args> Exa<'args> {
let recurse = self.options.dir_action.recurse_options();
let git_ignoring = self.options.filter.git_ignore == GitIgnore::CheckAndIgnore;
let mercurial_ignoring =
self.options.filter.mercurial_ignore == MercurialIgnore::CheckAndIgnore;
let git = self.git.as_ref();
let mercurial = self.mercurial.as_ref();
let git_repos = self.git_repos;
let r = details::Render {
dir,
@ -440,6 +462,8 @@ impl<'args> Exa<'args> {
git_ignoring,
git,
git_repos,
mercurial,
mercurial_ignoring,
};
r.render(&mut self.writer)
}
@ -451,7 +475,10 @@ impl<'args> Exa<'args> {
let filter = &self.options.filter;
let git_ignoring = self.options.filter.git_ignore == GitIgnore::CheckAndIgnore;
let mercurial_ignoring =
self.options.filter.mercurial_ignore == MercurialIgnore::CheckAndIgnore;
let git = self.git.as_ref();
let mercurial = self.mercurial.as_ref();
let git_repos = self.git_repos;
let r = grid_details::Render {
@ -467,6 +494,8 @@ impl<'args> Exa<'args> {
git,
console_width,
git_repos,
mercurial,
mercurial_ignoring,
};
r.render(&mut self.writer)
}
@ -476,7 +505,10 @@ impl<'args> Exa<'args> {
let filter = &self.options.filter;
let recurse = self.options.dir_action.recurse_options();
let git_ignoring = self.options.filter.git_ignore == GitIgnore::CheckAndIgnore;
let mercurial_ignoring =
self.options.filter.mercurial_ignore == MercurialIgnore::CheckAndIgnore;
let git = self.git.as_ref();
let mercurial = self.mercurial.as_ref();
let git_repos = self.git_repos;
let r = details::Render {
@ -490,6 +522,8 @@ impl<'args> Exa<'args> {
git_ignoring,
git,
git_repos,
mercurial,
mercurial_ignoring,
};
r.render(&mut self.writer)
}

View file

@ -1,7 +1,7 @@
//! Parsing the options for `FileFilter`.
use crate::fs::filter::{
FileFilter, FileFilterFlags, GitIgnore, IgnorePatterns, SortCase, SortField,
FileFilter, FileFilterFlags, GitIgnore, IgnorePatterns, MercurialIgnore, SortCase, SortField,
};
use crate::fs::DotFilter;
@ -32,6 +32,7 @@ impl FileFilter {
dot_filter: DotFilter::deduce(matches)?,
ignore_patterns: IgnorePatterns::deduce(matches)?,
git_ignore: GitIgnore::deduce(matches)?,
mercurial_ignore: MercurialIgnore::deduce(matches)?,
});
}
}
@ -193,6 +194,16 @@ impl GitIgnore {
}
}
impl MercurialIgnore {
pub fn deduce(matches: &MatchedFlags<'_>) -> Result<Self, OptionsError> {
if matches.has(&flags::MERCURIAL_IGNORE)? {
Ok(Self::CheckAndIgnore)
} else {
Ok(Self::Off)
}
}
}
#[cfg(test)]
mod test {
use super::*;

View file

@ -82,6 +82,9 @@ pub static OCTAL: Arg = Arg { short: Some(b'o'), long: "octal-permis
pub static SECURITY_CONTEXT: Arg = Arg { short: Some(b'Z'), long: "context", takes_value: TakesValue::Forbidden };
pub static STDIN: Arg = Arg { short: None, long: "stdin", takes_value: TakesValue::Forbidden };
pub static FILE_FLAGS: Arg = Arg { short: Some(b'O'), long: "flags", takes_value: TakesValue::Forbidden };
pub static MERCURIAL: Arg = Arg { short: None, long: "mercurial", takes_value: TakesValue::Forbidden };
pub static NO_MERCURIAL: Arg = Arg { short: None, long: "no-mercurial", takes_value: TakesValue::Forbidden };
pub static MERCURIAL_IGNORE: Arg = Arg { short: None, long: "mercurial-ignore", takes_value: TakesValue::Forbidden };
pub static ALL_ARGS: Args = Args(&[
&VERSION, &HELP,
@ -98,5 +101,6 @@ pub static ALL_ARGS: Args = Args(&[
&NO_PERMISSIONS, &NO_FILESIZE, &NO_USER, &NO_TIME, &SMART_GROUP,
&GIT, &NO_GIT, &GIT_REPOS, &GIT_REPOS_NO_STAT,
&EXTENDED, &OCTAL, &SECURITY_CONTEXT, &STDIN, &FILE_FLAGS
&EXTENDED, &OCTAL, &SECURITY_CONTEXT, &STDIN, &FILE_FLAGS,
&MERCURIAL, &NO_MERCURIAL, &MERCURIAL_IGNORE,
]);

View file

@ -90,6 +90,11 @@ static EXTENDED_HELP: &str = " \
static SECATTR_HELP: &str = " \
-Z, --context list each file's security context";
static MERCURIAL_HELP: &str = " \
--mercurial list each file's Mercurial status (hg command needed) \
--no-mercurial ignore ignored files in Mercurial (hg command needed) \
--no-mercurial suppress Mercurial status (always overrides --mercurial)";
/// All the information needed to display the help text, which depends
/// on which features are enabled and whether the user only wants to
/// see one sections help.
@ -129,6 +134,10 @@ impl fmt::Display for HelpString {
write!(f, "\n{GIT_VIEW_HELP}")?;
}
if cfg!(feature = "mercurial") {
write!(f, "\n{MERCURIAL_HELP}")?;
}
if xattr::ENABLED {
write!(f, "\n{EXTENDED_HELP}")?;
write!(f, "\n{SECATTR_HELP}")?;

View file

@ -188,6 +188,24 @@ impl Options {
}
}
pub fn should_scan_for_mercurial(&self) -> bool {
match self.view.mode {
Mode::Details(details::Options {
table: Some(ref table),
..
})
| Mode::GridDetails(grid_details::Options {
details:
details::Options {
table: Some(ref table),
..
},
..
}) => table.columns.mercurial,
_ => false,
}
}
/// Determines the complete set of options based on the given command-line
/// arguments, after theyve been parsed.
fn deduce<V: Vars>(matches: &MatchedFlags<'_>, vars: &V) -> Result<Self, OptionsError> {
@ -201,6 +219,16 @@ impl Options {
)));
}
if cfg!(not(feature = "mercurial"))
&& matches
.has_where_any(|f| f.matches(&flags::MERCURIAL) || f.matches(&flags::NO_MERCURIAL))
.is_some()
{
return Err(OptionsError::Unsupported(String::from(
"Options --mercurial and --no-mercurial can't be used because `mercurial` feature is not enabled in this build of exa"
)));
}
let view = View::deduce(matches, vars)?;
let dir_action = DirAction::deduce(matches, matches!(view.mode, Mode::Details(_)))?;
let filter = FileFilter::deduce(matches)?;

View file

@ -266,6 +266,8 @@ impl Columns {
&& !matches.has(&flags::NO_GIT)?
&& !no_git_env;
let mercurial = matches.has(&flags::MERCURIAL)? && !matches.has(&flags::NO_MERCURIAL)?;
let blocksize = matches.has(&flags::BLOCKSIZE)?;
let group = matches.has(&flags::GROUP)?;
let inode = matches.has(&flags::INODE)?;
@ -285,6 +287,7 @@ impl Columns {
blocksize,
group,
git,
mercurial,
subdir_git_repos,
subdir_git_repos_no_stat,
octal,

View file

@ -2,6 +2,7 @@ use ansiterm::{Colour, Style};
use log::trace;
use palette::{FromColor, Oklab, Srgb};
use crate::fs::feature::mercurial::MercurialCache;
use crate::{
fs::{dir_action::RecurseOptions, feature::git::GitCache, fields::Size, DotFilter, File},
output::{table::TimeType, tree::TreeDepth},
@ -42,6 +43,8 @@ impl ColorScaleInformation {
git: Option<&GitCache>,
git_ignoring: bool,
r: Option<RecurseOptions>,
mercurial: Option<&MercurialCache>,
mercurial_ignoring: bool,
) -> Option<Self> {
if color_scale.mode == ColorScaleMode::Fixed {
None
@ -63,6 +66,8 @@ impl ColorScaleInformation {
git_ignoring,
TreeDepth::root(),
r,
mercurial,
mercurial_ignoring,
);
Some(information)
@ -110,6 +115,8 @@ fn update_information_recursively(
git_ignoring: bool,
depth: TreeDepth,
r: Option<RecurseOptions>,
mercurial: Option<&MercurialCache>,
mercurial_ignoring: bool,
) {
for file in files {
if information.options.age {
@ -143,7 +150,15 @@ fn update_information_recursively(
match file.to_dir() {
Ok(dir) => {
let files: Vec<File<'_>> = dir
.files(dot_filter, git, git_ignoring, false, false)
.files(
dot_filter,
git,
git_ignoring,
false,
false,
mercurial,
mercurial_ignoring,
)
.flatten()
.collect();
@ -155,6 +170,8 @@ fn update_information_recursively(
git_ignoring,
depth.deeper(),
r,
mercurial,
mercurial_ignoring,
);
}
Err(e) => trace!("Unable to access directory {}: {}", file.name, e),

View file

@ -71,6 +71,7 @@ use log::*;
use crate::fs::dir_action::RecurseOptions;
use crate::fs::feature::git::GitCache;
use crate::fs::feature::mercurial::MercurialCache;
use crate::fs::feature::xattr::Attribute;
use crate::fs::fields::SecurityContextType;
use crate::fs::filter::FileFilter;
@ -139,6 +140,10 @@ pub struct Render<'a> {
pub git: Option<&'a GitCache>,
pub git_repos: bool,
pub mercurial: Option<&'a MercurialCache>,
pub mercurial_ignoring: bool,
}
#[rustfmt::skip]
@ -172,6 +177,8 @@ impl<'a> Render<'a> {
self.git,
self.git_ignoring,
self.recurse,
self.mercurial,
self.mercurial_ignoring,
);
if let Some(ref table) = self.opts.table {
@ -189,7 +196,7 @@ impl<'a> Render<'a> {
(None, _) => { /* Keep Git how it is */ }
}
let mut table = Table::new(table, self.git, self.theme, self.git_repos);
let mut table = Table::new(table, self.git, self.theme, self.git_repos, self.mercurial);
if self.opts.header {
let header = table.header_row();
@ -365,6 +372,8 @@ impl<'a> Render<'a> {
self.git_ignoring,
egg.file.deref_links,
egg.file.is_recursive_size(),
self.mercurial,
self.mercurial_ignoring,
) {
match file_to_add {
Ok(f) => {

View file

@ -6,6 +6,7 @@ use ansiterm::ANSIStrings;
use term_grid as grid;
use crate::fs::feature::git::GitCache;
use crate::fs::feature::mercurial::MercurialCache;
use crate::fs::filter::FileFilter;
use crate::fs::{Dir, File};
use crate::output::cell::{DisplayWidth, TextCell};
@ -90,6 +91,10 @@ pub struct Render<'a> {
pub console_width: usize,
pub git_repos: bool,
pub mercurial: Option<&'a MercurialCache>,
pub mercurial_ignoring: bool,
}
impl<'a> Render<'a> {
@ -102,16 +107,18 @@ impl<'a> Render<'a> {
fn details_for_column(&self) -> DetailsRender<'a> {
#[rustfmt::skip]
return DetailsRender {
dir: self.dir,
files: Vec::new(),
theme: self.theme,
file_style: self.file_style,
opts: self.details,
recurse: None,
filter: self.filter,
git_ignoring: self.git_ignoring,
git: self.git,
git_repos: self.git_repos,
dir: self.dir,
files: Vec::new(),
theme: self.theme,
file_style: self.file_style,
opts: self.details,
recurse: None,
filter: self.filter,
git_ignoring: self.git_ignoring,
git: self.git,
git_repos: self.git_repos,
mercurial: self.mercurial,
mercurial_ignoring: self.mercurial_ignoring,
};
}
@ -122,16 +129,18 @@ impl<'a> Render<'a> {
pub fn give_up(self) -> DetailsRender<'a> {
#[rustfmt::skip]
return DetailsRender {
dir: self.dir,
files: self.files,
theme: self.theme,
file_style: self.file_style,
opts: self.details,
recurse: None,
filter: self.filter,
git_ignoring: self.git_ignoring,
git: self.git,
git_repos: self.git_repos,
dir: self.dir,
files: self.files,
theme: self.theme,
file_style: self.file_style,
opts: self.details,
recurse: None,
filter: self.filter,
git_ignoring: self.git_ignoring,
git: self.git,
git_repos: self.git_repos,
mercurial: self.mercurial,
mercurial_ignoring: self.mercurial_ignoring,
};
}
@ -162,6 +171,8 @@ impl<'a> Render<'a> {
self.git,
self.git_ignoring,
None,
self.mercurial,
self.mercurial_ignoring,
);
let (first_table, _) = self.make_table(options, &drender);
@ -275,7 +286,13 @@ impl<'a> Render<'a> {
(None, _) => { /* Keep Git how it is */ }
}
let mut table = Table::new(options, self.git, self.theme, self.git_repos);
let mut table = Table::new(
options,
self.git,
self.theme,
self.git_repos,
self.mercurial,
);
let mut rows = Vec::new();
if self.details.header {

View file

@ -0,0 +1,34 @@
use crate::fs::fields as f;
use crate::fs::fields::MercurialStatus;
use crate::output::{DisplayWidth, TextCell};
use ansiterm::Style;
impl f::Mercurial {
pub fn render(self, colours: &dyn MercurialColours) -> TextCell {
let status = match self.status {
MercurialStatus::Modified => colours.modified().paint("M"),
MercurialStatus::Added => colours.added().paint("A"),
MercurialStatus::Removed => colours.removed().paint("R"),
MercurialStatus::Clean => colours.clean().paint("C"),
MercurialStatus::Missing => colours.missing().paint("!"),
MercurialStatus::NotTracked => colours.not_tracked().paint("?"),
MercurialStatus::Ignored => colours.ignored().paint("I"),
MercurialStatus::Directory => colours.ignored().paint("-"),
};
TextCell {
width: DisplayWidth::from(1),
contents: vec![status].into(),
}
}
}
pub trait MercurialColours {
fn modified(&self) -> Style;
fn added(&self) -> Style;
fn removed(&self) -> Style;
fn clean(&self) -> Style;
fn missing(&self) -> Style;
fn not_tracked(&self) -> Style;
fn ignored(&self) -> Style;
}

View file

@ -67,3 +67,5 @@ mod flags_windows;
target_os = "windows"
)))]
mod flags;
mod mercurial;
pub use self::mercurial::MercurialColours;

View file

@ -11,6 +11,7 @@ use once_cell::sync::Lazy;
use uzers::UsersCache;
use crate::fs::feature::git::GitCache;
use crate::fs::feature::mercurial::MercurialCache;
use crate::fs::{fields as f, File};
use crate::options::vars::EZA_WINDOWS_ATTRIBUTES;
use crate::options::Vars;
@ -48,6 +49,7 @@ pub struct Columns {
pub blocksize: bool,
pub group: bool,
pub git: bool,
pub mercurial: bool,
pub subdir_git_repos: bool,
pub subdir_git_repos_no_stat: bool,
pub octal: bool,
@ -61,7 +63,12 @@ pub struct Columns {
}
impl Columns {
pub fn collect(&self, actually_enable_git: bool, git_repos: bool) -> Vec<Column> {
pub fn collect(
&self,
actually_enable_git: bool,
git_repos: bool,
actually_enable_mercurial: bool,
) -> Vec<Column> {
let mut columns = Vec::with_capacity(4);
if self.inode {
@ -139,6 +146,10 @@ impl Columns {
columns.push(Column::SubdirGitRepo(false));
}
if self.mercurial && actually_enable_mercurial {
columns.push(Column::MercurialStatus);
}
columns
}
}
@ -161,6 +172,7 @@ pub enum Column {
Inode,
GitStatus,
SubdirGitRepo(bool),
MercurialStatus,
#[cfg(unix)]
Octal,
#[cfg(unix)]
@ -219,6 +231,7 @@ impl Column {
Self::Inode => "inode",
Self::GitStatus => "Git",
Self::SubdirGitRepo(_) => "Repo",
Self::MercurialStatus => "Mercurial",
#[cfg(unix)]
Self::Octal => "Octal",
#[cfg(unix)]
@ -417,6 +430,7 @@ pub struct Table<'a> {
group_format: GroupFormat,
flags_format: FlagsFormat,
git: Option<&'a GitCache>,
mercurial: Option<&'a MercurialCache>,
}
#[derive(Clone)]
@ -430,8 +444,11 @@ impl<'a> Table<'a> {
git: Option<&'a GitCache>,
theme: &'a Theme,
git_repos: bool,
mercurial: Option<&'a MercurialCache>,
) -> Table<'a> {
let columns = options.columns.collect(git.is_some(), git_repos);
let columns = options
.columns
.collect(git.is_some(), git_repos, mercurial.is_some());
let widths = TableWidths::zero(columns.len());
let env = &*ENVIRONMENT;
@ -450,6 +467,7 @@ impl<'a> Table<'a> {
#[cfg(unix)]
group_format: options.group_format,
flags_format: options.flags_format,
mercurial,
}
}
@ -554,6 +572,7 @@ impl<'a> Table<'a> {
Column::FileFlags => file.flags().render(self.theme.ui.flags, self.flags_format),
Column::GitStatus => self.git_status(file).render(self.theme),
Column::SubdirGitRepo(status) => self.subdir_git_repo(file, status).render(self.theme),
Column::MercurialStatus => self.mercurial_status(file).render(self.theme),
#[cfg(unix)]
Column::Octal => self.octal_permissions(file).render(self.theme.ui.octal),
@ -582,6 +601,14 @@ impl<'a> Table<'a> {
.unwrap_or_default()
}
fn mercurial_status(&self, file: &File<'_>) -> f::Mercurial {
debug!("Getting Mercurial status for file {:?}", file.path);
self.mercurial
.map(|m| m.get(&file.path))
.unwrap_or_default()
}
fn subdir_git_repo(&self, file: &File<'_>, status: bool) -> f::SubdirGitRepo {
debug!("Getting subdir repo status for path {:?}", file.path);

View file

@ -81,6 +81,17 @@ impl UiStyles {
git_dirty: Yellow.bold(),
},
#[rustfmt::skip]
mercurial: Mercurial {
modified: Blue.normal(),
added: Green.normal(),
removed: Red.normal(),
clean: Green.normal(),
missing: Red.normal(),
not_tracked: Red.normal(),
ignored: Style::default().dimmed(),
},
security_context: SecurityContext {
none: Style::default(),
#[rustfmt::skip]

View file

@ -275,6 +275,37 @@ impl render::GitColours for Theme {
fn conflicted(&self) -> Style { self.ui.git.conflicted }
}
#[rustfmt::skip]
impl render::MercurialColours for Theme {
fn modified(&self) -> Style {
self.ui.mercurial.modified
}
fn added(&self) -> Style {
self.ui.mercurial.added
}
fn removed(&self) -> Style {
self.ui.mercurial.removed
}
fn clean(&self) -> Style {
self.ui.mercurial.clean
}
fn missing(&self) -> Style {
self.ui.mercurial.missing
}
fn not_tracked(&self) -> Style {
self.ui.mercurial.not_tracked
}
fn ignored(&self) -> Style {
self.ui.mercurial.ignored
}
}
#[rustfmt::skip]
impl render::GitRepoColours for Theme {
fn branch_main(&self) -> Style { self.ui.git_repo.branch_main }

View file

@ -16,6 +16,7 @@ pub struct UiStyles {
pub git_repo: GitRepo,
pub security_context: SecurityContext,
pub file_type: FileType,
pub mercurial: Mercurial,
pub punctuation: Style, // xx
pub date: Style, // da
@ -117,6 +118,18 @@ pub struct Git {
pub conflicted: Style, // gc
}
#[rustfmt::skip]
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub struct Mercurial {
pub modified: Style, // mm
pub added: Style, // ma
pub removed: Style, // mr
pub clean: Style, // mc
pub missing: Style, // mx
pub not_tracked: Style, // mn
pub ignored: Style, // mi
}
#[rustfmt::skip]
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub struct GitRepo {
@ -243,6 +256,14 @@ impl UiStyles {
"gi" => self.git.ignored = pair.to_style(),
"gc" => self.git.conflicted = pair.to_style(),
"mm" => self.mercurial.modified = pair.to_style(),
"ma" => self.mercurial.added = pair.to_style(),
"mr" => self.mercurial.removed = pair.to_style(),
"mc" => self.mercurial.clean = pair.to_style(),
"mx" => self.mercurial.missing = pair.to_style(),
"mn" => self.mercurial.not_tracked = pair.to_style(),
"mi" => self.mercurial.ignored = pair.to_style(),
"Gm" => self.git_repo.branch_main = pair.to_style(),
"Go" => self.git_repo.branch_other = pair.to_style(),
"Gc" => self.git_repo.git_clean = pair.to_style(),