aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
Diffstat (limited to 'tasks/signoff')
-rwxr-xr-xtasks/signoff155+155 −0
1 files changed, 155 insertions, 0 deletions
diff --git a/tasks/signoff b/tasks/signoff
new file mode 100755
--- /dev/null
+++ b/tasks/signoff
@@ -0,0 +1,155 @@
+#!/bin/sh -eu
+# SPDX-FileCopyrightText: 2026 Nikolay Govorov
+# SPDX-License-Identifier: 0BSD
+#MISE description="Verify contributor identities and CLA acceptance trailers"
+
+root=$(git rev-parse --show-toplevel)
+cd "$root"
+
+task_dir=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
+approved_emails_file="$task_dir/../config/signoff-approved-emails"
+unsupported_commits_file="$task_dir/../config/cla-unsupported-commits"
+
+if [ ! -f CLA.md ]; then
+ echo "CLA.md is missing" >&2
+ exit 1
+fi
+
+head_cla_version_count=$(grep -c '^Version ' CLA.md || :)
+if [ "$head_cla_version_count" -ne 1 ]; then
+ echo "CLA.md must declare exactly one version" >&2
+ exit 1
+fi
+
+head_cla_version=$(sed -n 's/^Version //p' CLA.md)
+if [ -z "$head_cla_version" ]; then
+ echo "CLA.md declares an empty version" >&2
+ exit 1
+fi
+
+is_approved_email() {
+ grep -v '^[[:space:]]*#' "$approved_emails_file" |
+ grep -Fqx -- "$1"
+}
+
+is_unsupported_commit() {
+ grep -v '^[[:space:]]*#' "$unsupported_commits_file" |
+ grep -Fqx -- "$1"
+}
+
+check_identity() {
+ identity=$1
+ role=$2
+ email=$(printf '%s\n' "$identity" | sed -n 's/^.*<\([^<>]*\)>$/\1/p')
+
+ if [ -z "$email" ]; then
+ echo "Commit $short_sha has an invalid $role identity: $identity"
+ bad=1
+ return
+ fi
+
+ if is_approved_email "$email"; then
+ return
+ fi
+
+ requires_cla=1
+ if ! printf '%s\n' "$signoffs" | grep -Fqx -- "$identity"; then
+ echo "Commit $short_sha $role $identity is missing a matching Signed-off-by"
+ bad=1
+ fi
+}
+
+if [ ! -f "$approved_emails_file" ]; then
+ echo "Approved email configuration is missing: $approved_emails_file" >&2
+ exit 1
+fi
+
+if [ ! -f "$unsupported_commits_file" ]; then
+ echo "Unsupported commit configuration is missing: $unsupported_commits_file" >&2
+ exit 1
+fi
+
+bad=0
+for sha in $(git log --no-merges --format=%H); do
+ short_sha=$(printf '%.8s' "$sha")
+ signoffs=$(git show -s --format='%(trailers:key=Signed-off-by,valueonly)' "$sha")
+ requires_cla=0
+
+ author=$(git show -s --format='%an <%ae>' "$sha")
+ check_identity "$author" "author"
+
+ coauthors=$(git show -s --format='%(trailers:key=Co-authored-by,valueonly)' "$sha")
+ if [ -n "$coauthors" ]; then
+ old_ifs=$IFS
+ IFS='
+'
+ for coauthor in $coauthors; do
+ check_identity "$coauthor" "co-author"
+ done
+ IFS=$old_ifs
+ fi
+
+ if [ "$requires_cla" -eq 1 ] && ! is_unsupported_commit "$sha"; then
+ expected_cla_version_count=$(
+ git show "$sha:CLA.md" 2>/dev/null | grep -c '^Version ' || :
+ )
+ if [ "$expected_cla_version_count" -ne 1 ]; then
+ echo "Commit $short_sha does not contain a CLA.md with exactly one version"
+ bad=1
+ continue
+ fi
+
+ expected_cla_version=$(
+ git show "$sha:CLA.md" | sed -n 's/^Version //p'
+ )
+ if [ -z "$expected_cla_version" ]; then
+ echo "Commit $short_sha contains an empty CLA version"
+ bad=1
+ continue
+ fi
+
+ commit_cla_version=$(
+ git show -s --format='%(trailers:key=CLA-Version,valueonly)' "$sha"
+ )
+ if [ "$commit_cla_version" != "$expected_cla_version" ]; then
+ if [ -z "$commit_cla_version" ]; then
+ echo "Commit $short_sha is missing CLA-Version: $expected_cla_version"
+ else
+ echo "Commit $short_sha has invalid CLA-Version: $commit_cla_version"
+ echo "Expected CLA-Version: $expected_cla_version"
+ fi
+ bad=1
+ fi
+ fi
+done
+
+if [ "$bad" -ne 0 ]; then
+ echo "Every non-approved author and co-author must accept the CLA in their commit"
+ echo "Required trailers:"
+ echo " CLA-Version: <version from CLA.md>"
+ echo " Signed-off-by: Name <email>"
+ echo "See CLA.md"
+ exit 1
+fi
+
+if [ ! -f .mailmap ]; then
+ echo "Contributor registry .mailmap is missing" >&2
+ exit 1
+fi
+
+missing=0
+for email in $(git log --no-merges --format='%ae%n%ce' | sort -u); do
+ if is_approved_email "$email"; then
+ continue
+ fi
+
+ if ! grep -v '^[[:space:]]*#' .mailmap | grep -qF -- "<${email}>"; then
+ echo "Email <${email}> is not in .mailmap"
+ missing=1
+ fi
+done
+
+if [ "$missing" -ne 0 ]; then
+ echo "All authors and committers must be listed in .mailmap"
+ exit 1
+fi