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
1 change: 1 addition & 0 deletions .github/PULL_REQUEST_TEMPLATE.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
- [ ] **`scripts/check.sh` green** under the project's interpreter.
- [ ] **Independent review** requested for anything touching authorization, identity, delegation, the gateway, an adapter or the store.
- [ ] **Nothing in `src/` merges on green CI alone**; a maintainer reads it.
- [ ] **Signed off.** Every commit carries `Signed-off-by` with its author's email (`git commit -s`); CONTRIBUTING.md's *Sign your work* says what that certifies.

## Mutation table

Expand Down
35 changes: 35 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -408,3 +408,38 @@ jobs:
timeout 60 /tmp/qs/bin/ctrlrun demo
/tmp/qs/bin/ctrlrun init
test -f ctrlrun.yaml

dco:
# Every commit on a pull request carries a `Signed-off-by:` trailer naming its author's
# email: the Developer Certificate of Origin, per CONTRIBUTING.md. A shell loop rather than
# the DCO app, so the rule lives in the repository, pinned like everything else, with
# nothing to install and nothing that can be quietly uninstalled. Only git's own trailer
# block counts, so a sentence in the body that happens to mention the trailer does not.
# Nobody is exempt, bots included: an exemption keyed on a name or an email is a string
# anyone can set, and dependabot signs its commits off anyway.
if: github.event_name == 'pull_request'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 0

- name: Every commit is signed off by its author
env:
BASE: ${{ github.event.pull_request.base.sha }}
HEAD: ${{ github.event.pull_request.head.sha }}
run: |
missing=0
for sha in $(git rev-list --no-merges "$BASE..$HEAD"); do
name="$(git log -1 --format=%an "$sha")"
email="$(git log -1 --format=%ae "$sha")"
# `only` restricts the output to the trailer block git itself recognises, and
# `valueonly` leaves the `Name <email>` part; the value has to end in the author's
# email, angle brackets included.
if ! git log -1 --format='%(trailers:key=Signed-off-by,valueonly,only)' "$sha" \
| awk -v want="<$email>" 'substr($0, length($0) - length(want) + 1) == want { found = 1 } END { exit !found }'; then
echo "::error::$sha ($(git log -1 --format=%s "$sha")) has no 'Signed-off-by: $name <$email>' trailer. Add one with 'git commit --amend -s', or to every commit on the branch with 'git rebase --signoff $BASE'."
missing=1
fi
done
[ "$missing" -eq 0 ]
21 changes: 20 additions & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,26 @@ opening the pull request.
A pull request touching only tooling, docs or CI merges on green.
- Merge stacked pull requests bottom-up, and never delete a branch another open pull request
targets.
- Commit messages say what changed and why, in prose. No attribution trailers.
- Commit messages say what changed and why, in prose, and end with the `Signed-off-by`
trailer described below. No attribution trailers.

## Sign your work

Every commit carries a `Signed-off-by` trailer, and the trailer certifies the
[Developer Certificate of Origin](DCO): that you wrote the change or have the right to submit
it under the licence in [LICENSE](LICENSE), and that you know the contribution and its record
are public. That is the whole agreement. There is no contributor licence agreement and no
copyright assignment: your copyright stays yours, and what you contribute goes out under
Apache-2.0 like the rest of the code.

`git commit -s` adds the trailer from your git identity:

Signed-off-by: Your Name <you@example.com>

The email has to be the commit's author email. CI's `dco` job reads every commit on a pull
request and names any that lack the trailer; `git rebase --signoff main` adds it to a whole
branch at once. Only git's own trailer block counts, and nobody is exempt, bots included:
dependabot signs its commits off already.

## Releases

Expand Down
34 changes: 34 additions & 0 deletions DCO
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
Developer Certificate of Origin
Version 1.1

Copyright (C) 2004, 2006 The Linux Foundation and its contributors.

Everyone is permitted to copy and distribute verbatim copies of this
license document, but changing it is not allowed.


Developer's Certificate of Origin 1.1

By making a contribution to this project, I certify that:

(a) The contribution was created in whole or in part by me and I
have the right to submit it under the open source license
indicated in the file; or

(b) The contribution is based upon previous work that, to the best
of my knowledge, is covered under an appropriate open source
license and I have the right under that license to submit that
work with modifications, whether created in whole or in part
by me, under the same open source license (unless I am
permitted to submit under a different license), as indicated
in the file; or

(c) The contribution was provided directly to me by some other
person who certified (a), (b) or (c) and I have not modified
it.

(d) I understand and agree that this project and the contribution
are public and that a record of the contribution (including all
personal information I submit with it, including my sign-off) is
maintained indefinitely and may be redistributed consistent with
this project or the open source license(s) involved.
19 changes: 19 additions & 0 deletions tests/test_repository_signals.py
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,9 @@ def test_the_community_files_exist_and_say_what_they_must():
"CTRLRun/ctrlrun-docs",
"tools/docs_audit",
"trusted publishing",
# The contribution agreement is the DCO and nothing more; the file has to say so.
"Developer Certificate of Origin",
"git commit -s",
):
assert phrase in contributing, phrase

Expand Down Expand Up @@ -215,6 +218,7 @@ def test_the_community_files_exist_and_say_what_they_must():
"Mutation table",
"CLAIMS.md",
"docs_audit",
"Signed-off-by",
):
assert phrase in pr_template, phrase

Expand All @@ -223,6 +227,21 @@ def test_the_community_files_exist_and_say_what_they_must():
)


def test_every_pull_request_commit_is_signed_off():
"""CONTRIBUTING.md asks for a `Signed-off-by` trailer on every commit; the `dco` job is
what makes that a gate rather than a request. It runs on pull requests only: a push to
`main` is a merge of commits the job already read."""
job = _workflow("ci.yml")["jobs"]["dco"]
assert job["if"] == "github.event_name == 'pull_request'"
run = "\n".join(step.get("run", "") for step in job["steps"])
assert "--no-merges" in run
# Only git's parsed trailer block counts, so a sentence in the body that mentions the
# trailer cannot satisfy the check; and there is no exemption, because one keyed on a
# name or an email is a string anyone can set.
assert "trailers:key=Signed-off-by" in run
assert "[bot]" not in run


def test_the_citation_names_the_repository_the_version_and_the_tagline():
citation = yaml.safe_load((REPO_ROOT / "CITATION.cff").read_text(encoding="utf-8"))
with (REPO_ROOT / "pyproject.toml").open("rb") as handle:
Expand Down
Loading