Browse Source

Merge branch 'master' into chesterliu/dev/win-support

Chester Liu 4 năm trước cách đây
mục cha
commit
99d653b7fa

+ 54 - 0
.github/workflows/unit-tests.yml

@@ -0,0 +1,54 @@
+name: Unit tests
+
+on:
+  push:
+    branches: [ master ]
+    paths:
+      - '.github/workflows/*'
+      - 'src/**'
+      - 'Cargo.*'
+      - build.rs
+  pull_request:
+    branches: [ master ]
+    paths:
+      - '.github/workflows/*'
+      - 'src/**'
+      - 'Cargo.*'
+      - build.rs
+
+env:
+  CARGO_TERM_COLOR: always
+
+jobs:
+  unit-tests:
+    runs-on: ${{ matrix.os }}
+
+    continue-on-error: ${{ matrix.rust == 'nightly' }}
+
+    strategy:
+      matrix:
+        os: [ubuntu-latest, macos-latest]
+        rust: [1.48.0, stable, beta, nightly]
+
+    steps:
+      - name: Checkout repository
+        uses: actions/checkout@v2
+
+      - name: Install Rust toolchain
+        uses: actions-rs/toolchain@v1
+        with:
+          profile: minimal
+          toolchain: ${{ matrix.rust }}
+          override: true
+
+      - name: Install cargo-hack
+        uses: actions-rs/cargo@v1
+        with:
+          command: install
+          args: cargo-hack
+
+      - name: Run unit tests
+        uses: actions-rs/cargo@v1
+        with:
+          command: hack
+          args: test --feature-powerset

+ 0 - 19
.travis.yml

@@ -1,19 +0,0 @@
-language: rust
-rust:
-  - 1.45.2
-  - stable
-  - beta
-  - nightly
-
-jobs:
-  fast_finish: true
-  allow_failures:
-    - rust: nightly
-
-  include:
-    - name: 'Rust: test with all features'
-      rust: stable
-      install:
-        - cargo install cargo-hack
-      script:
-        - cargo hack test --feature-powerset

+ 6 - 4
Cargo.lock

@@ -1,5 +1,7 @@
 # This file is automatically @generated by Cargo.
 # It is not intended for manual editing.
+version = 3
+
 [[package]]
 name = "ansi_term"
 version = "0.12.1"
@@ -90,9 +92,9 @@ dependencies = [
 
 [[package]]
 name = "git2"
-version = "0.13.18"
+version = "0.13.20"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b483c6c2145421099df1b4efd50e0f6205479a072199460eff852fa15e5603c7"
+checksum = "d9831e983241f8c5591ed53f17d874833e2fa82cac2625f3888c50cbfe136cba"
 dependencies = [
  "bitflags",
  "libc",
@@ -151,9 +153,9 @@ checksum = "9385f66bf6105b241aa65a61cb923ef20efc665cb9f9bb50ac2f0c4b7f378d41"
 
 [[package]]
 name = "libgit2-sys"
-version = "0.12.19+1.1.0"
+version = "0.12.21+1.1.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f322155d574c8b9ebe991a04f6908bb49e68a79463338d24a43d6274cb6443e6"
+checksum = "86271bacd72b2b9e854c3dcfb82efd538f15f870e4c11af66900effb462f6825"
 dependencies = [
  "cc",
  "libc",

+ 7 - 4
Cargo.toml

@@ -1,11 +1,11 @@
 [package]
 name = "exa"
 description = "A modern replacement for ls"
-
 authors = ["Benjamin Sago <ogham@bsago.me>"]
 categories = ["command-line-utilities"]
 edition = "2018"
 exclude = ["/devtools/*", "/Justfile", "/Vagrantfile", "/screenshots.png"]
+readme = "README.md"
 homepage = "https://the.exa.website/"
 license = "MIT"
 repository = "https://github.com/ogham/exa"
@@ -65,7 +65,7 @@ lto = true
 
 
 [package.metadata.deb]
-license-file = [ "LICENCE" ]
+license-file = [ "LICENCE", "4" ]
 depends = "$auto"
 extended-description = """
 exa is a replacement for ls written in Rust.
@@ -74,6 +74,9 @@ section = "utils"
 priority = "optional"
 assets = [
     [ "target/release/exa", "/usr/bin/exa", "0755" ],
-    [ "contrib/man/exa.1", "/usr/share/man/man1/exa.1", "0644" ],
-    [ "contrib/completions.bash", "/etc/bash_completion.d/exa", "0644" ],
+    [ "target/release/../man/exa.1", "/usr/share/man/man1/exa.1", "0644" ],
+    [ "target/release/../man/exa_colors.5", "/usr/share/man/man5/exa_colors.5", "0644" ],
+    [ "completions/bash/exa", "/usr/share/bash-completion/completions/exa", "0644" ],
+    [ "completions/zsh/_exa", "/usr/share/zsh/site-functions/_exa", "0644" ],
+    [ "completions/fish/exa.fish", "/usr/share/fish/vendor_completions.d/exa.fish", "0644" ],
 ]

+ 4 - 8
README.md

@@ -1,17 +1,13 @@
 <div align="center">
-<h1>exa</h1>
+
+# exa
 
 [exa](https://the.exa.website/) is a modern replacement for _ls_.
 
 **README Sections:** [Options](#options) — [Installation](#installation) — [Development](#development)
 
-<a href="https://travis-ci.org/github/ogham/exa">
-    <img src="https://travis-ci.org/ogham/exa.svg?branch=master" alt="Build status" />
-</a>
-
-<a href="https://saythanks.io/to/ogham%40bsago.me">
-    <img src="https://img.shields.io/badge/Say%20Thanks-!-1EAEDB.svg" alt="Say thanks!" />
-</a>
+[![Unit tests](https://github.com/ogham/exa/actions/workflows/unit-tests.yml/badge.svg)](https://github.com/ogham/exa/actions/workflows/unit-tests.yml)
+[![Say thanks!](https://img.shields.io/badge/Say%20Thanks-!-1EAEDB.svg)](https://saythanks.io/to/ogham%40bsago.me)
 </div>
 
 ![Screenshots of exa](screenshots.png)

+ 2 - 1
build.rs

@@ -38,9 +38,10 @@ fn main() -> io::Result<()> {
 
     // We need to create these files in the Cargo output directory.
     let out = PathBuf::from(env::var("OUT_DIR").unwrap());
+    let path = &out.join("version_string.txt");
 
     // Bland version text
-    let mut f = File::create(&out.join("version_string.txt"))?;
+    let mut f = File::create(path).expect(&path.to_string_lossy());
     writeln!(f, "{}", strip_codes(&ver))?;
 
     Ok(())

+ 17 - 3
completions/completions.bash → completions/bash/exa

@@ -8,6 +8,11 @@ _exa()
             return
             ;;
 
+        --colour)
+            COMPREPLY=( $( compgen -W 'always auto never' -- "$cur" ) )
+            return
+            ;;
+
         -L|--level)
             COMPREPLY=( $( compgen -W '{0..9}' -- "$cur" ) )
             return
@@ -19,19 +24,28 @@ _exa()
             ;;
 
         -t|--time)
-            COMPREPLY=( $( compgen -W 'modified changed accessed created --' -- $cur ) )
+            COMPREPLY=( $( compgen -W 'modified changed accessed created --' -- "$cur" ) )
             return
             ;;
 
         --time-style)
-            COMPREPLY=( $( compgen -W 'default iso long-iso full-iso --' -- $cur ) )
+            COMPREPLY=( $( compgen -W 'default iso long-iso full-iso --' -- "$cur" ) )
             return
             ;;
     esac
 
     case "$cur" in
+        # _parse_help doesn’t pick up short options when they are on the same line than long options
+        --*)
+            # colo[u]r isn’t parsed correctly so we filter these options out and add them by hand
+            parse_help=$( exa --help | grep -oE ' (\-\-[[:alnum:]@-]+)' | tr -d ' ' | grep -v '\-\-colo' )
+            completions=$( echo '--color --colour --color-scale --colour-scale' $parse_help )
+            COMPREPLY=( $( compgen -W "$completions" -- "$cur" ) )
+            ;;
+
         -*)
-            COMPREPLY=( $( compgen -W '$( _parse_help "$1" )' -- "$cur" ) )
+            completions=$( exa --help | grep -oE ' (\-[[:alnum:]@])' | tr -d ' ' )
+            COMPREPLY=( $( compgen -W "$completions" -- "$cur" ) )
             ;;
 
         *)

+ 13 - 9
completions/completions.fish → completions/fish/exa.fish

@@ -10,10 +10,14 @@ complete -c exa -s 'x' -l 'across'       -d "Sort the grid across, rather than d
 complete -c exa -s 'R' -l 'recurse'      -d "Recurse into directories"
 complete -c exa -s 'T' -l 'tree'         -d "Recurse into directories as a tree"
 complete -c exa -s 'F' -l 'classify'     -d "Display type indicator by file names"
-complete -c exa        -l 'color'        -d "When to use terminal colours"
-complete -c exa        -l 'colour'       -d "When to use terminal colours"
-complete -c exa        -l 'color-scale'  -d "Highlight levels of file sizes distinctly"
-complete -c exa        -l 'colour-scale' -d "Highlight levels of file sizes distinctly"
+complete -c exa        -l 'color' \
+                       -l 'colour'       -d "When to use terminal colours" -x -a "
+    always\t'Always use colour'
+    auto\t'Use colour if standard output is a terminal'
+    never\t'Never use colour'
+"
+complete -c exa        -l 'color-scale' \
+                       -l 'colour-scale' -d "Highlight levels of file sizes distinctly"
 complete -c exa        -l 'icons'        -d "Display icons"
 complete -c exa        -l 'no-icons'     -d "Don't display icons"
 
@@ -22,9 +26,9 @@ complete -c exa -l 'group-directories-first' -d "Sort directories before other f
 complete -c exa -l 'git-ignore'           -d "Ignore files mentioned in '.gitignore'"
 complete -c exa -s 'a' -l 'all'       -d "Show hidden and 'dot' files"
 complete -c exa -s 'd' -l 'list-dirs' -d "List directories like regular files"
-complete -c exa -s 'L' -l 'level'     -d "Limit the depth of recursion" -a "1 2 3 4 5 6 7 8 9"
+complete -c exa -s 'L' -l 'level'     -d "Limit the depth of recursion" -x -a "1 2 3 4 5 6 7 8 9"
 complete -c exa -s 'r' -l 'reverse'   -d "Reverse the sort order"
-complete -c exa -s 's' -l 'sort'   -x -d "Which field to sort by" -a "
+complete -c exa -s 's' -l 'sort'      -d "Which field to sort by" -x -a "
     accessed\t'Sort by file accessed time'
     age\t'Sort by file modified time (newest first)'
     changed\t'Sort by changed time'
@@ -56,10 +60,10 @@ complete -c exa -s 'b' -l 'binary'   -d "List file sizes with binary prefixes"
 complete -c exa -s 'B' -l 'bytes'    -d "List file sizes in bytes, without any prefixes"
 complete -c exa -s 'g' -l 'group'    -d "List each file's group"
 complete -c exa -s 'h' -l 'header'   -d "Add a header row to each column"
-complete -c exa -s 'h' -l 'links'    -d "List each file's number of hard links"
+complete -c exa -s 'H' -l 'links'    -d "List each file's number of hard links"
 complete -c exa -s 'g' -l 'group'    -d "List each file's inode number"
 complete -c exa -s 'S' -l 'blocks'   -d "List each file's number of filesystem blocks"
-complete -c exa -s 't' -l 'time'  -x -d "Which timestamp field to list" -a "
+complete -c exa -s 't' -l 'time'     -d "Which timestamp field to list" -x -a "
     modified\t'Display modified time'
     changed\t'Display changed time'
     accessed\t'Display accessed time'
@@ -70,7 +74,7 @@ complete -c exa -s 'n' -l 'numeric'       -d "List numeric user and group IDs."
 complete -c exa        -l 'changed'       -d "Use the changed timestamp field"
 complete -c exa -s 'u' -l 'accessed'      -d "Use the accessed timestamp field"
 complete -c exa -s 'U' -l 'created'       -d "Use the created timestamp field"
-complete -c exa        -l 'time-style' -x -d "How to format timestamps" -a "
+complete -c exa        -l 'time-style'    -d "How to format timestamps" -x -a "
     default\t'Use the default time style'
     iso\t'Display brief ISO timestamps'
     long-iso\t'Display longer ISO timestaps, up to the minute'

+ 2 - 2
completions/completions.zsh → completions/zsh/_exa

@@ -1,7 +1,7 @@
 #compdef exa
 
 # Save this file as _exa in /usr/local/share/zsh/site-functions or in any
-# other folder in $fpath.  E. g. save it in a folder called ~/.zfunc and add a
+# other folder in $fpath.  E.g. save it in a folder called ~/.zfunc and add a
 # line containing `fpath=(~/.zfunc $fpath)` somewhere before `compinit` in your
 # ~/.zshrc.
 
@@ -19,7 +19,7 @@ __exa() {
         {-R,--recurse}"[Recurse into directories]" \
         {-T,--tree}"[Recurse into directories as a tree]" \
         {-F,--classify}"[Display type indicator by file names]" \
-        --colo{,u}r"[When to use terminal colours]" \
+        --colo{,u}r="[When to use terminal colours]:(when):(always auto never)" \
         --colo{,u}r-scale"[Highlight levels of file sizes distinctly]" \
         --icons"[Display icons]" \
         --no-icons"[Hide icons]" \

+ 12 - 11
devtools/dev-bash.sh

@@ -11,17 +11,17 @@ bash /vagrant/devtools/dev-versions.sh
 # Configure the Cool Prompt™ (not actually trademarked).
 # The Cool Prompt tells you whether you’re in debug or strict mode, whether
 # you have colours configured, and whether your last command failed.
-function nonzero_return() { RETVAL=$?; [ $RETVAL -ne 0 ] && echo "$RETVAL "; }
-function debug_mode()  { [ "$EXA_DEBUG" == "trace" ] && echo -n "trace-"; [ -n "$EXA_DEBUG" ]  && echo "debug "; }
-function strict_mode() { [ -n "$EXA_STRICT" ] && echo "strict "; }
-function lsc_mode()    { [ -n "$LS_COLORS" ]  && echo "lsc "; }
-function exac_mode()   { [ -n "$EXA_COLORS" ] && echo "exac "; }
+nonzero_return() { RETVAL=$?; [ "$RETVAL" -ne 0 ] && echo "$RETVAL "; }
+debug_mode()  { [ "$EXA_DEBUG" == "trace" ] && echo -n "trace-"; [ -n "$EXA_DEBUG" ] && echo "debug "; }
+strict_mode() { [ -n "$EXA_STRICT" ] && echo "strict "; }
+lsc_mode()    { [ -n "$LS_COLORS" ]  && echo "lsc "; }
+exac_mode()   { [ -n "$EXA_COLORS" ] && echo "exac "; }
 export PS1="\[\e[1;36m\]\h \[\e[32m\]\w \[\e[31m\]\`nonzero_return\`\[\e[35m\]\`debug_mode\`\[\e[32m\]\`lsc_mode\`\[\e[1;32m\]\`exac_mode\`\[\e[33m\]\`strict_mode\`\[\e[36m\]\\$\[\e[0m\] "
 
 
 # The ‘debug’ function lets you switch debug mode on and off.
 # Turn it on if you need to see exa’s debugging logs.
-function debug () {
+debug() {
   case "$1" in
     ""|"on")  export EXA_DEBUG=1 ;;
     "off")    export EXA_DEBUG= ;;
@@ -33,11 +33,12 @@ function debug () {
 
 # The ‘strict’ function lets you switch strict mode on and off.
 # Turn it on if you’d like exa’s command-line arguments checked.
-function strict () {
-  case "$1" in "on") export EXA_STRICT=1 ;;
+strict() {
+  case "$1" in
+    "on")  export EXA_STRICT=1 ;;
     "off") export EXA_STRICT= ;;
-    "") [ -n "$EXA_STRICT" ] && echo "strict on" || echo "strict off" ;;
-    *) echo "Usage: strict on|off"; return 1 ;;
+    "")    [ -n "$EXA_STRICT" ] && echo "strict on" || echo "strict off" ;;
+    *)     echo "Usage: strict on|off"; return 1 ;;
   esac;
 }
 
@@ -45,7 +46,7 @@ function strict () {
 # environment variables. There’s also a ‘hacker’ theme which turns everything
 # green, which is usually used for checking that all colour codes work, and
 # for looking cool while you phreak some mainframes or whatever.
-function colors () {
+colors() {
   case "$1" in
     "ls")
       export LS_COLORS="di=34:ln=35:so=32:pi=33:ex=31:bd=34;46:cd=34;43:su=30;41:sg=30;46:tw=30;42:ow=30;43"

+ 17 - 3
devtools/dev-create-test-filesystem.sh

@@ -252,7 +252,7 @@ sudo chown $FIXED_USER:$FIXED_USER -R "$TEST_ROOT/attributes"
 
 # A sample Git repository
 # This uses cd because it's easier than telling Git where to go each time
-echo -e "\033[1m[10/13]\033[0m Creating Git testcases (1/3)"
+echo -e "\033[1m[10/13]\033[0m Creating Git testcases (1/4)"
 mkdir "$TEST_ROOT/git"
 cd    "$TEST_ROOT/git"
 git init >/dev/null
@@ -281,7 +281,7 @@ sudo chown $FIXED_USER:$FIXED_USER -R "$TEST_ROOT/git"
 
 # A second Git repository
 # for testing two at once
-echo -e "\033[1m[11/13]\033[0m Creating Git testcases (2/3)"
+echo -e "\033[1m[11/13]\033[0m Creating Git testcases (2/4)"
 mkdir -p "$TEST_ROOT/git2/deeply/nested/directory"
 cd       "$TEST_ROOT/git2"
 git init >/dev/null
@@ -321,7 +321,7 @@ sudo chown $FIXED_USER:$FIXED_USER -R "$TEST_ROOT/git2"
 
 # A third Git repository
 # Regression test for https://github.com/ogham/exa/issues/526
-echo -e "\033[1m[12/13]\033[0m Creating Git testcases (3/3)"
+echo -e "\033[1m[12/13]\033[0m Creating Git testcases (3/4)"
 mkdir -p "$TEST_ROOT/git3"
 cd       "$TEST_ROOT/git3"
 git init >/dev/null
@@ -334,6 +334,20 @@ find "$TEST_ROOT/git3" -exec touch {} -h -t $FIXED_DATE \;
 sudo chown $FIXED_USER:$FIXED_USER -R "$TEST_ROOT/git3"
 
 
+# A fourth Git repository
+# Regression test for https://github.com/ogham/exa/issues/698
+echo -e "\033[1m[12/13]\033[0m Creating Git testcases (4/4)"
+mkdir -p "$TEST_ROOT/git4"
+cd       "$TEST_ROOT/git4"
+git init >/dev/null
+
+# Create a non UTF-8 file
+touch 'P'$'\b\211''UUU'
+
+find "$TEST_ROOT/git4" -exec touch {} -h -t $FIXED_DATE \;
+sudo chown $FIXED_USER:$FIXED_USER -R "$TEST_ROOT/git4"
+
+
 # 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.

+ 7 - 6
devtools/dev-package-for-linux.sh

@@ -9,7 +9,7 @@ set -e
 
 
 # Linux check!
-uname=`uname -s`
+uname=$(uname -s)
 if [[ "$uname" != "Linux" ]]; then
   echo "Gotta be on Linux to run this (detected '$uname')!"
   exit 1
@@ -29,8 +29,8 @@ fi
 
 # Weekly builds have a bit more information in their version number (see build.rs).
 if [[ "$1" == "--weekly" ]]; then
-  git_hash=`GIT_DIR=/vagrant/.git git rev-parse --short --verify HEAD`
-  date=`date +"%Y-%m-%d"`
+  git_hash=$(GIT_DIR=/vagrant/.git git rev-parse --short --verify HEAD)
+  date=$(date +"%Y-%m-%d")
   echo "Building exa weekly v$exa_version, date $date, Git hash $git_hash"
 else
   echo "Building exa v$exa_version"
@@ -57,9 +57,10 @@ strip -v "$exa_linux_binary"
 # the binaries can have consistent names, and it’s still possible to tell
 # different *downloads* apart.
 echo -e "\n\033[4mZipping binary...\033[0m"
-if [[ "$1" == "--weekly" ]]
-  then exa_linux_zip="/vagrant/exa-linux-x86_64-${exa_version}-${date}-${git_hash}.zip"
-  else exa_linux_zip="/vagrant/exa-linux-x86_64.zip"
+if [[ "$1" == "--weekly" ]]; then
+  exa_linux_zip="/vagrant/exa-linux-x86_64-${exa_version}-${date}-${git_hash}.zip"
+else
+  exa_linux_zip="/vagrant/exa-linux-x86_64.zip"
 fi
 rm -vf "$exa_linux_zip"
 zip -j "$exa_linux_zip" "$exa_linux_binary"

+ 7 - 6
devtools/local-package-for-macos.sh

@@ -11,7 +11,7 @@ set -e
 
 # Virtualising macOS is a legal minefield, so this script is ‘local’ instead
 # of ‘dev’: I run it from my actual machine, rather than from a VM.
-uname=`uname -s`
+uname=$(uname -s)
 if [[ "$uname" != "Darwin" ]]; then
   echo "Gotta be on Darwin to run this (detected '$uname')!"
   exit 1
@@ -36,8 +36,8 @@ fi
 
 # Weekly builds have a bit more information in their version number (see build.rs).
 if [[ "$1" == "--weekly" ]]; then
-  git_hash=`GIT_DIR=$exa_root/.git git rev-parse --short --verify HEAD`
-  date=`date +"%Y-%m-%d"`
+  git_hash=$(GIT_DIR=$exa_root/.git git rev-parse --short --verify HEAD)
+  date=$(date +"%Y-%m-%d")
   echo "Building exa weekly v$exa_version, date $date, Git hash $git_hash"
 else
   echo "Building exa v$exa_version"
@@ -65,9 +65,10 @@ echo "strip $exa_macos_binary"
 # the binaries can have consistent names, and it’s still possible to tell
 # different *downloads* apart.
 echo -e "\n\033[4mZipping binary...\033[0m"
-if [[ "$1" == "--weekly" ]]
-  then exa_macos_zip="$exa_root/exa-macos-x86_64-${exa_version}-${date}-${git_hash}.zip"
-  else exa_macos_zip="$exa_root/exa-macos-x86_64-${exa_version}.zip"
+if [[ "$1" == "--weekly" ]]; then
+  exa_macos_zip="$exa_root/exa-macos-x86_64-${exa_version}-${date}-${git_hash}.zip"
+else
+  exa_macos_zip="$exa_root/exa-macos-x86_64-${exa_version}.zip"
 fi
 rm -vf "$exa_macos_zip" | sed 's/^/removing /'
 zip -j "$exa_macos_zip" "$exa_macos_binary"

+ 1 - 1
src/fs/dir_action.rs

@@ -51,7 +51,7 @@ impl DirAction {
         match self {
             Self::AsFile      => true,
             Self::Recurse(o)  => o.tree,
-            _                 => false,
+            Self::List        => false,
         }
     }
 }

+ 8 - 0
src/fs/feature/git.rs

@@ -1,5 +1,8 @@
 //! Getting the Git status of files and directories.
 
+use std::ffi::OsStr;
+#[cfg(target_family = "unix")]
+use std::os::unix::ffi::OsStrExt;
 use std::path::{Path, PathBuf};
 use std::sync::Mutex;
 
@@ -205,6 +208,11 @@ fn repo_to_statuses(repo: &git2::Repository, workdir: &Path) -> Git {
     match repo.statuses(None) {
         Ok(es) => {
             for e in es.iter() {
+                #[cfg(target_family = "unix")]
+                let path = workdir.join(Path::new(OsStr::from_bytes(e.path_bytes())));
+                // TODO: handle non Unix systems better:
+                // https://github.com/ogham/exa/issues/698
+                #[cfg(not(target_family = "unix"))]
                 let path = workdir.join(Path::new(e.path().unwrap()));
                 let elem = (path, e.status());
                 statuses.push(elem);

+ 7 - 7
src/info/filetype.rs

@@ -26,7 +26,7 @@ impl FileExtensions {
         file.name_is_one_of( &[
             "Makefile", "Cargo.toml", "SConstruct", "CMakeLists.txt",
             "build.gradle", "pom.xml", "Rakefile", "package.json", "Gruntfile.js",
-            "Gruntfile.coffee", "BUILD", "BUILD.bazel", "WORKSPACE", "build.xml",
+            "Gruntfile.coffee", "BUILD", "BUILD.bazel", "WORKSPACE", "build.xml", "Podfile",
             "webpack.config.js", "meson.build", "composer.json", "RoboFile.php", "PKGBUILD",
             "Justfile", "Procfile", "Dockerfile", "Containerfile", "Vagrantfile", "Brewfile",
             "Gemfile", "Pipfile", "build.sbt", "mix.exs", "bsconfig.json", "tsconfig.json",
@@ -35,10 +35,10 @@ impl FileExtensions {
 
     fn is_image(&self, file: &File<'_>) -> bool {
         file.extension_is_one_of( &[
-            "png", "jpeg", "jpg", "gif", "bmp", "tiff", "tif",
-            "ppm", "pgm", "pbm", "pnm", "webp", "raw", "arw",
-            "svg", "stl", "eps", "dvi", "ps", "cbr", "jpf",
-            "cbz", "xpm", "ico", "cr2", "orf", "nef", "heif",
+            "png", "jfi", "jfif", "jif", "jpe", "jpeg", "jpg", "gif", "bmp",
+            "tiff", "tif", "ppm", "pgm", "pbm", "pnm", "webp", "raw", "arw",
+            "svg", "stl", "eps", "dvi", "ps", "cbr", "jpf", "cbz", "xpm",
+            "ico", "cr2", "orf", "nef", "heif", "avif", "jxl",
         ])
     }
 
@@ -80,14 +80,14 @@ impl FileExtensions {
         file.extension_is_one_of( &[
             "zip", "tar", "Z", "z", "gz", "bz2", "a", "ar", "7z",
             "iso", "dmg", "tc", "rar", "par", "tgz", "xz", "txz",
-            "lz", "tlz", "lzma", "deb", "rpm", "zst",
+            "lz", "tlz", "lzma", "deb", "rpm", "zst", "lz4",
         ])
     }
 
     fn is_temp(&self, file: &File<'_>) -> bool {
         file.name.ends_with('~')
             || (file.name.starts_with('#') && file.name.ends_with('#'))
-            || file.extension_is_one_of( &[ "tmp", "swp", "swo", "swn", "bak", "bk" ])
+            || file.extension_is_one_of( &[ "tmp", "swp", "swo", "swn", "bak", "bkp", "bk" ])
     }
 
     fn is_compiled(&self, file: &File<'_>) -> bool {

+ 1 - 0
src/main.rs

@@ -18,6 +18,7 @@
 #![allow(clippy::non_ascii_literal)]
 #![allow(clippy::option_if_let_else)]
 #![allow(clippy::too_many_lines)]
+#![allow(clippy::unnested_or_patterns)] // TODO: remove this when we support Rust 1.53.0
 #![allow(clippy::unused_self)]
 #![allow(clippy::upper_case_acronyms)]
 #![allow(clippy::wildcard_imports)]

+ 1 - 1
src/options/parser.rs

@@ -430,7 +430,7 @@ impl<'a> MatchedFlags<'a> {
                             .filter(|tuple| tuple.1.is_some() && predicate(&tuple.0))
                             .collect::<Vec<_>>();
 
-            if those.len() < 2 { Ok(those.first().cloned().map(|t| t.1.unwrap())) }
+            if those.len() < 2 { Ok(those.first().copied().map(|t| t.1.unwrap())) }
                           else { Err(OptionsError::Duplicate(those[0].0, those[1].0)) }
         }
         else {

+ 2 - 2
src/output/grid_details.rs

@@ -158,7 +158,7 @@ impl<'a> Render<'a> {
                              .collect::<Vec<_>>();
 
         let mut last_working_grid = self.make_grid(1, options, &file_names, rows.clone(), &drender);
-        
+
         if file_names.len() == 1 {
             return Some((last_working_grid, 1));
         }
@@ -176,7 +176,7 @@ impl<'a> Render<'a> {
             if the_grid_fits {
                 last_working_grid = grid;
             }
-            
+
             if !the_grid_fits || column_count == file_names.len() {
                 let last_column_count = if the_grid_fits { column_count } else { column_count - 1 };
                 // If we’ve figured out how many columns can fit in the user’s terminal,

+ 13 - 1
src/output/icons.rs

@@ -80,7 +80,7 @@ lazy_static! {
         m.insert("include", '\u{e5fc}'); // 
         m.insert("lib", '\u{f121}'); // 
         m.insert("localized", '\u{f179}'); // 
-        m.insert("Makefile", '\u{e779}'); // 
+        m.insert("Makefile", '\u{f489}'); // 
         m.insert("node_modules", '\u{e718}'); // 
         m.insert("npmignore", '\u{e71e}'); // 
         m.insert("rubydoc", '\u{e73b}'); // 
@@ -110,6 +110,7 @@ pub fn icon_for_file(file: &File<'_>) -> char {
             "apk"           => '\u{e70e}', // 
             "apple"         => '\u{f179}', // 
             "avi"           => '\u{f03d}', // 
+            "avif"          => '\u{f1c5}', // 
             "avro"          => '\u{e60b}', // 
             "awk"           => '\u{f489}', // 
             "bash"          => '\u{f489}', // 
@@ -206,11 +207,16 @@ pub fn icon_for_file(file: &File<'_>) -> char {
             "jad"           => '\u{e256}', // 
             "jar"           => '\u{e204}', // 
             "java"          => '\u{e204}', // 
+            "jfi"           => '\u{f1c5}', // 
+            "jfif"          => '\u{f1c5}', // 
+            "jif"           => '\u{f1c5}', // 
+            "jpe"           => '\u{f1c5}', // 
             "jpeg"          => '\u{f1c5}', // 
             "jpg"           => '\u{f1c5}', // 
             "js"            => '\u{e74e}', // 
             "json"          => '\u{e60b}', // 
             "jsx"           => '\u{e7ba}', // 
+            "jxl"           => '\u{f1c5}', // 
             "ksh"           => '\u{f489}', // 
             "latex"         => '\u{f034}', // 
             "less"          => '\u{e758}', // 
@@ -221,6 +227,7 @@ pub fn icon_for_file(file: &File<'_>) -> char {
             "log"           => '\u{f18d}', // 
             "lua"           => '\u{e620}', // 
             "lz"            => '\u{f410}', // 
+            "lz4"           => '\u{f410}', // 
             "lzh"           => '\u{f410}', // 
             "lzma"          => '\u{f410}', // 
             "lzo"           => '\u{f410}', // 
@@ -230,6 +237,7 @@ pub fn icon_for_file(file: &File<'_>) -> char {
             "markdown"      => '\u{f48a}', // 
             "md"            => '\u{f48a}', // 
             "mjs"           => '\u{e74e}', // 
+            "mk"            => '\u{f489}', // 
             "mkd"           => '\u{f48a}', // 
             "mkv"           => '\u{f03d}', // 
             "mobi"          => '\u{e28b}', // 
@@ -292,6 +300,7 @@ pub fn icon_for_file(file: &File<'_>) -> char {
             "so"            => '\u{f17c}', // 
             "sql"           => '\u{f1c0}', // 
             "sqlite3"       => '\u{e7c4}', // 
+            "sty"           => '\u{f034}', // 
             "styl"          => '\u{e600}', // 
             "stylus"        => '\u{e600}', // 
             "svg"           => '\u{f1c5}', // 
@@ -301,7 +310,9 @@ pub fn icon_for_file(file: &File<'_>) -> char {
             "tbz"           => '\u{f410}', // 
             "tbz2"          => '\u{f410}', // 
             "tex"           => '\u{f034}', // 
+            "tgz"           => '\u{f410}', // 
             "tiff"          => '\u{f1c5}', // 
+            "tlz"           => '\u{f410}', // 
             "toml"          => '\u{e615}', // 
             "ts"            => '\u{e628}', // 
             "tsv"           => '\u{f1c3}', // 
@@ -309,6 +320,7 @@ pub fn icon_for_file(file: &File<'_>) -> char {
             "ttf"           => '\u{f031}', // 
             "twig"          => '\u{e61c}', // 
             "txt"           => '\u{f15c}', // 
+            "txz"           => '\u{f410}', // 
             "tz"            => '\u{f410}', // 
             "tzo"           => '\u{f410}', // 
             "video"         => '\u{f03d}', // 

+ 1 - 1
src/output/render/size.rs

@@ -46,7 +46,7 @@ impl f::Size {
         } else {
             numerics.format_int(n.round() as isize)
         };
-        
+
         TextCell {
             // symbol is guaranteed to be ASCII since unit prefixes are hardcoded.
             width: DisplayWidth::from(&*number) + symbol.len(),

+ 12 - 0
xtests/git.toml

@@ -144,6 +144,18 @@ status = 0
 tags = [ 'long', 'git' ]
 
 
+
+# The forth Git repo: non UTF-8 file
+
+[[cmd]]
+name = "‘exa --git -l’ handles non UTF8 file in Git repositories"
+shell = "exa --git -l /testcases/git4"
+stdout = { file = "outputs/git4_long.ansitxt" }
+stderr = { empty = true }
+status = 0
+tags = [ 'long', 'git' ]
+
+
 # Both repositories 1 and 2 at once
 
 [[cmd]]

+ 1 - 0
xtests/outputs/git4_long.ansitxt

@@ -0,0 +1 @@
+.rw-rw-r-- 0 cassowary  1 Jan 12:34 -N P\u{8}�UUU