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
13 changes: 5 additions & 8 deletions .husky/pre-commit
Original file line number Diff line number Diff line change
Expand Up @@ -25,16 +25,13 @@ uvx ruff@latest check .
uvx mypy@latest

# The interpreter is chosen by running the probes spec/host-tools.json declares, in its order.
# On native Windows the python.org install registers `py` and not `python3`.
# That name resolves to a Microsoft Store alias stub, and Git Bash inherits the Windows PATH.
# The stub is on PATH and fails when run, so a presence test selects it and the hook then breaks.
# Running the probe is the whole point: it is what tells a working interpreter from a name.
if python3 --version >/dev/null 2>&1; then
run_py() { python3 "$@"; }
elif py -3 --version >/dev/null 2>&1; then
# Running one rather than testing for a name is the point, since Git Bash inherits a Windows PATH that can carry a name which fails when run.
if py -3 --version >/dev/null 2>&1; then
run_py() { py -3 "$@"; }
elif python3 --version >/dev/null 2>&1; then
run_py() { python3 "$@"; }
else
echo "pre-commit: neither 'python3 --version' nor 'py -3 --version' ran, so the doc gates did not run." >&2
echo "pre-commit: neither 'py -3 --version' nor 'python3 --version' ran, so the doc gates did not run." >&2
echo "pre-commit: see docs/host-setup.md 'What a Host Must Provide'." >&2
exit 1
fi
Expand Down
27 changes: 21 additions & 6 deletions .husky/pre-push
Original file line number Diff line number Diff line change
Expand Up @@ -64,15 +64,30 @@ elif [ "$tree_status" -ne 0 ]; then
fi

# The interpreter is chosen by running the probes spec/host-tools.json declares, in its order.
# The pre-commit hook states why a presence test picks the wrong name on native Windows.
if python3 --version >/dev/null 2>&1; then
run_py() { python3 "$@"; }
py_name=python3
elif py -3 --version >/dev/null 2>&1; then
# The py launcher answers first because it reaches a registered interpreter whatever is active, where a bare python3 is reached through PATH and a virtual environment can answer there instead.
if py -3 --version >/dev/null 2>&1; then
run_py() { py -3 "$@"; }
py_name="py -3"
elif python3 --version >/dev/null 2>&1; then
run_py() { python3 "$@"; }
py_name=python3
else
echo "pre-push: neither 'python3 --version' nor 'py -3 --version' ran, so the review gate did not run." >&2
echo "pre-push: neither 'py -3 --version' nor 'python3 --version' ran, so the review gate did not run." >&2
echo "pre-push: see docs/host-setup.md 'What a Host Must Provide'." >&2
exit 1
fi

# The engines import datetime.UTC at module level, so an interpreter below the floor fails at import and exits 1, which reads as a refusal rather than as the gate never running.
# The interpreter answers about its own version, which needs no text comparison and so no sort -V, a GNU spelling this repository uses only in Linux-only scripts.
# The floor is read out of the spec rather than repeated here, since a copy drifts the way the probe order above already did.
py_floor=$(run_py -c "import json; print(next(t['minimum'] for t in json.load(open('spec/host-tools.json'))['tools'] if t['name'] == 'python3'))" 2>/dev/null) || py_floor=""
if [ -z "$py_floor" ]; then
echo "pre-push: could not read the python3 floor from spec/host-tools.json, so the review gate did not run." >&2
exit 1
fi
if ! run_py -c "import sys; raise SystemExit(0 if sys.version_info >= tuple(int(n) for n in '$py_floor'.split('.')) else 1)"; then
py_version=$(run_py -c "import sys; print('.'.join(str(n) for n in sys.version_info[:3]))" 2>/dev/null || true)
echo "pre-push: '$py_name' is Python ${py_version:-of an unreadable version}, below the $py_floor floor, so the review gate did not run." >&2
echo "pre-push: see docs/host-setup.md 'What a Host Must Provide'." >&2
exit 1
fi
Expand Down