Repository navigation
65 lines (65 loc) · 2.86 KB
/
Copy pathjob-dco.yaml
File metadata and controls
65 lines (65 loc) · 2.86 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
name: DCO
# Developer Certificate of Origin: every commit in a pull request must carry a
# `Signed-off-by:` trailer (`git commit -s`) naming the commit's author, with
# the same name and email (compared case-insensitively). Merge commits are
# skipped, since they add no authored change of their own.
#
# The commit list comes from the pull request API, so the job needs no
# checkout, no Node action and no third-party DCO app: `gh` and `jq` ship on
# the hosted runner. The API returns at most 250 commits for a pull request;
# a longer one fails with a message rather than passing unchecked.
#
# Pull requests only, and not while the PR is a draft.
on:
pull_request:
branches: [main]
types: [opened, synchronize, reopened, ready_for_review]
# One run per PR per workflow: a new push cancels the older PR run.
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number }}
cancel-in-progress: true
permissions:
contents: read
pull-requests: read
jobs:
dco:
name: ✍️ DCO sign-off
if: github.event.pull_request.draft == false
runs-on: ubuntu-24.04
timeout-minutes: 5
steps:
- name: ✍️ Check every commit is signed off by its author
env:
GH_TOKEN: ${{ github.token }}
REPO: ${{ github.repository }}
PR: ${{ github.event.pull_request.number }}
EXPECTED: ${{ github.event.pull_request.commits }}
run: |
set -euo pipefail
commits="$(gh api --paginate "repos/$REPO/pulls/$PR/commits?per_page=100" --jq '.[]' | jq -s '.')"
total="$(jq 'length' <<<"$commits")"
if [ "$total" -lt "$EXPECTED" ]; then
echo "::error::The API listed $total of $EXPECTED commits (it stops at 250). Split the pull request so every commit can be checked."
exit 1
fi
missing="$(jq -r '
.[]
| select((.parents | length) < 2)
| . as $k
| ($k.commit.author.name | ascii_downcase) as $name
| ($k.commit.author.email | ascii_downcase) as $email
| select([$k.commit.message
| scan("(?m)^Signed-off-by:[ \t]*([^<\n]*?)[ \t]*<([^>\n]*)>[ \t]*$")
| select((.[0] | ascii_downcase) == $name and (.[1] | ascii_downcase) == $email)
] | length == 0)
| "\($k.sha[0:12]) \($k.commit.author.name) <\($k.commit.author.email)>"
' <<<"$commits")"
if [ -n "$missing" ]; then
while IFS= read -r line; do
echo "::error::Commit $line has no Signed-off-by line matching its author."
done <<<"$missing"
echo "Sign off each commit with 'git commit -s'. To fix commits already pushed,"
echo "run 'git rebase --signoff origin/main' and force-push the branch."
exit 1
fi
echo "All $total commits carry a sign-off matching their author."