Generating a separate commit for Husky lint-staged tasks (ESLint, Prettier) using the post-commit Git hook
There are a lot of tutorials out there related to running autoformatting or linting tools (such as ESLint or Prettier) on the pre-commit
Git hook with Husky and lint-staged,¹ ² ³ ⁴ ⁵ but I couldn’t find any related to running these tasks on post-commit
. Why would you want to run autoformatting/linting (henceforth “autoformatting”) on post-commit
instead of pre-commit
? To save their modifications in a separate commit, of course!
Background
My team decided to add autoformatting to a mature React (create-react-app) project. Noble, right? I’m not a super-fan of opinionated autoformatters (ahem, Prettier), but I realize maintaining consistent code formatting can improve readability and maintainability, and it’s a hell of a lot easier to automate formatting than enforce it.
Egad! Suddenly, when a developer changed any part of a file that hadn’t yet been formatted by Prettier a slew of syntactical changes were committed inline with the initial code modification, attributed to the initial commit — the horror! Subsequently tracing bugs through commit diffs became a nightmare. We had to try and parse out a multitude of formatting changes from the actual code change — a veritable needle in a haystack!
The fact that these modifications happened opaquely, without input or manual review, only exacerbated the problem — especially when it resulted in unexpectedly committing code that was not initially staged for commit (from a partially-staged file).
Solution
Seeing the ongoing nightmare in front of us we decided we had two options:
- Run autoformatting on all files at once, after which subsequent commits would only autoformat new code, or
- Add the autoformatting changes to a separate, dedicated commit
We decided to proceed with option 2 since it required minimal up-front effort and would allow us to a) undo the autoformatting without affecting the initial commit, and b) provide appropriate “blame” when autoformatting broke something.
Instructions
This is targeted to projects using NPM, but would work in any environment Husky/lint-staged supports with some modifications (see package docs).
1. Install & Set Up
Since this guide is aimed at moving autoformatting from pre-commit
to post-commit
git hooks I’ll assume you already have Husky, lint-staged, ESLint and/or Prettier already set up. If not follow their docs (or one of the five links in the footnotes) and come back when you’re done.
2. Convert pre-commit
to post-commit
Rename .husky/pre-commit
to .husky/post-commit
(if using the Husky file-based configuration, otherwise rename pre-commit
to post-commit
in your package.json
)
3. Adapt lint-staged to post-commit environment
Change lint-staged
to lint-staged --diff="HEAD^HEAD"
.
— By default lint-staged uses git diff --staged
, but there aren’t any staged files in the post-commit hook!
4. Commit the changes
Add a git commit
command to your lint-staged
commands:
"*": [
"eslint --fix",
"prettier --ignore-unknown --write",
"git commit -m \"🤖 Autoformatted\""
]
Conclusion
And there you have it, a separate commit will be generated for all robo-changes! I hope this tutorial was helpful — please leave suggestions for improvement in the comments.
Caveats
Until lint-staged Issue #1197: Allow stash when using --diff is resolved partially committing a file will result in the unstaged/uncommitted changes being included in the auto-commit.