2025-10-28 02:50:31 +01:00
|
|
|
#!/usr/bin/env python3
|
|
|
|
|
"""Commitizen commit message checker that is .venv aware.
|
|
|
|
|
|
|
|
|
|
Works for commits with -m or commit message file.
|
2026-02-23 20:59:03 +01:00
|
|
|
|
|
|
|
|
Cross-platform + uv/.venv aware:
|
|
|
|
|
- Prefers activated virtual environment (VIRTUAL_ENV)
|
|
|
|
|
- Falls back to uv-managed .uv/venv
|
|
|
|
|
- Falls back to .venv
|
|
|
|
|
- Falls back to global cz
|
2025-10-28 02:50:31 +01:00
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
import os
|
|
|
|
|
import subprocess
|
|
|
|
|
import sys
|
|
|
|
|
from pathlib import Path
|
|
|
|
|
|
|
|
|
|
|
2026-02-23 20:59:03 +01:00
|
|
|
def find_cz() -> list[str]:
|
|
|
|
|
"""Return command to invoke Commitizen via virtualenv or globally."""
|
|
|
|
|
candidates = []
|
2025-10-28 02:50:31 +01:00
|
|
|
|
2026-02-23 20:59:03 +01:00
|
|
|
# 1️⃣ Currently active virtualenv
|
|
|
|
|
venv = os.getenv("VIRTUAL_ENV")
|
|
|
|
|
if venv:
|
|
|
|
|
candidates.append(Path(venv))
|
|
|
|
|
|
|
|
|
|
# 2️⃣ uv-managed virtualenv
|
|
|
|
|
uv_venv = Path(".uv") / "venv"
|
|
|
|
|
if uv_venv.exists():
|
|
|
|
|
candidates.append(uv_venv)
|
|
|
|
|
|
|
|
|
|
# 3️⃣ traditional .venv
|
|
|
|
|
dot_venv = Path(".venv")
|
|
|
|
|
if dot_venv.exists():
|
|
|
|
|
candidates.append(dot_venv)
|
|
|
|
|
|
|
|
|
|
# Check each candidate for Commitizen binary
|
|
|
|
|
for base in candidates:
|
|
|
|
|
cz_path = base / ("Scripts" if os.name == "nt" else "bin") / ("cz.exe" if os.name == "nt" else "cz")
|
|
|
|
|
if cz_path.exists():
|
|
|
|
|
return [str(cz_path)]
|
|
|
|
|
|
|
|
|
|
# 4️⃣ fallback to uv run cz
|
|
|
|
|
try:
|
|
|
|
|
subprocess.run(["uv", "run", "cz", "--version"], check=True, stdout=subprocess.DEVNULL)
|
|
|
|
|
return ["uv", "run", "cz"]
|
|
|
|
|
except (subprocess.CalledProcessError, FileNotFoundError):
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
# 5️⃣ fallback to system cz
|
|
|
|
|
return ["cz"]
|
2025-10-28 02:50:31 +01:00
|
|
|
|
|
|
|
|
|
|
|
|
|
def main():
|
|
|
|
|
cz = find_cz()
|
|
|
|
|
|
|
|
|
|
# 1️⃣ Try commit-msg file (interactive commit)
|
|
|
|
|
commit_msg_file = sys.argv[1] if len(sys.argv) > 1 else None
|
|
|
|
|
|
|
|
|
|
# 2️⃣ If not file, fallback to -m message (Git sets GIT_COMMIT_MSG in some environments, or we create a temp file)
|
|
|
|
|
if not commit_msg_file:
|
|
|
|
|
msg = os.getenv("GIT_COMMIT_MSG") or ""
|
|
|
|
|
if not msg:
|
|
|
|
|
print("⚠️ No commit message file or environment message found. Skipping Commitizen check.")
|
|
|
|
|
return 0
|
|
|
|
|
import tempfile
|
|
|
|
|
|
|
|
|
|
with tempfile.NamedTemporaryFile("w+", delete=False) as tmp:
|
|
|
|
|
tmp.write(msg)
|
|
|
|
|
tmp.flush()
|
|
|
|
|
commit_msg_file = tmp.name
|
|
|
|
|
|
|
|
|
|
print(f"🔍 Checking commit message using {cz}...")
|
|
|
|
|
|
|
|
|
|
try:
|
2026-02-23 20:59:03 +01:00
|
|
|
subprocess.check_call(cz + ["check", "--commit-msg-file", commit_msg_file])
|
2025-10-28 02:50:31 +01:00
|
|
|
print("✅ Commit message follows Commitizen convention.")
|
|
|
|
|
return 0
|
|
|
|
|
except subprocess.CalledProcessError:
|
|
|
|
|
print("❌ Commit message validation failed.")
|
|
|
|
|
return 1
|
|
|
|
|
finally:
|
|
|
|
|
# Clean up temp file if we created one
|
|
|
|
|
if 'tmp' in locals():
|
|
|
|
|
os.unlink(tmp.name)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
|
sys.exit(main())
|