diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index d02acb3..bc5c95a 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -12,3 +12,21 @@ jobs: - uses: actions/checkout@v4 - name: Validate package rules run: bash check + + dco: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: Check DCO sign-off + env: + BASE: ${{ github.event_name == 'pull_request' && github.event.pull_request.base.sha || github.event.before }} + HEAD: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }} + run: | + # New branch / unknown base -> fall back to comparing against master. + if [[ -z "$BASE" || "$BASE" =~ ^0+$ ]]; then + git fetch --no-tags origin master + BASE=$(git rev-parse origin/master) + fi + bash dco "$BASE..$HEAD" diff --git a/.gitignore b/.gitignore index e737f56..1b46976 100644 --- a/.gitignore +++ b/.gitignore @@ -29,6 +29,8 @@ pkgs/**/src/ !pkgs/**/*.tmpfiles !pkgs/**/*.desktop !pkgs/**/*.asc +# .install scriptlets only in -sys recipe dirs (the suffix that opts in). +!pkgs/*-sys/*.install # --- editor / OS noise --- *.swp diff --git a/check b/check index 8633c2e..726fbad 100755 --- a/check +++ b/check @@ -1,11 +1,12 @@ #!/bin/bash # ACR policy checks. Run from the repo root: ./check # Validates every pkgs//PKGBUILD against the repository rules. -# 1. tracked-upstream source, by suffix: -# *-git -> source must be a git+ VCS source -# *-rel -> source must be a releases/download/ URL (a published git tag) +# 1. tracked-upstream source, by suffix (an optional trailing -sys is allowed): +# *-git[-sys] -> source must be a git+ VCS source +# *-rel[-sys] -> source must be a releases/download/ URL (a published git tag) # any other pkgname suffix is rejected. # 2. no .install pre/post scriptlets (no install= line, no *.install file) +# UNLESS the pkgname ends in -sys, which opts in to allowing them. # 3. at least one '# Maintainer:' line set -u shopt -s nullglob @@ -22,33 +23,35 @@ for pkgbuild in pkgs/*/PKGBUILD; do # Rule 1: tracked-upstream source, validated against the pkgname suffix pkgname=$(sed -n "s/^pkgname=//p" "$pkgbuild" | tr -d "'\"" | head -1) case "$pkgname" in - *-git) + *-git|*-git-sys) if ! grep -qE '^[[:space:]]*source.*=.*git\+' "$pkgbuild"; then echo "FAIL [$name]: -git package has no git+ VCS source" fail=1 fi ;; - *-rel) + *-rel|*-rel-sys) if ! grep -qE '^[[:space:]]*source.*=.*releases/download/' "$pkgbuild"; then echo "FAIL [$name]: -rel package has no releases/download/ source" fail=1 fi ;; *) - echo "FAIL [$name]: pkgname '$pkgname' must end in -git or -rel" + echo "FAIL [$name]: pkgname '$pkgname' must end in -git or -rel (optionally + -sys)" fail=1 ;; esac - # Rule 2: no install scriptlets - if grep -qE '^[[:space:]]*install=' "$pkgbuild"; then - echo "FAIL [$name]: install= scriptlet declared in PKGBUILD" - fail=1 - fi - installs=("$dir"/*.install) - if (( ${#installs[@]} > 0 )); then - echo "FAIL [$name]: .install file present in recipe dir" - fail=1 + # Rule 2: no install scriptlets, unless the pkgname opts in with a -sys suffix + if [[ "$pkgname" != *-sys ]]; then + if grep -qE '^[[:space:]]*install=' "$pkgbuild"; then + echo "FAIL [$name]: install= scriptlet declared in PKGBUILD (use a -sys suffix to allow)" + fail=1 + fi + installs=("$dir"/*.install) + if (( ${#installs[@]} > 0 )); then + echo "FAIL [$name]: .install file present in recipe dir (use a -sys suffix to allow)" + fail=1 + fi fi # Rule 3: at least one Maintainer line diff --git a/dco b/dco new file mode 100755 index 0000000..28c382d --- /dev/null +++ b/dco @@ -0,0 +1,33 @@ +#!/bin/bash +# DCO sign-off check. Verifies every non-merge commit in a range carries a +# 'Signed-off-by:' trailer matching the commit author (name ). +# This is the homebaked replacement for the DCO GitHub App. +# +# Usage: ./dco [] (default: origin/master..HEAD) +set -u + +range="${1:-origin/master..HEAD}" +fail=0 +checked=0 + +while read -r sha; do + [[ -z "$sha" ]] && continue + # Skip merge commits (more than one parent) — sign-off lives on the real commits. + if (( $(git rev-list --parents -n1 "$sha" | wc -w) > 2 )); then + continue + fi + checked=$((checked + 1)) + + author=$(git show -s --format='%an <%ae>' "$sha") + short=$(git show -s --format='%h' "$sha") + if ! git show -s --format='%(trailers:key=Signed-off-by,valueonly)' "$sha" \ + | grep -qxF "$author"; then + echo "FAIL [$short]: missing 'Signed-off-by: $author'" + fail=1 + fi +done < <(git rev-list "$range") + +if [[ $fail -eq 0 ]]; then + echo "ok: $checked commit(s) carry a matching DCO sign-off" +fi +exit $fail diff --git a/readme.md b/readme.md index 935eb64..fc62155 100644 --- a/readme.md +++ b/readme.md @@ -19,9 +19,15 @@ Every `pkgs//` recipe must satisfy (enforced by `./check`): - `-rel` — `source=()` is a `releases/download/` URL (a published git tag). Both track upstream git; they differ only in whether we build the clone or - install a published release artifact. No other suffix is allowed. -2. **No `.install` scriptlets** no `install=` line and no `*.install` file. - `*.install` is not on the allowlist, so it cannot even be committed. + install a published release artifact. No other source suffix is allowed. + A trailing `-sys` may be appended to either (`-git-sys` / `-rel-sys`) — see + rule 2. +2. **No `.install` scriptlets, unless the package is `-sys`.** By default a + recipe may have no `install=` line and no `*.install` file. A package that + genuinely needs pre/post scriptlets (systemd units, user creation, cache + refresh, …) must opt in by appending `-sys` to its `pkgname` + (e.g. `foo-git-sys`). Only `*-sys/` dirs can commit a `*.install` — the + allowlist forbids it everywhere else. 3. **At least one `# Maintainer:` line** in the PKGBUILD. Run `./check` before committing; CI runs it on every PR. @@ -45,6 +51,9 @@ Repo-root helper scripts: - `./check` — validate every recipe against the package rules above. Exits non-zero on any violation. +- `./dco []` — verify every non-merge commit in `` (default + `origin/master..HEAD`) carries a `Signed-off-by:` trailer matching its + author. Homebaked DCO check; CI runs it on every push and PR. - `./clean` — remove all git-ignored build debris (`pkg/`, `src/`, fetched sources, built packages) repo-wide, after a confirmation prompt. - `./bump [pkg...]` — refresh packages to the latest upstream state, by tier: @@ -56,8 +65,9 @@ Packaging you'll want: `nvchecker`, `devtools`, `base-devel` and `pacman-contrib ## CI -GitHub Actions (`.github/workflows/check.yml`) runs `bash check` on **every -branch push and every pull request**. A branch that violates any package rule -fails CI and cannot be merged into `master`. +GitHub Actions (`.github/workflows/check.yml`) runs two jobs on **every branch +push and every pull request**: `policy` (`bash check`) and `dco` (`bash dco`). +A branch that violates any package rule, or carries a commit without a matching +`Signed-off-by:` trailer, fails CI and cannot be merged into `master`. ---