#!/usr/bin/env bash # # A 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. # The hook is allowed to edit the commit message file. # # To enable this hook, save this file in ".git/hooks/commit-msg". die() { printf "$1" >&2 exit ${2:-1} } subject="$(head -1 "$1")" # Ignore "fixup! " and "squash! " prefix' added by `git-commit` subject="${subject#fixup! }" subject="${subject#squash! }" # git's character to comment out lines in commit messages. The `auto` value is # handled specially (see the different uses of $git_comment_char). # NOTE: Only `^` is escaped as $git_comment_char is used in character classes # and thus all other characters are free to use. git_comment_char="$(git config --get --default='#' core.commentChar \ | sed 's:\^:\\&:g')" # git's cut-line to cut off everything behind it (e.g. commit patch with # `commit.verbose`) cut_line='------------------------ >8 ------------------------' # Take all lines after the subject until EOF or the cut-line (here `auto` is # replaced with 'any character' as the cut-line in itself should hopefully be # unambiguous enough) and remove all lines starting with the comment-char # (`auto` is not handled properly but rather replaced with the default `#`) body="$( tail +2 "$1" \ | sed -n "/^[${git_comment_char/auto/[:print:]\t}] $cut_line\$/q /^[^${git_comment_char/auto/\#}]/p" )" [[ ${#subject} -le 50 ]] || die "Subject too long. (<= 50)\n" # The subject line has to match "${pats[@]}", but to be more verbose different # error messages are printed for the different 'levels' of the pattern. declare -a pats msg pats=( "^([-_,*(){}./a-zA-Z0-9]+:)+ " "[A-Z]" ".*[^.]$" ) msg=( "Specify which program was modified. (e.g. \"zsh:p10k: \")\n" "Start subject with a capital letter.\n" "Remove punctuation mark from end.\n" ) [[ ${#msg[@]} -ge ${#pats[@]} ]] || die "Something went wrong internally.\n" for ((i = 0; i < ${#pats[@]}; i++)); do if ! grep -qE "$(printf "%s" "${pats[@]:0:$i+1}")" <<<"$subject"; then die "${msg[$i]}" fi done BKP_IFS="$IFS" IFS=' ' for line in $body; do [[ ${#line} -le 72 ]] || die "Body lines too long. (<= 72)\n" done IFS="$BKP_IFS"