From d39fd8d8e7263916bf90f04b8ec2df3d90c7cad0 Mon Sep 17 00:00:00 2001 From: Riyyi Date: Thu, 18 Aug 2022 22:15:40 +0200 Subject: [PATCH] Meta: Add commit linting commit-hook --- script/lint-commit.sh | 64 +++++++++++++++++++++++++++++++++++++++++++ script/pre-commit.sh | 40 +++++++++++++++------------ 2 files changed, 86 insertions(+), 18 deletions(-) create mode 100755 script/lint-commit.sh diff --git a/script/lint-commit.sh b/script/lint-commit.sh new file mode 100755 index 0000000..e4577b8 --- /dev/null +++ b/script/lint-commit.sh @@ -0,0 +1,64 @@ +#!/bin/sh + +# Lint commit message +# Depends: git + +# ------------------------------------------ + +error() { + b="$(tput bold)" + red="$(tput setf 4)" + n="$(tput sgr0)" + + echo "${b}${red}Error:${n} $1" >&2 + exit 1 +} + +if [ ! -d ".git" ]; then + error "please run this script from the project root" +fi + +# The file containing the commit message is passed as the first argument +file="$1" +message="$(cat "$file")" + +newline="$(printf '\x0D')" +if grep -Uq "$newline" "$file"; then + error "commit message contains CRLF line breaks (only unix-style LF linebreaks are allowed)" +fi + +lineNumber=0 +echo "$message" | while read -r line; do + lineNumber=$((lineNumber + 1)) + lineLength=${#line} + + # Ignore comment lines + if echo "$line" | awk '$0 !~ /^#.*/ { exit 1 }'; then continue; fi + # Ignore overlong 'fixup!' commit descriptions + if echo "$line" | awk '$0 !~ /^fixup! .*/ { exit 1 }'; then continue; fi + + if [ "$lineNumber" -eq 2 ] && [ "$lineLength" -ne 0 ]; then + error "empty line between commit title and body is missing" + fi + + categoryPattern="^\S.*?\S: .+" + if [ $lineNumber -eq 1 ] && (echo "$line" | grep -Evq "$categoryPattern"); then + error "missing category in commit title (if this is a fix up of a previous commit, it should be squashed)" + fi + + titleCasePattern="^\S.*?: [A-Z0-9]" + if [ $lineNumber -eq 1 ] && (echo "$line" | grep -Evq "$titleCasePattern"); then + error "first word of commit after the subsystem is not capitalized" + fi + + if [ $lineNumber -eq 1 ] && (echo "$line" | awk '$0 !~ /\.$/ { exit 1 }' ); then + error "commit title ends in a period" + fi + + urlPattern="([a-z]+:\/\/)?(([a-zA-Z0-9_]|-)+\.)+[a-z]{2,}(:\d+)?([a-zA-Z_0-9@:%\+.~\?&\/=]|-)+" + if [ "$lineLength" -gt 72 ] && (echo "$line" | grep -Evq "$urlPattern"); then + error "commit message lines are too long (maximum allowed is 72 characters)" + fi +done + +exit 0 diff --git a/script/pre-commit.sh b/script/pre-commit.sh index 9b9bac8..527774e 100755 --- a/script/pre-commit.sh +++ b/script/pre-commit.sh @@ -40,33 +40,37 @@ fi # Get the path from the project root to the script subDir="$(dirname -- "$0")" -hooks=" -lint-ci.sh -" +create() { + file="$1" + if ! test -f "$file"; then + touch "$file" + chmod +x "$file" + echo "#!/bin/sh" > "$file" + fi +} install() { - echo "Installing pre-commit hooks" + echo "Installing commit hooks" preCommit=".git/hooks/pre-commit" - if ! test -f "$preCommit"; then - touch "$preCommit" - chmod +x "$preCommit" - echo "#!/bin/sh" > "$preCommit" - fi - - for hook in $hooks; do - sed -Ei "/$hook/d" "$preCommit" - sed -Ei "\$ a $subDir/$hook" "$preCommit" - done + create "$preCommit" + sed -Ei "/lint-ci.sh/d" "$preCommit" + sed -Ei "\$ a $subDir/lint-ci.sh" "$preCommit" + + commitMsg=".git/hooks/commit-msg" + create "$commitMsg" + sed -Ei "/lint-commit.sh/d" "$commitMsg" + sed -Ei "\$ a $subDir/lint-commit.sh" "$commitMsg" } remove() { - echo "Removing pre-commit hooks" + echo "Removing commit hooks" preCommit=".git/hooks/pre-commit" - for hook in $hooks; do - sed -Ei "/$hook/d" "$preCommit" - done + sed -Ei "/lint-ci.sh/d" "$preCommit" + + commitMsg=".git/hooks/commit-msg" + sed -Ei "/lint-commit.sh/d" "$commitMsg" } # Command handling