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

ci: introduce a script to validate git commit messages #2

Merged
merged 1 commit into from
Jul 24, 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
16 changes: 6 additions & 10 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,18 @@ on: [push, pull_request]
jobs:
lint:
runs-on: ubuntu-latest
env:
REVISION_RANGE: "${{ github.event.pull_request.base.sha }}..${{ github.event.pull_request.head.sha }}"
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0 # force fetch all history
- uses: actions/setup-python@v5
with:
python-version: 3.x
- uses: actions/cache@v4
with:
path: ~/.cache/pip
key: pip
restore-keys: pip
- run: make lint
- run: make check-patches
if: ${{ github.event.pull_request.base.sha && github.event.pull_request.head.sha }}

deploy:
needs:
Expand All @@ -32,11 +33,6 @@ jobs:
- uses: actions/setup-python@v5
with:
python-version: 3.x
- uses: actions/cache@v4
with:
path: ~/.cache/pip
key: pip
restore-keys: pip
- run: python -m pip install --upgrade build
- run: python -m build
- uses: pypa/gh-action-pypi-publish@release/v1
6 changes: 6 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,12 @@ format: $(VENV)/.stamp
@echo "[black]"
@$(in_venv) $(PYTHON) -m black -q $(PY_FILES)

REVISION_RANGE ?= origin/main..

.PHONY: check-patches
check-patches:
@./check-patches $(REVISION_RANGE)

.PHONY: tag-release
tag-release:
@cur_version=`sed -En 's/^version = "(.*)"$$/\1/p' pyproject.toml` && \
Expand Down
134 changes: 134 additions & 0 deletions check-patches
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
#!/bin/sh
# SPDX-License-Identifier: Apache-2.0
# Copyright (c) 2024 Robin Jarry

set -eu

revision_range="${1?revision range}"

valid=0
revisions=$(git rev-list --reverse "$revision_range")
total=$(echo $revisions | wc -w)
if [ "$total" -eq 0 ]; then
exit 0
fi
tmp=$(mktemp)
trap "rm -f $tmp" EXIT

allowed_trailers="
Fixes
Closes
Link
Cc
Suggested-by
Requested-by
Reported-by
Co-authored-by
Signed-off-by
Tested-by
Reviewed-by
Acked-by
"

n=0
title=
fail=false
repo=rjarry/sosviz
repo_url=https://github.com/$repo
api_url=https://api.github.com/repos/$repo

err() {
echo "error [PATCH $n/$total] '$title' $*" >&2
fail=true
}

check_issue() {
json=$(curl -f -X GET -L --no-progress-meter \
-H "Accept: application/vnd.github+json" \
-H "X-GitHub-Api-Version: 2022-11-28" \
"$api_url/issues/${1##*/}") || return 1
test "$(echo "$json" | jq -r .state)" = open
}

for rev in $revisions; do
n=$((n + 1))
title=$(git log --format='%s' -1 "$rev")
fail=false

if [ "$(echo "$title" | wc -m)" -gt 72 ]; then
err "title is longer than 72 characters, please make it shorter"
fi
if ! echo "$title" | grep -qE '^[a-z0-9,{}/_-]+: '; then
err "title lacks a lowercase topic prefix (e.g. 'ipv6:')"
fi
if echo "$title" | grep -qE '^[a-z0-9,{}/_-]+: [A-Z][a-z]'; then
err "title starts with an capital letter, please use lower case"
fi
if ! echo "$title" | grep -qE '[A-Za-z0-9]$'; then
err "title ends with punctuation, please remove it"
fi

author=$(git log --format='%an <%ae>' -1 "$rev")
if ! git log --format="%(trailers:key=Signed-off-by,only,valueonly,unfold)" -1 "$rev" |
grep -qFx "$author"; then
err "'Signed-off-by: $author' trailer is missing"
fi

for trailer in $(git log --format="%(trailers:only,keyonly)" -1 "$rev"); do
if ! echo "$allowed_trailers" | grep -qFx "$trailer"; then
err "trailer '$trailer' is misspelled or not in the sanctioned list"
fi
done

git log --format="%(trailers:key=Closes,only,valueonly,unfold)" -1 "$rev" > $tmp
while read -r value; do
if [ -z "$value" ]; then
continue
fi
case "$value" in
$repo_url/*/[0-9]*)
if ! check_issue "$value"; then
err "'$value' does not reference a valid open issue"
fi
;;
\#[0-9]*)
value=${value#\#}
err "please use the full issue URL: 'Closes: $repo_url/issues/$value'"
;;
*)
err "invalid trailer value '$value'. The 'Closes:' trailer must only be used to reference issue URLs"
;;
esac
done < "$tmp"

git log --format="%(trailers:key=Fixes,only,valueonly,unfold)" -1 "$rev" > $tmp
while read -r value; do
if [ -z "$value" ]; then
continue
fi
fixes_rev=$(echo "$value" | sed -En 's/([A-Fa-f0-9]{7,})[[:space:]]\(".*"\)/\1/p')
if ! git cat-file commit "$fixes_rev" >/dev/null; then
err "trailer 'Fixes: $value' does not refer to a known commit"
fi
done < "$tmp"

body=$(git log --format='%b' -1 "$rev")
body=${body%$(git log --format='%(trailers)' -1 "$rev")}
if [ "$(echo "$body" | wc -w)" -lt 3 ]; then
err "body has less than three words, please describe your changes"
fi
if ! [ "$(git log --format='%P' -1 "$rev" | wc -w)" = 1 ]; then
err "merge commit found, please rebase your code"
fi

if [ "$fail" = true ]; then
continue
fi
echo "ok [PATCH $n/$total] '$title'"
valid=$((valid + 1))
done

echo "$valid/$total valid patches"
if [ "$valid" -ne "$total" ]; then
exit 1
fi