Iftekhar EatherTechnical Lead · System Architecture · Cloud
Back to Blog
#Git#DevOps#VersionControl#GitHub#SoftwareDevelopment#Programming#BeginnerGuide#Collaboration#CLI#Engineering

Part 5: Git Hooks

A Practical Tutorial for Beginners to Advanced Users

Iftekhar Ahmed Eather6 min read
Part 5: Git Hooks

In the previous article, we learned about Git Squash and Git Cherry-pick.


Now let's take the next step toward automation.


Until now, we have mostly used Git by explicitly running commands:

git add
git commit
git push
git merge


But imagine if Git could automatically perform certain checks for us.


For example:

"Before creating a commit, check whether the code follows our standards."


Or:

"Before pushing the code, run the tests."


This is where Git Hooks become useful.


What Is a Git Hook?

A Git Hook is a script that Git automatically executes when a specific Git event occurs.


Think of it like this:

Git Event
    ↓
Git Hook
    ↓
Automatic Check or Action


For example:

git commit
    ↓
pre-commit hook
    ↓
run lint
    ↓
check formatting
    ↓
allow or reject commit


Instead of depending entirely on developers to remember every check manually, we can automate repetitive checks.


Why Do We Need Git Hooks?

Imagine a team of 20 developers.


One developer creates a commit without running the tests.


Another developer forgets to format the code.


Someone else accidentally commits a debug statement.


These small mistakes can eventually reach the shared repository.


Git Hooks can provide an early safety net.

Developer
    ↓
Git Hook
    ↓
Validation
    ↓
Commit / Push


The idea is simple:

Catch problems as early as possible.


Common Git Hooks

Some commonly used Git Hooks are:

  • pre-commit

  • commit-msg

  • pre-push

  • post-commit

  • post-merge


Different hooks are triggered at different points in the Git workflow.


Let's look at the most useful ones.


1. pre-commit

The pre-commit hook runs before Git creates a commit.


This makes it useful for checks such as:

  • Code formatting

  • Linting

  • Static analysis

  • Basic tests

  • Detecting accidentally committed files


The workflow could look like this:

git commit
    ↓
pre-commit hook
    ↓
Run checks
    ↓
PASS → Commit created
FAIL → Commit stopped


For example, imagine your project uses a linter.

Instead of asking every developer to remember:

npm run lint

the hook can execute it automatically.


If the linting fails, the commit can be stopped.


2. commit-msg

The commit-msg hook runs when Git is preparing the commit message.


This can be used to enforce a commit-message convention.


For example, your team may use:

feat: add login API
fix: resolve payment timeout
docs: update README
refactor: improve authentication service


But someone might write:

update

or:

fix

or even:

asdf


A commit-msg hook can validate the message and reject commits that don't follow the team's convention.


Consistent commit messages make Git history much easier to understand.


3. pre-push

The pre-push hook runs before Git pushes changes to a remote repository.


For example:

git push
    ↓
pre-push hook
    ↓
Run tests
    ↓
PASS → Push
FAIL → Push stopped


This gives developers one more opportunity to catch problems before sharing their changes.


Imagine you have accidentally broken an important test.

Instead of:

Developer
    ↓
git push
    ↓
GitHub
    ↓
CI fails

you might catch it earlier:

Developer
    ↓
git push
    ↓
pre-push
    ↓
Test fails
    ↓
Push stopped


That's a much faster feedback loop.


Where Are Git Hooks Stored?

Git stores repository-specific hooks inside the .git/hooks directory.


You can inspect it with:

ls .git/hooks


You may see files such as:

pre-commit.sample
commit-msg.sample
pre-push.sample


These .sample files are examples provided with Git.


A Simple Git Hook Example

Suppose we want to create a basic pre-commit hook.


Inside:

.git/hooks/

we can create:

pre-commit


For example:

#!/bin/sh

echo "Running pre-commit checks..."

npm test


The hook can then run automatically when you execute:

git commit


If the command succeeds, the commit can continue.


If the command fails, the commit can be stopped depending on the script's exit status.


The important lesson isn't memorizing the script.

It's understanding the concept:

Git Command
    ↓
Hook
    ↓
Automated Validation


Git Hooks Are Local

There is an important limitation you should understand.


Git Hooks generally live in the local repository environment.


That means different developers could potentially have different hook configurations.

Developer A → Hook installed
Developer B → Hook missing
Developer C → Different configuration


This creates an important engineering problem.


What if your project requires every developer to run the same checks?


You shouldn't rely entirely on everyone's local Git configuration.


This is one reason why professional teams combine Git Hooks with CI/CD.


Git Hooks vs CI/CD

Think about the two layers like this:

Developer Machine
        ↓
Git Hook
        ↓
Local Validation
        ↓
GitHub
        ↓
CI/CD
        ↓
Central Validation


Git Hooks provide a local safety net.


CI/CD provides a centralized safety net.


You want both.


Git Hooks and DevOps

This is where Git Hooks become particularly interesting for DevOps engineers.


A modern development workflow could look like:

Developer
    ↓
Create Branch
    ↓
Write Code
    ↓
git commit
    ↓
Pre-commit Hook
    ↓
git push
    ↓
Pull Request
    ↓
CI Pipeline
    ↓
Tests
    ↓
Security Scan
    ↓
Code Review
    ↓
Merge
    ↓
Deploy


Notice what happened.

Git is no longer simply being used to store source code.

It has become part of the software delivery process.


An Important Engineering Principle

There is a valuable principle behind Git Hooks:

Don't depend on human memory for repeatable checks when you can automate them.


For example, if every developer must remember:

Run formatter
Run lint
Run tests
Check commit message
Check for secrets

eventually someone will forget something.


Automation reduces that dependency on memory.


This principle goes far beyond Git.

It's one of the fundamental ideas behind DevOps.


But Don't Put Everything Into Git Hooks

It can be tempting to put every possible check into a pre-commit or pre-push hook.


That can create another problem.


Imagine a developer runs:

git commit

and then has to wait 15 minutes while a huge test suite executes.


The development experience becomes painful.


A better approach is to keep local checks relatively fast and move heavier validation into CI.


For example:

Local
 ↓
Formatting
Lint
Fast unit tests

CI
 ↓
Full test suite
Integration tests
Security scanning
Build
Deployment checks


This creates a better balance between developer productivity and quality control.


Git Hooks + GitHub Actions

Now we can see how the two technologies complement each other.

                 Developer
                     ↓
              Git Hook Checks
                     ↓
                  git push
                     ↓
                GitHub
                     ↓
             GitHub Actions
                     ↓
        ┌────────────┼────────────┐
        ↓            ↓            ↓
      Build         Test       Security
        └────────────┼────────────┘
                     ↓
               Pull Request
                     ↓
                Code Review
                     ↓
                  Merge


This creates multiple layers of protection.


Final Thought

Git Hooks may look like a small Git feature, but they introduce an important engineering mindset:

Automate repeatable work and catch problems early.


A good development process doesn't wait until production to discover problems.


It tries to catch them:

While coding
    ↓
Before commit
    ↓
Before push
    ↓
During CI
    ↓
During code review
    ↓
Before deployment


Each layer provides another opportunity to prevent a problem from moving forward.


That's the real value of Git Hooks.


They are not just scripts attached to Git commands.


They are one small piece of a much larger idea:

Build quality into the development process instead of relying on people to remember everything.