From fa00f9af0a8be1edb4bdb667ed6505fce30ededd Mon Sep 17 00:00:00 2001 From: "R. Garcia-Dias" Date: Fri, 4 Sep 2026 10:12:50 +0100 Subject: [PATCH 1/4] ci: add local pre-commit DCO hook and setup command Enforce the DCO sign-off locally so contributors catch a missing Signed-off-by before CI, alongside the existing black/isort/ruff formatting hooks. - .pre-commit-config.yaml: install the commit-msg hook by default (default_install_hook_types) and add a local DCO check hook. - .github/hooks/check-dco.sh: commit-msg hook that fails when the Signed-off-by line is missing. - runtests.sh: new --setup option that runs 'pre-commit install'. - CONTRIBUTING.md: document installing the hooks and the DCO check. Related to #9058 and #8683. Signed-off-by: R. Garcia-Dias --- .github/hooks/check-dco.sh | 33 +++++++++++++++++++++++++++++++++ .pre-commit-config.yaml | 9 +++++++++ CONTRIBUTING.md | 16 +++++++++++++++- runtests.sh | 22 +++++++++++++++++++++- 4 files changed, 78 insertions(+), 2 deletions(-) create mode 100755 .github/hooks/check-dco.sh diff --git a/.github/hooks/check-dco.sh b/.github/hooks/check-dco.sh new file mode 100755 index 0000000000..5f7f88654b --- /dev/null +++ b/.github/hooks/check-dco.sh @@ -0,0 +1,33 @@ +#!/usr/bin/env bash +# DCO sign-off check for the pre-commit commit-msg stage. +# +# Mirrors the GitHub DCO app requirement: every commit must carry a +# "Signed-off-by:" line identifying the author. +# +# Usage: check-dco.sh + +set -euo pipefail + +msg_file="${1:-}" + +if [[ -z "${msg_file}" || ! -f "${msg_file}" ]]; then + echo "DCO check: no commit message file supplied." >&2 + exit 1 +fi + +if grep -qE '^Signed-off-by: .+ <[^@ ]+@[^@ ]+>$' "${msg_file}"; then + exit 0 +fi + +cat >&2 <<'EOF' +DCO check failed: commit message is missing a "Signed-off-by:" line. + +Add a sign-off using one of: + git commit -s # sign as you create the commit + git commit --amend -s # sign the most recent commit + +The line must identify the commit author, for example: + Signed-off-by: Your Name +EOF + +exit 1 diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index ae03b5bae9..a3fe2d7d83 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,8 @@ default_language_version: python: python3 +default_install_hook_types: [pre-commit, commit-msg] + ci: autofix_prs: true autoupdate_commit_msg: '[pre-commit.ci] pre-commit suggestions' @@ -62,3 +64,10 @@ repos: ^versioneer.py| ^monai/_version.py ) + - repo: local + hooks: + - id: dco + name: DCO sign-off + entry: .github/hooks/check-dco.sh + language: script + stages: [commit-msg] diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 1fb68b4b58..318433a48e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -51,7 +51,19 @@ To collaborate efficiently, please read through this section and follow them. #### Checking the coding style -Coding style is checked and enforced by black, isort, and ruff. +Coding style is checked and enforced by black, isort, and ruff, and every commit must carry a DCO sign-off. +To catch formatting and DCO failures before they reach CI, install the git pre-commit hooks once per checkout: + +```bash +# install the git hooks: black, isort, ruff, and the DCO sign-off check +pre-commit install + +# or, via the test runner: +./runtests.sh --setup +``` + +These hooks run automatically on every `git commit`: `black`, `isort`, and `ruff` reformat the staged files, and the `commit-msg` hook blocks a commit that is missing a `Signed-off-by` line. + Before submitting a pull request, we recommend that all linting should pass, by running the following command locally: ```bash @@ -247,6 +259,8 @@ Git has a `-s` (or `--signoff`) command-line option to append this automatically git commit -s -m 'a new commit' ``` +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. + The commit message will be: ``` diff --git a/runtests.sh b/runtests.sh index 0fc18b36ec..6a092f57bf 100755 --- a/runtests.sh +++ b/runtests.sh @@ -53,6 +53,7 @@ doPyreflyFormat=false doCleanup=false doDistTests=false doPrecommit=false +doSetup=false testTimeout=0 NUM_PARALLEL=1 @@ -61,7 +62,7 @@ PY_EXE=${MONAI_PY_EXE:-$(which python)} function print_usage { echo "runtests.sh [--codeformat] [--autofix] [--black] [--isort] [--pylint] [--ruff]" - echo " [--clangformat] [--precommit] [--pytype] [-j number] [--pyrefly]" + echo " [--clangformat] [--precommit] [--pytype] [-j number] [--pyrefly] [--setup]" echo " [--unittests] [--disttests] [--coverage] [--quick] [--min] [--net] [--build] [--list_tests]" echo " [--dryrun] [--copyright] [--clean] [--help] [--version] [--path] [--formatfix]" echo "" @@ -103,6 +104,7 @@ function print_usage { echo "" echo "Misc. options:" echo " --dryrun : display the commands to the screen without running" + echo " --setup : install git pre-commit hooks (black, isort, ruff, DCO sign-off)" echo " --copyright : check whether every source code has a copyright header" echo " -f, --codeformat : shorthand to run all code style and static analysis tests" echo " -c, --clean : clean temporary files from tests and exit" @@ -320,6 +322,9 @@ do --precommit) doPrecommit=true ;; + --setup) + doSetup=true + ;; --pytype) echo "${yellow}WARNING: --pytype is deprecated and may be removed in a future release.${noColor}" doPytypeFormat=true @@ -429,6 +434,21 @@ then echo "${green}done!${noColor}" fi +if [ $doSetup = true ] +then + echo "${separator}${blue}setup${noColor}" + + # ensure pre-commit is available + if ! is_pip_installed pre_commit + then + install_deps + fi + + ${cmdPrefix}"${PY_EXE}" -m pre_commit install + + echo "${green}done! git hooks installed (black, isort, ruff, DCO sign-off).${noColor}" +fi + # unconditionally report on the state of monai print_version From 7faec0a1150c02c8150b5bf8b95def4384e4c2f1 Mon Sep 17 00:00:00 2001 From: "R. Garcia-Dias" Date: Mon, 7 Sep 2026 09:08:27 +0100 Subject: [PATCH 2/4] fix: address PR #9094 review feedback - .github/hooks/check-dco.sh: accept CRLF-terminated Signed-off-by lines - runtests.sh: don't claim hooks were installed under --dryrun --setup - CONTRIBUTING.md: move DCO sign-off details out of the coding-style section, document setting git user.name/email (including GitHub's no-reply address) and VS Code's auto sign-off setting Signed-off-by: R. Garcia-Dias --- .github/hooks/check-dco.sh | 2 +- CONTRIBUTING.md | 19 +++++++++++++++---- runtests.sh | 6 +++++- 3 files changed, 21 insertions(+), 6 deletions(-) diff --git a/.github/hooks/check-dco.sh b/.github/hooks/check-dco.sh index 5f7f88654b..82f3c64fbc 100755 --- a/.github/hooks/check-dco.sh +++ b/.github/hooks/check-dco.sh @@ -15,7 +15,7 @@ if [[ -z "${msg_file}" || ! -f "${msg_file}" ]]; then exit 1 fi -if grep -qE '^Signed-off-by: .+ <[^@ ]+@[^@ ]+>$' "${msg_file}"; then +if grep -qE $'^Signed-off-by: .+ <[^@ ]+@[^@ ]+>\r?$' "${msg_file}"; then exit 0 fi diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 318433a48e..3cf7bcd65f 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -51,18 +51,18 @@ To collaborate efficiently, please read through this section and follow them. #### Checking the coding style -Coding style is checked and enforced by black, isort, and ruff, and every commit must carry a DCO sign-off. -To catch formatting and DCO failures before they reach CI, install the git pre-commit hooks once per checkout: +Coding style is checked and enforced by black, isort, and ruff. +To catch formatting failures before they reach CI, install the git pre-commit hooks once per checkout: ```bash -# install the git hooks: black, isort, ruff, and the DCO sign-off check +# install the git hooks: black, isort, ruff pre-commit install # or, via the test runner: ./runtests.sh --setup ``` -These hooks run automatically on every `git commit`: `black`, `isort`, and `ruff` reformat the staged files, and the `commit-msg` hook blocks a commit that is missing a `Signed-off-by` line. +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). Before submitting a pull request, we recommend that all linting should pass, by running the following command locally: @@ -259,6 +259,17 @@ Git has a `-s` (or `--signoff`) command-line option to append this automatically git commit -s -m 'a new commit' ``` +For `-s` to add the correct identity, set your name and email in your git configuration (`git config --global --edit`, or the commands below): + +```bash +git config --global user.name "Your Name" +git config --global user.email "you@example.com" +``` + +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. + +VS Code can also be configured to sign off every commit automatically: enable the `git.alwaysSignOff` setting (**Settings > Git: Always Sign Off**). + 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. The commit message will be: diff --git a/runtests.sh b/runtests.sh index 6a092f57bf..5b48c6a1ba 100755 --- a/runtests.sh +++ b/runtests.sh @@ -446,7 +446,11 @@ then ${cmdPrefix}"${PY_EXE}" -m pre_commit install - echo "${green}done! git hooks installed (black, isort, ruff, DCO sign-off).${noColor}" + if [[ -z "$cmdPrefix" ]]; then + echo "${green}done! git hooks installed (black, isort, ruff, DCO sign-off).${noColor}" + else + echo "dry-run: git hooks would be installed (black, isort, ruff, DCO sign-off)." + fi fi # unconditionally report on the state of monai From 75619196241f5e500672fa111272736386ddeae2 Mon Sep 17 00:00:00 2001 From: "R. Garcia-Dias" Date: Mon, 7 Sep 2026 13:21:50 +0100 Subject: [PATCH 3/4] docs: update code formatting section to reflect pre-commit workflow The /black GitHub Action slash command has not been used in a long while; formatting is now enforced locally via pre-commit hooks. Signed-off-by: R. Garcia-Dias --- CONTRIBUTING.md | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 3cf7bcd65f..e3da62e379 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -190,18 +190,9 @@ Please type `make help` in `docs/` folder for all supported format options. #### Automatic code formatting -MONAI provides support of automatic Python code formatting via [a customised GitHub action](https://github.com/Project-MONAI/monai-code-formatter). -This makes the project's Python coding style consistent and reduces maintenance burdens. -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)). +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. -Steps for the formatting process: - -- After submitting a pull request or push to an existing pull request, -make a comment to the pull request to trigger the formatting action. -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). -- [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). -- [Auto] After the formatting commit, the GitHub action adds an emoji to the comment that triggered the process. -- Repeat the above steps if necessary. +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. #### Adding new optional dependencies @@ -478,7 +469,7 @@ All code review comments should be specific, constructive, and actionable. 1. Read carefully the descriptions of the pull request and the files changed, write comments if needed. 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. 1. Review any further code changes until all comments addressed by the contributors. -1. Comment to trigger `/black` and/or `/integration-test` for optional auto code formatting and [integration tests](.github/workflows/integration.yml). +1. If formatting checks fail, ask the contributor to install the pre-commit hooks (see [Checking the coding style](#checking-the-coding-style)) and re-push; comment `/integration-test` to trigger optional [integration tests](.github/workflows/integration.yml) if needed. 1. [Maintainers] Review the changes and comment `/build` to trigger internal full tests. 1. Merge the pull request to the dev branch. 1. Close the corresponding task ticket on [the issue list][monai issue list]. From 407c126ce6aedb64dd9764b4c4d0d058e541d1c4 Mon Sep 17 00:00:00 2001 From: "R. Garcia-Dias" Date: Mon, 7 Sep 2026 13:28:02 +0100 Subject: [PATCH 4/4] docs: clarify autofix-then-repush steps for formatting failures Reviewer feedback: reviewer guidance should ask contributors to run the autofix, commit, and re-push before suggesting hook installation for next time. Signed-off-by: R. Garcia-Dias --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index e3da62e379..630fbfbb8e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -469,7 +469,7 @@ All code review comments should be specific, constructive, and actionable. 1. Read carefully the descriptions of the pull request and the files changed, write comments if needed. 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. 1. Review any further code changes until all comments addressed by the contributors. -1. If formatting checks fail, ask the contributor to install the pre-commit hooks (see [Checking the coding style](#checking-the-coding-style)) and re-push; comment `/integration-test` to trigger optional [integration tests](.github/workflows/integration.yml) if needed. +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. 1. [Maintainers] Review the changes and comment `/build` to trigger internal full tests. 1. Merge the pull request to the dev branch. 1. Close the corresponding task ticket on [the issue list][monai issue list].