spiegel-keyman/resources/git-hooks/prepare-commit-msg
Eberhard Beilharz 1becb87547
maint(resources): honor configure comment character in commit message
Git allows to specify a comment character different from the default
`#`. This is helpful when using markdown in commit messages, especially
when adding a user test section that has to start with `# User testing`
in order for the test bot to detect it.

This change modifies the `prepare-commit-msg` hook and adds a check for
a configured `core.commentString` or `core.commentChar`. If both are not
set it falls back to the default `#` character.

You can set a different comment character with
`git config core.commentChar //`.

Build-bot: skip
Test-bot: skip
2025-09-05 10:45:58 +02:00

180 lines
5.5 KiB
Bash
Executable file

#!/usr/bin/env bash
#
# This prepare-commit-msg hook prepares commit messages to ensure
# that they meet our conventional commit standard.
#
# Whenever `git` detects an incoming commit, it will trigger this hook. If the message does not
# our conventional commit format, it will set it to the following format. (commit and merge only)
#
# "type(scope): message"
# If an issue number is detected at the front of the message and it is not a merge, also adds "___. Fixes #1234"
#
# Reference: https://github.com/keymanapp/keyman/wiki/Pull-Request-and-Commit-workflow-notes
#
COMMIT_MSG_FILE="$1"
COMMIT_SOURCE="$2"
SHA1="$3"
# 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
# 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%?})?"
recherry="(cherry-pick\/)?"
# Optionally captures an issue number at the front of the branch's name.
rebranchname="(([[:digit:]]+)\-)?(.+)"
regexp="^${retypes}\/${rescope}\/?${recherry}${rebranchname}\s*$"
}
function prepend_scope() {
# COMMIT_MSG_FILE and $COMMIT_SOURCE are already available.
# Step 1: if the branch follows our preferred branch conventions, we can use that!
branch="$(git rev-parse --abbrev-ref HEAD)"
build_regex
# Perform the actual regex check.
if [[ $branch =~ $regexp ]]; then
TYPE="${BASH_REMATCH[1]}"
SCOPE="${BASH_REMATCH[2]}"
CHERRYPICK="${BASH_REMATCH[3]}"
# 4 - just ISSUE, but with an appended '-'. Ashame BASH doesn't support non-capture groups.
ISSUE="${BASH_REMATCH[5]}"
NAME="${BASH_REMATCH[6]}"
EXTRA_WHITESPACE="\n"
# For known merge commits, consider the merge operation to be a 'chore'.
if [ "${COMMIT_SOURCE}" = "merge" ]; then
TYPE="chore"
ISSUE= # Invalidates adding the 'postfix' for these commit types.
EXTRA_WHITESPACE=
fi
# Build commit message title
if [ -z "$SCOPE" ]; then
prefix="$TYPE: $EXTRA_WHITESPACE"
else
prefix="$TYPE($SCOPE): $EXTRA_WHITESPACE"
fi
if [ ! -z "$CHERRYPICK" ]; then
prefix ="${prefix}🍒"
fi
# Add commit message trailers
if [ "${COMMIT_SOURCE}" != "merge" ]; then
if [ -n "${ISSUE// }" ] || [ ! -z "$CHERRYPICK" ]; then
prefix="${prefix}\n"
fi
if [ -n "${ISSUE// }" ]; then # Refer to https://unix.stackexchange.com/a/146945 for explanation.
prefix="${prefix}Fixes: #$ISSUE\n"
fi
if [ ! -z "$CHERRYPICK" ]; then
prefix="${prefix}Cherry-pick-of: #_ID_"
fi
fi
commentchar="#"
if [[ -n "$(git config core.commentString)" ]]; then
commentchar="$(git config core.commentString)"
elif [[ -n "$(git config core.commentChar)" ]]; then
commentchar="$(git config core.commentChar)"
fi
postfix=$(
cat <<EOF
\n${commentchar} Keyman Conventional Commit suggestions:
${commentchar}
${commentchar} - Link to a Sentry issue with git trailer:
${commentchar} Fixes: _MODULE_-_ID_
${commentchar} - Give credit to co-authors:
${commentchar} Co-authored-by: _Name_ <_email_>
${commentchar} - Use imperative, present tense ('attach' not 'attaches', 'attached' etc)
${commentchar} - Don't include a period at the end of the title
${commentchar} - Always include a blank line before trailers
${commentchar} - More: https://github.com/keymanapp/keyman/wiki/Pull-Request-and-Commit-workflow-notes
EOF
)
# Reuse any existing message text, wrapping it in our conventional commit formatting before
# presenting it to the user for any final edits.
echo -e "$prefix$(cat $COMMIT_MSG_FILE)$postfix" > $COMMIT_MSG_FILE
fi
# If it doesn't follow our branch-name format, we can't provide any help.
}
#
# Configurable options for types, scopes and message length.
#
# Step 1 - is the prepared message already in our conventional commits format?
$HOOK_DIRECTORY/commit-msg -q $COMMIT_MSG_FILE
isConventional=$? # Grabs the exit code.
if [ $isConventional -eq 0 ]; then
# If so, great! Don't do a thing!
exit 0
fi
# Debugging aides
#echo "COMMIT_MSG_FILE=$COMMIT_MSG_FILE"
#echo "COMMIT_SOURCE=$COMMIT_SOURCE"
# The default message is NOT in conventional format. Time to help out with that.
case $COMMIT_SOURCE in
squash)
# We don't help with squashes - this tends to combine two separate messages,
# and that can be messy.
exit 0
;;
merge|commit|'') # '' = plain `git commit`
# We can help here!
prepend_scope
;;
*)
# Default handler - we don't handle these types yet. Might be worth considering.
# Known possibilities: message (-m), template (-t)
exit 0
;;
esac