Skip to content
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
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ pwsh -File .\code_checking\bin\sync-consumer.ps1
```

This command syncs the `code_checking` ref, writes the recommended GitHub
workflow (`pull_request` trigger only, to avoid duplicate `push` + PR runs),
workflows (`pull_request` trigger only, to avoid duplicate `push` + PR runs),
bootstraps or refreshes pre-commit hooks, and updates the consumer `README.md`
managed section. It also seeds baseline `.gitignore`,
`cspell.config.yaml`, `.yamllint`, and `vscode-project-words.txt` in the
Expand Down Expand Up @@ -187,7 +187,7 @@ Do not stage `code-checking-ref` for normal integration commits. An
intentional validation PR may track it temporarily when testing a
`code_checking` PR ref.

To install or update only the recommended GitHub workflow without a full
To install or update only the recommended GitHub workflows without a full
submodule sync, run:

Linux/macOS:
Expand All @@ -203,7 +203,7 @@ bash .\code_checking\bin\setup-github-workflow.sh --apply
```

This keeps workflow ownership in the consumer repo while providing a shared
script to sync the recommended workflow after submodule add/update.
script to sync the recommended workflows after submodule add/update.

Shared linter entrypoints are available at `bin/run-linters.sh` and
`bin/run-linters.ps1`.
Expand Down
72 changes: 44 additions & 28 deletions bin/setup-github-workflow.sh
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ set -euo pipefail

TARGET_ROOT="$(pwd)"
SUBMODULE_PATH="code_checking"
WORKFLOW_RELATIVE_PATH=".github/workflows/basic-source-checks.yml"
MODE="check"
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"

Expand All @@ -14,8 +13,9 @@ escape_sed_replacement() {

render_workflow_yaml() {
local code_checking_path="$1"
local template_name="$2"
local template_path="${SCRIPT_DIR}/../checks/workflow_d/"
template_path+="basic-source-checks.template.yml"
template_path+="${template_name}"

if [[ ! -f "${template_path}" ]]; then
printf '%s\n' "[setup-github-workflow] missing template:" \
Expand All @@ -34,15 +34,14 @@ usage() {
Usage: setup-github-workflow.sh [--target-root PATH] \
[--submodule-path PATH] [--apply]

Checks or writes the recommended GitHub workflow that runs shared linters from
this repository when used as a submodule.
Checks or writes the recommended GitHub workflows for consumer repositories.

Defaults:
- target root: current directory
- submodule path: code_checking
- mode: check (non-mutating)

Use --apply to write/update the workflow file.
Use --apply to write/update workflow files.
EOF
}

Expand Down Expand Up @@ -73,31 +72,48 @@ while [[ $# -gt 0 ]]; do
done

TARGET_ROOT="$(cd "${TARGET_ROOT}" && pwd)"
WORKFLOW_PATH="${TARGET_ROOT}/${WORKFLOW_RELATIVE_PATH}"
TMP_EXPECTED="$(mktemp)"
trap 'rm -f "${TMP_EXPECTED}"' EXIT
render_workflow_yaml "${SUBMODULE_PATH}" > "${TMP_EXPECTED}"

if [[ "${MODE}" == "check" ]]; then
if [[ ! -f "${WORKFLOW_PATH}" ]]; then
printf '%s\n' "[setup-github-workflow] missing workflow:" \
"${WORKFLOW_RELATIVE_PATH}" >&2
printf '%s\n' "[setup-github-workflow] run with --apply to create it" >&2
exit 1
fi
WORKFLOW_TARGETS=(
".github/workflows/basic-source-checks.yml:basic-source-checks.template.yml"
".github/workflows/dco-signoff.yml:dco-signoff.template.yml"
)

for workflow_target in "${WORKFLOW_TARGETS[@]}"; do
workflow_relative_path="${workflow_target%%:*}"
template_name="${workflow_target#*:}"
workflow_path="${TARGET_ROOT}/${workflow_relative_path}"
tmp_expected="$(mktemp)"

render_workflow_yaml "${SUBMODULE_PATH}" "${template_name}" > "${tmp_expected}"

if [[ "${MODE}" == "check" ]]; then
if [[ ! -f "${workflow_path}" ]]; then
printf '%s\n' "[setup-github-workflow] missing workflow:" \
"${workflow_relative_path}" >&2
printf '%s\n' "[setup-github-workflow] run with --apply to create it" >&2
rm -f "${tmp_expected}"
exit 1
fi

if ! cmp -s "${WORKFLOW_PATH}" "${TMP_EXPECTED}"; then
printf '%s\n' "[setup-github-workflow] workflow differs from" \
"recommended content" >&2
printf '%s\n' "[setup-github-workflow] file: ${WORKFLOW_RELATIVE_PATH}" >&2
printf '%s\n' "[setup-github-workflow] run with --apply to update it" >&2
exit 1
if ! cmp -s "${workflow_path}" "${tmp_expected}"; then
printf '%s\n' "[setup-github-workflow] workflow differs from" \
"recommended content" >&2
printf '%s\n' "[setup-github-workflow] file: ${workflow_relative_path}" >&2
printf '%s\n' "[setup-github-workflow] run with --apply to update it" >&2
rm -f "${tmp_expected}"
exit 1
fi

rm -f "${tmp_expected}"
continue
fi

printf '%s\n' "[setup-github-workflow] workflow is up to date"
exit 0
fi
mkdir -p "$(dirname "${workflow_path}")"
cp "${tmp_expected}" "${workflow_path}"
rm -f "${tmp_expected}"
printf '%s\n' "[setup-github-workflow] wrote ${workflow_relative_path}"
done

mkdir -p "$(dirname "${WORKFLOW_PATH}")"
cp "${TMP_EXPECTED}" "${WORKFLOW_PATH}"
printf '%s\n' "[setup-github-workflow] wrote ${WORKFLOW_RELATIVE_PATH}"
if [[ "${MODE}" == "check" ]]; then
printf '%s\n' "[setup-github-workflow] workflows are up to date"
fi
51 changes: 51 additions & 0 deletions checks/workflow_d/dco-signoff.template.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
---
name: dco-signoff

'on':
pull_request:

permissions:
contents: read

jobs:
dco-signoff-check:
name: DCO / Signed-off-by
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
fetch-depth: 0

- name: Verify Signed-off-by trailers
env:
BASE_SHA: ${{ github.event.pull_request.base.sha }}
HEAD_SHA: ${{ github.event.pull_request.head.sha }}
run: |
set -euo pipefail

mapfile -t commits < <(git rev-list --reverse "${BASE_SHA}..${HEAD_SHA}")

if [[ ${#commits[@]} -eq 0 ]]; then
echo "[dco] no commits found in PR range"
exit 0
fi

missing=0
for commit in "${commits[@]}"; do
trailers="$(git log -1 --pretty=%B "${commit}" | git interpret-trailers --parse)"
if ! grep -qi '^signed-off-by:' <<< "${trailers}"; then
subject="$(git log -1 --pretty=%s "${commit}")"
echo "::error title=Missing Signed-off-by::${commit}: ${subject}"
missing=1
fi
done

if [[ ${missing} -ne 0 ]]; then
echo "[dco] one or more commits are missing Signed-off-by trailers" >&2
echo "[dco] amend commits with: git commit --amend --signoff" >&2
echo "[dco] for multiple commits: git rebase -i --signoff <base>" >&2
exit 1
fi

echo "[dco] all commits include Signed-off-by trailers"
10 changes: 5 additions & 5 deletions docs/integration.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ pwsh -File .\code_checking\bin\sync-consumer.ps1 -SkipReadme

See [README.md](../README.md) for details on what `sync-consumer` does.

Optionally, you can manually sync or validate the recommended GitHub workflow
Optionally, you can manually sync or validate the recommended GitHub workflows
(see [README.md](../README.md) for details on `setup-github-workflow.sh`).

## Initial Consumer Commit
Expand Down Expand Up @@ -117,17 +117,17 @@ commits; you need to fetch and hard-reset.
commit it. Keep the submodule pointer at the commit already recorded in
the consumer PR.

1. If the code_checking PR changed linter/tool requirements, regenerate the
consumer workflow.
1. If the code_checking PR changed linter/tool requirements or branch policy
checks, regenerate consumer workflows.

```bash
./code_checking/bin/setup-github-workflow.sh --apply
```

1. Stage and commit only the changed workflow file.
1. Stage and commit only changed workflow files.

```bash
git add .github/workflows/basic-source-checks.yml
git add .github/workflows/
git commit --amend --no-verify
```

Expand Down
Loading