Git has a robust system of hooks. Hooks are some scripts that fire off when specific actions occur. For example, you may want to make sure your code builds successfully before pushing it to the upstream. You can use Git hooks to define a script that builds your code when you issue
git push command. Then, Git pushes the code only when the script runs successfully. If there is a build error, the push will not happen.
Most Git hooks are local to your repository, called client-side hooks.
It’s important to note that client-side hooks are not copied when you clone a repository.
It means if you add a hook to your repository, your coworker working on a fork of the repository, does not have access to your hooks. It makes it challenging to share your hooks with others. Even if you email the script to your coworker, how do you update his copy of the script when you make a change in your copy?
Here, I explain the process of creating a Git hook that can be used with pre-commit.
We will use this repository to test the hook.
$ mkdir "test-my-hook" $ cd "test-my-hook/" $ git init Initialized empty Git repository in /Users/talha/Repos/temp/test-my-hook/.git/
Create a file
.pre-commit-config.yaml in the root of the repository.
$ pre-commit sample-config > .pre-commit-config.yaml $ git add .pre-commit-config.yaml $ git commit -m "add pre-commit configuration"
Add a file on which we want pre-commit to run the hook.
$ echo "some random text" > example.text $ git add example.text
Notice, I have staged the file
example.text but I have not committed it.
Now that we have a repository ready to run our test hook, let’s move to the next step: creating a pre-commit hook.
Create a new repository, outside “test-my-hook” directory, where you will define your pre-commit hook.
$ cd .. $ mkdir pre-commit-test-hook $ cd pre-commit-test-hook/ $ git init Initialized empty Git repository in /Users/talha/Repos/temp/pre-commit-test-hook/.git/
From the documentation, we know a hook repository must contain a
.pre-commit-hooks.yaml file. Let’s create it.
$ touch .pre-commit-hooks.yaml
Now edit the content of this file,
idis the id of the hook. Users of your hook refer to your hook using this ID.
entryis the name of the executable file that pre-commit runs. Think of it as the
main()of a C program.
typesare the types of file on which this hook runs. You can also use
excludeis a Python regular expression to exclude specific files. In this case, it tells pre-commit to not run the hook on files that have
languagetells pre-commit how to install the hook. If you use
'script', it means your hook is a Bash script.
argsthis is optional. It is used to pass arguments to your script. You can define it or leave it for your users to define it in their configuration. Of course, if your script does not accept any argument, then
argsis redundant. Here we pass two arguments,
You can use other languages like Python, to create your hook. But in that case, you must provide installation instructions so that pre-commit can install the hook. For example, in the case of Python, you need
A Bash script does not require any installation or extra tools. Bash is ubiquitously present wherever Git is, which is why Bash is preferred to write the hooks.
Now we are going to add some code to the
Commit your additions.
$ git add .pre-commit-hooks.yaml run-test-hook.sh $ git commit -m "add an example hook"
Luckily, you do not have to publish your hook first to test it. You can use pre-commit to run this hook locally.
Go to the test repository we created to test the hook.
$ cd "test-my-hook"
Run the following command in it,
pre-commit try-repo ../pre-commit-test-hook/ test-hook --all-files --verbose
pre-commit try-repois the command and the switch.
../pre-commit-test-hookis the path to your
pre-commit-test-hookrepository on your file system. This path depends on where you have created these repositories.
test-hookis the ID of the hook. Remember it?
--all-filesruns the hook on all the supported files.
--verboseis important for debugging. It displays the output of the
echostatements in the Bash script.
When you run the hook, with the
verbose option, you should see the following output besides a bunch of other pre-commit logs.
The hook is running --example1 --example2 example.text
--example2 arguments that we mentioned earlier?
Pre-commit passes those arguments first and then the list of files on which the hook could run to our script.
Notice, it does not pass
.pre-commit-config.yaml file although it is a text file and present in the repository. It is because we have excluded all files ending in
.yml from the hook.
Now that you have the hook running, you can modify your copy of
run-test-hook.sh. You can use a Bash script to parse the switches and arguments, using
getopts for example.
Then you can use Bash and other command-line utilities to modify the files.
When your hook is ready, you can push it Github so that others can use it.
TekWizely/pre-commit-golang has some brilliants scripts that you can use to learn and improve your hooks. I have created a simple hook for my Go projects, which you can see here, talha131/pre-commit-golang.