🚨 AI WAS NOT USED IN WRITING THIS POST 🚨
I've gone back and forth on this topic quite some time. As a solo dev, I liked it because I could run tests on commit. As a teammate, I liked everyone's code being formatted and linted.
For a few years, I was against git hooks. I'd just run everything in CI/CD. Pull requests get formatted and tested before the branch could be accepted. That's similar to how I run today, but I'm also leveraging git hooks.
The thought back then was the dev should own their git process. Whether it's dozens of commits or a large one. It's their machine, they can do what they want.
My current thought is to protect us from ourselves. There's many a time that I'm coding when tired; whether it's after a long day or early in the morning. Sometimes I forget to run my test suite. I typically work in some form of TDD but sometimes I'll forget to double-check my work. It's human.
What's not human is the AI I use to assist with development. The reality is a lot of us are using agents to push code. That right there is another factor that I hadn't considered before, a lot of devs are pair programming with agents now. That means you're also responsible for your sidekick's output when creating a pull request.
Git hooks will catch AI's commits as well. Holding the machine accountable if you overlook something. Here's how I have it set up in my projects.
Setting Up
Essential Tools:
Recommended Tools:
- Eslint
- Prettier
- Vitest
Your use-case will likely differ. But here's how I'd set up a test/linter check.
First, install Husky
# NPM
npm install --save-dev husky && npx husky init
# Bun
bun add --dev husky && bunx husky init
# Pnpm
pnpm add --save-dev husky && pnpm exec husky init
Next, install lint-staged
# NPM
npm install --save-dev lint-staged
# Bun
bun add --dev lint-staged
# Pnpm
pnpm add --save-dev lint-staged
Next, we're gonna steal an idea from Steve Kinney. He brings up a really good point about assumptions and shells. Not everyone has your environment ( and these scripts run outside of docker ), so we have to be considerate toward our teammates.
Steve mentions we should leverage node processes instead of the shell itself. Changing .husky/pre-commit to this:
# .husky/pre-commit
node .husky/pre-commit.mjs
then adding 2 new files
# .husky/pre-commit.mjs
import { execSync } from 'node:child_process';
execSync('npx lint-staged', { stdio: 'inherit' });
# .husky/install.mjs
# Skip Husky install in production and CI
if (process.env.NODE_ENV === 'production') {
process.exit(0)
}
const husky = (await import('husky')).default
husky()
That'll put us in a good spot for the .husky directory. Next, we can update package.json but please note that your lint-staged section will likely look different. This example is for a Vue app that lints, formats, and tests staged files before they're committed.
{{
// other json scripts...
"prepare": "node .husky/install.mjs", // invoke your husky install file when npm's post-install kicks off.
},
"lint-staged": {
"*.{ts,vue,js,mjs}": [
"eslint --ext .",
"prettier --write ",
"vitest related run" // run vitest only on new committed files.
],
"*.{json,md,css,scss,html,yml,yaml}": [
"prettier --write"
]
},
Cool, now let's try it out. Save these changes to your repo then make an edit to any file then commit again. You should see the hooks run those lint-staged commands. But before you go, we should probably talk about something...
When to Bypass Hooks
Listen, we've all been there. You don't care that a test is failing, you just want to save your work. It's totally ok to bypass hooks.
It's not ok to bypass them all the time. They exist to save us from ourselves. If you wanna know, here's how to bypass a commit.
git commit -m "saving work" --no-verify
That's it! I hope this was useful and thanks for reading 😊
How do you use git hooks? Let me know in the comments!
Photo by LynetteC on Pixabay
Top comments (0)