What are Git Hooks?
Git hooks are scripts that automatically run on a particular git event. These git events could be committing a change, checking out a branch or performing a rebase. Hence, making them an important mechanism to enhancing automation and removing tedious manual work that are prone to human error. In this blog post, we will explore three custom git hook implementations that can be added to an Android project that will not only enhance developer productivity but also increase automation throughout the development workflow. I highly encourage you to explore and customize these git hooks to match your needs.
Git hooks are files that are named in a certain way for git to start using them in the git workflow. To get started, create a .githooks folder in the root of the project. Then create the separate files for the desired git hook you wish to use. For example, if you want to run a command prior to a commit, you can create a file named pre-commit in the .githooks folder, which will contain the implementation on what needs to be executed. Then whenever a commit is being made, the pre-commit hook will run the script and create the commit only if the script has passed. In this blog post, we will create three files named pre-commit, commit-msg and post-commit respectively. Since we are adding them to the .githooks folder, we can push these to the remote source control repository to share them with the team.
Once the .githooks folder is created with all three git hook files, ensure that they have the permission to be executed. The next step is to configure the hooksPath to allow git to target this folder when running the git hooks for the repository. Open the terminal and navigate to the root of the project, then execute the following command shown in step 2.
Step 1 - Create the folder and files
.githooks/ - pre-commit - commit-msg - post-commit
Step 2 - Configure git to use this folder
git config core.hooksPath .githooks
The pre-commit is a git hook that gets executed prior to a commit. Once the changes have been added and a git message is provided, git will execute the pre-commit script. If the script has finished successfully, then it would proceed to making the commit. However, if the script has failed, the commit would not be created. This is extremely useful if you wish to perform additional verification on the changes prior to creating a commit on your local machine.
From prior experience, the lint task is usually the step that often fails a pipeline. Whether it is a code formatting issue or an unused import that has been added, it can be quite annoying to wait for the CI pipeline to finish to indicate whether the pull request has any lint issues. Rather than relying on the lint check on the CI pipeline, which can take a lot of resources and long time to process, the lint check can be executed on the local machine using the pre-commit hook. If no lint issues are present in the new changes, it will proceed with the commit. Or else, it will alert the developer that lint issues are present and the fix is required before a commit can be created. This will significantly reduce the feedback loop from minutes to just seconds. To run the link check in your pre-commit hook, check out the code below in step 3.
Note: pre-commit hooks can be used for anything, so feel free to use a different linter, such as ktlint or detekt, or perform a different task such as building the app or running unit tests. However, the pre-commit checks should be a quick and small task since they are expected to be executed every time a commit has been made.
Step 3 - Create a pre-commit file and add the lint check
#!/bin/sh # # An example hook script to verify that the changes do not contain # any lint issues. The hook will prevent a commit to be made if the # status of the lint task is a non-zero result. # To enable this hook, the file has to be named "pre-commit". # Run Android Lint ./gradlew lintDebug status=$? # Display messaging based on the result if [ $status -eq 0 ]; then echo "=======================================================" echo "Pre-commit check passed. Changes will now be committed." echo "=======================================================" else echo "=======================================================" echo "Pre-commit check failed. Changes include lint issues." echo "=======================================================" fi exit $status
The commit-msg hook is a git hook that executes a script whenever a commit message has been entered. The script is provided the path of a temporary file that contains the proposed git message. This is the perfect place to validate the commit message. If the git commit does not the meet the standard set by the team, the commit-msg could fail the script and prevent the commit to be made. This is a great and easy way to align the team to use a specific git commit structure.
An example of a commit-msg hook could be to examine the commit message that is going to be added and determine if it includes vital information, such as the corresponding ticket number. Since most teams use some sort of project management tool, such as Jira, all tasks need to be associated with a ticket number. This can be a useful technique for debugging which commits belong to which tasks when parsing the develop or main branch. Ideally, not much work should be included in this git hook apart from checking the git commit (consider using post-commit instead). The code in step 4 shows an example of a commit-msg hook that parses the commit message and validate if it starts with a ticket number.
Step 4- Create a commit-msg file and add the check
#!/bin/sh # # An example hook script to check the commit log message. # Called by "git commit" with one argument, the name of the file # that has the commit message. The hook should exit with non-zero # status after issuing an appropriate message if it wants to stop the # commit. # To enable this hook, rename this file to "commit-msg". commit_msg=$(cat $1) # Check if commit message starts with DROID. Replace with your ticket name. if [[ $commit_msg == DROID-* ]]; then echo "=======================================================" echo "Commit-msg check passed. Changes will now be committed." echo "=======================================================" exit 0 else echo "=======================================================" echo "Commit-msg check failed. Commits must start with DROID-" echo "=======================================================" exit 1 fi
The post-commit hook is a git hook that executes a script after the commit has been created. The status of the hook, unlike the previous hooks, have no impact on the commit itself. It is ideally used in scenarios to notify or perform additional tasks after the commit has successfully been created. Example of this could be capturing the commit log statement from the previously created commit, building an app for distribution or notifying various stakeholders through some communication channel.
The example of the post-commit hook in this blog post is to build a release APK once a commit has been made on the release branch. These commits are usually updating the build version and name. Once the developer has updated the version and starts to make the release, they would often push the update to their CI/CD pipeline. In parallel, the post-commit hook could generate a release APK that can be distributed internally or stored in a specific directory on the local machine. However, automatic distribution to Firebase App Distribution or Google Play Console should be done on the CI/CD pipeline as it is more efficient and secure. The code in step 5 shows an example of a post-commit hook creating a release APK when a commit is made on a release branch.
Step 5 - Create a post-commit file and add the check
#!/bin/sh # # An example hook script to perform additional tasks during the # post-commit flow. In this case, it creates a release APK if # the commit has been made on the release branch. # Status of this script does not impact the commit. # To enable this hook, rename this file to "post-commit". branch_name=$(git symbolic-ref --short -q HEAD) if [[ $branch_name == release* ]]; then echo "=======================================================" echo "Performing post-commit hook. Building release APK..." ./gradlew assembleRelease echo "=======================================================" else echo "===========================================================" echo "Committing on a non-release branch, skipping post-commit..." echo "===========================================================" fi
Git hooks are a powerful tool to enhance automation within the codebase. Developers spend a lot of time performing manual tedious tasks that take them away from problem solving or adding any value to the business. With git hooks, some of the tasks can be done more quickly and efficiently on the local machine to boost developer productivity. Best of all, these hooks can be shared with the team which can help create a standard of practice when developing and maintaining an Android project.
Till next time, keep automating!