Git: Task Automation Using Hooks
Bài đăng này đã không được cập nhật trong 5 năm
Git hooks are event callbacks that fire when an important event occurs (e.g. a commit is made, a rebase is about to take place, etc.). Hooks can be classified into,
- Server hooks
- Client hooks
As the name implies, client hook executes on the local repository, where server hook executes on the remote server. In this article, we'll briefly go through the features, usage and example usage scenarios of some client git hooks.
Hooks are powerful as they provide access to the critical transition points of a git operation flow. Many repetitive tasks can be automated by utilizing hooks. To name such,
- Performing static checks before a push
- Generating commit message before a commit
- Generating documentation on commit
Client Hooks
Client hooks are usually stored in the .git/hooks
sub-directory, in a repository. Initially, the following hook templates are provided with each repository.
applypatch-msg.sample
commit-msg.sample
post-update.sample
pre-applypatch.sample
pre-commit.sample
prepare-commit-msg.sample
pre-push.sample
pre-rebase.sample
update.sample
Any script (e.g. bash, ruby, python, ...) will work as git hook, as long as the formatting is correct, and execution runtime is available.
Installing a Hook
Each hook name must represent the associated hook type. For an example, if we want to deploy a pre-push
hook, we need to put the script exactly in the following file.
.git/hooks/pre-push
And, the hook file must be executable.
In order to install a dummy pre-commit
hook, we can do the followings.
$ cd $PROJECT_ROOT
$ printf '#!/bin/sh\necho "Hook invoked!"' > .git/hooks/pre-commit
$ chmod +x .git/hooks/pre-commit
Usage Scenarios
Example scenario #1: Generate default commit message
Consider a project that has a commit message template. In general, we need to repeatedly type the commit message before commiting.
Using hooks, it is possible to entirely skip writing the commit message during each commit.
Consider the following Ruby implementation of the prepare-commit-msg
hook that prepares the default git commit message for us.
#!/usr/bin/env ruby
File.open(ARGV.first, "w"){|file| file.write Time.now.strftime("%F %T Bump")}
The above hook prepare a default commit message, that can be edited as necessary, or we can just skip writing the commit message altogether! This way, in order to commit, we just need to invoke the followings,
git commit --no-edit
Please note that, in order to install the above hook we must put it in .git/hooks/prepare-commit-msg
file, and make the file executable (e.g. by running chmod +x .git/hooks/prepare-commit-msg
)
Example scenario #2: Build Docusaurus documentation on markdown change
Consider a project where documentation is written in Markdown, and the project script can automatically build HTML equivalent for the documentation (e.g. Docusaurus). Developer may want to track markdown and synchronized HTML, both.
The non-hook solution will be to run the build script manually, every time there is a change in the documentation.
But, if we use git client hook, we may skip the entire flow, since hook will take care of the file change checking and building, automatically.
An example bash script implementation of the above mentioned hook may look as follows.
#!/bin/sh
root_dir=$(git rev-parse --show-toplevel)
doc_app_dir="$root_dir/documentation/website"
doc_build_dir="$root_dir/docs"
target_dir_patt="documentation/"
if git diff --cached --name-only | grep --quiet "$target_dir_patt"
then
echo "Rebuilding docs before committing changes..."
(cd $doc_app_dir && npm run build)
git add $doc_build_dir
fi
exit 0
Let's go through the code a bit.
The bash script above collects references of relevant project directories.
root_dir=$(git rev-parse --show-toplevel)
doc_app_dir="$root_dir/documentation/website"
doc_build_dir="$root_dir/docs"
target_dir_patt="documentation/"
Then, it checks for any documentation (markdown) change in the target_dir_patt
.
if git diff --cached --name-only | grep --quiet "$target_dir_patt"
Then, if changes are found, it rebuilds the documentation. And, on a successful build, it stores the build files in doc_build_dir
.
echo "Rebuilding docs before committing changes..."
(cd $doc_app_dir && npm run build)
And, finally, the generated changes are staged for commit.
git add $doc_build_dir
We return the exit code 0
immediately before EOF to indicate a successful execution.
Our hook will effectively track the changes and build without developer's intervention, which effectively increases the productivity.
Some Significant Hooks
Committing-Workflow Hooks
pre-commit
Hook
Usage
- Used to inspect the snapshot that is about to be committed
- Used to check if something is forgotten
- Code style (e.g.
lint
) can be checked - Trailing whitespace check (default hook)
- To make sure that tests are run
- Check appropriate documentation on new methods
Execution
This hook runs before a commit, before commit message is provided
Behavior
Exiting non-zero from this hook aborts the commit
prepare-commit-msg
Hook
Usage
- Can be used to edit the default message before commit author sees it
- Can be used in conjunction with a commit template to programmatically insert information
NOTE
- Not usually useful for normal commits
- Useful for commits where default messages are auto-generated (e.g. templated commit messages, merge commits, squashed commits, amended commits)
Execution
Runs before commit message editor is fired up, but after the default message is created
Parameters
- Path to the file that holds commit message
- Type of commit
- Commit SHA-1, if this is an amended commit
commit-msg
Hook
Usage
- Can be used to validate project state or commit message
- Can be used to check that commit message is conformant to a required pattern
Parameters
Path to a temporary file that contains the commit message written by the developer
Behavior
If script exits non-zero, git aborts the commit process
post-commit
Hook
Usage
- Usually used for notification or something similar
Execution
Runs after the entire commit process is completed
Parameters
- None
Features
Last commit can be retrieved by running git log -1 HEAD
Other Hooks
pre-rebase
Hook
Usage
- Can be used to disallow rebasing any commit that have already been pushed
Execution
Runs before anything is rebased
Behavior
Non-zero exit halts the rebase process
post-rewrite
Hook
Usage
- Has identical usage like
post-checkout
andpost-merge
hooks
Execution
Runs by commands that replace commits (e.g. git commit --amend
, git rebase
)
Warning: This hook is not run by git filter-branch
Parameters
- The command that triggered the rewrite
- Also receives a list of rewrites on
stdin
post-checkout
Hook
Usage
- Can be used to setup working directory properly for project environment
- Example includes,
- Moving in large binary files that are not source controlled
- Auto generating documentation or something of that sorts
Execution
Runs after a successful git checkout
post-merge
Hook
Usage
- Can be used to restore data in the working tree that Git can't track (e.g. permission data)
- Can be used to validate the presence of files external to Git control that may need to be copied in when working tree changes
Execution
Runs after a successful merge
pre-push
Hook
Usage
- Can be used to validate a set of ref updates before a push occurs
WARNING: Updating commit (e.g. amend) will not be effective, cause, only the original commit gets submitted, regardless of any change in the hook
Execution
Runs during git push
, after the remote refs have been updated, but before any objects have been transferred.
Parameters
- Name of the remote
- Location of the remote
- List of to-be-updated refs through
stdin
Behavior
- A non-zero exit code aborts the push
pre-auto-gc
Hook
Usage
- Can be used for notification
- Can be used to abort
Execution
Occasionally Git does garbage collection as part of its normal operation. This hook runs before garbage collection process takes place
Bypassing Hooks
One of the advantage of Git client hook includes it's flexibility. For an example, when hooks are installed, you may need to skip the callback during a certain git operation. You may simply achieve this by using the --no-verify
flag with the git command. This will bypass all the associated hooks.
git commit --no-verify # No hooks will be triggered
Hooks can be integrated as a part of the development workflow to accelerate the development process. It is advantageous to use since it provides the flexibility of developing script in virtually any language with the expected runtime support. Give it a try, and, happy hacking!
References
All rights reserved