Git hooks are a pretty powerful way of improving your development workflow without having to remember to run additional scripts or perform additional tasks. In this article, we are going to be taking a look at how you can define your own simple git hooks within a project repository that can automatically perform the task of linting + formating your Go code.
This fairly simple example should give you a flavour as to how you can leverage git hooks for your own devious plans.
One example of this is from one of my current projects where the team had to encrypt secrets files before they were getting pushed to the project’s repository. Encrypting the file was a simple one liner, but due to the number of encrypted files we were working with, it became a challenge to remember to encrypt every file that has been changed and more often than not we would run our project and scratch our heads whilst we tried to figure out why it wasn’t picking up the new secrets.
When it came to attempting to solve this, we were fairly limited in the tools we could pull in and didn’t want to use something that would impact the current development flow for people in the team who had their own preferences.
This is where git hooks came into play. With hooks, I could define a simple
pre-commit hook script that would automatically perform the task of encrypting any un-encrypted files and adding them to the commit. I could also make this an optional addon to the team’s development flow by creating these hooks in a
hooks/ directory so that if they wished to add these git hooks to their workflow, they could with a simple
git config core.hooksPath hooks command.
Creating a Git Hook
Hooks are actually surprisingly simple to make as they are simply just bash scripts that have specific names in particular directory. When you next execute a given
git command, it would automatically execute this bash script provided it was the rightly named file for that particular command.
If you wish to only create git hooks for your particular project on your machine, you can do so by navigating to your current project directory and then into the
.git/hooks/ directory within that project.
If you view the contents of that directory you should see something like this:
31/07/2019 22:09 478 applypatch-msg.sample 31/07/2019 22:09 896 commit-msg.sample 31/07/2019 22:09 3,327 fsmonitor-watchman.sample 31/07/2019 22:09 189 post-update.sample 31/07/2019 22:09 424 pre-applypatch.sample 31/07/2019 22:09 1,642 pre-commit.sample 31/07/2019 22:09 1,348 pre-push.sample 31/07/2019 22:09 4,898 pre-rebase.sample 31/07/2019 22:09 544 pre-receive.sample 31/07/2019 22:09 1,492 prepare-commit-msg.sample 31/07/2019 22:09 3,610 update.sample
Each of these contains example git hooks that you can turn on simply by renaming the files and removing the
.sample file endings.
The file we are concerned about for the purpose of this article though is the
Within this same directory, let’s create a new file called
pre-commit and within this let’s add the following:
#!/bin/bash echo "Test Hook"
If you are running on Mac or Linux you will have to set the executable bit for this script using
chmod. On Windows, this should just work straight out of the box!
chmod +x pre-commit
With this set, try make an empty commit to test this:
git commit --allow-empty -m "Testing Git Hook"
When you run this, you should now see that
"Test Hook" is printed out just before git executes the
Awesome! You have successfully created your own git hook! This will now be executed every time you commit something within this given repository.
Improving our Git Hook
Ok, so we have been able to successfully create our first git hook, it’s now time to start improving this so that it actually performs a useful task for us other than just echo some text.
Let’s start by adding some go linting to this script:
#!/bin/bash echo "Test Hook" # this will retrieve all of the .go files that have been # changed since the last commit STAGED_GO_FILES=$(git diff --cached --name-only -- '*.go') # we can check to see if this is empty if [[ $STAGED_GO_FILES == "" ]]; then echo "No Go Files to Update" # otherwise we can do stuff with these changed go files else for file in $STAGED_GO_FILES; do echo $file done fi
With this in place, try changing a
.go file within your project and attempting a
git add -A following by a
git commit -m "Some Message".
You will now see that just below our
"Test Hooks" our
pre-commit hook is now printing out the path to the file which has been changed.
Test Hook test.go [master 82ab8c6] test 1 file changed, 2 insertions(+)
Aweomse, so it’s able to see that we’ve update a
.go file within our project directory and we are able to echo that file out in our
for loop. Let’s extend our git hook to automatically format this file for us whenever we commit:
#!/bin/bash echo "Test Hook" # this will retrieve all of the .go files that have been # changed since the last commit STAGED_GO_FILES=$(git diff --cached --name-only -- '*.go') # we can check to see if this is empty if [[ $STAGED_GO_FILES == "" ]]; then echo "No Go Files to Update" # otherwise we can do stuff with these changed go files else for file in $STAGED_GO_FILES; do # format our file go fmt $file # add any potential changes from our formatting to the # commit git add $file done fi
Now, when we go to save any of our files and subsequently add and then commit them, these will be automatically formatted for us and any changes made by the formatting will automatically be added to the given commit:
C:\Projects\test>git status On branch master modified: main.go
Test Hook main.go main.go [master 61e6ed4] Updates
Awesome! We have now been able to create a git hook that automatically improves our Go development workflow and ensures that whatever we commit is properly formatted code!
Distributing Git Hooks Between Teams
Now, unfortunately the changes we made within the
hooks/ directory under our project’s
.git/ directory will not be tracked
and therefore getting these changes out to various different members of your team becomes a bit of a challenge.
However, what you can do to get around this particular challenge is to create a directory called
.githooks/ within your current
project’s directory and store the
pre-commit git hook within that directory. You’ll be able to commit and track this just as you
would any other files within your project and in order to turn on these enable these hooks on other development machines you
simply need to run this command:
$ git config core.hooksPath .githooks
Once you have executed this particular command, you should now see that whenever you try and commit something, the hooks provided within that directory are now enabled!
Awesome, so in this tutorial, we’ve had a look at how you can improve your Go development workflow by using git hooks in conjunction
with existing tools such as
go fmt to ensure that whatever you commit up to your repositories has been properly formated!
This is just a very small taste of what you can achieve with git hooks and hopefully it gives you some ideas as to how you can take these further! If you have any ideas or examples as to how you have improved this workflow, then I would love to hear about them in the comments section below!