A Guide to Git Hooks for Enhanced Project Management and Collaboration

Badr Kouki
5 min readMar 11, 2024

Have you ever found yourself yearning for an impeccable workflow while tackling a project, particularly when employing tools like Git, GitHub/GitLab, CI/CD Pipelines, and Jira? Perhaps you’ve dreamt of simplifying the management of these elements and embracing a fully automated process, allowing you to effortlessly push your code and step out for coffee with friends. Look no further — this article is your guide to kickstarting the journey toward realizing this streamlined and efficient development experience.

Photo by Sven Mieke on Unsplash

This article is intended for all developers, encompassing not only mobile or Flutter developers but also those working with diverse technologies, including Excel files. The concept of streamlining your workflow applies universally, and this article aims to demonstrate how it can be effectively implemented using Git, regardless of the specific development environment.

Within this article, I will outline workflow principles that I personally employ within my team and extend to all projects I undertake. Before delving into these principles, I’ll provide a straightforward explanation of Git hooks, offering you a clear understanding of this essential aspect of version control.

Git Hooks ?

Git hooks consist of a series of scripts designed to be triggered during specific actions within a repository. For instance, when committing new changes, the commit-msg hook script is automatically invoked.

These scripts are inherent in every Git repository, residing within the .git folder. To locate them, navigate to the .hooks folder where you’ll find files with a .sample extension, indicating that the scripts are presently disabled. To activate a particular script, simply remove the .sample extension.

Feel free to activate as many hooks as needed, without the obligation to enable all of them simultaneously.

Force commit messages and branch ?

Initially, this might give you the sensation of being confined or constrained, almost like being tethered to a chair when executing a commit that demands a specific message following the script of the Git hook.

The intention here is to leverage the pre-commit and commit-msg hooks to enforce a standardized approach to branch naming and commit message formatting.

Benefits:

  • If you are utilizing Jira, you have the capability to automate ticket updates, eliminating the necessity for developers to manually adjust the task status with each modification. By incorporating Git hooks, you can seamlessly establish this integration between Jira and GitLab/GitHub, achieved through branch names and tagging tasks in commit messages.
  • The result is a well-organized Git repository where branches bear meaningful names.
  • providing clarity on the purpose of each commit.
  • This implementation not only ensures a clean repository but also promotes the application of Git best practices consistently across all developers involved in the project.

How to ?

To establish this workflow, we will exclusively utilize pre-commit and commit-msg hooks.

The pre-commit hook will verify the validity of the branch name against the predefined whitelist. Meanwhile, the commit-msg hook will scrutinize the commit message crafted by the developer.

Here is the pre-commit script I’ve devised to examine the current branch name. Let’s assume that the acronym for your Jira project is KP:

#!/usr/bin/env bash
LC_ALL=C

local_branch="$(git rev-parse --abbrev-ref HEAD)"

jira_branch_name_template="^(closes|resolves|fixes)-KP-[0-9]+-[a-z0-9-]+$"
valid_github_branch_template="^(hotfix|fix|enhance|feature)-#[0-9]+-[a-z0-9-]+$"
refactor_branch_template="^refactor(-[a-z]+)+$"
test_branch_template="^test(-[a-z]+)+$"
build_branch_template="^build(-[a-z]+)+$"
design_branch_template="^design(-[a-z]+)+$"
config_branch_template="^config(-[a-z]+)+$"

jira_branch_name_template="^($jira_branch_name_template)$"
test_branch_regex="^($test_branch_template)$"
valid_github_branch_template="^($valid_github_branch_template)$"

message="There is something wrong with your branch name."
message+="Branch names in this project must adhere to this contract: \n $jira_branch_name_template \n $valid_github_branch_template \n $test_branch_template \n examples: \n"
message+="\033[32m-fix-#3-update-login-screen-ui\033[0m \n\033[32m-resolves-KP-3-update-login-screen-ui\033[0m \n\033[32m-test-calendar-widget-time-tracking\033[0m \n"
message+="\033[31mYour commit will be rejected. You should rename your branch to a valid name and try again.\033[0m"

if ! [[ $local_branch =~ $jira_branch_name_template ]] && ! [[ $local_branch =~ $config_branch_template ]] && ! [[ $local_branch =~ $test_branch_template ]] && ! [[ $local_branch =~ $valid_github_branch_template ]] && ! [[ $local_branch =~ $refactor_branch_template ]] && ! [[ $local_branch =~ $build_branch_template ]] && ! [[ $local_branch =~ $design_branch_template ]]
then
echo -e "$message"
exit 1
fi

exit 0

The example above employs regular expressions to compare the local branch name with the predefined templates, providing a response accordingly. If the exit code is 0, the commit command proceeds to the second hook, commit-msg. Conversely, if the exit code is non-zero, the commit command is halted and aborted.

Here is the commit-msg script that will verify the commit message itself if it’s following the format needed or not

#!/usr/bin/env bash
INPUT_FILE=$1
START_LINE=$(head -n1 "$INPUT_FILE")
JIRA_PATTERN="^(Resolves|Closes|Fixes) KP-[0-9]+: "
MERGE_COMMIT_PATTERN="^Merge branch+"
GITLAB_PATTERN="^(Fix|Feature) #[0-9]+: "
SECOND_PATTERN="^(Test): "
REFACTOR_PATTERN="^(Refactor): "
BUILD_PATTERN="^(Build): "
DESIGN_PATTERN="^(Design): "
CONFIG_PATTERN="^(Config): "
FIX_BUG_PATTERN="^(Fix Bug): "

message="\033[31mCannot commit changes.\033[0m\n"
message+="Commit messages should follow this format example:\n"
message+="- $JIRA_PATTERN\n- $GITLAB_PATTERN\n- $SECOND_PATTERN\n- $REFACTOR_PATTERN\n- $BUILD_PATTERN\n- $DESIGN_PATTERN\n"
message+="Examples:\n"
message+="\033[32m-Fixes KP-9: Update user creation process (For jira ticket)\033[0m\n"
message+="\033[32m-Fix #9: Update user creation process (For gitlab issue)\033[0m\n"
message+="\033[32m-WIP: user creation process (For Work in progress)\033[0m \n"

if ! [[ "$START_LINE" =~ $JIRA_PATTERN ]] && ! [[ "$START_LINE" =~ $CONFIG_PATTERN ]] && ! [[ "$START_LINE" =~ $FIX_BUG_PATTERN ]] && ! [[ "$START_LINE" =~ $GITLAB_PATTERN ]] && ! [[ "$START_LINE" =~ $SECOND_PATTERN ]] && ! [[ "$START_LINE" =~ $MERGE_COMMIT_PATTERN ]] && ! [[ "$START_LINE" =~ $REFACTOR_PATTERN ]] && ! [[ "$START_LINE" =~ $BUILD_PATTERN ]] && ! [[ "$START_LINE" =~ $DESIGN_PATTERN ]]; then
echo -e "$message"
exit 1
fi
exit 0

As previously mentioned, if the pre-commit script succeeds, the commit command proceeds to the commit-msg script. In this scenario, if the commit message adheres to the specified criteria, the commit is executed. However, if the commit message is incorrect, the developer must make the necessary corrections before attempting the commit again.

Conclusion

In summary, optimizing your development workflow by strategically leveraging Git hooks can greatly improve project management and collaboration.

Through the integration of pre-commit and commit-msg hooks, developers can uphold branch name validity and commit message standards, ensuring a tidy and well-organized Git repository. These principles are universally applicable across different technologies.

Embracing automation with Git hooks cultivates a more streamlined and consistent development process, leading to enhanced code quality and improved collaboration within the team.

I encourage you to implement these practices in your workflow, unlocking the full potential of version control and collaboration in your development projects.

--

--

Badr Kouki

With more than 2 years of experience, Kouki Badr is a Flutter, Android and iOS developer. Bachelor degree in computer science and artificial intelligence.