Repository: sharkdp/fd
Pull request #779 (2021-06-22 01:44:08)

Added example for searching exactly a filename
Hello,

This PR is meant to address issue #692.

Let me know if any tweaks would make the example more clear. :)


Patch:
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
new file mode 100644
index 0000000..2cebf5b
--- /dev/null
+++ b/.github/dependabot.yml
@@ -0,0 +1,6 @@
+version: 2
+updates:
+  - package-ecosystem: "cargo"
+    directory: "/"
+    schedule:
+      interval: "monthly"
diff --git a/.github/workflows/CICD.yml b/.github/workflows/CICD.yml
new file mode 100644
index 0000000..22ba12d
--- /dev/null
+++ b/.github/workflows/CICD.yml
@@ -0,0 +1,333 @@
+name: CICD
+
+env:
+  MIN_SUPPORTED_RUST_VERSION: "1.42.0"
+  CICD_INTERMEDIATES_DIR: "_cicd-intermediates"
+
+on:
+  workflow_dispatch:
+  pull_request:
+  push:
+    branches:
+      - master
+    tags:
+      - '*'
+
+jobs:
+  min_version:
+    name: Minimum supported rust version
+    runs-on: ubuntu-18.04
+    steps:
+    - name: Checkout source code
+      uses: actions/checkout@v2
+
+    - name: Install rust toolchain (v${{ env.MIN_SUPPORTED_RUST_VERSION }})
+      uses: actions-rs/toolchain@v1
+      with:
+        toolchain: ${{ env.MIN_SUPPORTED_RUST_VERSION }}
+        default: true
+        profile: minimal # minimal component installation (ie, no documentation)
+        components: clippy
+    - name: Run clippy (on minimum supported rust version to prevent warnings we can't fix)
+      uses: actions-rs/cargo@v1
+      with:
+        command: clippy
+        args: --all-targets --all-features
+    - name: Run tests
+      uses: actions-rs/cargo@v1
+      with:
+        command: test
+
+  build:
+    name: ${{ matrix.job.os }} (${{ matrix.job.target }})
+    runs-on: ${{ matrix.job.os }}
+    strategy:
+      fail-fast: false
+      matrix:
+        job:
+          - { os: ubuntu-18.04, target: arm-unknown-linux-gnueabihf , use-cross: true }
+          - { os: ubuntu-18.04, target: arm-unknown-linux-musleabihf, use-cross: true }
+          - { os: ubuntu-18.04, target: aarch64-unknown-linux-gnu   , use-cross: true }
+          - { os: ubuntu-18.04, target: i686-unknown-linux-gnu      , use-cross: true }
+          - { os: ubuntu-18.04, target: i686-unknown-linux-musl     , use-cross: true }
+          - { os: ubuntu-18.04, target: x86_64-unknown-linux-gnu    }
+          - { os: ubuntu-18.04, target: x86_64-unknown-linux-musl   , use-cross: true }
+          - { os: macos-10.15 , target: x86_64-apple-darwin         }
+          # - { os: windows-2019, target: i686-pc-windows-gnu         }  ## disabled; error: linker `i686-w64-mingw32-gcc` not found
+          - { os: windows-2019, target: i686-pc-windows-msvc        }
+          - { os: windows-2019, target: x86_64-pc-windows-gnu       }
+          - { os: windows-2019, target: x86_64-pc-windows-msvc      }
+    steps:
+    - name: Checkout source code
+      uses: actions/checkout@v2
+
+    - name: Install prerequisites
+      shell: bash
+      run: |
+        case ${{ matrix.job.target }} in
+          arm-unknown-linux-*) sudo apt-get -y update ; sudo apt-get -y install gcc-arm-linux-gnueabihf ;;
+          aarch64-unknown-linux-gnu) sudo apt-get -y update ; sudo apt-get -y install gcc-aarch64-linux-gnu ;;
+        esac
+
+    - name: Extract crate information
+      shell: bash
+      run: |
+        echo "PROJECT_NAME=fd" >> $GITHUB_ENV
+        echo "PROJECT_VERSION=$(sed -n 's/^version = "\(.*\)"/\1/p' Cargo.toml | head -n1)" >> $GITHUB_ENV
+        echo "PROJECT_MAINTAINER=$(sed -n 's/^authors = \["\(.*\)"\]/\1/p' Cargo.toml)" >> $GITHUB_ENV
+        echo "PROJECT_HOMEPAGE=$(sed -n 's/^homepage = "\(.*\)"/\1/p' Cargo.toml)" >> $GITHUB_ENV
+
+    - name: Install Rust toolchain
+      uses: actions-rs/toolchain@v1
+      with:
+        toolchain: stable
+        target: ${{ matrix.job.target }}
+        override: true
+        profile: minimal # minimal component installation (ie, no documentation)
+
+    - name: Show version information (Rust, cargo, GCC)
+      shell: bash
+      run: |
+        gcc --version || true
+        rustup -V
+        rustup toolchain list
+        rustup default
+        cargo -V
+        rustc -V
+
+    - name: Build
+      uses: actions-rs/cargo@v1
+      with:
+        use-cross: ${{ matrix.job.use-cross }}
+        command: build
+        args: --release --target=${{ matrix.job.target }}
+
+    - name: Strip debug information from executable
+      id: strip
+      shell: bash
+      run: |
+        # Figure out suffix of binary
+        EXE_suffix=""
+        case ${{ matrix.job.target }} in
+          *-pc-windows-*) EXE_suffix=".exe" ;;
+        esac;
+
+        # Figure out what strip tool to use if any
+        STRIP="strip"
+        case ${{ matrix.job.target }} in
+          arm-unknown-linux-*) STRIP="arm-linux-gnueabihf-strip" ;;
+          aarch64-unknown-linux-gnu) STRIP="aarch64-linux-gnu-strip" ;;
+          *-pc-windows-msvc) STRIP="" ;;
+        esac;
+
+        # Setup paths
+        BIN_DIR="${{ env.CICD_INTERMEDIATES_DIR }}/stripped-release-bin/"
+        mkdir -p "${BIN_DIR}"
+        BIN_NAME="${{ env.PROJECT_NAME }}${EXE_suffix}"
+        BIN_PATH="${BIN_DIR}/${BIN_NAME}"
+
+        # Copy the release build binary to the result location
+        cp "target/${{ matrix.job.target }}/release/${BIN_NAME}" "${BIN_DIR}"
+
+        # Also strip if possible
+        if [ -n "${STRIP}" ]; then
+          "${STRIP}" "${BIN_PATH}"
+        fi
+
+        # Let subsequent steps know where to find the (stripped) bin
+        echo ::set-output name=BIN_PATH::${BIN_PATH}
+        echo ::set-output name=BIN_NAME::${BIN_NAME}
+
+    - name: Set testing options
+      id: test-options
+      shell: bash
+      run: |
+        unset CARGO_TEST_OPTIONS
+        unset CARGO_TEST_OPTIONS ; case ${{ matrix.job.target }} in arm-* | aarch64-*) CARGO_TEST_OPTIONS="--bin ${PROJECT_NAME}" ;; esac;
+        echo ::set-output name=CARGO_TEST_OPTIONS::${CARGO_TEST_OPTIONS}
+
+    - name: Run tests
+      uses: actions-rs/cargo@v1
+      with:
+        use-cross: ${{ matrix.job.use-cross }}
+        command: test
+        args: --target=${{ matrix.job.target }} ${{ steps.test-options.outputs.CARGO_TEST_OPTIONS}}
+
+    - name: Create tarball
+      id: package
+      shell: bash
+      run: |
+        PKG_suffix=".tar.gz" ; case ${{ matrix.job.target }} in *-pc-windows-*) PKG_suffix=".zip" ;; esac;
+        PKG_BASENAME=${PROJECT_NAME}-v${PROJECT_VERSION}-${{ matrix.job.target }}
+        PKG_NAME=${PKG_BASENAME}${PKG_suffix}
+        echo ::set-output name=PKG_NAME::${PKG_NAME}
+
+        PKG_STAGING="${{ env.CICD_INTERMEDIATES_DIR }}/package"
+        ARCHIVE_DIR="${PKG_STAGING}/${PKG_BASENAME}/"
+        mkdir -p "${ARCHIVE_DIR}"
+        mkdir -p "${ARCHIVE_DIR}/autocomplete"
+
+        # Binary
+        cp "${{ steps.strip.outputs.BIN_PATH }}" "$ARCHIVE_DIR"
+
+        # Man page
+        cp 'doc/${{ env.PROJECT_NAME }}.1' "$ARCHIVE_DIR"
+
+        # README, LICENSE and CHANGELOG files
+        cp "README.md" "LICENSE-MIT" "LICENSE-APACHE" "CHANGELOG.md" "$ARCHIVE_DIR"
+
+        # Autocompletion files
+        cp 'target/${{ matrix.job.target }}/release/build/${{ env.PROJECT_NAME }}'*/out/'${{ env.PROJECT_NAME }}.bash' "$ARCHIVE_DIR/autocomplete/"
+        cp 'target/${{ matrix.job.target }}/release/build/${{ env.PROJECT_NAME }}'*/out/'${{ env.PROJECT_NAME }}.fish' "$ARCHIVE_DIR/autocomplete/"
+        cp 'target/${{ matrix.job.target }}/release/build/${{ env.PROJECT_NAME }}'*/out/'_${{ env.PROJECT_NAME }}.ps1' "$ARCHIVE_DIR/autocomplete/"
+        cp 'contrib/completion/_fd' "$ARCHIVE_DIR/autocomplete/"
+
+        # base compressed package
+        pushd "${PKG_STAGING}/" >/dev/null
+        case ${{ matrix.job.target }} in
+          *-pc-windows-*) 7z -y a "${PKG_NAME}" "${PKG_BASENAME}"/* | tail -2 ;;
+          *) tar czf "${PKG_NAME}" "${PKG_BASENAME}"/* ;;
+        esac;
+        popd >/dev/null
+
+        # Let subsequent steps know where to find the compressed package
+        echo ::set-output name=PKG_PATH::"${PKG_STAGING}/${PKG_NAME}"
+
+    - name: Create Debian package
+      id: debian-package
+      shell: bash
+      if: startsWith(matrix.job.os, 'ubuntu')
+      run: |
+        COPYRIGHT_YEARS="2018 - "$(date "+%Y")
+        DPKG_STAGING="${{ env.CICD_INTERMEDIATES_DIR }}/debian-package"
+        DPKG_DIR="${DPKG_STAGING}/dpkg"
+        mkdir -p "${DPKG_DIR}"
+
+        DPKG_BASENAME=${PROJECT_NAME}
+        DPKG_CONFLICTS=${PROJECT_NAME}-musl
+        case ${{ matrix.job.target }} in *-musl) DPKG_BASENAME=${PROJECT_NAME}-musl ; DPKG_CONFLICTS=${PROJECT_NAME} ;; esac;
+        DPKG_VERSION=${PROJECT_VERSION}
+
+        unset DPKG_ARCH
+        case ${{ matrix.job.target }} in
+          aarch64-*-linux-*) DPKG_ARCH=arm64 ;;
+          arm-*-linux-*hf) DPKG_ARCH=armhf ;;
+          i686-*-linux-*) DPKG_ARCH=i686 ;;
+          x86_64-*-linux-*) DPKG_ARCH=amd64 ;;
+          *) DPKG_ARCH=notset ;;
+        esac;
+
+        DPKG_NAME="${DPKG_BASENAME}_${DPKG_VERSION}_${DPKG_ARCH}.deb"
+        echo ::set-output name=DPKG_NAME::${DPKG_NAME}
+
+        # Binary
+        install -Dm755 "${{ steps.strip.outputs.BIN_PATH }}" "${DPKG_DIR}/usr/bin/${{ steps.strip.outputs.BIN_NAME }}"
+
+        # Man page
+        install -Dm644 'doc/${{ env.PROJECT_NAME }}.1' "${DPKG_DIR}/usr/share/man/man1/${{ env.PROJECT_NAME }}.1"
+        gzip -n --best "${DPKG_DIR}/usr/share/man/man1/${{ env.PROJECT_NAME }}.1"
+
+        # Autocompletion files
+        install -Dm644 'target/${{ matrix.job.target }}/release/build/${{ env.PROJECT_NAME }}'*/out/'${{ env.PROJECT_NAME }}.bash' "${DPKG_DIR}/usr/share/bash-completion/completions/${{ env.PROJECT_NAME }}"
+        install -Dm644 'target/${{ matrix.job.target }}/release/build/${{ env.PROJECT_NAME }}'*/out/'${{ env.PROJECT_NAME }}.fish' "${DPKG_DIR}/usr/share/fish/vendor_completions.d/${{ env.PROJECT_NAME }}.fish"
+        install -Dm644 'contrib/completion/_fd' "${DPKG_DIR}/usr/share/zsh/vendor-completions/_${{ env.PROJECT_NAME }}"
+
+        # README and LICENSE
+        install -Dm644 "README.md" "${DPKG_DIR}/usr/share/doc/${DPKG_BASENAME}/README.md"
+        install -Dm644 "LICENSE-MIT" "${DPKG_DIR}/usr/share/doc/${DPKG_BASENAME}/LICENSE-MIT"
+        install -Dm644 "LICENSE-APACHE" "${DPKG_DIR}/usr/share/doc/${DPKG_BASENAME}/LICENSE-APACHE"
+        install -Dm644 "CHANGELOG.md" "${DPKG_DIR}/usr/share/doc/${DPKG_BASENAME}/changelog"
+        gzip -n --best "${DPKG_DIR}/usr/share/doc/${DPKG_BASENAME}/changelog"
+
+        cat > "${DPKG_DIR}/usr/share/doc/${DPKG_BASENAME}/copyright" <<EOF
+        Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
+        Upstream-Name: ${{ env.PROJECT_NAME }}
+        Source: ${{ env.PROJECT_HOMEPAGE }}
+
+        Files: *
+        Copyright: ${{ env.PROJECT_MAINTAINER }}
+        Copyright: $COPYRIGHT_YEARS ${{ env.PROJECT_MAINTAINER }}
+        License: Apache-2.0 or MIT
+
+        License: Apache-2.0
+          On Debian systems, the complete text of the Apache-2.0 can be found in the
+          file /usr/share/common-licenses/Apache-2.0.
+
+        License: MIT
+          Permission is hereby granted, free of charge, to any
+          person obtaining a copy of this software and associated
+          documentation files (the "Software"), to deal in the
+          Software without restriction, including without
+          limitation the rights to use, copy, modify, merge,
+          publish, distribute, sublicense, and/or sell copies of
+          the Software, and to permit persons to whom the Software
+          is furnished to do so, subject to the following
+          conditions:
+          .
+          The above copyright notice and this permission notice
+          shall be included in all copies or substantial portions
+          of the Software.
+          .
+          THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
+          ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
+          TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
+          PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+          SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+          CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+          OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
+          IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+          DEALINGS IN THE SOFTWARE.
+        EOF
+          chmod 644 "${DPKG_DIR}/usr/share/doc/${DPKG_BASENAME}/copyright"
+
+          # control file
+          mkdir -p "${DPKG_DIR}/DEBIAN"
+          cat > "${DPKG_DIR}/DEBIAN/control" <<EOF
+        Package: ${DPKG_BASENAME}
+        Version: ${DPKG_VERSION}
+        Section: utils
+        Priority: optional
+        Maintainer: ${{ env.PROJECT_MAINTAINER }}
+        Homepage: ${{ env.PROJECT_HOMEPAGE }}
+        Architecture: ${DPKG_ARCH}
+        Provides: ${{ env.PROJECT_NAME }}
+        Conflicts: ${DPKG_CONFLICTS}
+        Description: cat(1) clone with wings.
+          A cat(1) clone with syntax highlighting and Git integration.
+        EOF
+
+        DPKG_PATH="${DPKG_STAGING}/${DPKG_NAME}"
+        echo ::set-output name=DPKG_PATH::${DPKG_PATH}
+
+        # build dpkg
+        fakeroot dpkg-deb --build "${DPKG_DIR}" "${DPKG_PATH}"
+
+    - name: "Artifact upload: tarball"
+      uses: actions/upload-artifact@master
+      with:
+        name: ${{ steps.package.outputs.PKG_NAME }}
+        path: ${{ steps.package.outputs.PKG_PATH }}
+
+    - name: "Artifact upload: Debian package"
+      uses: actions/upload-artifact@master
+      if: steps.debian-package.outputs.DPKG_NAME
+      with:
+        name: ${{ steps.debian-package.outputs.DPKG_NAME }}
+        path: ${{ steps.debian-package.outputs.DPKG_PATH }}
+
+    - name: Check for release
+      id: is-release
+      shell: bash
+      run: |
+        unset IS_RELEASE ; if [[ $GITHUB_REF =~ ^refs/tags/v[0-9].* ]]; then IS_RELEASE='true' ; fi
+        echo ::set-output name=IS_RELEASE::${IS_RELEASE}
+
+    - name: Publish archives and packages
+      uses: softprops/action-gh-release@v1
+      if: steps.is-release.outputs.IS_RELEASE
+      with:
+        files: |
+          ${{ steps.package.outputs.PKG_PATH }}
+          ${{ steps.debian-package.outputs.DPKG_PATH }}
+      env:
+        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
diff --git a/.travis.yml b/.travis.yml
deleted file mode 100644
index 339d6fa..0000000
--- a/.travis.yml
+++ /dev/null
@@ -1,104 +0,0 @@
-language: rust
-jobs:
-  include:
-    # Stable channel.
-    - os: linux
-      rust: stable
-      env: TARGET=x86_64-unknown-linux-gnu
-    - os: linux
-      rust: stable
-      env: TARGET=x86_64-unknown-linux-musl
-    - os: linux
-      rust: stable
-      env: TARGET=i686-unknown-linux-gnu
-    - os: linux
-      rust: stable
-      env: TARGET=i686-unknown-linux-musl
-    - os: osx
-      osx_image: xcode11.4 # Catalina
-      rust: stable
-      env: TARGET=x86_64-apple-darwin
-    - os: linux
-      rust: stable
-      env:
-        - TARGET=arm-unknown-linux-gnueabihf
-        - CC_arm_unknown_linux_gnueabihf=/usr/bin/arm-linux-gnueabihf-gcc-4.8
-        - CARGO_TARGET_ARM_UNKNOWN_LINUX_GNUEABIHF_LINKER=arm-linux-gnueabihf-gcc-4.8
-    - os: linux
-      rust: stable
-      env:
-        - TARGET=arm-unknown-linux-musleabihf
-        - CARGO_TARGET_ARM_UNKNOWN_LINUX_MUSLEABIHF_LINKER=arm-linux-gnueabihf-gcc-4.8
-
-    # Beta channel.
-    - os: linux
-      rust: beta
-      env: TARGET=x86_64-unknown-linux-gnu
-
-    # Minimum Rust supported channel.
-    - os: linux
-      rust: 1.40.0
-      env: TARGET=x86_64-unknown-linux-gnu
-    - os: linux
-      rust: 1.40.0
-      env: TARGET=x86_64-unknown-linux-musl
-    - os: linux
-      rust: 1.40.0
-      env: TARGET=i686-unknown-linux-gnu
-    - os: linux
-      rust: 1.40.0
-      env: TARGET=i686-unknown-linux-musl
-    - os: osx
-      rust: 1.40.0
-      env: TARGET=x86_64-apple-darwin
-
-before_install:
-  - ci/before_install.bash
-
-env:
-  global:
-    # Default target on travis-ci.
-    # Used as conditional check in the install stage
-    - HOST=x86_64-unknown-linux-gnu
-    # Used on the deployment script
-    - PROJECT_NAME=fd
-
-install:
-  # prevent target re-add error from rustup
-  - if [[ $TRAVIS_OS_NAME = linux && $HOST != $TARGET ]]; then rustup target add $TARGET; fi
-
-script:
-  - ci/script.bash
-
-before_deploy:
-  - bash ci/before_deploy.bash
-
-deploy:
-  provider: releases
-  # NOTE updating the `token.secure`
-  # - go to: https://github.com/settings/tokens/new
-  # - generate new token using `public_repo` scope
-  # - encrypt it using: `travis encrypt API_KEY_HERE`
-  # - paste the output below
-  token:
-    secure: "RyFdh2lpDmaNhPar7ezsb18Xz+6XFM40y7cZCDRML+Sk+eYK1xtDNfEhDRJU5Qo1ReVsByds/QJTSXr2KmZPk3lXwG3SiN7UtrLUxCxFr6qrcM/iujlKTf5UxeRklkzPXxnH95DEyEgxvgbVhWTGVDWoyMnrVQXZKDy6z1iAiYB5h2Zl1rs+MRb/Enlt5q6XIKAlG0ppGtl8CfYudq5ZiqfJaMWTt9SWm2YskC8FeMc0S3IM6/EhTvaNYLdaarFqVWQEVql+6oCuL3ayPzmGyxLdxM37tIMNQ0f97zxqWodacXTG5ULdRD8if1l/SmTujrtjbZ0KWRjsjOq4vBtxBJKGdprcSiB0xH/hToqqtTSO0z5FPXi5cB8UlK6YLDDHcP3kXNer8CYMLI1VPaUDLTF57/0/RPi2DZiiGfZsIAS6PsICbHdTQVzxQckM4lN1vnAGgkhXIMbztml21pv+QrGy98OZJ0ubf5ztgQhpT0WPH4JXT8M6htsoo8dZf8lQ5aLfmW9RKePJDqixQwPqmimPIkrlxRDTDGII0ZAZws7l779eOLmEcM2tH2HbsUKUCZIG/pRHLSlP45Jn2bULGzuXZ2daq70z6zvIbom0CUzSXIvdTXEZI2AM5RBvPYGGaKI8YlxgRdQvJp3h0BzPdFOXI3RAxscCY7PJpa/RdIg="
-  # for uploading multiple files
-  file_glob: true
-  # NOTE explanation on each env variable
-  # - PROJECT_NAME: name of the project, set on the `env.global` above
-  # - TRAVIS_TAG: tag name that the build is being deployed for, usually the version number
-  # - TARGET: target triple of the build
-  file:
-    - $PROJECT_NAME-$TRAVIS_TAG-$TARGET.*
-    - $PROJECT_NAME*.deb
-  # don't delete artifacts from previous stage
-  skip_cleanup: true
-  on:
-    # deploy only if we push a tag
-    tags: true
-    # deploy only on stable channel that has TARGET env variable sets
-    condition: $TRAVIS_RUST_VERSION = stable && $TARGET != ""
-
-notifications:
-  email:
-    on_success: never
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 27b037a..d9d98d6 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,6 +5,10 @@
 ## Bugfixes
 
 - Set default path separator to `/` in MSYS, see #537 and #730 (@aswild)
+- fd cannot search files under a RAM disk, see #752
+- fd doesn't show substituted drive on Windows, see #365
+- Properly handle write errors to devices that are full, see #737
+- Use local time zone for time functions (`--change-newer-than`, `--change-older-than`), see #631 (@jacobmischka)
 
 ## Changes
 
diff --git a/Cargo.lock b/Cargo.lock
index fa3c14d..85e7279 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1,10 +1,12 @@
 # This file is automatically @generated by Cargo.
 # It is not intended for manual editing.
+version = 3
+
 [[package]]
 name = "aho-corasick"
-version = "0.7.15"
+version = "0.7.18"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7404febffaa47dac81aa44dba71523c9d069b1bdc50a77db41195149e17f68e5"
+checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f"
 dependencies = [
  "memchr",
 ]
@@ -29,9 +31,21 @@ dependencies = [
 
 [[package]]
 name = "anyhow"
-version = "1.0.35"
+version = "1.0.31"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "85bb70cc08ec97ca5450e6eba421deeea5f172c0fc61f78b5357b2a8e8be195f"
+
+[[package]]
+name = "arrayref"
+version = "0.3.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2c0df63cb2955042487fad3aefd2c6e3ae7389ac5dc1beb28921de0b69f779d4"
+checksum = "a4c527152e37cf757a3f78aae5a06fbeefdb07ccc535c980a3208ee3060dd544"
+
+[[package]]
+name = "arrayvec"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cff77d8686867eceff3105329d4698d96c2391c176d5d03adc90c7389162b5b8"
 
 [[package]]
 name = "atty"
@@ -46,9 +60,15 @@ dependencies = [
 
 [[package]]
 name = "autocfg"
-version = "1.0.1"
+version = "1.0.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
+checksum = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d"
+
+[[package]]
+name = "base64"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b41b7ea54a0c9d92199de89e20e58d49f02f8e699814ef3fdf266f6f748d15c7"
 
 [[package]]
 name = "bitflags"
@@ -56,20 +76,31 @@ version = "1.2.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
 
+[[package]]
+name = "blake2b_simd"
+version = "0.5.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d8fb2d74254a3a0b5cac33ac9f8ed0e44aa50378d9dbb2e5d83bd21ed1dc2c8a"
+dependencies = [
+ "arrayref",
+ "arrayvec",
+ "constant_time_eq",
+]
+
 [[package]]
 name = "bstr"
-version = "0.2.14"
+version = "0.2.13"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "473fc6b38233f9af7baa94fb5852dca389e3d95b8e21c8e3719301462c5d9faf"
+checksum = "31accafdb70df7871592c058eca3985b71104e15ac32f64706022c58867da931"
 dependencies = [
  "memchr",
 ]
 
 [[package]]
 name = "cc"
-version = "1.0.66"
+version = "1.0.53"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4c0496836a84f8d0495758516b8621a622beb77c0fed418570e50764093ced48"
+checksum = "404b1fe4f65288577753b17e3b36a04596ee784493ec249bf81c7f2d2acd751c"
 
 [[package]]
 name = "cfg-if"
@@ -83,11 +114,24 @@ version = "1.0.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
 
+[[package]]
+name = "chrono"
+version = "0.4.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73"
+dependencies = [
+ "libc",
+ "num-integer",
+ "num-traits",
+ "time",
+ "winapi",
+]
+
 [[package]]
 name = "clap"
-version = "2.33.3"
+version = "2.33.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "37e58ac78573c40708d45522f0d80fa2f01cc4f9b4e2bf749807255454312002"
+checksum = "bdfa80d47f954d53a35a64987ca1422f495b8d6483c0fe9f7117b36c2a792129"
 dependencies = [
  "ansi_term 0.11.0",
  "atty",
@@ -99,22 +143,28 @@ dependencies = [
  "vec_map",
 ]
 
+[[package]]
+name = "constant_time_eq"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc"
+
 [[package]]
 name = "crossbeam-utils"
-version = "0.8.1"
+version = "0.7.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "02d96d1e189ef58269ebe5b97953da3274d83a93af647c2ddd6f9dab28cedb8d"
+checksum = "c3c7c73a2d1e9fc0886a08b93e98eb643461230d5f1925e4036204d5f2e261a8"
 dependencies = [
  "autocfg",
- "cfg-if 1.0.0",
+ "cfg-if 0.1.10",
  "lazy_static",
 ]
 
 [[package]]
 name = "ctrlc"
-version = "3.1.7"
+version = "3.1.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b57a92e9749e10f25a171adcebfafe72991d45e7ec2dcb853e8f83d9dafaeb08"
+checksum = "7a4ba686dff9fa4c1c9636ce1010b0cf98ceb421361b0bb3d6faeec43bd217a7"
 dependencies = [
  "nix",
  "winapi",
@@ -138,9 +188,9 @@ dependencies = [
 
 [[package]]
 name = "dirs-sys-next"
-version = "0.1.1"
+version = "0.1.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "99de365f605554ae33f115102a02057d4fc18b01f3284d6870be0938743cfe7d"
+checksum = "9c60f7b8a8953926148223260454befb50c751d3c50e1c178c4fd1ace4083c9a"
 dependencies = [
  "libc",
  "redox_users",
@@ -154,6 +204,7 @@ dependencies = [
  "ansi_term 0.12.1",
  "anyhow",
  "atty",
+ "chrono",
  "clap",
  "ctrlc",
  "diff",
@@ -166,6 +217,7 @@ dependencies = [
  "lazy_static",
  "libc",
  "lscolors",
+ "normpath",
  "num_cpus",
  "regex",
  "regex-syntax",
@@ -176,13 +228,13 @@ dependencies = [
 
 [[package]]
 name = "filetime"
-version = "0.2.13"
+version = "0.2.14"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0c122a393ea57648015bf06fbd3d372378992e86b9ff5a7a497b076a28c79efe"
+checksum = "1d34cfa13a63ae058bfa601fe9e313bbdb3746427c1459185464ce0fcf62e1e8"
 dependencies = [
  "cfg-if 1.0.0",
  "libc",
- "redox_syscall",
+ "redox_syscall 0.2.10",
  "winapi",
 ]
 
@@ -194,9 +246,9 @@ checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
 
 [[package]]
 name = "fs_extra"
-version = "1.2.0"
+version = "1.1.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2022715d62ab30faffd124d40b76f4134a550a87792276512b18d63272333394"
+checksum = "5f2a4a2034423744d2cc7ca2068453168dcdb82c438419e639a26bd87839c674"
 
 [[package]]
 name = "fuchsia-cprng"
@@ -206,20 +258,20 @@ checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba"
 
 [[package]]
 name = "getrandom"
-version = "0.1.15"
+version = "0.1.14"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fc587bc0ec293155d5bfa6b9891ec18a1e330c234f896ea47fbada4cadbe47e6"
+checksum = "7abc8dd8451921606d809ba32e95b6111925cd2906060d2dcc29c070220503eb"
 dependencies = [
  "cfg-if 0.1.10",
  "libc",
- "wasi",
+ "wasi 0.9.0+wasi-snapshot-preview1",
 ]
 
 [[package]]
 name = "globset"
-version = "0.4.6"
+version = "0.4.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c152169ef1e421390738366d2f796655fec62621dabbd0fd476f905934061e4a"
+checksum = "7ad1da430bd7281dde2576f44c84cc3f0f7b475e7202cd503042dff01a8c8120"
 dependencies = [
  "aho-corasick",
  "bstr",
@@ -230,24 +282,24 @@ dependencies = [
 
 [[package]]
 name = "hermit-abi"
-version = "0.1.17"
+version = "0.1.13"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5aca5565f760fb5b220e499d72710ed156fdb74e631659e99377d9ebfbd13ae8"
+checksum = "91780f809e750b0a89f5544be56617ff6b1227ee485bcb06ebe10cdf89bd3b71"
 dependencies = [
  "libc",
 ]
 
 [[package]]
 name = "humantime"
-version = "2.0.1"
+version = "2.0.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3c1ad908cc71012b7bea4d0c53ba96a8cba9962f048fa68d143376143d863b7a"
+checksum = "b9b6c53306532d3c8e8087b44e6580e10db51a023cf9b433cea2ac38066b92da"
 
 [[package]]
 name = "ignore"
-version = "0.4.17"
+version = "0.4.15"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b287fb45c60bb826a0dc68ff08742b9d88a2fea13d6e0c286b3172065aaf878c"
+checksum = "128b9e89d15a3faa642ee164c998fd4fae3d89d054463cddb2c25a7baad3a352"
 dependencies = [
  "crossbeam-utils",
  "globset",
@@ -290,44 +342,73 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
 
 [[package]]
 name = "libc"
-version = "0.2.80"
+version = "0.2.70"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4d58d1b70b004888f764dfbf6a26a3b0342a1632d33968e4a179d8011c760614"
+checksum = "3baa92041a6fec78c687fa0cc2b3fae8884f743d672cf551bed1d6dac6988d0f"
 
 [[package]]
 name = "log"
-version = "0.4.11"
+version = "0.4.8"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4fabed175da42fed1fa0746b0ea71f412aa9d35e76e95e59b192c64b9dc2bf8b"
+checksum = "14b6052be84e6b71ab17edffc2eeabf5c2c3ae1fdb464aae35ac50c67a44e1f7"
 dependencies = [
  "cfg-if 0.1.10",
 ]
 
 [[package]]
 name = "lscolors"
-version = "0.7.1"
+version = "0.7.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d24b894c45c9da468621cdd615a5a79ee5e5523dd4f75c76ebc03d458940c16e"
+checksum = "1f77452267149eac960ded529fe5f5460ddf792845a1d71b5d0cfcee5642e47e"
 dependencies = [
  "ansi_term 0.12.1",
 ]
 
 [[package]]
 name = "memchr"
-version = "2.3.4"
+version = "2.4.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0ee1c47aaa256ecabcaea351eae4a9b01ef39ed810004e298d2511ed284b1525"
+checksum = "b16bd47d9e329435e309c58469fe0791c2d0d1ba96ec0954152a5ae2b04387dc"
 
 [[package]]
 name = "nix"
-version = "0.18.0"
+version = "0.17.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "83450fe6a6142ddd95fb064b746083fc4ef1705fe81f64a64e1d4b39f54a1055"
+checksum = "50e4785f2c3b7589a0d0c1dd60285e1188adac4006e8abd6dd578e1567027363"
 dependencies = [
  "bitflags",
  "cc",
  "cfg-if 0.1.10",
  "libc",
+ "void",
+]
+
+[[package]]
+name = "normpath"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "27e6e8f70e9fbbe3752d330d769e3424f24b9458ce266df93a3b456902fd696a"
+dependencies = [
+ "winapi",
+]
+
+[[package]]
+name = "num-integer"
+version = "0.1.43"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8d59457e662d541ba17869cf51cf177c0b5f0cbf476c66bdc90bf1edac4f875b"
+dependencies = [
+ "autocfg",
+ "num-traits",
+]
+
+[[package]]
+name = "num-traits"
+version = "0.2.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac267bcc07f48ee5f8935ab0d24f316fb722d7a1292e2913f0cc196b29ffd611"
+dependencies = [
+ "autocfg",
 ]
 
 [[package]]
@@ -379,47 +460,68 @@ dependencies = [
 
 [[package]]
 name = "redox_syscall"
-version = "0.1.57"
+version = "0.1.56"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2439c63f3f6139d1b57529d16bc3b8bb855230c8efcc5d3a896c8bea7c3b1e84"
+
+[[package]]
+name = "redox_syscall"
+version = "0.2.10"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce"
+checksum = "8383f39639269cde97d255a32bdb68c047337295414940c68bdd30c2e13203ff"
+dependencies = [
+ "bitflags",
+]
 
 [[package]]
 name = "redox_users"
-version = "0.3.5"
+version = "0.3.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "de0737333e7a9502c789a36d7c7fa6092a49895d4faa31ca5df163857ded2e9d"
+checksum = "09b23093265f8d200fa7b4c2c76297f47e681c655f6f1285a8780d6a022f7431"
 dependencies = [
  "getrandom",
- "redox_syscall",
+ "redox_syscall 0.1.56",
+ "rust-argon2",
 ]
 
 [[package]]
 name = "regex"
-version = "1.4.2"
+version = "1.5.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "38cf2c13ed4745de91a5eb834e11c00bcc3709e773173b2ce4c56c9fbde04b9c"
+checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461"
 dependencies = [
  "aho-corasick",
  "memchr",
  "regex-syntax",
- "thread_local",
 ]
 
 [[package]]
 name = "regex-syntax"
-version = "0.6.21"
+version = "0.6.25"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3b181ba2dcf07aaccad5448e8ead58db5b742cf85dfe035e2227f137a539a189"
+checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b"
 
 [[package]]
 name = "remove_dir_all"
-version = "0.5.3"
+version = "0.5.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7"
+checksum = "4a83fa3702a688b9359eccba92d153ac33fd2e8462f9e0e3fdf155239ea7792e"
 dependencies = [
  "winapi",
 ]
 
+[[package]]
+name = "rust-argon2"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2bc8af4bda8e1ff4932523b94d3dd20ee30a87232323eda55903ffd71d2fb017"
+dependencies = [
+ "base64",
+ "blake2b_simd",
+ "constant_time_eq",
+ "crossbeam-utils",
+]
+
 [[package]]
 name = "same-file"
 version = "1.0.6"
@@ -474,11 +576,22 @@ dependencies = [
  "lazy_static",
 ]
 
+[[package]]
+name = "time"
+version = "0.1.44"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255"
+dependencies = [
+ "libc",
+ "wasi 0.10.0+wasi-snapshot-preview1",
+ "winapi",
+]
+
 [[package]]
 name = "unicode-width"
-version = "0.1.8"
+version = "0.1.7"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3"
+checksum = "caaa9d531767d1ff2150b9332433f32a24622147e5ebb1f26409d5da67afd479"
 
 [[package]]
 name = "users"
@@ -498,9 +611,15 @@ checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191"
 
 [[package]]
 name = "version_check"
-version = "0.9.2"
+version = "0.9.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "078775d0255232fb988e6fccf26ddc9d1ac274299aaedcedce21c6f72cc533ce"
+
+[[package]]
+name = "void"
+version = "1.0.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b5a972e5669d67ba988ce3dc826706fb0a8b01471c088cb0b6110b805cc36aed"
+checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d"
 
 [[package]]
 name = "walkdir"
@@ -519,11 +638,17 @@ version = "0.9.0+wasi-snapshot-preview1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519"
 
+[[package]]
+name = "wasi"
+version = "0.10.0+wasi-snapshot-preview1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f"
+
 [[package]]
 name = "winapi"
-version = "0.3.9"
+version = "0.3.8"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
+checksum = "8093091eeb260906a183e6ae1abdba2ef5ef2257a21801128899c3fc699229c6"
 dependencies = [
  "winapi-i686-pc-windows-gnu",
  "winapi-x86_64-pc-windows-gnu",
diff --git a/Cargo.toml b/Cargo.toml
index c3e4ccc..f7e0f67 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -39,7 +39,7 @@ atty = "0.2"
 ignore = "0.4.3"
 lazy_static = "1.1.0"
 num_cpus = "1.8"
-regex = "1.0.0"
+regex = "1.5.4"
 regex-syntax = "0.6"
 ctrlc = "3.1"
 humantime = "2.0"
@@ -47,6 +47,8 @@ lscolors = "0.7"
 globset = "0.4"
 anyhow = "1.0"
 dirs-next = "2.0"
+normpath = "0.3"
+chrono = "0.4"
 
 [dependencies.clap]
 version = "2.31.2"
@@ -67,7 +69,7 @@ jemallocator = "0.3.0"
 [dev-dependencies]
 diff = "0.1"
 tempdir = "0.3"
-filetime = "0.2.1"
+filetime = "0.2.14"
 
 [profile.release]
 lto = true
diff --git a/README.md b/README.md
index 511b23a..39f926e 100644
--- a/README.md
+++ b/README.md
@@ -1,7 +1,6 @@
 # fd
 
-[![Build Status](https://travis-ci.org/sharkdp/fd.svg?branch=master)](https://travis-ci.org/sharkdp/fd)
-[![Build status](https://ci.appveyor.com/api/projects/status/21c4p5fwggc5gy3j/branch/master?svg=true)](https://ci.appveyor.com/project/sharkdp/fd/branch/master)
+[![CICD](https://github.com/sharkdp/fd/actions/workflows/CICD.yml/badge.svg)](https://github.com/sharkdp/fd/actions/workflows/CICD.yml)
 [![Version info](https://img.shields.io/crates/v/fd-find.svg)](https://crates.io/crates/fd-find)
 [[中文](https://github.com/chinanf-boy/fd-zh)]
 [[한국어](https://github.com/spearkkk/fd-kor)]
@@ -116,6 +115,15 @@ src/lscolors/mod.rs
 tests/testenv/mod.rs
 ```
 
+### Searching for a particular file name
+
+ To find files with exactly the provided search pattern, use the `-g` (or `--glob`) option:
+``` bash
+> fd -g libc.so /usr
+/usr/lib32/libc.so
+/usr/lib/libc.so
+```
+
 ### Hidden and ignored files
 By default, *fd* does not search hidden directories and does not show hidden files in the
 search results. To disable this behavior, we can use the `-H` (or `--hidden`) option:
@@ -500,6 +508,7 @@ sudo apt install fd-find
 Note that the binary is called `fdfind` as the binary name `fd` is already used by another package.
 It is recommended that after installation, you add a link to `fd` by executing command
 `ln -s $(which fdfind) ~/.local/bin/fd`, in order to use `fd` in the same way as in this documentation.
+Make sure that `$HOME/.local/bin` is in your `$PATH`.
 
 If you use an older version of Ubuntu, you can download the latest `.deb` package from the
 [release page](https://github.com/sharkdp/fd/releases) and install it via:
@@ -517,6 +526,7 @@ sudo apt-get install fd-find
 Note that the binary is called `fdfind` as the binary name `fd` is already used by another package.
 It is recommended that after installation, you add a link to `fd` by executing command
 `ln -s $(which fdfind) ~/.local/bin/fd`, in order to use `fd` in the same way as in this documentation.
+Make sure that `$HOME/.local/bin` is in your `$PATH`.
 
 ### On Fedora
 
@@ -620,7 +630,7 @@ With Rust's package manager [cargo](https://github.com/rust-lang/cargo), you can
 ```
 cargo install fd-find
 ```
-Note that rust version *1.36.0* or later is required.
+Note that rust version *1.42.0* or later is required.
 
 ### From binaries
 
@@ -645,6 +655,7 @@ cargo install --path .
 
 - [sharkdp](https://github.com/sharkdp)
 - [tmccombs](https://github.com/tmccombs)
+- [tavianator](https://github.com/tavianator)
 
 ## License
 
diff --git a/appveyor.yml b/appveyor.yml
deleted file mode 100644
index a7f7075..0000000
--- a/appveyor.yml
+++ /dev/null
@@ -1,71 +0,0 @@
-# Source: https://github.com/starkat99/appveyor-rust/
-
-environment:
-  global:
-    PROJECT_NAME: fd
-  matrix:
-    # Stable channel
-    - TARGET: i686-pc-windows-gnu
-      CHANNEL: stable
-    - TARGET: i686-pc-windows-msvc
-      CHANNEL: stable
-    - TARGET: x86_64-pc-windows-gnu
-      CHANNEL: stable
-    - TARGET: x86_64-pc-windows-msvc
-      CHANNEL: stable
-
-# Install Rust and Cargo
-# (Based on from https://github.com/rust-lang/libc/blob/master/appveyor.yml)
-install:
-  - curl -sSf -o rustup-init.exe https://win.rustup.rs
-  - rustup-init.exe --default-host %TARGET% --default-toolchain %CHANNEL% -y
-  - set PATH=%PATH%;C:\Users\appveyor\.cargo\bin
-  - rustc -Vv
-  - cargo -V
-
-# 'cargo test' takes care of building for us, so disable Appveyor's build stage. This prevents
-# the "directory does not contain a project or solution file" error.
-# source: https://github.com/starkat99/appveyor-rust/blob/master/appveyor.yml#L113
-build: false
-
-# Equivalent to Travis' `script` phase
-test_script:
-  - cargo build --verbose
-  - cargo run
-  - cargo test
-
-before_deploy:
-  # Generate artifacts for release
-  - cargo build --release
-  - mkdir staging
-  - copy target\release\fd.exe staging
-  - ps: copy target\release\build\fd-find*\out\_fd.ps1 staging
-  - cd staging
-    # release zipfile will look like 'rust-everywhere-v1.2.3-x86_64-pc-windows-msvc'
-  - 7z a ../%PROJECT_NAME%-%APPVEYOR_REPO_TAG_NAME%-%TARGET%.zip *
-  - appveyor PushArtifact ../%PROJECT_NAME%-%APPVEYOR_REPO_TAG_NAME%-%TARGET%.zip
-
-deploy:
-  description: 'Windows release'
-  # All the zipped artifacts will be deployed
-  artifact: /.*\.zip/
-  # Here's how:
-  # - Go to 'https://github.com/settings/tokens/new' and generate a Token with only the
-  # `public_repo` scope enabled
-  # - Then go to 'https://ci.appveyor.com/tools/encrypt' and enter the newly generated token.
-  # - Enter the "encrypted value" below
-  auth_token:
-    secure: mWJ8ieZdGEgHf232fdMyzb9T1rKbkJivYbp/REMo8ax3X5vkQJDHhFjWeuWA3wIC
-  provider: GitHub
-  # deploy when a new tag is pushed and only on the stable channel
-  on:
-    # channel to use to produce the release artifacts
-    CHANNEL: stable
-    appveyor_repo_tag: true
-
-branches:
-  only:
-    - master
-    # IMPORTANT Regex to match tags. Required, or appveyor may not trigger deploys when a new tag
-    # is pushed. This regex matches semantic versions like v1.2.3-rc4+2016.02.22
-    - /^v\d+\.\d+\.\d+.*$/
\ No newline at end of file
diff --git a/build.rs b/build.rs
index a560325..ddf9e15 100644
--- a/build.rs
+++ b/build.rs
@@ -5,7 +5,7 @@ use clap::Shell;
 include!("src/app.rs");
 
 fn main() {
-    let min_version = "1.36";
+    let min_version = "1.42";
 
     match version_check::is_min_version(min_version) {
         Some(true) => {}
diff --git a/ci/.gitattributes b/ci/.gitattributes
deleted file mode 100644
index 36eaad9..0000000
--- a/ci/.gitattributes
+++ /dev/null
@@ -1,1 +0,0 @@
-* linguist-vendored
diff --git a/ci/before_deploy.bash b/ci/before_deploy.bash
deleted file mode 100755
index 8041415..0000000
--- a/ci/before_deploy.bash
+++ /dev/null
@@ -1,179 +0,0 @@
-#!/usr/bin/env bash
-# Building and packaging for release
-
-set -ex
-
-build() {
-    cargo build --target "$TARGET" --release --verbose
-}
-
-pack() {
-    local tempdir
-    local out_dir
-    local package_name
-    local gcc_prefix
-
-    tempdir=$(mktemp -d 2>/dev/null || mktemp -d -t tmp)
-    out_dir=$(pwd)
-    package_name="$PROJECT_NAME-$TRAVIS_TAG-$TARGET"
-
-    if [[ $TARGET == arm-unknown-linux-* ]]; then
-        gcc_prefix="arm-linux-gnueabihf-"
-    else
-        gcc_prefix=""
-    fi
-
-    # create a "staging" directory
-    mkdir "$tempdir/$package_name"
-    mkdir "$tempdir/$package_name/autocomplete"
-
-    # copying the main binary
-    cp "target/$TARGET/release/$PROJECT_NAME" "$tempdir/$package_name/"
-    "${gcc_prefix}"strip "$tempdir/$package_name/$PROJECT_NAME"
-
-    # manpage, readme and license
-    cp "doc/$PROJECT_NAME.1" "$tempdir/$package_name"
-    cp README.md "$tempdir/$package_name"
-    cp LICENSE-MIT "$tempdir/$package_name"
-    cp LICENSE-APACHE "$tempdir/$package_name"
-
-    # various autocomplete
-    cp target/"$TARGET"/release/build/"$PROJECT_NAME"-*/out/"$PROJECT_NAME".bash "$tempdir/$package_name/autocomplete/${PROJECT_NAME}.bash-completion"
-    cp target/"$TARGET"/release/build/"$PROJECT_NAME"-*/out/"$PROJECT_NAME".fish "$tempdir/$package_name/autocomplete"
-    cp contrib/completion/_"$PROJECT_NAME" "$tempdir/$package_name/autocomplete"
-
-    # archiving
-    pushd "$tempdir"
-    tar czf "$out_dir/$package_name.tar.gz" "$package_name"/*
-    popd
-    rm -r "$tempdir"
-}
-
-make_deb() {
-    local tempdir
-    local architecture
-    local version
-    local dpkgname
-    local conflictname
-    local homepage
-    local maintainer
-    local gcc_prefix
-
-    homepage="https://github.com/sharkdp/fd"
-    maintainer="David Peter <mail@david-peter.de>"
-
-    case $TARGET in
-        x86_64*)
-            architecture=amd64
-            gcc_prefix=""
-            ;;
-        i686*)
-            architecture=i386
-            gcc_prefix=""
-            ;;
-        arm*hf)
-            architecture=armhf
-            gcc_prefix="arm-linux-gnueabihf-"
-            ;;
-        *)
-            echo "make_deb: skipping target '${TARGET}'" >&2
-            return 0
-            ;;
-    esac
-    version=${TRAVIS_TAG#v}
-    if [[ $TARGET = *musl* ]]; then
-      dpkgname=$PROJECT_NAME-musl
-      conflictname=$PROJECT_NAME
-    else
-      dpkgname=$PROJECT_NAME
-      conflictname=$PROJECT_NAME-musl
-    fi
-
-    tempdir=$(mktemp -d 2>/dev/null || mktemp -d -t tmp)
-
-    # copy the main binary
-    install -Dm755 "target/$TARGET/release/$PROJECT_NAME" "$tempdir/usr/bin/$PROJECT_NAME"
-    "${gcc_prefix}"strip "$tempdir/usr/bin/$PROJECT_NAME"
-
-    # manpage
-    install -Dm644 "doc/$PROJECT_NAME.1" "$tempdir/usr/share/man/man1/$PROJECT_NAME.1"
-    gzip --best "$tempdir/usr/share/man/man1/$PROJECT_NAME.1"
-
-    # readme and license
-    install -Dm644 README.md "$tempdir/usr/share/doc/$PROJECT_NAME/README.md"
-    install -Dm644 LICENSE-MIT "$tempdir/usr/share/doc/$PROJECT_NAME/LICENSE-MIT"
-    install -Dm644 LICENSE-APACHE "$tempdir/usr/share/doc/$PROJECT_NAME/LICENSE-APACHE"
-    cat > "$tempdir/usr/share/doc/$PROJECT_NAME/copyright" <<EOF
-Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
-Upstream-Name: $PROJECT_NAME
-Source: $homepage
-
-Files: *
-Copyright: $maintainer
-License: Apache-2.0 or MIT
-
-License: Apache-2.0
- On Debian systems, the complete text of the Apache-2.0 can be found in the
- file /usr/share/common-licenses/Apache-2.0.
-
-License: MIT
- Permission is hereby granted, free of charge, to any
- person obtaining a copy of this software and associated
- documentation files (the "Software"), to deal in the
- Software without restriction, including without
- limitation the rights to use, copy, modify, merge,
- publish, distribute, sublicense, and/or sell copies of
- the Software, and to permit persons to whom the Software
- is furnished to do so, subject to the following
- conditions:
- .
- The above copyright notice and this permission notice
- shall be included in all copies or substantial portions
- of the Software.
- .
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
- ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
- TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
- PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
- SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
- CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
- OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
- IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
- DEALINGS IN THE SOFTWARE.
-EOF
-
-    # completions
-    install -Dm644 target/$TARGET/release/build/$PROJECT_NAME-*/out/$PROJECT_NAME.bash "$tempdir/usr/share/bash-completion/completions/${PROJECT_NAME}"
-    install -Dm644 target/$TARGET/release/build/$PROJECT_NAME-*/out/$PROJECT_NAME.fish "$tempdir/usr/share/fish/completions/$PROJECT_NAME.fish"
-    install -Dm644 contrib/completion/_"$PROJECT_NAME" "$tempdir/usr/share/zsh/vendor-completions/_$PROJECT_NAME"
-
-    # Control file
-    mkdir "$tempdir/DEBIAN"
-    cat > "$tempdir/DEBIAN/control" <<EOF
-Package: $dpkgname
-Version: $version
-Section: utils
-Priority: optional
-Maintainer: $maintainer
-Architecture: $architecture
-Provides: $PROJECT_NAME
-Conflicts: $conflictname
-Homepage: $homepage
-Description: Simple, fast and user-friendly alternative to find
- While fd does not seek to mirror all of find's powerful functionality, it
- provides sensible (opinionated) defaults for 80% of the use cases.
-EOF
-
-    fakeroot dpkg-deb --build "$tempdir" "${dpkgname}_${version}_${architecture}.deb"
-}
-
-
-main() {
-    build
-    pack
-    if [[ $TARGET = *linux* ]]; then
-      make_deb
-    fi
-}
-
-main
diff --git a/ci/before_install.bash b/ci/before_install.bash
deleted file mode 100755
index 3e3f141..0000000
--- a/ci/before_install.bash
+++ /dev/null
@@ -1,26 +0,0 @@
-#!/usr/bin/env bash
-
-set -ex
-
-if [ "$TRAVIS_OS_NAME" != linux ]; then
-    exit 0
-fi
-
-sudo apt-get update
-
-# needed to build deb packages
-sudo apt-get install -y fakeroot
-
-# needed for i686 linux gnu target
-if [[ $TARGET == i686-unknown-linux-gnu ]]; then
-    sudo apt-get install -y gcc-multilib
-fi
-
-# needed for cross-compiling for arm
-if [[ $TARGET == arm-unknown-linux-* ]]; then
-    sudo apt-get install -y \
-        gcc-4.8-arm-linux-gnueabihf \
-        binutils-arm-linux-gnueabihf \
-        libc6-armhf-cross \
-        libc6-dev-armhf-cross
-fi
diff --git a/ci/script.bash b/ci/script.bash
deleted file mode 100755
index 5316210..0000000
--- a/ci/script.bash
+++ /dev/null
@@ -1,11 +0,0 @@
-#!/usr/bin/env bash
-
-set -ex
-
-# Incorporate TARGET env var to the build and test process
-cargo build --target "$TARGET" --verbose
-
-# We cannot run arm executables on linux
-if [[ $TARGET != arm-unknown-linux-* ]]; then
-    cargo test --target "$TARGET" --verbose
-fi
diff --git a/doc/fd.1 b/doc/fd.1
index 7824987..94feaa1 100644
--- a/doc/fd.1
+++ b/doc/fd.1
@@ -216,8 +216,11 @@ tebibytes
 .RE
 .TP
 .BI "\-\-changed-within " date|duration
-Filter results based on the file modification time. The argument can be provided as a specific
-point in time (\fIYYYY-MM-DD HH:MM:SS\fR) or as a duration (\fI10h, 1d, 35min\fR).
+Filter results based on the file modification time.
+Files with modification times greater than or equal to the argument will be returned.
+The argument can be provided as a duration (\fI10h, 1d, 35min\fR) or as a specific point
+in time in either full RFC3339 format with time zone, or as a date or datetime in the
+local time zone (\fIYYYY-MM-DD\fR or \fIYYYY-MM-DD HH:MM:SS\fR).
 .B --change-newer-than
 can be used as an alias.
 
@@ -226,8 +229,11 @@ Examples:
   \-\-change-newer-than "2018-10-27 10:00:00"
 .TP
 .BI "\-\-changed-before " date|duration
-Filter results based on the file modification time. The argument can be provided as a specific
-point in time (\fIYYYY-MM-DD HH:MM:SS\fR) or as a duration (\fI10h, 1d, 35min\fR).
+Filter results based on the file modification time.
+Files with modification times less than or equal to the argument will be returned.
+The argument can be provided as a duration (\fI10h, 1d, 35min\fR) or as a specific point
+in time in either full RFC3339 format with time zone, or as a date or datetime in the
+local time zone (\fIYYYY-MM-DD\fR or \fIYYYY-MM-DD HH:MM:SS\fR).
 .B --change-older-than
 can be used as an alias.
 
@@ -259,12 +265,22 @@ Provide paths to search as an alternative to the positional \fIpath\fR argument.
 \'fd [FLAGS/OPTIONS] \-\-search\-path PATH \-\-search\-path PATH2 [PATTERN]\'
 .TP
 .BI "\-x, \-\-exec " command
+.RS
 Execute
 .I command
-for each search result in parallel (use --threads=1 for sequential command execution). The following placeholders are substituted by a path derived from the current search result:
+for each search result in parallel (use --threads=1 for sequential command execution).
+
+Note that all subsequent positional arguments are considered to be arguments to the
+.I command
+- not to fd.
+It is therefore recommended to place the \-x/\-\-exec option last. Alternatively, you can supply
+a ';' argument to end the argument list and continue with more fd options.
+Most shells require ';' to be escaped: '\\;'.
+
+The following placeholders are substituted before the command is executed:
 .RS
 .IP {}
-path
+path (of the current search result)
 .IP {/}
 basename
 .IP {//}
@@ -274,15 +290,33 @@ path without file extension
 .IP {/.}
 basename without file extension
 .RE
+
+If no placeholder is present, an implicit "{}" at the end is assumed.
+
+Examples:
+
+  - find all *.zip files and unzip them:
+
+        fd -e zip -x unzip
+
+  - find *.h and *.cpp files and run "clang-format -i .." for each of them:
+
+        fd -e h -e cpp -x clang-format -i
+
+  - Convert all *.jpg files to *.png files:
+
+        fd -e jpg -x convert {} {.}.png
+.RE
 .TP
 .BI "\-X, \-\-exec-batch " command
+.RS
 Execute
 .I command
-with all search results at once.
-A single occurence of the following placeholders is authorized and substituted by the paths derived from the search results before the command is executed:
+once, with all search results as arguments.
+One of the following placeholders is substituted before the command is executed:
 .RS
 .IP {}
-path
+path (of all search results)
 .IP {/}
 basename
 .IP {//}
@@ -292,6 +326,22 @@ path without file extension
 .IP {/.}
 basename without file extension
 .RE
+
+If no placeholder is present, an implicit "{}" at the end is assumed.
+
+Examples:
+
+  - Find all test_*.py files and open them in your favorite editor:
+
+        fd -g 'test_*.py' -X vim
+
+    Note that this executes a single "vim" process with all search results as arguments.
+
+  - Find all *.rs files and count the lines with "wc -l ...":
+
+        fd -e rs -X wc -l
+.RE
+
 .SH PATTERN SYNTAX
 The regular expression syntax used by fd is documented here:
 
diff --git a/src/app.rs b/src/app.rs
index 5781daa..2d6ec94 100644
--- a/src/app.rs
+++ b/src/app.rs
@@ -298,16 +298,24 @@ pub fn build_app() -> App<'static, 'static> {
                 .conflicts_with("list-details")
                 .help("Execute a command for each search result")
                 .long_help(
-                    "Execute a command for each search result in parallel (use --threads=1 for sequential command execution).\n\
-                     All arguments following --exec are taken to be arguments to the command until the \
-                     argument ';' is encountered.\n\
-                     Each occurrence of the following placeholders is substituted by a path derived from the \
-                     current search result before the command is executed:\n  \
-                       '{}':   path\n  \
+                    "Execute a command for each search result in parallel (use --threads=1 for sequential command execution). \
+                     All positional arguments following --exec are considered to be arguments to the command - not to fd. \
+                     It is therefore recommended to place the '-x'/'--exec' option last.\n\
+                     The following placeholders are substituted before the command is executed:\n  \
+                       '{}':   path (of the current search result)\n  \
                        '{/}':  basename\n  \
                        '{//}': parent directory\n  \
                        '{.}':  path without file extension\n  \
-                       '{/.}': basename without file extension",
+                       '{/.}': basename without file extension\n\n\
+                     If no placeholder is present, an implicit \"{}\" at the end is assumed.\n\n\
+                     Examples:\n\n  \
+                       - find all *.zip files and unzip them:\n\n      \
+                           fd -e zip -x unzip\n\n  \
+                       - find *.h and *.cpp files and run \"clang-format -i ..\" for each of them:\n\n      \
+                           fd -e h -e cpp -x clang-format -i\n\n  \
+                       - Convert all *.jpg files to *.png files:\n\n      \
+                           fd -e jpg -x convert {} {.}.png\
+                    ",
                 ),
         )
         .arg(
@@ -321,16 +329,20 @@ pub fn build_app() -> App<'static, 'static> {
                 .conflicts_with_all(&["exec", "list-details"])
                 .help("Execute a command with all search results at once")
                 .long_help(
-                    "Execute a command with all search results at once.\n\
-                     All arguments following --exec-batch are taken to be arguments to the command until the \
-                     argument ';' is encountered.\n\
-                     A single occurrence of the following placeholders is authorized and substituted by the paths derived from the \
-                     search results before the command is executed:\n  \
-                       '{}':   path\n  \
+                    "Execute the given command once, with all search results as arguments.\n\
+                     One of the following placeholders is substituted before the command is executed:\n  \
+                       '{}':   path (of all search results)\n  \
                        '{/}':  basename\n  \
                        '{//}': parent directory\n  \
                        '{.}':  path without file extension\n  \
-                       '{/.}': basename without file extension",
+                       '{/.}': basename without file extension\n\n\
+                     If no placeholder is present, an implicit \"{}\" at the end is assumed.\n\n\
+                     Examples:\n\n  \
+                       - Find all test_*.py files and open them in your favorite editor:\n\n      \
+                           fd -g 'test_*.py' -X vim\n\n  \
+                       - Find all *.rs files and count the lines with \"wc -l ...\":\n\n      \
+                           fd -e rs -X wc -l\
+                     "
                 ),
         )
         .arg(
diff --git a/src/exec/mod.rs b/src/exec/mod.rs
index c47e32c..f24c0e3 100644
--- a/src/exec/mod.rs
+++ b/src/exec/mod.rs
@@ -201,10 +201,7 @@ enum ArgumentTemplate {
 
 impl ArgumentTemplate {
     pub fn has_tokens(&self) -> bool {
-        match self {
-            ArgumentTemplate::Tokens(_) => true,
-            _ => false,
-        }
+        matches!(self, ArgumentTemplate::Tokens(_))
     }
 
     /// Generate an argument from this template. If path_separator is Some, then it will replace
diff --git a/src/exit_codes.rs b/src/exit_codes.rs
index 96bce96..720440f 100644
--- a/src/exit_codes.rs
+++ b/src/exit_codes.rs
@@ -5,9 +5,9 @@ pub enum ExitCode {
     KilledBySigint,
 }
 
-impl Into<i32> for ExitCode {
-    fn into(self) -> i32 {
-        match self {
+impl From<ExitCode> for i32 {
+    fn from(code: ExitCode) -> Self {
+        match code {
             ExitCode::Success => 0,
             ExitCode::GeneralError => 1,
             ExitCode::KilledBySigint => 130,
@@ -16,13 +16,13 @@ impl Into<i32> for ExitCode {
 }
 
 impl ExitCode {
-    fn is_error(&self) -> bool {
-        *self != ExitCode::Success
+    fn is_error(self) -> bool {
+        self != ExitCode::Success
     }
 }
 
 pub fn merge_exitcodes(results: &[ExitCode]) -> ExitCode {
-    if results.iter().any(ExitCode::is_error) {
+    if results.iter().any(|&c| ExitCode::is_error(c)) {
         return ExitCode::GeneralError;
     }
     ExitCode::Success
diff --git a/src/filesystem.rs b/src/filesystem.rs
index 15ca56d..6680ae5 100644
--- a/src/filesystem.rs
+++ b/src/filesystem.rs
@@ -7,6 +7,8 @@ use std::io;
 use std::os::unix::fs::{FileTypeExt, PermissionsExt};
 use std::path::{Path, PathBuf};
 
+use normpath::PathExt;
+
 use crate::walk;
 
 pub fn path_absolute_form(path: &Path) -> io::Result<PathBuf> {
@@ -33,10 +35,10 @@ pub fn absolute_path(path: &Path) -> io::Result<PathBuf> {
     Ok(path_buf)
 }
 
-// Path::is_dir() is not guaranteed to be intuitively correct for "." and ".."
-// See: https://github.com/rust-lang/rust/issues/45302
-pub fn is_dir(path: &Path) -> bool {
-    path.is_dir() && (path.file_name().is_some() || path.canonicalize().is_ok())
+pub fn is_existing_directory(path: &Path) -> bool {
+    // Note: we do not use `.exists()` here, as `.` always exists, even if
+    // the CWD has been deleted.
+    path.is_dir() && (path.file_name().is_some() || path.normalize().is_ok())
 }
 
 #[cfg(any(unix, target_os = "redox"))]
@@ -68,22 +70,22 @@ pub fn is_empty(entry: &walk::DirEntry) -> bool {
 }
 
 #[cfg(any(unix, target_os = "redox"))]
-pub fn is_socket(ft: &fs::FileType) -> bool {
+pub fn is_socket(ft: fs::FileType) -> bool {
     ft.is_socket()
 }
 
 #[cfg(windows)]
-pub fn is_socket(_: &fs::FileType) -> bool {
+pub fn is_socket(_: fs::FileType) -> bool {
     false
 }
 
 #[cfg(any(unix, target_os = "redox"))]
-pub fn is_pipe(ft: &fs::FileType) -> bool {
+pub fn is_pipe(ft: fs::FileType) -> bool {
     ft.is_fifo()
 }
 
 #[cfg(windows)]
-pub fn is_pipe(_: &fs::FileType) -> bool {
+pub fn is_pipe(_: fs::FileType) -> bool {
     false
 }
 
diff --git a/src/filter/size.rs b/src/filter/size.rs
index d6eaf14..5435b19 100644
--- a/src/filter/size.rs
+++ b/src/filter/size.rs
@@ -93,70 +93,70 @@ mod tests {
         kilo_plus:                ("+1k",     SizeFilter::Min(1000)),
         kilo_plus_suffix:         ("+1kb",    SizeFilter::Min(1000)),
         kilo_minus:               ("-1k",     SizeFilter::Max(1000)),
-        kilo_minus_multiplier:    ("-100k",   SizeFilter::Max(100000)),
+        kilo_minus_multiplier:    ("-100k",   SizeFilter::Max(100_000)),
         kilo_minus_suffix:        ("-1kb",    SizeFilter::Max(1000)),
         kilo_plus_upper:          ("+1K",     SizeFilter::Min(1000)),
         kilo_plus_suffix_upper:   ("+1KB",    SizeFilter::Min(1000)),
         kilo_minus_upper:         ("-1K",     SizeFilter::Max(1000)),
         kilo_minus_suffix_upper:  ("-1Kb",    SizeFilter::Max(1000)),
         kibi_plus:                ("+1ki",    SizeFilter::Min(1024)),
-        kibi_plus_multiplier:     ("+10ki",   SizeFilter::Min(10240)),
+        kibi_plus_multiplier:     ("+10ki",   SizeFilter::Min(10_240)),
         kibi_plus_suffix:         ("+1kib",   SizeFilter::Min(1024)),
         kibi_minus:               ("-1ki",    SizeFilter::Max(1024)),
-        kibi_minus_multiplier:    ("-100ki",  SizeFilter::Max(102400)),
+        kibi_minus_multiplier:    ("-100ki",  SizeFilter::Max(102_400)),
         kibi_minus_suffix:        ("-1kib",   SizeFilter::Max(1024)),
         kibi_plus_upper:          ("+1KI",    SizeFilter::Min(1024)),
         kibi_plus_suffix_upper:   ("+1KiB",   SizeFilter::Min(1024)),
         kibi_minus_upper:         ("-1Ki",    SizeFilter::Max(1024)),
         kibi_minus_suffix_upper:  ("-1KIB",   SizeFilter::Max(1024)),
-        mega_plus:                ("+1m",     SizeFilter::Min(1000000)),
-        mega_plus_suffix:         ("+1mb",    SizeFilter::Min(1000000)),
-        mega_minus:               ("-1m",     SizeFilter::Max(1000000)),
-        mega_minus_suffix:        ("-1mb",    SizeFilter::Max(1000000)),
-        mega_plus_upper:          ("+1M",     SizeFilter::Min(1000000)),
-        mega_plus_suffix_upper:   ("+1MB",    SizeFilter::Min(1000000)),
-        mega_minus_upper:         ("-1M",     SizeFilter::Max(1000000)),
-        mega_minus_suffix_upper:  ("-1Mb",    SizeFilter::Max(1000000)),
-        mebi_plus:                ("+1mi",    SizeFilter::Min(1048576)),
-        mebi_plus_suffix:         ("+1mib",   SizeFilter::Min(1048576)),
-        mebi_minus:               ("-1mi",    SizeFilter::Max(1048576)),
-        mebi_minus_suffix:        ("-1mib",   SizeFilter::Max(1048576)),
-        mebi_plus_upper:          ("+1MI",    SizeFilter::Min(1048576)),
-        mebi_plus_suffix_upper:   ("+1MiB",   SizeFilter::Min(1048576)),
-        mebi_minus_upper:         ("-1Mi",    SizeFilter::Max(1048576)),
-        mebi_minus_suffix_upper:  ("-1MIB",   SizeFilter::Max(1048576)),
-        giga_plus:                ("+1g",     SizeFilter::Min(1000000000)),
-        giga_plus_suffix:         ("+1gb",    SizeFilter::Min(1000000000)),
-        giga_minus:               ("-1g",     SizeFilter::Max(1000000000)),
-        giga_minus_suffix:        ("-1gb",    SizeFilter::Max(1000000000)),
-        giga_plus_upper:          ("+1G",     SizeFilter::Min(1000000000)),
-        giga_plus_suffix_upper:   ("+1GB",    SizeFilter::Min(1000000000)),
-        giga_minus_upper:         ("-1G",     SizeFilter::Max(1000000000)),
-        giga_minus_suffix_upper:  ("-1Gb",    SizeFilter::Max(1000000000)),
-        gibi_plus:                ("+1gi",    SizeFilter::Min(1073741824)),
-        gibi_plus_suffix:         ("+1gib",   SizeFilter::Min(1073741824)),
-        gibi_minus:               ("-1gi",    SizeFilter::Max(1073741824)),
-        gibi_minus_suffix:        ("-1gib",   SizeFilter::Max(1073741824)),
-        gibi_plus_upper:          ("+1GI",    SizeFilter::Min(1073741824)),
-        gibi_plus_suffix_upper:   ("+1GiB",   SizeFilter::Min(1073741824)),
-        gibi_minus_upper:         ("-1Gi",    SizeFilter::Max(1073741824)),
-        gibi_minus_suffix_upper:  ("-1GIB",   SizeFilter::Max(1073741824)),
-        tera_plus:                ("+1t",     SizeFilter::Min(1000000000000)),
-        tera_plus_suffix:         ("+1tb",    SizeFilter::Min(1000000000000)),
-        tera_minus:               ("-1t",     SizeFilter::Max(1000000000000)),
-        tera_minus_suffix:        ("-1tb",    SizeFilter::Max(1000000000000)),
-        tera_plus_upper:          ("+1T",     SizeFilter::Min(1000000000000)),
-        tera_plus_suffix_upper:   ("+1TB",    SizeFilter::Min(1000000000000)),
-        tera_minus_upper:         ("-1T",     SizeFilter::Max(1000000000000)),
-        tera_minus_suffix_upper:  ("-1Tb",    SizeFilter::Max(1000000000000)),
-        tebi_plus:                ("+1ti",    SizeFilter::Min(1099511627776)),
-        tebi_plus_suffix:         ("+1tib",   SizeFilter::Min(1099511627776)),
-        tebi_minus:               ("-1ti",    SizeFilter::Max(1099511627776)),
-        tebi_minus_suffix:        ("-1tib",   SizeFilter::Max(1099511627776)),
-        tebi_plus_upper:          ("+1TI",    SizeFilter::Min(1099511627776)),
-        tebi_plus_suffix_upper:   ("+1TiB",   SizeFilter::Min(1099511627776)),
-        tebi_minus_upper:         ("-1Ti",    SizeFilter::Max(1099511627776)),
-        tebi_minus_suffix_upper:  ("-1TIB",   SizeFilter::Max(1099511627776)),
+        mega_plus:                ("+1m",     SizeFilter::Min(1_000_000)),
+        mega_plus_suffix:         ("+1mb",    SizeFilter::Min(1_000_000)),
+        mega_minus:               ("-1m",     SizeFilter::Max(1_000_000)),
+        mega_minus_suffix:        ("-1mb",    SizeFilter::Max(1_000_000)),
+        mega_plus_upper:          ("+1M",     SizeFilter::Min(1_000_000)),
+        mega_plus_suffix_upper:   ("+1MB",    SizeFilter::Min(1_000_000)),
+        mega_minus_upper:         ("-1M",     SizeFilter::Max(1_000_000)),
+        mega_minus_suffix_upper:  ("-1Mb",    SizeFilter::Max(1_000_000)),
+        mebi_plus:                ("+1mi",    SizeFilter::Min(1_048_576)),
+        mebi_plus_suffix:         ("+1mib",   SizeFilter::Min(1_048_576)),
+        mebi_minus:               ("-1mi",    SizeFilter::Max(1_048_576)),
+        mebi_minus_suffix:        ("-1mib",   SizeFilter::Max(1_048_576)),
+        mebi_plus_upper:          ("+1MI",    SizeFilter::Min(1_048_576)),
+        mebi_plus_suffix_upper:   ("+1MiB",   SizeFilter::Min(1_048_576)),
+        mebi_minus_upper:         ("-1Mi",    SizeFilter::Max(1_048_576)),
+        mebi_minus_suffix_upper:  ("-1MIB",   SizeFilter::Max(1_048_576)),
+        giga_plus:                ("+1g",     SizeFilter::Min(1_000_000_000)),
+        giga_plus_suffix:         ("+1gb",    SizeFilter::Min(1_000_000_000)),
+        giga_minus:               ("-1g",     SizeFilter::Max(1_000_000_000)),
+        giga_minus_suffix:        ("-1gb",    SizeFilter::Max(1_000_000_000)),
+        giga_plus_upper:          ("+1G",     SizeFilter::Min(1_000_000_000)),
+        giga_plus_suffix_upper:   ("+1GB",    SizeFilter::Min(1_000_000_000)),
+        giga_minus_upper:         ("-1G",     SizeFilter::Max(1_000_000_000)),
+        giga_minus_suffix_upper:  ("-1Gb",    SizeFilter::Max(1_000_000_000)),
+        gibi_plus:                ("+1gi",    SizeFilter::Min(1_073_741_824)),
+        gibi_plus_suffix:         ("+1gib",   SizeFilter::Min(1_073_741_824)),
+        gibi_minus:               ("-1gi",    SizeFilter::Max(1_073_741_824)),
+        gibi_minus_suffix:        ("-1gib",   SizeFilter::Max(1_073_741_824)),
+        gibi_plus_upper:          ("+1GI",    SizeFilter::Min(1_073_741_824)),
+        gibi_plus_suffix_upper:   ("+1GiB",   SizeFilter::Min(1_073_741_824)),
+        gibi_minus_upper:         ("-1Gi",    SizeFilter::Max(1_073_741_824)),
+        gibi_minus_suffix_upper:  ("-1GIB",   SizeFilter::Max(1_073_741_824)),
+        tera_plus:                ("+1t",     SizeFilter::Min(1_000_000_000_000)),
+        tera_plus_suffix:         ("+1tb",    SizeFilter::Min(1_000_000_000_000)),
+        tera_minus:               ("-1t",     SizeFilter::Max(1_000_000_000_000)),
+        tera_minus_suffix:        ("-1tb",    SizeFilter::Max(1_000_000_000_000)),
+        tera_plus_upper:          ("+1T",     SizeFilter::Min(1_000_000_000_000)),
+        tera_plus_suffix_upper:   ("+1TB",    SizeFilter::Min(1_000_000_000_000)),
+        tera_minus_upper:         ("-1T",     SizeFilter::Max(1_000_000_000_000)),
+        tera_minus_suffix_upper:  ("-1Tb",    SizeFilter::Max(1_000_000_000_000)),
+        tebi_plus:                ("+1ti",    SizeFilter::Min(1_099_511_627_776)),
+        tebi_plus_suffix:         ("+1tib",   SizeFilter::Min(1_099_511_627_776)),
+        tebi_minus:               ("-1ti",    SizeFilter::Max(1_099_511_627_776)),
+        tebi_minus_suffix:        ("-1tib",   SizeFilter::Max(1_099_511_627_776)),
+        tebi_plus_upper:          ("+1TI",    SizeFilter::Min(1_099_511_627_776)),
+        tebi_plus_suffix_upper:   ("+1TiB",   SizeFilter::Min(1_099_511_627_776)),
+        tebi_minus_upper:         ("-1Ti",    SizeFilter::Max(1_099_511_627_776)),
+        tebi_minus_suffix_upper:  ("-1TIB",   SizeFilter::Max(1_099_511_627_776)),
     }
 
     /// Invalid parse testing
diff --git a/src/filter/time.rs b/src/filter/time.rs
index ca37f94..39979af 100644
--- a/src/filter/time.rs
+++ b/src/filter/time.rs
@@ -1,3 +1,5 @@
+use chrono::{offset::TimeZone, DateTime, Local, NaiveDate};
+
 use std::time::SystemTime;
 
 /// Filter based on time ranges.
@@ -11,9 +13,20 @@ impl TimeFilter {
     fn from_str(ref_time: &SystemTime, s: &str) -> Option<SystemTime> {
         humantime::parse_duration(s)
             .map(|duration| *ref_time - duration)
-            .or_else(|_| humantime::parse_rfc3339_weak(s))
-            .or_else(|_| humantime::parse_rfc3339_weak(&(s.to_owned() + " 00:00:00")))
             .ok()
+            .or_else(|| {
+                DateTime::parse_from_rfc3339(s)
+                    .map(|dt| dt.into())
+                    .ok()
+                    .or_else(|| {
+                        NaiveDate::parse_from_str(s, "%F")
+                            .map(|nd| nd.and_hms(0, 0, 0))
+                            .ok()
+                            .and_then(|ndt| Local.from_local_datetime(&ndt).single())
+                    })
+                    .or_else(|| Local.datetime_from_str(s, "%F %T").ok())
+                    .map(|dt| dt.into())
+            })
     }
 
     pub fn before(ref_time: &SystemTime, s: &str) -> Option<TimeFilter> {
@@ -39,7 +52,11 @@ mod tests {
 
     #[test]
     fn is_time_filter_applicable() {
-        let ref_time = humantime::parse_rfc3339("2010-10-10T10:10:10Z").unwrap();
+        let ref_time = Local
+            .datetime_from_str("2010-10-10 10:10:10", "%F %T")
+            .unwrap()
+            .into();
+
         assert!(TimeFilter::after(&ref_time, "1min")
             .unwrap()
             .applies_to(&ref_time));
@@ -76,5 +93,39 @@ mod tests {
         assert!(!TimeFilter::after(&ref_time, t10s_before)
             .unwrap()
             .applies_to(&t1m_ago));
+
+        let same_day = "2010-10-10";
+        assert!(!TimeFilter::before(&ref_time, same_day)
+            .unwrap()
+            .applies_to(&ref_time));
+        assert!(!TimeFilter::before(&ref_time, same_day)
+            .unwrap()
+            .applies_to(&t1m_ago));
+
+        assert!(TimeFilter::after(&ref_time, same_day)
+            .unwrap()
+            .applies_to(&ref_time));
+        assert!(TimeFilter::after(&ref_time, same_day)
+            .unwrap()
+            .applies_to(&t1m_ago));
+
+        let ref_time = DateTime::parse_from_rfc3339("2010-10-10T10:10:10+00:00")
+            .unwrap()
+            .into();
+        let t1m_ago = ref_time - Duration::from_secs(60);
+        let t10s_before = "2010-10-10T10:10:00+00:00";
+        assert!(!TimeFilter::before(&ref_time, t10s_before)
+            .unwrap()
+            .applies_to(&ref_time));
+        assert!(TimeFilter::before(&ref_time, t10s_before)
+            .unwrap()
+            .applies_to(&t1m_ago));
+
+        assert!(TimeFilter::after(&ref_time, t10s_before)
+            .unwrap()
+            .applies_to(&ref_time));
+        assert!(!TimeFilter::after(&ref_time, t10s_before)
+            .unwrap()
+            .applies_to(&t1m_ago));
     }
 }
diff --git a/src/main.rs b/src/main.rs
index 6f0eece..8654688 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -21,6 +21,7 @@ use atty::Stream;
 use globset::GlobBuilder;
 use lscolors::LsColors;
 use regex::bytes::{RegexBuilder, RegexSetBuilder};
+use normpath::PathExt;
 
 use crate::error::print_error;
 use crate::exec::CommandTemplate;
@@ -55,7 +56,7 @@ fn run() -> Result<ExitCode> {
     // Set the current working directory of the process
     if let Some(base_directory) = matches.value_of_os("base-directory") {
         let base_directory = Path::new(base_directory);
-        if !filesystem::is_dir(base_directory) {
+        if !filesystem::is_existing_directory(base_directory) {
             return Err(anyhow!(
                 "The '--base-directory' path '{}' is not a directory.",
                 base_directory.to_string_lossy()
@@ -70,7 +71,7 @@ fn run() -> Result<ExitCode> {
     }
 
     let current_directory = Path::new(".");
-    if !filesystem::is_dir(current_directory) {
+    if !filesystem::is_existing_directory(current_directory) {
         return Err(anyhow!(
             "Could not retrieve current directory (has it been deleted?)."
         ));
@@ -95,7 +96,7 @@ fn run() -> Result<ExitCode> {
         let mut directories = vec![];
         for path in paths {
             let path_buffer = PathBuf::from(path);
-            if filesystem::is_dir(&path_buffer) {
+            if filesystem::is_existing_directory(&path_buffer) {
                 directories.push(path_buffer);
             } else {
                 print_error(format!(
@@ -120,7 +121,7 @@ fn run() -> Result<ExitCode> {
             .iter()
             .map(|path_buffer| {
                 path_buffer
-                    .canonicalize()
+                    .normalize()
                     .and_then(|pb| filesystem::absolute_path(pb.as_path()))
                     .unwrap()
             })
@@ -130,7 +131,7 @@ fn run() -> Result<ExitCode> {
     // Detect if the user accidentally supplied a path instead of a search pattern
     if !matches.is_present("full-path")
         && pattern.contains(std::path::MAIN_SEPARATOR)
-        && filesystem::is_dir(Path::new(pattern))
+        && Path::new(pattern).is_dir()
     {
         return Err(anyhow!(
             "The search pattern '{pattern}' contains a path-separation character ('{sep}') \
@@ -177,6 +178,22 @@ fn run() -> Result<ExitCode> {
         .value_of("path-separator")
         .map_or_else(filesystem::default_path_separator, |s| Some(s.to_owned()));
 
+    #[cfg(windows)]
+    {
+        if let Some(ref sep) = path_separator {
+            if sep.len() > 1 {
+                return Err(anyhow!(
+                    "A path separator must be exactly one byte, but \
+                 the given separator is {} bytes: '{}'.\n\
+                 In some shells on Windows, '/' is automatically \
+                 expanded. Try to use '//' instead.",
+                    sep.len(),
+                    sep
+                ));
+            };
+        };
+    }
+
     let ls_colors = if colored_output {
         Some(LsColors::from_env().unwrap_or_else(|| LsColors::from_string(DEFAULT_LS_COLORS)))
     } else {
@@ -331,20 +348,20 @@ fn run() -> Result<ExitCode> {
             .value_of("max-depth")
             .or_else(|| matches.value_of("rg-depth"))
             .or_else(|| matches.value_of("exact-depth"))
-            .map(|n| usize::from_str_radix(n, 10))
+            .map(|n| n.parse::<usize>())
             .transpose()
             .context("Failed to parse argument to --max-depth/--exact-depth")?,
         min_depth: matches
             .value_of("min-depth")
             .or_else(|| matches.value_of("exact-depth"))
-            .map(|n| usize::from_str_radix(n, 10))
+            .map(|n| n.parse::<usize>())
             .transpose()
             .context("Failed to parse argument to --min-depth/--exact-depth")?,
         prune: matches.is_present("prune"),
         threads: std::cmp::max(
             matches
                 .value_of("threads")
-                .map(|n| usize::from_str_radix(n, 10))
+                .map(|n| n.parse::<usize>())
                 .transpose()
                 .context("Failed to parse number of threads")?
                 .map(|n| {
@@ -360,7 +377,7 @@ fn run() -> Result<ExitCode> {
         ),
         max_buffer_time: matches
             .value_of("max-buffer-time")
-            .map(|n| u64::from_str_radix(n, 10))
+            .map(|n| n.parse::<u64>())
             .transpose()
             .context("Failed to parse max. buffer time argument")?
             .map(time::Duration::from_millis),
@@ -420,7 +437,7 @@ fn run() -> Result<ExitCode> {
         path_separator,
         max_results: matches
             .value_of("max-results")
-            .map(|n| usize::from_str_radix(n, 10))
+            .map(|n| n.parse::<usize>())
             .transpose()
             .context("Failed to parse --max-results argument")?
             .filter(|&n| n > 0)
diff --git a/src/output.rs b/src/output.rs
index 008f3a9..471b8fb 100644
--- a/src/output.rs
+++ b/src/output.rs
@@ -1,41 +1,47 @@
 use std::io::{self, StdoutLock, Write};
-use std::path::{Path, PathBuf};
+use std::path::Path;
 use std::process;
 use std::sync::atomic::{AtomicBool, Ordering};
 use std::sync::Arc;
 
 use lscolors::{LsColors, Style};
 
+use crate::error::print_error;
 use crate::exit_codes::ExitCode;
 use crate::filesystem::strip_current_dir;
 use crate::options::Options;
 
 fn replace_path_separator(path: &str, new_path_separator: &str) -> String {
-    path.replace(std::path::MAIN_SEPARATOR, &new_path_separator)
+    path.replace(std::path::MAIN_SEPARATOR, new_path_separator)
 }
 
 // TODO: this function is performance critical and can probably be optimized
 pub fn print_entry(
     stdout: &mut StdoutLock,
-    entry: &PathBuf,
+    entry: &Path,
     config: &Options,
     wants_to_quit: &Arc<AtomicBool>,
 ) {
     let path = if entry.is_absolute() {
-        entry.as_path()
+        entry
     } else {
         strip_current_dir(entry)
     };
 
     let r = if let Some(ref ls_colors) = config.ls_colors {
-        print_entry_colorized(stdout, path, config, ls_colors, &wants_to_quit)
+        print_entry_colorized(stdout, path, config, ls_colors, wants_to_quit)
     } else {
         print_entry_uncolorized(stdout, path, config)
     };
 
-    if r.is_err() {
-        // Probably a broken pipe. Exit gracefully.
-        process::exit(ExitCode::GeneralError.into());
+    if let Err(e) = r {
+        if e.kind() == ::std::io::ErrorKind::BrokenPipe {
+            // Exit gracefully in case of a broken pipe (e.g. 'fd ... | head -n 3').
+            process::exit(0);
+        } else {
+            print_error(format!("Could not write to output: {}", e));
+            process::exit(ExitCode::GeneralError.into());
+        }
     }
 }
 
@@ -57,7 +63,7 @@ fn print_entry_colorized(
 
         let mut path_string = component.to_string_lossy();
         if let Some(ref separator) = config.path_separator {
-            *path_string.to_mut() = replace_path_separator(&path_string, &separator);
+            *path_string.to_mut() = replace_path_separator(&path_string, separator);
         }
         write!(stdout, "{}", style.paint(path_string))?;
 
@@ -85,7 +91,7 @@ fn print_entry_uncolorized_base(
 
     let mut path_string = path.to_string_lossy();
     if let Some(ref separator) = config.path_separator {
-        *path_string.to_mut() = replace_path_separator(&path_string, &separator);
+        *path_string.to_mut() = replace_path_separator(&path_string, separator);
     }
     write!(stdout, "{}{}", path_string, separator)
 }
diff --git a/src/walk.rs b/src/walk.rs
index 456b717..a073873 100644
--- a/src/walk.rs
+++ b/src/walk.rs
@@ -358,27 +358,21 @@ fn spawn_senders(
                         DirEntry::BrokenSymlink(path)
                     }
                     _ => {
-                        match tx_thread.send(WorkerResult::Error(ignore::Error::WithPath {
+                        return match tx_thread.send(WorkerResult::Error(ignore::Error::WithPath {
                             path,
                             err: inner_err,
                         })) {
-                            Ok(_) => {
-                                return ignore::WalkState::Continue;
-                            }
-                            Err(_) => {
-                                return ignore::WalkState::Quit;
-                            }
+                            Ok(_) => ignore::WalkState::Continue,
+                            Err(_) => ignore::WalkState::Quit,
                         }
                     }
                 },
-                Err(err) => match tx_thread.send(WorkerResult::Error(err)) {
-                    Ok(_) => {
-                        return ignore::WalkState::Continue;
+                Err(err) => {
+                    return match tx_thread.send(WorkerResult::Error(err)) {
+                        Ok(_) => ignore::WalkState::Continue,
+                        Err(_) => ignore::WalkState::Quit,
                     }
-                    Err(_) => {
-                        return ignore::WalkState::Quit;
-                    }
-                },
+                }
             };
 
             if let Some(min_depth) = config.min_depth {
@@ -426,8 +420,8 @@ fn spawn_senders(
                     if (!file_types.files && entry_type.is_file())
                         || (!file_types.directories && entry_type.is_dir())
                         || (!file_types.symlinks && entry_type.is_symlink())
-                        || (!file_types.sockets && filesystem::is_socket(entry_type))
-                        || (!file_types.pipes && filesystem::is_pipe(entry_type))
+                        || (!file_types.sockets && filesystem::is_socket(*entry_type))
+                        || (!file_types.pipes && filesystem::is_pipe(*entry_type))
                         || (file_types.executables_only
                             && !entry
                                 .metadata()
@@ -437,8 +431,8 @@ fn spawn_senders(
                         || !(entry_type.is_file()
                             || entry_type.is_dir()
                             || entry_type.is_symlink()
-                            || filesystem::is_socket(entry_type)
-                            || filesystem::is_pipe(entry_type))
+                            || filesystem::is_socket(*entry_type)
+                            || filesystem::is_pipe(*entry_type))
                     {
                         return ignore::WalkState::Continue;
                     }
@@ -451,7 +445,7 @@ fn spawn_senders(
             {
                 if let Some(ref owner_constraint) = config.owner_constraint {
                     if let Ok(ref metadata) = entry_path.metadata() {
-                        if !owner_constraint.matches(&metadata) {
+                        if !owner_constraint.matches(metadata) {
                             return ignore::WalkState::Continue;
                         }
                     } else {


Test patch:
diff --git a/tests/testenv/mod.rs b/tests/testenv/mod.rs
index 6b58fc0..cc439bc 100644
--- a/tests/testenv/mod.rs
+++ b/tests/testenv/mod.rs
@@ -119,7 +119,7 @@ fn normalize_output(s: &str, trim_start: bool, normalize_line: bool) -> String {
             let line = line.replace('/', &std::path::MAIN_SEPARATOR.to_string());
             if normalize_line {
                 let mut words: Vec<_> = line.split_whitespace().collect();
-                words.sort();
+                words.sort_unstable();
                 return words.join(" ");
             }
             line
@@ -197,7 +197,7 @@ impl TestEnv {
 
         // Check for exit status.
         if !output.status.success() {
-            panic!(format_exit_error(args, &output));
+            panic!("{}", format_exit_error(args, &output));
         }
 
         output
@@ -236,7 +236,7 @@ impl TestEnv {
 
         // Compare actual output to expected output.
         if expected != actual {
-            panic!(format_output_error(args, &expected, &actual));
+            panic!("{}", format_output_error(args, &expected, &actual));
         }
     }
 
@@ -289,10 +289,13 @@ impl TestEnv {
 
             // Compare actual output to expected output.
             if !actual_err.trim_start().starts_with(&expected_error) {
-                panic!(format_output_error(args, &expected_error, &actual_err));
+                panic!(
+                    "{}",
+                    format_output_error(args, &expected_error, &actual_err)
+                );
             }
         }
 
-        return output.status;
+        output.status
     }
 }
diff --git a/tests/tests.rs b/tests/tests.rs
index 056a064..0b6e6b5 100644
--- a/tests/tests.rs
+++ b/tests/tests.rs
@@ -5,6 +5,7 @@ use std::io::Write;
 use std::path::Path;
 use std::time::{Duration, SystemTime};
 
+use normpath::PathExt;
 use regex::escape;
 
 use crate::testenv::TestEnv;
@@ -23,11 +24,13 @@ static DEFAULT_FILES: &[&str] = &[
     "e1 e2",
 ];
 
+#[allow(clippy::let_and_return)]
 fn get_absolute_root_path(env: &TestEnv) -> String {
     let path = env
         .test_root()
-        .canonicalize()
+        .normalize()
         .expect("absolute path")
+        .as_path()
         .to_str()
         .expect("string")
         .to_string();
@@ -1090,16 +1093,19 @@ fn test_symlink_as_root() {
 fn test_symlink_and_absolute_path() {
     let (te, abs_path) = get_test_env_with_abs_path(DEFAULT_DIRS, DEFAULT_FILES);
 
+    let expected_path = if cfg!(windows) { "symlink" } else { "one/two" };
+
     te.assert_output_subdirectory(
         "symlink",
         &["--absolute-path"],
         &format!(
-            "{abs_path}/one/two/c.foo
-            {abs_path}/one/two/C.Foo2
-            {abs_path}/one/two/three
-            {abs_path}/one/two/three/d.foo
-            {abs_path}/one/two/three/directory_foo",
-            abs_path = &abs_path
+            "{abs_path}/{expected_path}/c.foo
+            {abs_path}/{expected_path}/C.Foo2
+            {abs_path}/{expected_path}/three
+            {abs_path}/{expected_path}/three/d.foo
+            {abs_path}/{expected_path}/three/directory_foo",
+            abs_path = &abs_path,
+            expected_path = expected_path
         ),
     );
 }
@@ -1127,6 +1133,8 @@ fn test_symlink_and_full_path() {
     let root = te.system_root();
     let prefix = escape(&root.to_string_lossy());
 
+    let expected_path = if cfg!(windows) { "symlink" } else { "one/two" };
+
     te.assert_output_subdirectory(
         "symlink",
         &[
@@ -1135,10 +1143,11 @@ fn test_symlink_and_full_path() {
             &format!("^{prefix}.*three", prefix = prefix),
         ],
         &format!(
-            "{abs_path}/one/two/three
-            {abs_path}/one/two/three/d.foo
-            {abs_path}/one/two/three/directory_foo",
-            abs_path = &abs_path
+            "{abs_path}/{expected_path}/three
+            {abs_path}/{expected_path}/three/d.foo
+            {abs_path}/{expected_path}/three/directory_foo",
+            abs_path = &abs_path,
+            expected_path = expected_path
         ),
     );
 }
@@ -1663,7 +1672,7 @@ fn test_base_directory() {
     let (te, abs_path) = get_test_env_with_abs_path(DEFAULT_DIRS, DEFAULT_FILES);
     let abs_base_dir = &format!("{abs_path}/one/two", abs_path = &abs_path);
     te.assert_output(
-        &["--base-directory", &abs_base_dir, "foo", &abs_path],
+        &["--base-directory", abs_base_dir, "foo", &abs_path],
         &format!(
             "{abs_path}/a.foo
             {abs_path}/one/b.foo