Git Pre-Commit Hook: A Practical Guide (with Examples)

Updated: January 28, 2024 By: Guest Contributor Post a comment

Introduction

Git hooks are scripts that Git executes before or after events such as: commit, push, and receive. A pre-commit hook is a type of hook that is run before a commit is finalized. These hooks can be used to inspect the snapshot that’s being committed. For example, you can set up hooks to run tests or lint code before it’s committed, ensuring that only high-quality code is part of the repository.

This tutorial will guide you through the steps of creating, configuring, and working with pre-commit hooks in your Git repository. We’ll start simple and gradually progress to more advanced examples. By the end of this guide, you’ll have the knowledge to implement pre-commit hooks effectively in your projects.

Setting Up a Basic Pre-Commit Hook

Let’s start by setting up a basic pre-commit hook that will check for TODO comments in your code. If any TODOs are found, the commit will be aborted.

# Step 1: Navigate to the .git/hooks directory in your repository
$ cd .git/hooks

# Step 2: Create the pre-commit script
$ touch pre-commit

# Step 3: Make the script executable
$ chmod +x pre-commit

# Step 4: Edit the pre-commit script
$ nano pre-commit

# Add the following script
#!/bin/sh

if git grep --cached -q 'TODO'; then
    echo 'Your commit contains TODO comments. Resolve them before committing.'
    exit 1
fi

With this basic hook, if you try to commit a file that contains ‘TODO’, it will be rejected with a message.

Advanced Pre-Commit Hook with Linting

Next, let’s advance our pre-commit hook to include linting. We’ll use ESLint as an example:

# Step 1: Make sure ESLint is installed in your project
$ npm install eslint --save-dev

# Step 2: Modify the pre-commit hook to include ESLint check
$ nano .git/hooks/pre-commit

# Add the following code to the pre-commit hook
#!/bin/sh

ESLINT="$(npm bin)/eslint"

# Run ESLint on staged .js files
for file in $(git diff --cached --name-only --diff-filter=ACM | grep ".js$"); do
  if ! $ESLINT "$file"; then
    echo "ESLint failed on staged file '$file'. Please fix the errors and try again."
    exit 1
  fi
done

When attempting a commit with JavaScript files that fail the linting process, the hook will display which files have errors, and the commit will be aborted until the issues are resolved.

Automating Hook Installation with a Script

To ensure everyone in your team uses the same pre-commit hook, you can automate its installation with a script:

# Create an install-hooks.sh file in the root of your repository
$ touch install-hooks.sh

# Edit the install-hooks.sh file to the following
#!/bin/bash

cd .git/hooks
if [ ! -f pre-commit ]; then
    cat << 'EOF' > pre-commit
#!/bin/sh

if git grep --cached -q 'TODO'; then
    echo 'Your commit contains TODO comments. Resolve them before committing.'
    exit 1
fi

EOF
chmod +x pre-commit
fi

This script can be run by any team member to automatically set up the pre-commit hook in their local repository.

Ensuring Code Quality with Unit Tests in Hooks

A pre-commit hook can run tests to ensure code quality. Here’s a basic setup using Jest:

# Assure Jest is installed
$ npm install jest --save-dev

# Modify the pre-commit hook
$ nano .git/hooks/pre-commit

# Add the following code:
#!/bin/sh

JEST="$(npm bin)/jest"

# Running Jest tests
if ! $JEST; then
    echo "Tests failed. Fix errors and try committing again."
    exit 1
fi

This will run your Jest tests, and if any tests fail, the commit will be halted with a corresponding message.

Conclusion

Git pre-commit hooks are a powerful tool to maintain code quality and enforce project standards. You can start small by checking for TODO comments or lint errors, and build up to more complex workflows like running automated tests. The key is to integrate these hooks into your development process, ensuring that everyone in your team consistently follows the best practices defined by your hooks.