Skip to content

Commit a82b463

Browse files
authored
ci: add local pre-commit DCO hook and setup command (#9094)
Related to #9058 and #8683. ### Description Enforce the DCO sign-off locally so contributors catch a missing `Signed-off-by` line before CI, alongside the existing black/isort/ruff formatting hooks. The black and isort hooks already run via pre-commit (#9061), but the DCO check still only lives in the GitHub DCO app, so a missing sign-off is only discovered after pushing. This change: - Adds `.github/hooks/check-dco.sh`, a `commit-msg` hook that fails when the `Signed-off-by` line is missing. - Sets `default_install_hook_types: [pre-commit, commit-msg]` in `.pre-commit-config.yaml` so a plain `pre-commit install` wires up both stages, and registers the new local DCO hook. - Adds a `--setup` option to `runtests.sh` that runs `pre-commit install`. - Updates `CONTRIBUTING.md` to document installing the hooks and the DCO check. The black/isort -> ruff format consolidation remains tracked separately in #9066. ### Verification - `pre-commit validate-config` passes - `bash -n runtests.sh` passes - DCO hook verified: fails without a `Signed-off-by` line, passes with one - commit created with the hook active (DCO sign-off check passed) ### Types of changes <!--- Put an `x` in all the boxes that apply, and remove the not applicable items --> - [x] Non-breaking change (fix or new feature that would not break existing functionality). - [ ] Breaking change (fix or new feature that would cause existing functionality to change). - [ ] New tests added to cover the changes. - [ ] Integration tests passed locally by running `./runtests.sh -f -u --net --coverage`. - [ ] Quick tests passed locally by running `./runtests.sh --quick --unittests --disttests`. - [ ] In-line docstrings updated. - [x] Documentation updated, tested `make html` command in the `docs/` folder. --------- Signed-off-by: R. Garcia-Dias <rafaelagd@gmail.com>
1 parent 1f60f13 commit a82b463

4 files changed

Lines changed: 95 additions & 13 deletions

File tree

.github/hooks/check-dco.sh

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
#!/usr/bin/env bash
2+
# DCO sign-off check for the pre-commit commit-msg stage.
3+
#
4+
# Mirrors the GitHub DCO app requirement: every commit must carry a
5+
# "Signed-off-by:" line identifying the author.
6+
#
7+
# Usage: check-dco.sh <commit-message-file>
8+
9+
set -euo pipefail
10+
11+
msg_file="${1:-}"
12+
13+
if [[ -z "${msg_file}" || ! -f "${msg_file}" ]]; then
14+
echo "DCO check: no commit message file supplied." >&2
15+
exit 1
16+
fi
17+
18+
if grep -qE $'^Signed-off-by: .+ <[^@ ]+@[^@ ]+>\r?$' "${msg_file}"; then
19+
exit 0
20+
fi
21+
22+
cat >&2 <<'EOF'
23+
DCO check failed: commit message is missing a "Signed-off-by:" line.
24+
25+
Add a sign-off using one of:
26+
git commit -s # sign as you create the commit
27+
git commit --amend -s # sign the most recent commit
28+
29+
The line must identify the commit author, for example:
30+
Signed-off-by: Your Name <you@example.com>
31+
EOF
32+
33+
exit 1

.pre-commit-config.yaml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
default_language_version:
22
python: python3
33

4+
default_install_hook_types: [pre-commit, commit-msg]
5+
46
ci:
57
autofix_prs: true
68
autoupdate_commit_msg: '[pre-commit.ci] pre-commit suggestions'
@@ -62,3 +64,10 @@ repos:
6264
^versioneer.py|
6365
^monai/_version.py
6466
)
67+
- repo: local
68+
hooks:
69+
- id: dco
70+
name: DCO sign-off
71+
entry: .github/hooks/check-dco.sh
72+
language: script
73+
stages: [commit-msg]

CONTRIBUTING.md

Lines changed: 28 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,18 @@ To collaborate efficiently, please read through this section and follow them.
5252
#### Checking the coding style
5353

5454
Coding style is checked and enforced by black, isort, and ruff.
55+
To catch formatting failures before they reach CI, install the git pre-commit hooks once per checkout:
56+
57+
```bash
58+
# install the git hooks: black, isort, ruff
59+
pre-commit install
60+
61+
# or, via the test runner:
62+
./runtests.sh --setup
63+
```
64+
65+
These hooks run automatically on every `git commit`: `black`, `isort`, and `ruff` reformat the staged files. The same install also wires up the `commit-msg` hook that enforces the DCO sign-off described in [Signing your work](#signing-your-work).
66+
5567
Before submitting a pull request, we recommend that all linting should pass, by running the following command locally:
5668

5769
```bash
@@ -178,18 +190,9 @@ Please type `make help` in `docs/` folder for all supported format options.
178190

179191
#### Automatic code formatting
180192

181-
MONAI provides support of automatic Python code formatting via [a customised GitHub action](https://github.com/Project-MONAI/monai-code-formatter).
182-
This makes the project's Python coding style consistent and reduces maintenance burdens.
183-
Commenting a pull request with `/black` triggers the formatting action based on [`psf/Black`](https://github.com/psf/black) (this is implemented with [`slash command dispatch`](https://github.com/marketplace/actions/slash-command-dispatch)).
184-
185-
Steps for the formatting process:
193+
Code formatting is now handled locally via the [pre-commit](https://pre-commit.com/) hooks described in [Checking the coding style](#checking-the-coding-style): once installed, `black`, `isort`, and `ruff` reformat staged files automatically on every `git commit`, so formatting issues are caught before a pull request is even opened.
186194

187-
- After submitting a pull request or push to an existing pull request,
188-
make a comment to the pull request to trigger the formatting action.
189-
The first line of the comment must be `/black` so that it will be interpreted by [the comment parser](https://github.com/marketplace/actions/slash-command-dispatch#how-are-comments-parsed-for-slash-commands).
190-
- [Auto] The GitHub action tries to format all Python files (using [`psf/Black`](https://github.com/psf/black)) in the branch and makes a commit under the name "MONAI bot" if there's code change. The actual formatting action is deployed at [project-monai/monai-code-formatter](https://github.com/Project-MONAI/monai-code-formatter).
191-
- [Auto] After the formatting commit, the GitHub action adds an emoji to the comment that triggered the process.
192-
- Repeat the above steps if necessary.
195+
MONAI previously offered a `/black` slash command that triggered [a customised GitHub action](https://github.com/Project-MONAI/monai-code-formatter) to auto-format a pull request's branch based on [`psf/Black`](https://github.com/psf/black). This action hasn't been used in a long while and is no longer the recommended workflow. If a pull request still fails formatting checks in CI, install the pre-commit hooks locally and run `./runtests.sh --autofix` to fix the branch instead.
193196

194197
#### Adding new optional dependencies
195198

@@ -247,6 +250,19 @@ Git has a `-s` (or `--signoff`) command-line option to append this automatically
247250
git commit -s -m 'a new commit'
248251
```
249252

253+
For `-s` to add the correct identity, set your name and email in your git configuration (`git config --global --edit`, or the commands below):
254+
255+
```bash
256+
git config --global user.name "Your Name"
257+
git config --global user.email "you@example.com"
258+
```
259+
260+
If you'd rather not use a personal address, GitHub provides a no-reply email tied to your account under [Settings > Emails](https://github.com/settings/emails), for example `12345678+yourusername@users.noreply.github.com`. Using it still associates the sign-off with your GitHub username without exposing a personal email.
261+
262+
VS Code can also be configured to sign off every commit automatically: enable the `git.alwaysSignOff` setting (**Settings > Git: Always Sign Off**).
263+
264+
If the git pre-commit hooks are installed (`pre-commit install` or `./runtests.sh --setup`), the local `commit-msg` hook blocks any commit that is missing this line, so the DCO check fails locally rather than in CI.
265+
250266
The commit message will be:
251267

252268
```
@@ -453,7 +469,7 @@ All code review comments should be specific, constructive, and actionable.
453469
1. Read carefully the descriptions of the pull request and the files changed, write comments if needed.
454470
1. Make in-line comments to specific code segments, [request for changes](https://help.github.com/en/github/collaborating-with-issues-and-pull-requests/about-pull-request-reviews) if needed.
455471
1. Review any further code changes until all comments addressed by the contributors.
456-
1. Comment to trigger `/black` and/or `/integration-test` for optional auto code formatting and [integration tests](.github/workflows/integration.yml).
472+
1. If formatting checks fail, ask the contributor to run `./runtests.sh --autofix`, commit the resulting changes, and re-push; suggest installing the pre-commit hooks (see [Checking the coding style](#checking-the-coding-style)) to catch this locally next time. Comment `/integration-test` to trigger optional [integration tests](.github/workflows/integration.yml) if needed.
457473
1. [Maintainers] Review the changes and comment `/build` to trigger internal full tests.
458474
1. Merge the pull request to the dev branch.
459475
1. Close the corresponding task ticket on [the issue list][monai issue list].

runtests.sh

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ doPyreflyFormat=false
5353
doCleanup=false
5454
doDistTests=false
5555
doPrecommit=false
56+
doSetup=false
5657
testTimeout=0
5758

5859
NUM_PARALLEL=1
@@ -61,7 +62,7 @@ PY_EXE=${MONAI_PY_EXE:-$(which python)}
6162

6263
function print_usage {
6364
echo "runtests.sh [--codeformat] [--autofix] [--black] [--isort] [--pylint] [--ruff]"
64-
echo " [--clangformat] [--precommit] [--pytype] [-j number] [--pyrefly]"
65+
echo " [--clangformat] [--precommit] [--pytype] [-j number] [--pyrefly] [--setup]"
6566
echo " [--unittests] [--disttests] [--coverage] [--quick] [--min] [--net] [--build] [--list_tests]"
6667
echo " [--dryrun] [--copyright] [--clean] [--help] [--version] [--path] [--formatfix]"
6768
echo ""
@@ -103,6 +104,7 @@ function print_usage {
103104
echo ""
104105
echo "Misc. options:"
105106
echo " --dryrun : display the commands to the screen without running"
107+
echo " --setup : install git pre-commit hooks (black, isort, ruff, DCO sign-off)"
106108
echo " --copyright : check whether every source code has a copyright header"
107109
echo " -f, --codeformat : shorthand to run all code style and static analysis tests"
108110
echo " -c, --clean : clean temporary files from tests and exit"
@@ -320,6 +322,9 @@ do
320322
--precommit)
321323
doPrecommit=true
322324
;;
325+
--setup)
326+
doSetup=true
327+
;;
323328
--pytype)
324329
echo "${yellow}WARNING: --pytype is deprecated and may be removed in a future release.${noColor}"
325330
doPytypeFormat=true
@@ -429,6 +434,25 @@ then
429434
echo "${green}done!${noColor}"
430435
fi
431436

437+
if [ $doSetup = true ]
438+
then
439+
echo "${separator}${blue}setup${noColor}"
440+
441+
# ensure pre-commit is available
442+
if ! is_pip_installed pre_commit
443+
then
444+
install_deps
445+
fi
446+
447+
${cmdPrefix}"${PY_EXE}" -m pre_commit install
448+
449+
if [[ -z "$cmdPrefix" ]]; then
450+
echo "${green}done! git hooks installed (black, isort, ruff, DCO sign-off).${noColor}"
451+
else
452+
echo "dry-run: git hooks would be installed (black, isort, ruff, DCO sign-off)."
453+
fi
454+
fi
455+
432456
# unconditionally report on the state of monai
433457
print_version
434458

0 commit comments

Comments
 (0)