Skip to content

Commit 3ccafa2

Browse files
committed
toto (#30)
Signed-off-by: Ronan Abhamon <ronan.abhamon@vates.tech>
1 parent cf5d261 commit 3ccafa2

7 files changed

Lines changed: 344 additions & 17 deletions

File tree

‎.github/commit-scopes.txt‎

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

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

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
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 script refuses to rewrite anything if a commit already ends with an ID:
31+
# base is wrong or the range contains commits of other pull requests.
32+
# ==============================================================================
33+
34+
set -euo pipefail
35+
36+
SCRIPT_DIR=$(dirname "$(readlink -f "$0")")
37+
readonly SCRIPT_DIR
38+
39+
readonly AMEND_SCRIPT_PATH="${SCRIPT_DIR}/amend-commit-pr-id"
40+
41+
source "${SCRIPT_DIR}/lib/git.sh"
42+
source "${SCRIPT_DIR}/lib/pr-id.sh"
43+
44+
# ==============================================================================
45+
46+
readonly FORCE_OPTION="--force"
47+
48+
FORCE=false
49+
ARGS=()
50+
for arg in "$@"; do
51+
if [ "$arg" = "$FORCE_OPTION" ]; then
52+
FORCE=true
53+
else
54+
ARGS+=("$arg")
55+
fi
56+
done
57+
readonly FORCE
58+
readonly ARGS
59+
60+
if [ "${#ARGS[@]}" -ne 3 ]; then
61+
echo "Usage: $0 [${FORCE_OPTION}] <base-sha> <head-sha> <pr-number>" >&2
62+
exit 2
63+
fi
64+
65+
BASE_SHA=$(resolve_commit "${ARGS[0]}")
66+
readonly BASE_SHA
67+
68+
HEAD_SHA=$(resolve_commit "${ARGS[1]}")
69+
readonly HEAD_SHA
70+
71+
readonly PR_NUMBER=${ARGS[2]}
72+
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+
MERGE_BASE_SHA=$(git merge-base "$BASE_SHA" "$HEAD_SHA")
93+
readonly MERGE_BASE_SHA
94+
95+
readonly COMMIT_RANGE="${MERGE_BASE_SHA}..${HEAD_SHA}"
96+
97+
if [ -n "$(git rev-list --merges "${MERGE_BASE_SHA}..${HEAD_SHA}")" ]; then
98+
echo "Merge commits found in \`${COMMIT_RANGE}\`: this script only supports linear history." >&2
99+
exit 1
100+
fi
101+
102+
mapfile -t hashes < <(git log --format=%H "${MERGE_BASE_SHA}..${HEAD_SHA}")
103+
104+
# ------------------------------------------------------------------------------
105+
106+
PR_ID_SUFFIX=$(get_pr_id_suffix "$PR_NUMBER")
107+
readonly PR_ID_SUFFIX
108+
109+
missing=0
110+
foreign=0
111+
for hash in "${hashes[@]}"; do
112+
subject=$(git log -1 --format=%s "$hash")
113+
if [ "$(with_pr_id "$subject" "$PR_ID_SUFFIX")" = "$subject" ]; then
114+
continue
115+
fi
116+
117+
missing=$((missing + 1))
118+
# The title already ends with a pull request ID other than the requested one.
119+
if has_pr_id "$subject"; then
120+
foreign=$((foreign + 1))
121+
echo "Commit ${hash} already has another pull request ID: \"${subject}\"." >&2
122+
fi
123+
done
124+
125+
if [ "$foreign" -ne 0 ] && ! $FORCE; then
126+
echo "${foreign} commit(s) of \`${COMMIT_RANGE}\` would have their pull request ID replaced: check the base SHA, or use \`${FORCE_OPTION}\`." >&2
127+
exit 1
128+
fi
129+
130+
if [ "$missing" -eq 0 ]; then
131+
echo "Nothing to do: every commit of \`${COMMIT_RANGE}\` already ends with \"${PR_ID_SUFFIX}\"."
132+
exit 0
133+
fi
134+
135+
# ------------------------------------------------------------------------------
136+
137+
readonly PREVIOUS_HEAD=$HEAD_SHA
138+
printf -v EXEC_COMMAND '%q %q' "$AMEND_SCRIPT_PATH" "$PR_ID_SUFFIX"
139+
readonly EXEC_COMMAND
140+
141+
if ! rebase_output=$(GIT_SEQUENCE_EDITOR=: git rebase --interactive --exec "$EXEC_COMMAND" "$MERGE_BASE_SHA" 2>&1); then
142+
echo "$rebase_output" >&2
143+
git rebase --abort || true
144+
echo "Rewrite failed, branch restored (previous HEAD: ${PREVIOUS_HEAD})." >&2
145+
exit 1
146+
fi
147+
148+
echo "${missing} commit title(s) updated with \"${PR_ID_SUFFIX}\" (previous HEAD: ${PREVIOUS_HEAD})."

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

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
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 <pr-id-suffix>
22+
#
23+
# Run by `git rebase --exec` from `add-commit-pr-id`, once for each commit it replays. It does
24+
# none of the checks of `add-commit-pr-id`: do not use it directly.
25+
# ==============================================================================
26+
27+
set -euo pipefail
28+
29+
source "$(dirname "$0")/lib/pr-id.sh"
30+
31+
# ==============================================================================
32+
33+
if [ "$#" -ne 1 ]; then
34+
echo "Usage: $0 <pr-id-suffix>" >&2
35+
exit 2
36+
fi
37+
38+
readonly PR_ID_SUFFIX=$1
39+
40+
# ------------------------------------------------------------------------------
41+
42+
subject=$(git log -1 --format=%s)
43+
new_subject=$(with_pr_id "$subject" "$PR_ID_SUFFIX")
44+
if [ "$new_subject" != "$subject" ]; then
45+
# Only the first line of the message (the title) changes, the body is kept as is.
46+
{ echo "$new_subject"; git log -1 --format=%B | tail -n +2; } | git commit --amend --allow-empty --no-verify -q -F -
47+
fi
Lines changed: 57 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,27 @@
11
#!/usr/bin/env bash
22
#
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+
# ==============================================================================
318
# Check the commit message conventions of a pull request.
419
#
5-
# Usage: check-commit-messages.sh <base-sha> <head-sha> <pr-number>
20+
# Usage: check-commit-messages <base-sha> <head-sha> <pr-number>
21+
#
22+
# - `<base-sha>`: base of the range to check.
23+
# - `<head-sha>`: head of the range to check.
24+
# - `<pr-number>`: number of the pull request.
625
#
726
# Rules:
827
# - Format: `<type>(<scope>): <subject>`, with type in `feat`, `fix`, `docs` or `chore`.
@@ -15,26 +34,50 @@
1534
# - A commit that only touches files in `tests/` must have a scope starting with `tests/`
1635
# (even for `chore`), and a scope starting with `tests/` is only allowed for such commits.
1736
# - A `chore` commit touching files in `src/` or `tests/` only triggers a warning (never a failure).
37+
# - A subject ends with at most one pull request ID.
1838
# - If the pull request has several commits, each one should end with ` (#<pr-number>)`.
1939
# With a single commit nothing is checked: the ID is added when the pull request is merged.
40+
# ==============================================================================
2041

2142
set -euo pipefail
2243

23-
readonly SCOPES_FILE_PATH="$(dirname "$0")/../commit-scopes.txt"
44+
SCRIPT_DIR=$(dirname "$(readlink -f "$0")")
45+
readonly SCRIPT_DIR
46+
47+
readonly SCOPES_FILE_PATH="${SCRIPT_DIR}/../commit-scopes.txt"
2448

2549
readonly MAX_SUBJECT_LENGTH=70
2650

51+
source "${SCRIPT_DIR}/lib/git.sh"
52+
source "${SCRIPT_DIR}/lib/pr-id.sh"
53+
2754
# ==============================================================================
2855

2956
if [ "$#" -ne 3 ]; then
3057
echo "Usage: $0 <base-sha> <head-sha> <pr-number>" >&2
3158
exit 2
3259
fi
3360

34-
readonly BASE_SHA=$1
35-
readonly HEAD_SHA=$2
61+
BASE_SHA=$(resolve_commit "$1")
62+
readonly BASE_SHA
63+
64+
HEAD_SHA=$(resolve_commit "$2")
65+
readonly HEAD_SHA
66+
3667
readonly PR_NUMBER=$3
3768

69+
if ! is_pr_number "$PR_NUMBER"; then
70+
echo "Invalid pull request number: \`${PR_NUMBER}\`." >&2
71+
exit 2
72+
fi
73+
74+
# Read the allowed scopes, one per line, ignoring comments and blank lines.
75+
mapfile -t scopes < <(grep -vE '^[[:space:]]*(#|$)' "$SCOPES_FILE_PATH" | sed -E 's/^[[:space:]]+|[[:space:]]+$//g')
76+
if [ "${#scopes[@]}" -eq 0 ]; then
77+
report_error "No allowed scope found in \`${SCOPES_FILE_PATH}\`."
78+
exit 2
79+
fi
80+
3881
# ------------------------------------------------------------------------------
3982

4083
report_error() {
@@ -64,22 +107,13 @@ get_format_error() {
64107

65108
# ------------------------------------------------------------------------------
66109

67-
# Read the allowed scopes, one per line, ignoring comments and blank lines.
68-
mapfile -t scopes < <(grep -vE '^[[:space:]]*(#|$)' "$SCOPES_FILE_PATH" | sed -E 's/^[[:space:]]+|[[:space:]]+$//g')
69-
if [ "${#scopes[@]}" -eq 0 ]; then
70-
report_error "No allowed scope found in \`${SCOPES_FILE_PATH}\`."
71-
exit 2
72-
fi
73-
74110
# Escape the regex special characters of each scope, then join them with `|`.
75111
ALLOWED_SCOPES=$(printf '%s\n' "${scopes[@]}" | sed -E 's/[][\\.^$*+?(){}|]/\\&/g' | paste -sd'|')
76112
readonly ALLOWED_SCOPES
77113

78-
DISPLAYED_SCOPES=$(printf '`%s`, ' "${scopes[@]}")
114+
DISPLAYED_SCOPES=$(printf "\`%s\`, " "${scopes[@]}")
79115
readonly DISPLAYED_SCOPES
80116

81-
readonly PR_ID_SUFFIX=" (#${PR_NUMBER})"
82-
83117
readonly SUBJECT_REGEX='.*[^ ]'
84118
readonly SCOPE_REGEX="\\((${ALLOWED_SCOPES})\\)"
85119
readonly COMMIT_REGEX="^((feat|fix|docs)${SCOPE_REGEX}|chore(${SCOPE_REGEX})?): ${SUBJECT_REGEX}$"
@@ -108,11 +142,19 @@ is_touching_src_or_tests() {
108142

109143
# ------------------------------------------------------------------------------
110144

145+
PR_ID_SUFFIX=$(get_pr_id_suffix "$PR_NUMBER")
146+
readonly PR_ID_SUFFIX
147+
111148
errors=0
112149
for hash in "${hashes[@]}"; do
113150
subject=$(git log -1 --format=%s "$hash")
114151
# The ID is optional here: the format is checked without it.
115-
message=${subject% (#[0-9]*)}
152+
message=$(strip_pr_id "$subject")
153+
154+
if has_pr_id "$message"; then
155+
report_error "Commit subject must not end with several pull request IDs: \"${subject}\"."
156+
continue
157+
fi
116158

117159
if ! [[ $message =~ $COMMIT_REGEX ]]; then
118160
reason=$(get_format_error "$message")

‎.github/scripts/lib/git.sh‎

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
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+
# Git helpers.
19+
# ==============================================================================
20+
21+
# Print the full SHA of the commit a revision points to, or fail with the exit code 2 (invalid
22+
# argument) if there is none.
23+
resolve_commit() {
24+
if ! git rev-parse --verify --quiet "$1^{commit}"; then
25+
echo "Unknown commit: \`$1\`." >&2
26+
exit 2
27+
fi
28+
}

0 commit comments

Comments
 (0)