#!/usr/bin/env bash # # This commit-msg hook validates commit messages # to ensure that they meet our conventional commit standard. # # # "type: message" # "type(scope): message" # Optionally, append ". Fixes #1234" # # Reference: https://github.com/keymanapp/keyman/wiki/Pull-Request-and-Commit-workflow-notes # # Get the directory where this script lives, i.e. resources/git-hooks HOOK_DIRECTORY="$(dirname "$(readlink -f "${BASH_SOURCE[0]}")")" if [ -f $(dirname "${BASH_SOURCE[0]}")/commit-msg-defs ]; then # This script was run from a symlink and the repo in question has its own commit-msg-defs. . $(dirname "${BASH_SOURCE[0]}")/commit-msg-defs else # We'll grab the settings from the keymanapp/keyman repository. . "$HOOK_DIRECTORY/../build/jq.inc.sh" # Configurable options for message length. min_length=4 max_length=128 # Read from our globally shared JSON-formatted scopes and types scopes=( $("$JQ" -r '[.scopes | paths | reduce .[] as $item (""; if . == "" then $item else . + "/" + $item end)] | join(" ")' < "$HOOK_DIRECTORY/../scopes/scopes.json") ) types=( $("$JQ" -r '.commitTypes | join(" ")' < "$HOOK_DIRECTORY/../scopes/commit-types.json") ) fi # # Define terminal colours. # if [ -t 2 ]; then t_red=$'\e[1;31m' t_grn=$'\e[1;32m' t_yel=$'\e[1;33m' t_blu=$'\e[1;34m' t_mag=$'\e[1;35m' t_cyn=$'\e[1;36m' t_end=$'\e[0m' fi # # Test the repository upstream/origin: ignore if not keymanapp/keyman # GIT_ORIGIN="$(git remote get-url origin)" if [[ ! "$GIT_ORIGIN" =~ github\.com(/|:)keymanapp ]]; then # Not a Keyman repository. We have no opinion. # echo "DEBUG: Not a Keyman repository. We don't care" exit 0 fi if [[ "$GIT_ORIGIN" =~ keyboards|lexical-models ]]; then # We don't enforce commit messages on keyboards or lexical-models repositories # echo "DEBUG: Keyboards or Lexical-Models. We still don't care." exit 0 fi # If this script is called by another hook, it may insert a -q parameter to silence the help text. QUIET=0 case $1 in -q|--quiet) shift # Remove the parameter, making it equivalent to a standard git-hook call. QUIET=1 ;; esac # build the regex pattern based on the config file function build_regex() { retypes= for type in "${types[@]}" do retypes="${retypes}$type|" done retypes="(${retypes%?})" rescope= for scope in "${scopes[@]}" do rescope="${rescope}$scope|" done # %? removes last | rescope="(\((${rescope%?})\))?" remessage=".{$min_length,$max_length}[^.]" refixes="(\. Fixes #[[:digit:]]+)?" # Regex for the commit message title. It's ok if the commit message starts with "fixup!". # (This allows to run `git commit --fixup=` without complaining about the commit # message not being in conventional commit format. This helps in squashing with a previous # commit.) regexp="^(fixup\! )?${retypes}${rescope}: ${remessage}${refixes}[[:space:]]*$" } # Print out a standard error message which explains # how the commit message should be structured function print_error() { echo -e "\n${t_red}ERROR: Commit message is not in Conventional Commit format" echo -e "----------------------------------------------------------${t_end}" echo -e "Valid types: ${t_grn}${types[@]}${t_end}" echo -e "Valid scopes: ${t_grn}${scopes[@]}${t_end}" echo -e "" echo -e "Min length (first line): ${t_grn}$min_length${t_end}" echo -e "Max length (first line): ${t_grn}$max_length${t_end}" echo -e "" echo -e "If possible, append git trailers:" echo -e " * ${t_grn}Fixes: #1234${t_end}" echo -e " * ${t_grn}Fixes: KEYMAN-MODULE-XYZ${t_end}" echo -e " * ${t_grn}Cherry-pick-of: #2468${t_end}" echo -e " * ${t_grn}Co-authored-by: Firstname Lastname ${t_end}" echo -e "" echo -e "${t_cyn}Example:${t_end} fix(windows): Re-attach the widget plug which had fallen out" echo -e "" echo -e "${t_cyn}Reference${t_end}: https://github.com/keymanapp/keyman/wiki/Pull-Request-and-Commit-workflow-notes" echo -e "" echo -e "Tips: " echo -e " * Don't include a period at the end of the title" echo -e " * Always include a blank line before trailers" echo -e " * Use imperative, present tense ('attach' instead of 'attaches', 'attached' etc)" echo -e "" echo -e "This script: $0" } # get the first line of the commit message msg=$(head -1 $1) build_regex if [[ ! $msg =~ $regexp ]]; then # commit message is invalid according to config - block commit if [ $QUIET -eq 0 ]; then print_error fi exit 1 fi