Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Pre-commit hook for eslint, linting *only* staged changes.
#!/bin/bash
for file in $(git diff --cached --name-only | grep -E '\.(js|jsx)$')
do
git show ":$file" | node_modules/.bin/eslint --stdin --stdin-filename "$file" # we only want to lint the staged changes, not any un-staged changes
if [ $? -ne 0 ]; then
echo "ESLint failed on staged file '$file'. Please check your code and try again. You can run ESLint manually via npm run eslint."
exit 1 # exit with failure status
fi
done
@gichuwil

This comment has been minimized.

Copy link

@gichuwil gichuwil commented Sep 9, 2016

Thanks. Very helpful.

@Fer0x

This comment has been minimized.

Copy link

@Fer0x Fer0x commented Jan 20, 2017

Option --diff-filter=d should be specified in git diff to exclude deleted files.

@alburritos

This comment has been minimized.

Copy link

@alburritos alburritos commented Jan 28, 2017

@Fer0x Where would that go?

@rashtay

This comment has been minimized.

Copy link

@rashtay rashtay commented Apr 24, 2017

@Alberth8 It'd be like this -> git diff --diff-filter=d --cached --name-only

@gilesbutler

This comment has been minimized.

Copy link

@gilesbutler gilesbutler commented May 30, 2017

Thanks @dahjelle! Great stuff :)

@krishnasaga

This comment has been minimized.

Copy link

@krishnasaga krishnasaga commented Aug 8, 2017

Thank you very much

@julianlam

This comment has been minimized.

Copy link

@julianlam julianlam commented Aug 23, 2017

eslint is quite slow when it runs against my entire project, this is much faster, thanks!!

@SalTor

This comment has been minimized.

Copy link

@SalTor SalTor commented Dec 19, 2017

Has anyone figured out a workflow for using the --fix flag during this pre-commit hook?

What I've come across is that when the pre-commit hook is triggered for files that are staged, the new changes aren't included in the commit.

It makes sense why it wouldn't be included: this is changing a staged file, and those changes would also have to be staged.

lint-staged has a partly-working fix for this, however, and as they note, this doesn't work for partially-staged files, AKA files whose changes have been cherry-picked for a commit.

@artem-litvinov

This comment has been minimized.

Copy link

@artem-litvinov artem-litvinov commented Jan 30, 2018

@SalTor take a look at this. It works well for me.

@suyanhanx

This comment has been minimized.

Copy link

@suyanhanx suyanhanx commented Apr 20, 2018

Thank you very much.

@jancimajek

This comment has been minimized.

Copy link

@jancimajek jancimajek commented Apr 24, 2018

One-liner version of the above:

git diff --diff-filter=d --cached --name-only | grep -E '\.(js|jsx)$' | xargs -I % sh -c 'git show ":%" | eslint --stdin --stdin-filename "%";'
@richistron

This comment has been minimized.

Copy link

@richistron richistron commented Apr 24, 2018

you can do this in one line git diff --cached --name-only | grep ".js$" | xargs ./node_modules/.bin/eslint

@Brantron

This comment has been minimized.

Copy link

@Brantron Brantron commented Apr 25, 2018

nice, @richistron

@luuuis

This comment has been minimized.

Copy link

@luuuis luuuis commented May 11, 2018

@jancimajek's one-liner is great but does not handle spaces (and other funny chars) in the filename. Using git diff -z with xargs -0 handles that nicely: luuuis/pre-commit.sh.

The @richistron one liner will lint the changed files in the local checkout, which is not necessarily what is being committed to Git if you have not staged every hunk in a file.

@TNT-Likely

This comment has been minimized.

Copy link

@TNT-Likely TNT-Likely commented Sep 29, 2018

thanks

@sampathBlam

This comment has been minimized.

Copy link

@sampathBlam sampathBlam commented Mar 14, 2019

Thanks @dahjelle.

@lifenstein

This comment has been minimized.

Copy link

@lifenstein lifenstein commented Jul 4, 2019

The scripts above works well enough for a handful of changes, but for large projects when things like a project-wide search & replace is performed, it takes ages because the files are sent one by one to eslint. Running over the whole project only takes 20 seconds, but the commit hook takes 4 minutes! Is there a way to batch the files instead of calling eslint for every file one by one?

@mihow

This comment has been minimized.

Copy link

@mihow mihow commented Feb 4, 2020

This is great! @lifenstein That seems like a less common case, so when it happens you could run git commit --no-verify to skip the 4 minute check and then run eslint directly. For most commits this will be much faster than linting the entire project every time.

@Alynva

This comment has been minimized.

Copy link

@Alynva Alynva commented Feb 10, 2020

One-liner version of the above:

git diff --diff-filter=d --cached --name-only | grep -E '\.(js|jsx)$' | xargs -I % sh -c 'git show ":%" | eslint --stdin --stdin-filename "%";'

I had to change the @jancimajek version a little bit, because % is reserved in my shell and eslint is not global, so:

git diff --diff-filter=d --cached --name-only | grep -E '\.(js|jsx)$' | xargs -I A sh -c 'git show ":A" | .\\node_modules\\.bin\\eslint --stdin --stdin-filename "A";'
@mholtzhausen

This comment has been minimized.

Copy link

@mholtzhausen mholtzhausen commented Mar 19, 2020

Thank you to OP -- very useful. I adapted and my lint-staged.sh file looks like this:

#!/bin/bash
clear
fileList=$(git diff --diff-filter=d --cached --name-only | grep -E '\.(js|vue)$')
if [ ${#fileList} -lt 1 ]; then
    echo -e "You have no staged .js or .vue files to test\n"
    exit
fi
npx eslint ${fileList[*]} "$@"
if [ $? -ne 0 ]; then
    echo -e "\nPlease fix the above linting issues before committing.\n"
    exit 1
fi

Benefits are:

  • using npx allows running local or global eslint
  • Passing the file list to eslint limits this to a single run -- so eslint only has to start up once, and not once for every file
  • It only exits after all staged files have been tested
  • Has an out for when no files are staged
  • allows you to configure eslint by adding arguments to shell command. ie. ./lint-staged.sh --fix
@kuladeeparun

This comment has been minimized.

Copy link

@kuladeeparun kuladeeparun commented Jul 21, 2020

Thanks @nemesarial! This works like a charm

@cowlicks

This comment has been minimized.

Copy link

@cowlicks cowlicks commented Dec 11, 2020

This does not lint "only staged changes" it lints each file that is changed. Which is annoying because it can block your commit for stuff that isn't even in the commit! Eslint should just have a --diff options like flake8

@aperkaz

This comment has been minimized.

Copy link

@aperkaz aperkaz commented Mar 11, 2021

There is an cleaner way to accomplish this, with husky:

// package.json
...
"husky": {
    "hooks": {
      "pre-commit": "lint-staged"
    }
  },
  "lint-staged": {
    "*.{js,ts,tsx}": [
      "prettier --write",
      "eslint --ext .ts,.tsx,.js --fix"
    ]
  },
@bartenra

This comment has been minimized.

Copy link

@bartenra bartenra commented Mar 17, 2021

@Mouvedia

This comment has been minimized.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment