Quick Links

Git hooks are bash scripts that run before or after Git commands, such as commits and pushes. They allow you to automate repetitive actions in your repository, as well as apply filters and checks to your Git workflow.

What Are Git Hooks?

Git hooks really are just bash scripts with a special name, in the

        .git/hooks/
    

 folder. Git will automatically invoke these functions when performing certain tasks, allowing you to "hook" into the Git workflow to modify it with your own code.

Git repositories are initialized with a few sample ones; all you have to do to apply them is uncomment the extension. This means you will only be able to have one script per-hook, so if you want to do multiple things, you will need to combine them or delegate to other scripts.

So what can you use them for? Well, any task a bash script can accomplish will work. Two common use cases are automated testing, and applying filters/checks on outgoing commits.

Tests are an important part of any workflow. While Git hooks absolutely do not replace having a proper continuous integration/continuous deployment (CI/CD) pipeline, which will run tests before review and deployment, running them locally will help you catch failures before they go out.

Similarly, checking the contents of commits to prevent pushing unwanted code can be very useful, though this does require you to be smart enough to catch the problem before it becomes a problem. If you often use debug code that should never be committed, you can test for that and prevent it.

There are a lot of Git hooks for you to choose from, which you can read about in Git's documentation, but the useful ones are:

  • pre-commit, post-commit
  • pre-push
  • post-checkout
  • commit-msg

Each hook will take in arguments to the script, which you can access with

        $1
    

        $2
    

, etc.

Sharing Git Hooks

Git hooks are only for the local repository, and aren't pushed to the remote. You're free to set up whatever Git hooks you'd like without affecting your coworkers, so you could, for example, spin up a local testing environment that depends on your PC setup, on every commit, without any issues.

If you'd like to actually share Git hooks with your team though, you can make a new folder for them which will be tracked in Git, like

        .githooks
    

, and set the config value for

        core.hooksPath
    

:

git config core.hooksPath .githooks

Like default hooks though, this config is per-repo, so your team will also need to set this config value.

How To Use Git Hooks

Like most automation techniques, how you use Git hooks is largely up to you and your repository's workflow, but there are a few common use cases.

If you wanted to check the contents of commits, you could use git diff to display the line-by-line diff, and then grep it to find matches. In this case, it's blocking all usage of the function Debug.Log by exiting with a non-zero code:

if test $(git diff --cached | grep -E "Debug.Log(" | wc -l) != 0 then

exit 1

fi

Or, you can test the actual commit message. The pre-push sample uses a similar test, but greps the output of git rev-list instead. This checks for commits marked as work-in-progress (WIP) and refuses to push them.

Another common use case is running tests automatically. It's up to you whether you want that to be on every commit, or just before changes are pushed to the remote.

In either case, it's as simple as running your test command, getting the last command's exit status, and throwing an error if it fails. This example runs tests for a .NET app:

#!/bin/sh

exec dotnet test "./UnitTests/UnitTests.csproj" --filter "Category!=Integration"

if [ $? -ne 0 ]; then

echo "Tests must pass before commit!"

exit 1

fi

There are some tools to help with this; Husky will easily run NodeJS tests with a config setting in package.json, which will apply to all your teammates as well.

{

"husky": {

"hooks": {

"pre-commit": "npm test",

"pre-push": "npm test",

"...": "..."

}

}

}

However, this is not a replacement for having actual tests in place on the authoritative repo. There are scenarios where your local tests might pass, but remote tests would fail, namely in cases where you do not stage all changes to be pushed.