Have you ever pushed code forgetting to run local tests? Or merged code that didn't pass linting? It happens to everyone. With git hooks, you can automate these checks before the damage reaches the remote repository. At Meteora Web, we use git hooks on every serious project. Not for fashion — code quality is not improvised, it's automated.
What are git hooks and how to automate code quality with them?
Git hooks are scripts that git runs automatically when certain events occur: commit, push, merge, rebase, etc. They are executable files (bash, Python, PHP, Node.js, whatever you want) inside the .git/hooks folder of your repository. Names like pre-commit, pre-push, post-commit, pre-rebase. The idea is simple: block or modify an operation if it doesn't meet certain criteria. Automating code quality with git hooks means catching errors before they become problems on shared branches. You don't need an external tool — just a well-written script.
Where to find them and how to enable them
Every repository already has a .git/hooks folder with samples (ending in .sample). Just remove the suffix and make it executable:
Sponsored Protocol
cd .git/hooks
mv pre-commit.sample pre-commit
chmod +x pre-commit
The problem is that .git is not versioned: if you clone the repo, hooks are not there. The solution? Put hooks in a versioned folder (e.g. .githooks) and configure git to use them:
git config core.hooksPath .githooks
Run this command once per repo (or in a setup script). This way the whole team shares the same checks.
How to set up a pre-commit hook for automatic linting?
The pre-commit runs right before git creates the commit. If the script returns a non-zero exit code, the commit is aborted. Here's the classic case: before committing, run a linter on the modified files. Example for a PHP project with PHP_CodeSniffer:
#!/bin/sh
# file .githooks/pre-commit
echo "Running PHPCS on changed files..."
# Get staged files (only .php)
staged=$(git diff --cached --name-only --diff-filter=ACM | grep '\.php$')
if [ -z "$staged" ]; then
echo "No PHP files changed. Skipping check."
exit 0
fi
# Run phpcs on all staged files
vendor/bin/phpcs --standard=PSR12 $staged
if [ $? -ne 0 ]; then
echo "ERROR: coding standard violations found. Fix before committing."
exit 1
fi
echo "PHPCS passed."
Save the file as .githooks/pre-commit and make it executable. Next time you run git commit, if there is PHP code that does not conform to PSR12, the commit is blocked. No more merging of badly formatted code. For JavaScript, just replace with eslint: npx eslint $staged.
Sponsored Protocol
How to use pre-push for tests and validations before pushing?
The pre-push hook runs after the commit but before the data is sent to the remote. This is the perfect moment to run heavier tests: unit tests, integration tests, static analysis. Example with PHPUnit:
#!/bin/sh
# file .githooks/pre-push
echo "Running tests before push..."
# Execute test suite
vendor/bin/phpunit --configuration phpunit.xml
if [ $? -ne 0 ]; then
echo "ERROR: tests failed. Fix before pushing."
exit 1
fi
echo "Tests passed. Push allowed."
Watch out: tests can be slow. We recommend keeping pre-push to essential tests (max one minute). If you have a long suite, use a flag to skip (e.g. git push --no-verify) but only in exceptional cases.
Sponsored Protocol
Handling skips responsibly
Git allows skipping hooks with --no-verify for commits and pushes. We only use it for emergencies (urgent production fixes). Otherwise, hooks are mandatory. To avoid abuse, you can log who skipped the check, or integrate a CI that verifies the same.
Tools for managing git hooks in a team: Husky, Lefthook, and custom scripts
When working with JavaScript teams, we often use Husky: an npm package that simplifies hook configuration. Install and define scripts in package.json:
npx husky-init && npm install
Then add a hook: npx husky add .husky/pre-commit "npx lint-staged". But be careful: Husky creates a .husky folder that must be versioned. It works well but ties you to Node.js. Lefthook is a cross-platform alternative (Go) that reads a YAML config. We prefer simple bash scripts for PHP projects because we don't want extra dependencies. The rule: choose the tool your team understands and maintains.
Common mistakes to avoid with git hooks
The most frequent? Hook too slow or too strict. A pre-commit that runs the entire test suite (5 minutes) will annoy the team. Better a fast lint (milliseconds) and tests only in pre-push. Second mistake: forgetting to make scripts executable on macOS/Linux (chmod +x). Third: not testing the script on multiple environments (Windows with Git Bash needs attention). Fourth: not versioning hooks → each developer has to set them up manually.
Sponsored Protocol
A checklist to get started right now
- Identify essential checks: lint, unit tests, security scan, static analysis.
- Create a
.githooksfolder and place your scripts. - Configure
core.hooksPathin local .gitconfig or in a setup script. - Test with a fake commit to verify the block works.
- Document in README how to activate hooks for new developers.
What to do now
Don't wait for the next disastrous merge. Implement a pre-commit hook for your language right now. If you work with PHP, copy the script above. For JavaScript, use npx lint-staged. For Python, flake8 or black. The time invested today pays back tenfold in hours of debugging saved. At Meteora Web, we do this on every project we develop – in our pillar guide on Git you'll find the full team workflow. Happy hooking!