Add automatic patch-version bumping via a tracked git pre-commit hook
pyproject.toml's version has never moved past the 1.0.0 scaffold value across 368 commits; there was no bump mechanism at all, Claude-driven or otherwise. Add .githooks/pre-commit (stdlib Python, no dependencies) that increments the patch version on every commit and stages it automatically, deferring to a deliberate version edit already staged in the same commit and skipping merge commits. Wire it in via `make install` (git config core.hooksPath .githooks) so it activates for every clone without requiring any change to existing workflows. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TwLhnueWrsK15wrieXE5m7
This commit is contained in:
Executable
+51
@@ -0,0 +1,51 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# retoor <retoor@molodetz.nl>
|
||||||
|
|
||||||
|
import re
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
REPO_ROOT = Path(
|
||||||
|
subprocess.run(
|
||||||
|
["git", "rev-parse", "--show-toplevel"],
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
check=True,
|
||||||
|
).stdout.strip()
|
||||||
|
)
|
||||||
|
PYPROJECT = REPO_ROOT / "pyproject.toml"
|
||||||
|
VERSION_LINE = re.compile(r'^version = "(\d+)\.(\d+)\.(\d+)"$', re.MULTILINE)
|
||||||
|
STAGED_VERSION_CHANGE = re.compile(r'^[+-]version = "\d+\.\d+\.\d+"$', re.MULTILINE)
|
||||||
|
|
||||||
|
|
||||||
|
def staged_diff(path: Path) -> str:
|
||||||
|
result = subprocess.run(
|
||||||
|
["git", "diff", "--cached", "--unified=0", "--", str(path)],
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
cwd=REPO_ROOT,
|
||||||
|
)
|
||||||
|
return result.stdout
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> int:
|
||||||
|
if (REPO_ROOT / ".git" / "MERGE_HEAD").exists():
|
||||||
|
return 0
|
||||||
|
if not PYPROJECT.is_file():
|
||||||
|
return 0
|
||||||
|
if STAGED_VERSION_CHANGE.search(staged_diff(PYPROJECT)):
|
||||||
|
return 0
|
||||||
|
text = PYPROJECT.read_text()
|
||||||
|
match = VERSION_LINE.search(text)
|
||||||
|
if not match:
|
||||||
|
return 0
|
||||||
|
major, minor, patch = (int(part) for part in match.groups())
|
||||||
|
bumped = f'version = "{major}.{minor}.{patch + 1}"'
|
||||||
|
PYPROJECT.write_text(VERSION_LINE.sub(bumped, text, count=1))
|
||||||
|
subprocess.run(["git", "add", str(PYPROJECT)], cwd=REPO_ROOT, check=True)
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
sys.exit(main())
|
||||||
@@ -372,6 +372,12 @@ Failures at any implementation step block the workflow - never skip a failed ste
|
|||||||
|
|
||||||
Gitea Actions workflow at `.gitea/workflows/test.yaml` runs on every push/PR to `master`: installs dependencies + Playwright Chromium, runs the full suite serially under coverage, publishes coverage HTML as an artifact, uploads failure screenshots. CI must be green before merging. Changes move through DTAP: Development (`make dev`) -> Test (CI suite + coverage on `master`) -> Acceptance (`master` to `production` promotion via `make deploy`) -> Production (Docker Compose stack). Only CI-green `master` commits are promoted to `production`.
|
Gitea Actions workflow at `.gitea/workflows/test.yaml` runs on every push/PR to `master`: installs dependencies + Playwright Chromium, runs the full suite serially under coverage, publishes coverage HTML as an artifact, uploads failure screenshots. CI must be green before merging. Changes move through DTAP: Development (`make dev`) -> Test (CI suite + coverage on `master`) -> Acceptance (`master` to `production` promotion via `make deploy`) -> Production (Docker Compose stack). Only CI-green `master` commits are promoted to `production`.
|
||||||
|
|
||||||
|
## Version bumping
|
||||||
|
|
||||||
|
`pyproject.toml` `version` is bumped automatically, by a real git `pre-commit` hook, not by an agent remembering to edit it. The hook lives at `.githooks/pre-commit` (tracked in the repo, plain stdlib Python) and `make install` points git at it with `git config core.hooksPath .githooks` - run `make install` once per clone (or that one `git config` line by hand) to activate it; a clone that has never run `make install` simply gets no auto-bump, which is a safe, backwards-compatible no-op, never a broken commit.
|
||||||
|
|
||||||
|
On every commit the hook increments the patch component (`1.0.0` -> `1.0.1`) and stages the change, so the bump rides in the same commit with no extra step. It defers to a deliberate version edit already staged in that same commit (a hand-set major/minor bump in `pyproject.toml` is left exactly as written, never incremented further) and does nothing during a merge (`.git/MERGE_HEAD` present) or when `pyproject.toml` does not exist. It never blocks a commit - a missing or unparsable version line is a silent no-op, not a failure.
|
||||||
|
|
||||||
## Diagnosing a production failure (the order that finds it fastest)
|
## Diagnosing a production failure (the order that finds it fastest)
|
||||||
|
|
||||||
This procedure exists because a single "the editor is down" report turned out to be **three unrelated faults stacked on each other** (a stale URL, a firewalled network leg, and a corrupt database), and the investigation wasted hours by guessing before measuring. Work the layers outward from the browser; each step is cheap and each one eliminates a whole class of cause. **Never skip to a hypothesis, and never repair anything before the layer above it is proven healthy.**
|
This procedure exists because a single "the editor is down" report turned out to be **three unrelated faults stacked on each other** (a stale URL, a firewalled network leg, and a corrupt database), and the investigation wasted hours by guessing before measuring. Work the layers outward from the browser; each step is cheap and each one eliminates a whole class of cause. **Never skip to a hypothesis, and never repair anything before the layer above it is proven healthy.**
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ install: $(PYTHON)
|
|||||||
$(PYTHON) -m pip install -U pip
|
$(PYTHON) -m pip install -U pip
|
||||||
$(PYTHON) -m pip install -e ".[dev]"
|
$(PYTHON) -m pip install -e ".[dev]"
|
||||||
$(PYTHON) -m playwright install chromium
|
$(PYTHON) -m playwright install chromium
|
||||||
|
git config core.hooksPath .githooks
|
||||||
touch $(VENV_STAMP)
|
touch $(VENV_STAMP)
|
||||||
|
|
||||||
dev: $(VENV_STAMP)
|
dev: $(VENV_STAMP)
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
[project]
|
[project]
|
||||||
name = "devplacepy"
|
name = "devplacepy"
|
||||||
version = "1.0.0"
|
version = "1.0.1"
|
||||||
description = "DevPlace - The Developer Social Network"
|
description = "DevPlace - The Developer Social Network"
|
||||||
requires-python = ">=3.12"
|
requires-python = ">=3.12"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
|||||||
Reference in New Issue
Block a user