diff --git a/.github/workflows/flake.yml b/.github/workflows/flake.yml index a942e16ed..733ddd643 100644 --- a/.github/workflows/flake.yml +++ b/.github/workflows/flake.yml @@ -40,7 +40,7 @@ jobs: steps: - uses: actions/checkout@v4 - name: Install Nix - uses: DeterminateSystems/nix-installer-action@v12 + uses: DeterminateSystems/nix-installer-action@v13 - name: Setup Nix cache uses: DeterminateSystems/magic-nix-cache-action@main - name: Nix Flake Check @@ -57,7 +57,7 @@ jobs: steps: - uses: actions/checkout@v4 - name: Install Nix - uses: DeterminateSystems/nix-installer-action@v12 + uses: DeterminateSystems/nix-installer-action@v13 - name: Setup Nix cache uses: DeterminateSystems/magic-nix-cache-action@main - name: Nix Build diff --git a/.github/workflows/unit-tests-bsd.yml b/.github/workflows/unit-tests-bsd.yml index dc5544ba4..4c9a4a17a 100644 --- a/.github/workflows/unit-tests-bsd.yml +++ b/.github/workflows/unit-tests-bsd.yml @@ -21,29 +21,30 @@ concurrency: cancel-in-progress: true jobs: - unit-tests-freebsd: - runs-on: ubuntu-22.04 - timeout-minutes: 20 - steps: - - uses: actions/checkout@v4 - with: - submodules: recursive - - name: Compile - uses: vmactions/freebsd-vm@v1 - with: - release: '14.0' - usesh: true - prepare: | - pkg install -y rust git - cargo install cargo-hack - git config --global --add safe.directory /home/runner/work/eza/eza - run: | - set -e - export CARGO_TERM_COLOR="always" - export RUSTFLAGS="--deny warnings" - cargo fmt --check - cargo clippy -- -D warnings - cargo hack test + # BUG: These tests are broken + # unit-tests-freebsd: + # runs-on: ubuntu-22.04 + # timeout-minutes: 20 + # steps: + # - uses: actions/checkout@v4 + # with: + # submodules: recursive + # - name: Compile + # uses: vmactions/freebsd-vm@v1 + # with: + # release: '14.1' + # usesh: true + # prepare: | + # pkg install -y rust git + # cargo install cargo-hack + # git config --global --add safe.directory /home/runner/work/eza/eza + # run: | + # set -e + # export CARGO_TERM_COLOR="always" + # export RUSTFLAGS="--deny warnings" + # cargo fmt --check + # cargo clippy -- -D warnings + # cargo hack test unit-tests-netbsd: runs-on: ubuntu-22.04 diff --git a/.pre-commit-config-non-nix.yaml b/.pre-commit-config-non-nix.yaml new file mode 100644 index 000000000..97b00b074 --- /dev/null +++ b/.pre-commit-config-non-nix.yaml @@ -0,0 +1,15 @@ +repos: + - repo: local + hooks: + - id: rust-linting + name: Rust linting + description: Run rustfmt on included files + entry: cargo fmt -- + types: [file, rust] + language: system + - id: rust-clippy + name: Rust clippy + description: Run clippy on included files + entry: cargo clippy --workspace --all-targets --all-features -- + types: [file, rust] + language: system diff --git a/CHANGELOG.md b/CHANGELOG.md index 5e1ea0f8f..c7a780fc4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,73 @@ # Changelog +## [0.18.23] - 2024-07-25 + +### Bug Fixes + +- Disable broken freebsd tests + +### Documentation + +- Clear up confusion around ls + +### Build + +- Bump log from 0.4.21 to 0.4.22 +- Bump DeterminateSystems/nix-installer-action from 12 to 13 +- Bump plist from 1.6.1 to 1.7.0 + +## [0.18.22] - 2024-07-18 + +### Bug Fixes + +- Use NaiveDateTime::from_timestamp_opt instead of panicky From impl + +### Features + +- Add non-nix pre-commit rustfmt and clippy hooks + +### Miscellaneous Tasks + +- Release eza v0.18.22 + +### Ci + +- Bump FreeBSD version. + +## [0.18.21] - 2024-07-01 + +### Bug Fixes + +- Fix missing line breaks in _eza + +### Miscellaneous Tasks + +- Release eza v0.18.21 + +## [0.18.20] - 2024-06-27 + +### Features + +- Add --no-|show-symlinks flags for filtering output + +### Miscellaneous Tasks + +- Release eza v0.18.20 + +## [0.18.19] - 2024-06-20 + +### Bug Fixes + +- Ship release binaries + +### Miscellaneous Tasks + +- Release eza v0.18.19 + +### Build + +- Bump git2 from 0.18.3 to 0.19.0 + ## [0.18.18] - 2024-06-13 ### Features @@ -9,6 +77,10 @@ - Run on all features by default - Ask for shell and terminal in bug report template +### Miscellaneous Tasks + +- Release eza v0.18.18 + ### Build - Bump unicode-width from 0.1.12 to 0.1.13 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index ec3467b10..08906e65d 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -44,6 +44,11 @@ Some useful commands include: pre-commit-hooks.nix - `just itest`: runs integration tests +**For non-nix users,** +There are traditional `pre-commit` hooks, which you can install with your system package manager or +`brew|pip install pre-commit`, and run `pre-commit install -c .pre-commit-config-non-nix.yaml` in the root of the repository. +Then these hooks will run automatically when you commit. + The [just](https://github.com/casey/just) command runner can be used to run some helpful development commands, in a manner similar to `make`. Run `just --list` to get an overview of what’s available. diff --git a/Cargo.lock b/Cargo.lock index d6ac77118..e5afe781d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -117,9 +117,9 @@ dependencies = [ [[package]] name = "base64" -version = "0.21.5" +version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35636a1494ede3b646cc98f74f8e62c773a38a659ebc777a2cf26b9b74171df9" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] name = "bitflags" @@ -387,7 +387,7 @@ dependencies = [ [[package]] name = "eza" -version = "0.18.18" +version = "0.18.23" dependencies = [ "ansi-width", "chrono", @@ -454,9 +454,9 @@ dependencies = [ [[package]] name = "git2" -version = "0.18.3" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "232e6a7bfe35766bf715e55a88b39a700596c0ccfd88cd3680b4cdb40d66ef70" +checksum = "b903b73e45dc0c6c596f2d37eccece7c1c8bb6e4407b001096387c63d0d93724" dependencies = [ "bitflags 2.4.0", "libc", @@ -602,9 +602,9 @@ checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" [[package]] name = "libgit2-sys" -version = "0.16.2+1.7.2" +version = "0.17.0+1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee4126d8b4ee5c9d9ea891dd875cfdc1e9d0950437179104b183d7d8a74d24e8" +checksum = "10472326a8a6477c3c20a64547b0059e4b0d086869eee31e6d7da728a8eb7224" dependencies = [ "cc", "libc", @@ -625,12 +625,6 @@ dependencies = [ "vcpkg", ] -[[package]] -name = "line-wrap" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd1bc4d24ad230d21fb898d1116b1801d7adfc449d42026475862ab48b11e70e" - [[package]] name = "linux-raw-sys" version = "0.4.11" @@ -648,9 +642,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.21" +version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" +checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" [[package]] name = "matches" @@ -694,6 +688,12 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "num-conv" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" + [[package]] name = "num-traits" version = "0.2.14" @@ -848,13 +848,12 @@ checksum = "3831453b3449ceb48b6d9c7ad7c96d5ea673e9b470a1dc578c2ce6521230884c" [[package]] name = "plist" -version = "1.6.1" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9d34169e64b3c7a80c8621a48adaf44e0cf62c78a9b25dd9dd35f1881a17cf9" +checksum = "42cf17e9a1800f5f396bc67d193dc9411b59012a5876445ef450d449881e1016" dependencies = [ "base64", "indexmap", - "line-wrap", "quick-xml", "time", ] @@ -913,9 +912,9 @@ dependencies = [ [[package]] name = "quick-xml" -version = "0.31.0" +version = "0.32.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1004a344b30a54e2ee58d66a71b32d2db2feb0a31f9a2d302bf0536f15de2a33" +checksum = "1d3a6e5838b60e0e8fa7a43f22ade549a37d61f8bdbe636d0d7816191de969c2" dependencies = [ "memchr", ] @@ -1187,12 +1186,13 @@ dependencies = [ [[package]] name = "time" -version = "0.3.30" +version = "0.3.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4a34ab300f2dee6e562c10a046fc05e358b29f9bf92277f30c3c8d82275f6f5" +checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" dependencies = [ "deranged", "itoa", + "num-conv", "powerfmt", "serde", "time-core", @@ -1207,10 +1207,11 @@ checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" [[package]] name = "time-macros" -version = "0.2.15" +version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ad70d68dba9e1f8aceda7aa6711965dfec1cac869f311a51bd08b3a2ccbce20" +checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf" dependencies = [ + "num-conv", "time-core", ] diff --git a/Cargo.toml b/Cargo.toml index 052ad459c..f801efb57 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,7 +16,7 @@ readme = "README.md" homepage = "https://github.com/eza-community/eza" license = "MIT" repository = "https://github.com/eza-community/eza" -version = "0.18.18" +version = "0.18.23" [package.metadata.deb] @@ -84,7 +84,7 @@ palette = { version = "0.7.6", default-features = false, features = ["std"] } once_cell = "1.19.0" percent-encoding = "2.3.1" phf = { version = "0.11.2", features = ["macros"] } -plist = { version = "1.6.1", default-features = false } +plist = { version = "1.7.0", default-features = false } uutils_term_grid = "0.6.0" terminal_size = "0.3.0" timeago = { version = "0.4.2", default-features = false } @@ -94,7 +94,7 @@ rayon = "1.10.0" ansi-width = "0.1.0" [dependencies.git2] -version = "0.18" +version = "0.19" optional = true default-features = false diff --git a/README.md b/README.md index 976450005..be9a5cd1a 100644 --- a/README.md +++ b/README.md @@ -2,8 +2,7 @@ # eza -A modern, maintained replacement for ls. - +A modern replacement for ls. Gitter @@ -110,6 +109,8 @@ eza’s options are almost, but not quite, entirely unlike `ls`’s. Quick overv - **--group-directories-first**: list directories before other files - **-D**, **--only-dirs**: list only directories - **-f**, **--only-files**: list only files +- **--no-symlinks**: don't show symbolic links +- **--show-symlinks**: explicitly show links (with `--only-dirs`, `--only-files`, to show symlinks that match the filter) - **--git-ignore**: ignore files mentioned in `.gitignore` - **-I**, **--ignore-glob=(globs)**: glob patterns (pipe-separated) of files to ignore diff --git a/completions/fish/eza.fish b/completions/fish/eza.fish index 97a579c6f..91334b583 100644 --- a/completions/fish/eza.fish +++ b/completions/fish/eza.fish @@ -81,6 +81,8 @@ complete -c eza -s s -l sort -d "Which field to sort by" -x -a " complete -c eza -s I -l ignore-glob -d "Ignore files that match these glob patterns" -r complete -c eza -s D -l only-dirs -d "List only directories" complete -c eza -s f -l only-files -d "List only files" +complete -c eza -l show-symlinks -d "Explicitly show symbolic links (For use with --only-dirs | --only-files)" +complete -c eza -l no-symlinks -d "Do not show symbolic links" # Long view options complete -c eza -s b -l binary -d "List file sizes with binary prefixes" diff --git a/completions/nush/eza.nu b/completions/nush/eza.nu index 1859c0a1e..f3d42d33d 100644 --- a/completions/nush/eza.nu +++ b/completions/nush/eza.nu @@ -30,6 +30,8 @@ export extern "eza" [ --sort(-s) # Which field to sort by --only-dirs(-D) # List only directories --only-files(-f) # List only files + --show-symlinks # Explicitly show symbolic links (for use with --only-dirs | --only-files) + --no-symlinks # Do not show symbolic links --binary(-b) # List file sizes with binary prefixes --bytes(-B) # List file sizes in bytes, without any prefixes --group(-g) # List each file's group diff --git a/completions/zsh/_eza b/completions/zsh/_eza index ed1f0c299..67cf0284d 100644 --- a/completions/zsh/_eza +++ b/completions/zsh/_eza @@ -33,6 +33,8 @@ __eza() { {-A,--almost-all}"[Equivalent to --all; included for compatibility with \'ls -A\']" \ {-d,--list-dirs}"[List directories like regular files]" \ {-D,--only-dirs}"[List only directories]" \ + --no-symlinks"[Do not show symbolic links]" \ + --show-symlinks"[Explictly show symbolic links: for use with '--only-dirs'| '--only-files']" \ {-f,--only-files}"[List only files]" \ {-L,--level}"+[Limit the depth of recursion]" \ {-w,--width}"+[Limits column output of grid, 0 implies auto-width]" \ diff --git a/man/eza.1.md b/man/eza.1.md index a6d43fd86..56873d9cf 100644 --- a/man/eza.1.md +++ b/man/eza.1.md @@ -166,6 +166,11 @@ Sort fields starting with a capital letter will sort uppercase before lowercase: `-f`, `--only-files` : List only files, not directories. +`--show-symlinks` +: Explicitly show symbolic links (when used with `--only-files` | `--only-dirs`) + +`--no-symlinks` +: Do not show symbolic links LONG VIEW OPTIONS ================= diff --git a/src/fs/file.rs b/src/fs/file.rs index ba2703d91..b89d10cef 100644 --- a/src/fs/file.rs +++ b/src/fs/file.rs @@ -13,6 +13,7 @@ use std::str; #[cfg(unix)] use std::sync::Mutex; use std::sync::OnceLock; +use std::time::SystemTime; use chrono::prelude::*; @@ -716,6 +717,20 @@ impl<'dir> File<'dir> { } } + /// Converts a `SystemTime` to a `NaiveDateTime` without panicking. + /// + /// Fixes #655 and #667 in `Self::modified_time`, `Self::accessed_time` and + /// `Self::created_time`. + fn systemtime_to_naivedatetime(st: SystemTime) -> Option { + let duration = st.duration_since(SystemTime::UNIX_EPOCH).ok()?; + + // FIXME: NaiveDateTime::from_timestamp_opt is deprecated since chrono 0.4.35 + NaiveDateTime::from_timestamp_opt( + duration.as_secs().try_into().ok()?, + (duration.as_nanos() % 1_000_000_000).try_into().ok()?, + ) + } + /// This file’s last modified timestamp, if available on this platform. pub fn modified_time(&self) -> Option { if self.is_link() && self.deref_links { @@ -726,8 +741,8 @@ impl<'dir> File<'dir> { } self.metadata .modified() - .map(|st| DateTime::::from(st).naive_utc()) .ok() + .and_then(Self::systemtime_to_naivedatetime) } /// This file’s last changed timestamp, if available on this platform. @@ -757,8 +772,8 @@ impl<'dir> File<'dir> { } self.metadata .accessed() - .map(|st| DateTime::::from(st).naive_utc()) .ok() + .and_then(Self::systemtime_to_naivedatetime) } /// This file’s created timestamp, if available on this platform. @@ -769,10 +784,10 @@ impl<'dir> File<'dir> { _ => None, }; } - match self.metadata.created() { - Ok(btime) => Some(DateTime::::from(btime).naive_utc()), - Err(_) => None, - } + self.metadata + .created() + .ok() + .and_then(Self::systemtime_to_naivedatetime) } /// This file’s ‘type’. diff --git a/src/fs/filter.rs b/src/fs/filter.rs index 162cf0ac0..2fe8a4e19 100644 --- a/src/fs/filter.rs +++ b/src/fs/filter.rs @@ -21,6 +21,12 @@ pub enum FileFilterFlags { /// Whether to only show files. OnlyFiles, + + /// Whether to ignore symlinks + NoSymlinks, + + /// Whether to explicitly show symlinks + ShowSymlinks, } /// The **file filter** processes a list of files before displaying them to @@ -68,28 +74,47 @@ pub struct FileFilter { /// Whether to ignore Git-ignored patterns. pub git_ignore: GitIgnore, + + /// Whether to ignore symlinks + pub no_symlinks: bool, + + /// Whether to explicitly show symlinks + pub show_symlinks: bool, } impl FileFilter { /// Remove every file in the given vector that does *not* pass the /// filter predicate for files found inside a directory. pub fn filter_child_files(&self, files: &mut Vec>) { - use FileFilterFlags::{OnlyDirs, OnlyFiles}; + use FileFilterFlags::{NoSymlinks, OnlyDirs, OnlyFiles, ShowSymlinks}; files.retain(|f| !self.ignore_patterns.is_ignored(&f.name)); match ( self.flags.contains(&OnlyDirs), self.flags.contains(&OnlyFiles), + self.flags.contains(&NoSymlinks), + self.flags.contains(&ShowSymlinks), ) { - (true, false) => { - // On pass -'-only-dirs' flag only + (true, false, false, false) => { + // On pass '--only-dirs' flag only + files.retain(File::is_directory); + } + (true, false, true, false) => { files.retain(File::is_directory); } - (false, true) => { - // On pass -'-only-files' flag only + (true, false, false, true) => { + files.retain(|f| f.is_directory() || f.points_to_directory()); + } + (false, true, false, false) => { files.retain(File::is_file); } + (false, true, false, true) => { + files.retain(|f| f.is_file() || f.is_link() && !f.points_to_directory()); + } + (false, false, true, false) => { + files.retain(|f| !f.is_link()); + } _ => {} } } @@ -99,7 +124,7 @@ impl FileFilter { /// /// The rules are different for these types of files than the other /// type because the ignore rules can be used with globbing. For - /// example, running `exa -I='*.tmp' .vimrc` shouldn’t filter out the + /// example, running `exa -I='*. tmp' .vimrc` shouldn’t filter out the /// dotfile, because it’s been directly specified. But running /// `exa -I='*.ogg' music/*` should filter out the ogg files obtained /// from the glob, even though the globbing is done by the shell! @@ -249,7 +274,6 @@ impl SortField { Self::ChangedDate => a.changed_time().cmp(&b.changed_time()), Self::CreatedDate => a.created_time().cmp(&b.created_time()), Self::ModifiedAge => b.modified_time().cmp(&a.modified_time()), // flip b and a - Self::FileType => match a.type_char().cmp(&b.type_char()) { // todo: this recomputes Ordering::Equal => natord::compare(&a.name, &b.name), order => order, diff --git a/src/options/filter.rs b/src/options/filter.rs index 745fda689..fce5a9266 100644 --- a/src/options/filter.rs +++ b/src/options/filter.rs @@ -18,6 +18,8 @@ impl FileFilter { (matches.has(&flags::REVERSE)?, FFF::Reverse), (matches.has(&flags::ONLY_DIRS)?, FFF::OnlyDirs), (matches.has(&flags::ONLY_FILES)?, FFF::OnlyFiles), + (matches.has(&flags::NO_SYMLINKS)?, FFF::NoSymlinks), + (matches.has(&flags::SHOW_SYMLINKS)?, FFF::ShowSymlinks), ] { if *has { filter_flags.push(flag.clone()); @@ -27,6 +29,8 @@ impl FileFilter { #[rustfmt::skip] return Ok(Self { list_dirs_first: matches.has(&flags::DIRS_FIRST)?, + no_symlinks: filter_flags.contains(&FFF::NoSymlinks), + show_symlinks: filter_flags.contains(&FFF::ShowSymlinks), flags: filter_flags, sort_field: SortField::deduce(matches)?, dot_filter: DotFilter::deduce(matches)?, diff --git a/src/options/flags.rs b/src/options/flags.rs index 3d6d8359b..8cd538cf3 100644 --- a/src/options/flags.rs +++ b/src/options/flags.rs @@ -41,6 +41,8 @@ pub static GIT_IGNORE: Arg = Arg { short: None, long: "git-ignore", t pub static DIRS_FIRST: Arg = Arg { short: None, long: "group-directories-first", takes_value: TakesValue::Forbidden }; pub static ONLY_DIRS: Arg = Arg { short: Some(b'D'), long: "only-dirs", takes_value: TakesValue::Forbidden }; pub static ONLY_FILES: Arg = Arg { short: Some(b'f'), long: "only-files", takes_value: TakesValue::Forbidden }; +pub static NO_SYMLINKS: Arg = Arg { short: None, long: "no-symlinks", takes_value: TakesValue::Forbidden }; +pub static SHOW_SYMLINKS: Arg = Arg { short: None, long: "show-symlinks", takes_value: TakesValue::Forbidden }; const SORTS: Values = &[ "name", "Name", "size", "extension", "Extension", "modified", "changed", "accessed", "created", "inode", "type", "none" ]; @@ -97,7 +99,7 @@ pub static ALL_ARGS: Args = Args(&[ &BINARY, &BYTES, &GROUP, &NUMERIC, &HEADER, &ICONS, &INODE, &LINKS, &MODIFIED, &CHANGED, &BLOCKSIZE, &TOTAL_SIZE, &TIME, &ACCESSED, &CREATED, &TIME_STYLE, &HYPERLINK, &MOUNTS, - &NO_PERMISSIONS, &NO_FILESIZE, &NO_USER, &NO_TIME, &SMART_GROUP, + &NO_PERMISSIONS, &NO_FILESIZE, &NO_USER, &NO_TIME, &SMART_GROUP, &NO_SYMLINKS, &SHOW_SYMLINKS, &GIT, &NO_GIT, &GIT_REPOS, &GIT_REPOS_NO_STAT, &EXTENDED, &OCTAL, &SECURITY_CONTEXT, &STDIN, &FILE_FLAGS