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:

  1. Client-side hooks: Triggered during operations like commits, merges, and pushes.
  2. 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 the git commit command to skip the pre-commit hook:

    git commit --no-verify -m "Your commit message"
    
  • Bypassing Hooks for Pushes: Similarly, you can use the --no-verify flag with the git push command to skip the pre-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:

  1. Create a pre-commit file in the .git/hooks directory:

    touch .git/hooks/pre-commit
    
  2. Make the script executable (only necessary on non-Windows systems):

    chmod +x .git/hooks/pre-commit
    
  3. 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

  1. Create a pre-push file in the .git/hooks directory:

    touch .git/hooks/pre-push
    
  2. Make the script executable (only necessary on non-Windows systems):

    chmod +x .git/hooks/pre-push
    
  3. 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!