What are Git Hooks, and why should I use them?
Git Hooks are a Git feature, which allows developers to execute scripts at certain points in their Git lifecycle, like before accepting a commit, before pushing to a remote repository or before accepting a commit message. Git Hooks can be used to "shift left" and automate tasks and enforce policies throughout the Git workflow.
Git Hooks live in the
.git/hooks folder of a repository. Here, we can place scripts with the same name as the Git Hooks they should execute on (max. one per hook). Below, an example which checks the commit message for a certain pattern.
Keep in mind, that each script in this folder has to be marked as executable by running
chmod +x pre-commit-msg.
You can find more information and a list of available Git Hooks at githooks.com.
What to use them for?
- Check commit messages: Especially useful, if you use conventional commits. Also, to avoid the famous "WIP" or "fix" commit messages.
- Check coding style and Linters: A no-brainer. Nothing is more frustrating than going back to your code and fix that trailing space, after the CI pipeline failed because of it.
- Check for secrets: (Optional) We should check our code for accidentally checked-in secrets, like passwords or connection strings.
- Build and Test: (Optional) At least before pushing changes to a remote, we could make sure the code builds and the tests pass.
Can be bypassed
As Git Hooks can be bypassed. For example, with the
--no-verfify flag for the
git commit command. So we should never rely on their execution but rather see them as a helper to avoid frustration amongst developers, when they find their Pull Request checks failing.
As we can not rely on their execution, Git Hook checks should be repeated when checking the Pull Request on the server!
Not installed to each development environment by default
As Git Hooks live in the
.git/hooks folder and the
.git folder itself is not part of the version control, scripts that are placed into that folder will not be checked in. So new developers that are cloning the repository won't have the Git Hooks in their local
.git folder. Also, remote environments, like GitHub's In-Browser editing or Dev Containers won't have the Git Hooks setup by default.
A common practice around this behavior is placing the scripts into a
.githooks folder in the repository root, and then configuring Git to use its Hooks from there with the
git config core.hooksPath .githooks command. This allows to check in the scripts into version control, but the command still needs to be run in every new environment and won't make the Hooks available by default on new machines or remote environments.
Dependencies might not be installed
Most scripts for checking coding style violations or commit message patterns aren't as basic as the one from above but use other tools like Prettier, ESLint or editorconfig-checker for Coding Style checks or Gitlint (highly recommended) for commit message policies. These tools might not be installed on new or remote environments.
Managing Git Hooks (and their dependencies) with pre-commit
package.json file, where app dependencies and development dependencies are managed.
As I deal with projects across different programming languages and frameworks, I was looking for a more generic approach, which can be used with any project. The tool that convinced me the most is pre-commit, as it
- manages and installs Git Hooks
- manages and installs dependencies (= tools, that my Git Hooks use)
- can be run in CI pipelines to check, if Git Hooks haven't been skipped
- is technology independent (in contrast to popular Husky)
The configuration for pre-commit is stored in a
.pre-commit-config.yaml file at the repository root and contains the hooks to configure and the tools that should be executed.
Installing Git Hooks for each developer
The configuration above allows to manage Git Hooks and their dependencies centrally in the repository. But the pre-commit tool itself still needs to be installed on each machine. Once installed, the hooks (and their dependencies) can be installed with a single command.
Cross-Checking Git Hooks in the CI pipeline
As Git Hooks can be bypassed, it is highly recommended to cross-check them in Pull Request Checks or the CI pipeline. This is another reason, why I like pre-commit so much: The scripts can be executed at-hoc for checking the current Git repository and its commits for violations.
On GitHub, you can check Pull Requests with the following workflow.
But what to do, when Pre-Push or Pull Request checks find policy violations in our code or commit messages, but the commit is already created and part of the current branch's history?
In case of a committed file that needs to be adjusted, the history of the current branch can be soft-reset to undo all commits that happened on that branch. Don't worry, your work won't be lost. This will put all modifications back to the list of uncommitted changes.
git checkout pr_branch
git reset --soft main
Before creating a commit, we can now adjust the files. Afterward, we can create a new commit without the violation.
git add -A && git commit -m "commit message goes here"
git push --force
To change a commit message afterward, we can follow a comprehensive Guide from GitHub.
Both, resetting the history and changing commit messages is annoying. To avoid that, it is generally recommended to do most checks at the
☝️ Advertisement Block: I will buy myself a pizza every time I make enough money with these ads to do so. So please feed a hungry developer and consider disabling your Ad Blocker.