From e1c353fa69e11b929b6ffdb0ea0fac8e15caef21 Mon Sep 17 00:00:00 2001 From: Ashish Patel Date: Sat, 25 Jul 2026 13:38:03 +0530 Subject: [PATCH] ci: require DCO sign-off on every pull request (#153) Adds a Developer Certificate of Origin check. Every non-merge commit in a PR must carry a Signed-off-by line matching its author, which is what git commit -s produces. Chose DCO over a CLA deliberately. A CLA means collecting and storing contributor names and email addresses, which is personal data this project would then be responsible for - a heavy obligation for a repo where most contributions are a single table row. DCO makes the same assertion, that the contributor has the right to submit the work, with nothing stored beyond the commit itself. Implemented with actions/github-script rather than a third-party action so there is no external dependency in the merge path. Merge commits are skipped since GitHub generates them. On failure the check writes a job summary naming the offending commits and the exact rebase command to fix them. Signed-off-by: ashishpatel26 <3095771+ashishpatel26@users.noreply.github.com> Co-authored-by: ashishpatel26 <3095771+ashishpatel26@users.noreply.github.com> --- .github/workflows/dco.yml | 68 +++++++++++++++++++++++++++++++++++++++ CONTRIBUTION.md | 30 +++++++++++++++++ 2 files changed, 98 insertions(+) create mode 100644 .github/workflows/dco.yml diff --git a/.github/workflows/dco.yml b/.github/workflows/dco.yml new file mode 100644 index 0000000..19dbb70 --- /dev/null +++ b/.github/workflows/dco.yml @@ -0,0 +1,68 @@ +name: DCO + +on: + pull_request: + types: [opened, synchronize, reopened] + +permissions: + contents: read + pull-requests: read + +jobs: + DCO: + runs-on: ubuntu-latest + steps: + - name: Check every commit is signed off + uses: actions/github-script@v7 + with: + script: | + const commits = await github.paginate( + github.rest.pulls.listCommits, + { ...context.repo, pull_number: context.payload.pull_request.number } + ); + + const unsigned = []; + for (const c of commits) { + // Merge commits are generated by GitHub, not authored by the contributor. + if (c.parents && c.parents.length > 1) continue; + + const author = c.commit.author; + const expected = `Signed-off-by: ${author.name} <${author.email}>`; + const hasSignoff = c.commit.message + .split('\n') + .some(line => line.trim().toLowerCase() === expected.toLowerCase()); + + if (!hasSignoff) { + unsigned.push(`${c.sha.slice(0, 7)} ${c.commit.message.split('\n')[0]}`); + } + } + + if (unsigned.length === 0) { + core.info(`All ${commits.length} commit(s) signed off.`); + return; + } + + core.summary.addHeading('DCO check failed', 2); + core.summary.addRaw( + 'These commits are missing a `Signed-off-by` line matching their author:' + ); + core.summary.addCodeBlock(unsigned.join('\n')); + core.summary.addRaw([ + '', + 'Sign off your existing commits and force-push:', + '', + '```bash', + 'git rebase --signoff origin/main', + 'git push --force-with-lease', + '```', + '', + 'For future commits, `git commit -s` adds the line automatically.', + '', + 'The sign-off certifies you wrote the contribution or have the right to', + 'submit it under the repository\'s MIT licence. Full text: https://developercertificate.org/' + ].join('\n')); + await core.summary.write(); + + core.setFailed( + `${unsigned.length} of ${commits.length} commit(s) missing a valid Signed-off-by line.` + ); diff --git a/CONTRIBUTION.md b/CONTRIBUTION.md index 754042e..af67fe8 100644 --- a/CONTRIBUTION.md +++ b/CONTRIBUTION.md @@ -107,12 +107,42 @@ requirements: requirements.txt ## PR process and checklist Before opening a PR: - [ ] Fork and create a branch: feat/ or fix/ +- [ ] Sign off every commit with `git commit -s` (see below) - [ ] Update README and metadata - [ ] Paste real sample output from a run into your agent's README - [ ] Ensure no secrets or private data are included - [ ] Rebase onto current `main` — README moves fast and stale branches conflict - [ ] Confirm license compatibility for added assets +### Sign your commits (DCO) + +Every commit needs a `Signed-off-by` line. Use `-s` and git adds it for you: + +```bash +git commit -s -m "add my agent" +``` + +Which appends: + +``` +Signed-off-by: Your Name +``` + +Forgot on commits you already pushed? Fix them all at once: + +```bash +git rebase --signoff origin/main +git push --force-with-lease +``` + +Set `git config user.name` and `git config user.email` first, since the sign-off must +match the commit author. A CI check enforces this on every PR. + +This is the [Developer Certificate of Origin](https://developercertificate.org/) — by +signing off you're stating that you wrote the contribution, or otherwise have the right +to submit it under this repository's MIT licence. There's no separate form to fill in +and nothing is stored beyond the commit itself. + PR description should include: - What changed and why - How to run the example(s) and tests