Git hooks are an excellent way to automate tasks and enforce coding standards directly at the source control level. By using Git hooks, you can ensure that your codebase remains clean, tested, and compliant with your team’s standards before any changes are committed or pushed.
In this post, we’ll focus on two specific hooks: pre-commit and pre-push. We’ll also show how to use them to automatically run linters and tests before committing or pushing code.
What Are Git Hooks?
Git hooks are custom scripts that Git runs at specific points in your workflow. These scripts can perform various tasks, like checking the commit message format, running tests, or even blocking commits and pushes if certain conditions aren’t met.
There are two main types of hooks:
- Client-side hooks: Triggered during operations like commits, merges, and pushes.
- Server-side hooks: Triggered during server-side operations like receiving pushes.
For this article, we’ll focus on client-side hooks, specifically pre-commit
and pre-push
.
What Aren’t Git Hooks?
While Git hooks are a powerful tool, it’s important to understand their limitations and avoid misusing them. Here are some key points about what Git hooks are not:
Not a Replacement for Continuous Integration (CI): Git hooks are designed for local checks and actions within a developer’s environment. They cannot replace CI pipelines, which are centralized systems that ensure code quality and integration consistency across the entire team. For example, while you can run tests with a
pre-push
hook, it won’t replicate the comprehensive and isolated test environments provided by CI tools.Not Centralized: Hooks are specific to each repository clone and are not shared automatically among team members. This means they lack the consistency and scalability required for organization-wide workflows unless explicitly managed using tools like Husky.
Not a Guarantee of Code Quality: Because hooks are run locally, developers can bypass them (either intentionally or accidentally). This is another reason why Git hooks should supplement, not replace, processes like CI.
Not Always Appropriate for Heavy Tasks: Hooks should ideally be lightweight to avoid slowing down development. For example, running a long test suite during a
pre-commit
can lead to frustration and lower productivity.
Understanding these limitations will help you use Git hooks effectively as part of a broader strategy for maintaining code quality.
How to Bypass Git Hooks
Sometimes, you may need to bypass Git hooks, such as when debugging an issue or in an emergency scenario. Here’s how you can skip hooks when necessary:
Bypassing Hooks for Commits: Use the
--no-verify
flag with thegit commit
command to skip thepre-commit
hook:git commit --no-verify -m "Your commit message"
Bypassing Hooks for Pushes: Similarly, you can use the
--no-verify
flag with thegit push
command to skip thepre-push
hook:git push --no-verify
Keep in mind that bypassing hooks should only be done when absolutely necessary, as it may lead to unchecked or non-compliant code being committed or pushed.
Pre-Commit Hook: Lint Before Committing
The pre-commit
hook is executed before you finalize a commit. This is the perfect place to run linters or formatters to ensure your code adheres to your project’s standards.
Example: Running npm run lint
in a Pre-Commit Hook
Here’s how to set up a pre-commit
hook that runs a specific script:
Create a
pre-commit
file in the.git/hooks
directory:touch .git/hooks/pre-commit
Make the script executable (only necessary on non-Windows systems):
chmod +x .git/hooks/pre-commit
Add the following content to
.git/hooks/pre-commit
:#!/bin/sh echo "Running linting..." npm run lint if [ $? -ne 0 ]; then echo "Linting failed. Commit aborted." exit 1 fi
When you try to commit, this hook will automatically run npm run lint
. If any issues are found, the commit will fail.
Pre-Push Hook: Run Tests Before Pushing
The pre-push
hook is executed before your changes are pushed to a remote repository. This is an ideal place to run your test suite and ensure your changes won’t break the build.
Example: Running Tests in a Pre-Push Hook
Create a
pre-push
file in the.git/hooks
directory:touch .git/hooks/pre-push
Make the script executable (only necessary on non-Windows systems):
chmod +x .git/hooks/pre-push
Add the following content to
.git/hooks/pre-push
:#!/bin/sh echo "Running tests..." npm run test if [ $? -ne 0 ]; then echo "Testing failed. Push aborted." exit 1 fi
This script will run npm run test
before pushing. If any tests fail, the push will be blocked.
Tips for Managing Git Hooks
Share Hooks Across the Team: The
.git/hooks
directory is local to each repository clone and is not version-controlled. To share hooks, consider using a tool like Husky or include the hooks in a script that sets up your repository.Debugging Hooks: Use
set -x
at the start of your script to debug issues. This will print each command as it is executed.
Final Thoughts
Git hooks are a powerful tool to maintain code quality and enforce team standards automatically. By using the pre-commit
and pre-push
hooks, you can seamlessly integrate linting and testing into your workflow. Just remember their limitations and use them as part of a broader strategy, including CI pipelines and other tools.
Start using Git hooks today, and take your development workflow to the next level!