Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add commit message checks to ghaf-infra #78

Merged
merged 4 commits into from
Feb 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions .github/workflows/check-commit-message.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# SPDX-FileCopyrightText: 2022-2024 Technology Innovation Institute (TII)
# SPDX-License-Identifier: Apache-2.0

name: Check Commit Message

on:
push:
branches:
- main
pull_request:
branches:
- main

jobs:
commit-msg:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Check Commit Message
env:
GITHUB_CONTEXT: ${{ toJson(github) }}
run: ./githooks/check-commits.sh
16 changes: 16 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,22 @@ Each host's private ssh key is stored as sops secret and automatically deployed

The secrets configuration and the usage of `sops` is adopted from [nix-community infra](https://github.com/nix-community/infra) project.

## Git commit hook

When contributing to this repo you should take the git commit hook into use.

This hook will check the commit message for most trivial mistakes against [current Ghaf commit message guidelines](https://github.com/tiiuae/ghaf/blob/main/CONTRIBUTING.md#commit-message-guidelines)

### Installing git hooks

Just run ``./githooks/install-git-hooks.sh`` in repository main directory, and you should be good to go. Commit message checking script will then run when you commit something.

If you have branches before the git hooks were committed to the repo, you'll have to either rebase them on top of main branch or cherry pick the git hooks commit into your branch.

Also note that any existing commit messages in any branch won't be checked, only new commit messages will be checked.

If you encounter any issues with the git commit message hook, please report them. And while waiting for a fix, you may remove the hook by running ``rm -f .git/hooks/commit-msg`` in the main directory of the repository.

## License
This repository follows the Ghaf team licensing:

Expand Down
200 changes: 200 additions & 0 deletions githooks/check-commit.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
#!/usr/bin/env bash
# SPDX-FileCopyrightText: 2021-2024 Technology Innovation Institute (TII)
# SPDX-License-Identifier: Apache-2.0

function Error {
if [ -z "$NONINTERACTIVE" ]; then
echo "ERROR: ${1}"
else
echo "::error title=ERROR::${1}"
fi
FAILED=1
}

function Warning {
if [ -z "$NONINTERACTIVE" ]; then
echo "WARNING: ${1}"
else
echo "::warning title=WARNING::${1}"
fi
WARNED=1
}

# Checks count of given field
function Check_count {
local field
local count
local dest
local allowmany

field="$1"
dest="$2"
allowmany="${3:-0}"

count="$(grep -c -e "^${field}:" "$dest" || true)"

case "$count" in
0)
Error "Missing ${field} field"
;;
1)
: # Ok
;;
*)
if [ "$allowmany" == "0" ]; then
Error "Multiple ${field} fields (Only one required and allowed)"
fi
;;
esac
}

function Exit_message {
if [ -z "$NONINTERACTIVE" ]; then
echo ""
echo "Your commit message is not lost (yet), it's saved in the .git dir of the repo"
echo "You probably can use something like this to edit your message:"
echo "git commit -e --file=\$(git rev-parse --git-dir)/COMMIT_EDITMSG"
echo ""
fi
exit 2
}

set -e

MATCH=1
while [ "$MATCH" -eq 1 ]; do
case "${1,,}" in
check-script)
if shellcheck --help > /dev/null 2>&1 && bashate --help > /dev/null 2>&1; then
if shellcheck "$0" && bashate -i E006 "$0"; then
echo "Nothing to complain"
fi
else
echo "Please install shellcheck and bashate to use check-script functionality"
fi
exit 1
;;
""|help|-h|--help)
echo "This script is supposed to be called from the git commit-msg hook (or check-commits.sh)"
echo ""
echo "check-commit.sh [--noninteractive] COMMIT_MSG_FILE Check commit message (noninteractively if specified)"
echo "check-commit.sh check-script Run shellcheck and bashate on the script itself"
echo "check-commit.sh [help|-h|--help] Show this help"
echo ""
exit 1
;;
--noninteractive)
NONINTERACTIVE=1
shift
;;
*)
MATCH=0
;;
esac
done

DEST="$1"

if [ -z "$NONINTERACTIVE" ]; then
# Remove trailing spaces
sed -i 's/[[:space:]]*$//g' "$DEST"

# Remove preceding spaces from subject line
sed -i '1 s/^[[:space:]]*//' "$DEST"

# Reformat Signed-off-by field
sed -i 's/^[[:blank:]]*[sS][iI][gG][nN][eE][dD][ _-]*[oO][fF][fF][ _-]*[bB][yY][[:blank:]]*:[[:blank:]]*/Signed-off-by: /g' "$DEST"
fi

SUBJECT="$(head -n 1 "$DEST")"
SECONDLINE="$(head -n 2 "$DEST" | tail -n 1)"
# Get the longest line length ignoring comments and Signed-off-by field
BODYLINELEN="$(grep -v -e "^[[:blank:]]*#" -e "^Signed-off-by:" "$DEST" | tail -n +2 | wc -L | cut -d ' ' -f 1)"

FAILED=
WARNED=

if [ -z "$NONINTERACTIVE" ]; then
echo ""
fi

if [ -z "$SUBJECT" ]; then
Error "Subject line is empty"
else
if [ "${#SUBJECT}" -gt 50 ]; then
Error "Subject line is longer than 50 characters"
fi
fi

if [ -n "$SECONDLINE" ]; then
Error "There is no empty line after subject line"
fi

if [ "$BODYLINELEN" -gt 72 ]; then
Error "Message body contains lines longer than 72 characters"
fi

Check_count "Signed-off-by" "$DEST" 0

# If first word ends with "ing" or "ed" it is suspected that subject is not in imperative mood.
# If there is a colon (:) in the subject then check the first word after colon. (Allows e.g. a filename at the start)
# As the rule is not perfect, this will only give a warning and confirmation prompt.
if printf "%s" "$SUBJECT" | grep -q -e '\(^.*\?:[[:blank:]]*[^[:blank:]]*\([eE][dD]\|[iI][nN][gG]\)[[:blank:]]\|^[^[:blank:]]*\([eE][dD]\|[iI][nN][gG]\)[[:blank:]]\)'; then
Warning "Subject might not be in imperative (commanding) mood"
fi

# If first letter of first word is not upper case or if first letter of first word after colon (:) is not upper case
# Incorrect capitalization is suspected. As the rule might not be perfect, this only gives a warning and confirmation prompt.
if printf "%s" "$SUBJECT" | grep -q -e '\(^.*\?:[[:blank:]]*[a-z].*$\|^[a-z][^:]*$\)'; then
Warning "Subject capitalization might not be correct"
fi

if [ -n "$FAILED" ]; then
Exit_message
fi

if [ -z "$NONINTERACTIVE" ]; then
# Grab Signed-off-lines
SIGNOFF="$(grep -e "^Signed-off-by:" "$DEST")"

# Delete the Signed-off-by: -line
sed -i '/^Signed-off-by:/d' "$DEST"

# Delete leading and trailing empty lines
sed -i -e '/./,$!d' -e :a -e '/^\n*$/{$d;N;ba' -e '}' "$DEST"

{
# Add an empty line
printf "\n"
# Add Signed-off-by lines
printf "%s\n" "$SIGNOFF"
} >> "$DEST"

if [ -n "$WARNED" ]; then
STR=
while [ -z "$STR" ]; do
echo -e "\n${SUBJECT}\n"
echo -n "Are you sure you want to continue with this? (Y/N): "
read -r STR < /dev/tty
case "$STR" in
y|Y)
echo "Commit message accepted with warnings"
exit 0
;;
n|N)
echo "Aborted"
Exit_message
;;
*)
STR=
;;
esac
done
fi

echo "Commit message seems OK"
else
if [ -n "$WARNED" ]; then
exit 1
fi
fi
125 changes: 125 additions & 0 deletions githooks/check-commits.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
#!/usr/bin/env bash
# SPDX-FileCopyrightText: 2022-2024 Technology Innovation Institute (TII)
# SPDX-License-Identifier: Apache-2.0

# Get actual directory of this bash script
SDIR="$(dirname "${BASH_SOURCE[0]}")"
SDIR="$(realpath "$SDIR")"

TMPF="/tmp/TEMP.MSG"

function On_exit {
# My shellcheck did not complain, however github actions shellcheck did.
# Complaint was that this code is unreachable, it is actually reachable.
# shellcheck disable=SC2317
rm -f "$TMPF"
}

trap On_exit EXIT

function Check_commit {
printf "%s\n" "$2" > "$TMPF"

echo "--- Commit $1 ----------"
cat "$TMPF"
echo "-----------------------"
"${SDIR}/check-commit.sh" --noninteractive "$TMPF"
return $?
}

function Help {
echo "This script is supposed to be called from the github actions"
echo ""
echo "check-commits.sh check-script Run shellcheck and bashate on check-commit.sh and the script itself"
echo "check-commits.sh [help|-h|--help] Show this help"
echo ""
exit 7
}

case "${1,,}" in
check-script)
if shellcheck --help > /dev/null 2>&1 && bashate --help > /dev/null 2>&1; then
if shellcheck "$0" && bashate -i E006 "$0"; then
echo "Nothing to complain"
fi
else
echo "Please install shellcheck and bashate to use check-script functionality"
fi
exit 7
;;
help|-h|--help)
Help
;;
"")
: # Pass
;;
*)
echo "Invalid parameter: ${1}"
echo ""
Help
;;
esac

if [ -z "$GITHUB_CONTEXT" ]; then
echo "GITHUB_CONTEXT is not set"
echo ""
Help
fi

if ! jq --version > /dev/null 2>&1; then
echo "::error title=ERROR::jq required for check-commits.sh to run"
exit 254
fi

if ! EVENT="$(jq -r .event_name <<< "$GITHUB_CONTEXT")"; then
echo "Invalid GITHUB_CONTEXT"
exit 7
fi

RET=0
case $EVENT in
push)
COMMITS="$(jq '.event.commits | length' <<< "$GITHUB_CONTEXT")"
I=0
while [ "$I" -lt "$COMMITS" ]; do
CMSG="$(jq -r ".event.commits[${I}].message" <<< "$GITHUB_CONTEXT")"
I=$((I + 1))
Check_commit "$I" "$CMSG"
RET=$((RET | $?))
done
;;
pull_request)
HREF="$(jq -r .event.pull_request._links.commits.href <<< "$GITHUB_CONTEXT")"
if ! JSONS="$(curl -s "$HREF")" || [ "$(jq -r '. | type' <<< "$JSONS")" != "array" ]; then
echo "::error title=ERROR::Failed to retrieve commits (${HREF})"
if [ -n "$JSONS" ]; then
MSG="$(jq -r .message <<< "$JSONS")"
if [ "$MSG" != "null" ] && [ "$MSG" != "" ]; then
echo "::error title=MESSAGE::${MSG}"
fi
fi
exit 7
fi
COMMITS="$(jq '. | length' <<< "$JSONS")"
I=0
while [ "$I" -lt "$COMMITS" ]; do
CMSG="$(jq -r ".[${I}].commit.message" <<< "$JSONS")"
I=$((I + 1))
Check_commit "$I" "$CMSG"
RET=$((RET | $?))
done
;;
*)
echo "::error title=ERROR::Invalid event"
exit 255
;;
esac

case "$RET" in
1)
# Return success even if there were warnings
exit 0
;;
*)
exit $RET
esac
22 changes: 22 additions & 0 deletions githooks/install-git-hooks.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
#!/usr/bin/env bash
# SPDX-FileCopyrightText: 2022-2024 Technology Innovation Institute (TII)
# SPDX-License-Identifier: Apache-2.0

set -e

if [ ! -d ./.git/hooks ]; then
echo "./.git/hooks not found" >&2
exit 1
fi

if [ ! -f ./githooks/check-commit.sh ]; then
echo "./githooks/check-commit.sh not found" >&2
exit 1
fi

if [ -e ./.git/hooks/commit-msg ]; then
echo "./.git/hooks/commit-msg exists already" >&2
exit 1
fi

ln -s ../../githooks/check-commit.sh ./.git/hooks/commit-msg
Loading