pre-commit hooks for Techmo's projects.
- Python 3.8+
To install pre-commit within a target repository link this project as a submodule first then run the installation script once or better still integrate it with a setup script of the repository.
Example (isolated venv created at ./submodules/pre-commit/.venv):
./submodules/pre-commit/install.shExample (install into an existing project virtualenv so no separate activation is needed):
./submodules/pre-commit/install.sh --venv ".venv"Create the virtualenv once at the top level and pass its path down the tree — each
setup.sh accepts an optional venv path, creates one if absent, then forwards
it to install.sh --venv and to any child submodules.
Root setup.sh
#!/bin/bash
set -euo pipefail
venv_arg="${1:-}"
if [ -z "${venv_arg}" ]; then
uv venv .venv
venv_arg="$(pwd)/.venv"
fi
source "${venv_arg}/bin/activate"
uv pip install -e ".[test]"
./submodules/pre-commit/install.sh --venv "${venv_arg}"
# Propagate the shared venv to submodules.
./submodule-a/setup.sh "${venv_arg}"
./submodule-b/setup.sh "${venv_arg}"Submodule setup.sh scripts follow the same pattern without the propagation block.
Each install.sh registers git hooks for its own repository. Scripts work correctly both
standalone (create their own venv) and as child calls (reuse the shared one).
To perform a single check on demand run run.sh script.
Example:
./submodules/pre-commit/run.sh
When installed the hooks are called automatically on git commit. The commit operation is aborted if check fails.
The no-commit-to-branch hook prevents direct commits to master. To bypass it when necessary:
SKIP=no-commit-to-branch git commit -m "message"To call pre-commit hooks within a CI pipeline of the target repository add a relevant job.
The SKIP variable must be set to disable no-commit-to-branch in CI, since pipelines run on all branches.
Example (GitHub Actions):
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
submodules: recursive
- name: Run pre-commit hooks
run: |
./setup.sh
SKIP=no-commit-to-branch ./submodules/pre-commit/run.shThis example assumes setup.sh exists in the target repository and installs pre-commit hooks for it.
The default pre-commit configuration is listed in .pre-commit-config.yaml. If needed, a custom pre-commit config may be specified via --config.
Examples:
./submodules/pre-commit/install.sh --config path/to/custom/config./submodules/pre-commit/run.sh --config path/to/custom/configAs of v2.0.0, Python linting and formatting is handled by ruff, replacing the previous black + isort + autoflake setup. Ruff is configured via inline args in .pre-commit-config.yaml and provides:
ruff check --fix- linting with auto-fix (pyflakes, isort, bandit security, pyupgrade, bugbear rules)ruff format- code formatting (Black-compatible, 160 char line length)
The following security rules are ignored by default to reduce false positives: S101 (assert), S603/S607 (subprocess with list args). To suppress additional rules per project, add --extend-ignore=SXXX to the hook's args in your project's pre-commit config override, or configure exclusions via a project-level ruff.toml.
clang-format formats C/C++ source files. By default it uses --style=file, which looks for a .clang-format configuration in the project root. If none is found, files are left unchanged (--fallback-style=none).
cppcheck performs static analysis on C/C++ code, catching bugs, undefined behavior, and style issues. The default standard is C++17.
Both hooks are provided via pocc/pre-commit-hooks and declared with additional_dependencies, so pre-commit installs the PyPI-bundled binaries into the hook's own isolated environment automatically — no system-wide installation needed.
Gitleaks scans staged changes for secrets such as API keys, passwords, and tokens. It is more comprehensive than the built-in detect-private-key hook which only catches SSH private keys.
Hadolint lints Dockerfiles for best practices and uses ShellCheck under the hood to validate RUN instructions. The hook uses hadolint-py, which ships pre-compiled hadolint binaries as Python wheels — no system-wide hadolint installation is needed.
Codespell checks for common misspellings in code, comments, and documentation. To ignore specific words, add a [codespell] section to setup.cfg (e.g. ignore-words-list = foo,bar) or pass --ignore-words-list args in a project-level pre-commit config override.
The scripts and configuration files in this repository (install.sh, run.sh,
.pre-commit-config.yaml, etc.) are released under the MIT License.
The default configuration references several external tools that are downloaded and run by pre-commit in isolated environments on the user's machine. They are not bundled or redistributed here. Their licenses are listed for reference:
| Tool | License |
|---|---|
| pre-commit | MIT |
| pre-commit-hooks | MIT |
| ruff | MIT |
| mypy | MIT |
| gitleaks | MIT |
| codespell | GPL-2.0 |
| ShellCheck | GPL-3.0 |
| shfmt | BSD-3-Clause |
| hadolint | GPL-3.0 |
| clang-format | Apache-2.0 |
| cppcheck | GPL-3.0 |
Using a GPL-licensed tool as an external process does not affect the license of code that invokes it. The GPL copyleft applies to distribution of derivative works, not to runtime invocation. See the GNU FAQ for details.