Skip to content

Commit dc688ff

Browse files
committed
chore: add GH commit conventions check
Signed-off-by: Ronan Abhamon <ronan.abhamon@vates.tech>
1 parent 2c8cc00 commit dc688ff

8 files changed

Lines changed: 688 additions & 0 deletions

File tree

‎.github/commit-scopes.txt‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
# Allowed commit scopes, one per line (blank lines and lines starting with `#` are ignored).
2+
# Used by `.github/scripts/check-commit-messages`.
3+
core
4+
tests/core

‎.github/scripts/add-commit-pr-id‎

Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
#!/usr/bin/env bash
2+
#
3+
# Copyright (C) 2026 Vates SAS
4+
#
5+
# This program is free software: you can redistribute it and/or modify
6+
# it under the terms of the GNU General Public License as published by
7+
# the Free Software Foundation, either version 3 of the License, or
8+
# (at your option) any later version.
9+
# This program is distributed in the hope that it will be useful,
10+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
# GNU General Public License for more details.
13+
#
14+
# You should have received a copy of the GNU General Public License
15+
# along with this program. If not, see <https://www.gnu.org/licenses/>.
16+
17+
# ==============================================================================
18+
# Make the title of every commit of the current branch end with ` (#<pr-number>)`.
19+
# Commits whose title already ends with this ID only are left untouched, so the script can be run
20+
# several times.
21+
#
22+
# Usage: add-commit-pr-id [--force] <base-sha> <head-sha> <pr-number>
23+
#
24+
# - `<base-sha>`: base of the pull request to check and modify.
25+
# - `<head-sha>`: head of the pull request to check and modify.
26+
# - `<pr-number>`: number of the pull request.
27+
# - `--force`: replace the existing pull request IDs of the titles.
28+
#
29+
# Every trailing ` (#<digits>)` of a title is considered to be a pull request ID. Without `--force`,
30+
# the commits that already end with another ID are ignored (and listed), the others are rewritten:
31+
# an ignored commit is probably in the range because the base is wrong, or the range contains
32+
# commits of other pull requests.
33+
# ==============================================================================
34+
35+
set -euo pipefail
36+
37+
SCRIPT_DIR=$(dirname "$(readlink -f "$0")")
38+
readonly SCRIPT_DIR
39+
40+
readonly AMEND_SCRIPT_PATH="${SCRIPT_DIR}/amend-commit-pr-id"
41+
42+
source "${SCRIPT_DIR}/lib/git.sh"
43+
source "${SCRIPT_DIR}/lib/pr-id.sh"
44+
45+
# ==============================================================================
46+
47+
readonly FORCE_OPTION="--force"
48+
49+
FORCE=false
50+
ARGS=()
51+
for arg in "$@"; do
52+
if [ "$arg" = "$FORCE_OPTION" ]; then
53+
FORCE=true
54+
else
55+
ARGS+=("$arg")
56+
fi
57+
done
58+
readonly FORCE
59+
readonly ARGS
60+
61+
if [ "${#ARGS[@]}" -ne 3 ]; then
62+
echo "Usage: $0 [${FORCE_OPTION}] <base-sha> <head-sha> <pr-number>" >&2
63+
exit 2
64+
fi
65+
66+
BASE_SHA=$(resolve_commit "${ARGS[0]}")
67+
readonly BASE_SHA
68+
69+
HEAD_SHA=$(resolve_commit "${ARGS[1]}")
70+
readonly HEAD_SHA
71+
72+
readonly PR_NUMBER=${ARGS[2]}
73+
if ! is_pr_number "$PR_NUMBER"; then
74+
echo "Invalid pull request number: \`${PR_NUMBER}\`." >&2
75+
exit 2
76+
fi
77+
78+
# ------------------------------------------------------------------------------
79+
80+
if ! git diff --quiet || ! git diff --cached --quiet; then
81+
echo "The working tree has uncommitted changes: commit or stash them first." >&2
82+
exit 1
83+
fi
84+
85+
if [ "$(git rev-parse HEAD)" != "$HEAD_SHA" ]; then
86+
echo "\`HEAD\` is not \`${HEAD_SHA}\`: checkout the head of the pull request first." >&2
87+
exit 1
88+
fi
89+
90+
# ------------------------------------------------------------------------------
91+
92+
if ! MERGE_BASE_SHA=$(git merge-base "$BASE_SHA" "$HEAD_SHA"); then
93+
echo "No common ancestor between \`${BASE_SHA}\` and \`${HEAD_SHA}\`." >&2
94+
exit 2
95+
fi
96+
readonly MERGE_BASE_SHA
97+
98+
readonly COMMIT_RANGE="${MERGE_BASE_SHA}..${HEAD_SHA}"
99+
if [ -n "$(git rev-list --merges "${MERGE_BASE_SHA}..${HEAD_SHA}")" ]; then
100+
echo "Merge commits found in \`${COMMIT_RANGE}\`: this script only supports linear history." >&2
101+
exit 1
102+
fi
103+
104+
mapfile -t hashes < <(git log --format=%H "${MERGE_BASE_SHA}..${HEAD_SHA}")
105+
106+
# ------------------------------------------------------------------------------
107+
108+
PR_ID_SUFFIX=$(get_pr_id_suffix "$PR_NUMBER")
109+
readonly PR_ID_SUFFIX
110+
111+
to_rewrite=0
112+
to_ignore=0
113+
for hash in "${hashes[@]}"; do
114+
subject=$(git log -1 --format=%s "$hash")
115+
if [ "$(with_pr_id "$subject" "$PR_ID_SUFFIX")" = "$subject" ]; then
116+
continue
117+
fi
118+
119+
# The title already ends with a pull request ID other than the requested one.
120+
if has_pr_id "$subject" && ! $FORCE; then
121+
to_ignore=$((to_ignore + 1))
122+
echo "Commit ${hash} ignored, it already has another pull request ID: \"${subject}\"." >&2
123+
continue
124+
fi
125+
126+
to_rewrite=$((to_rewrite + 1))
127+
done
128+
129+
if [ "$to_rewrite" -eq 0 ]; then
130+
if [ "$to_ignore" -eq 0 ]; then
131+
echo "Nothing to do: every commit of \`${COMMIT_RANGE}\` already ends with \"${PR_ID_SUFFIX}\"."
132+
else
133+
echo "Nothing to do: ${to_ignore} commit(s) of \`${COMMIT_RANGE}\` ignored with another pull request ID, the others already end with \"${PR_ID_SUFFIX}\"."
134+
fi
135+
exit 0
136+
fi
137+
138+
# ------------------------------------------------------------------------------
139+
140+
exec_options=()
141+
if $FORCE; then
142+
exec_options+=("$FORCE_OPTION")
143+
fi
144+
145+
readonly PREVIOUS_HEAD=$HEAD_SHA
146+
printf -v EXEC_COMMAND '%q ' "$AMEND_SCRIPT_PATH" "${exec_options[@]}" "$PR_ID_SUFFIX"
147+
readonly EXEC_COMMAND
148+
149+
if ! rebase_output=$(GIT_SEQUENCE_EDITOR=: git rebase --interactive --no-autosquash --exec "$EXEC_COMMAND" "$MERGE_BASE_SHA" 2>&1); then
150+
echo "$rebase_output" >&2
151+
git rebase --abort || true
152+
echo "Rewrite failed, branch restored (previous HEAD: ${PREVIOUS_HEAD})." >&2
153+
exit 1
154+
fi
155+
156+
echo "${to_rewrite} commit title(s) updated with \"${PR_ID_SUFFIX}\" (previous HEAD: ${PREVIOUS_HEAD})."
157+
if [ "$to_ignore" -ne 0 ]; then
158+
echo "${to_ignore} commit(s) ignored because they already have another pull request ID."
159+
fi

‎.github/scripts/amend-commit-pr-id‎

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
#!/usr/bin/env bash
2+
#
3+
# Copyright (C) 2026 Vates SAS
4+
#
5+
# This program is free software: you can redistribute it and/or modify
6+
# it under the terms of the GNU General Public License as published by
7+
# the Free Software Foundation, either version 3 of the License, or
8+
# (at your option) any later version.
9+
# This program is distributed in the hope that it will be useful,
10+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
# GNU General Public License for more details.
13+
#
14+
# You should have received a copy of the GNU General Public License
15+
# along with this program. If not, see <https://www.gnu.org/licenses/>.
16+
17+
# ==============================================================================
18+
# Amend the title of `HEAD` so that it ends with the given pull request ID suffix, and only with it.
19+
# The title is left untouched if it already ends like that.
20+
#
21+
# Usage: amend-commit-pr-id [--force] <pr-id-suffix>
22+
#
23+
# - `--force`: replace the pull request IDs already at the end of the title. Without it, a title
24+
# that already ends with a pull request ID is left untouched.
25+
#
26+
# Run by `git rebase --exec` from `add-commit-pr-id`, once for each commit it replays. It does
27+
# none of the checks of `add-commit-pr-id`: do not use it directly.
28+
# ==============================================================================
29+
30+
set -euo pipefail
31+
32+
source "$(dirname "$0")/lib/pr-id.sh"
33+
34+
# ==============================================================================
35+
36+
readonly FORCE_OPTION="--force"
37+
38+
FORCE=false
39+
if [ "${1:-}" = "$FORCE_OPTION" ]; then
40+
FORCE=true
41+
shift
42+
fi
43+
readonly FORCE
44+
45+
if [ "$#" -ne 1 ]; then
46+
echo "Usage: $0 [${FORCE_OPTION}] <pr-id-suffix>" >&2
47+
exit 2
48+
fi
49+
50+
readonly PR_ID_SUFFIX=$1
51+
52+
# ------------------------------------------------------------------------------
53+
54+
subject=$(git log -1 --format=%s)
55+
if has_pr_id "$subject" && ! $FORCE; then
56+
exit 0
57+
fi
58+
59+
new_subject=$(with_pr_id "$subject" "$PR_ID_SUFFIX")
60+
if [ "$new_subject" != "$subject" ]; then
61+
# Only the first line of the message (the title) changes, the body is kept as is.
62+
{ echo "$new_subject"; git log -1 --format=%B | tail -n +2; } | git commit --amend --allow-empty --no-verify -q -F -
63+
fi

0 commit comments

Comments
 (0)