feat(server): version badge now shows ISO date + short SHA

Format: 'YYYY-MM-DD <short-sha>'. Preferred path uses
`git log -1 --format='%cs %h'`.

Fallback path (no git on PATH) resolves HEAD (loose or packed refs)
and parses the loose commit object via zlib to extract committer
date, converted to UTC date. Degrades gracefully to SHA-only if
the commit object is packed or unreadable.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Danny 2026-04-18 19:31:30 +02:00
parent 53dbaa1d4e
commit 7f061c5b11

View file

@ -11,6 +11,8 @@ import logging
import os import os
import pathlib import pathlib
import subprocess import subprocess
import zlib
from datetime import datetime, timezone
from urllib.parse import parse_qs from urllib.parse import parse_qs
from aiohttp import web from aiohttp import web
@ -27,17 +29,53 @@ logger = logging.getLogger(__name__)
# ── Version (computed once at startup) ─────────────────────────── # ── Version (computed once at startup) ───────────────────────────
def _resolve_ref(git_dir: pathlib.Path, ref: str) -> str | None:
"""Resolve a ref name to its SHA, handling loose and packed refs."""
loose = git_dir / ref
if loose.is_file():
return loose.read_text().strip()
packed = git_dir / "packed-refs"
if packed.is_file():
for line in packed.read_text().splitlines():
if not line or line.startswith("#") or line.startswith("^"):
continue
parts = line.split(maxsplit=1)
if len(parts) == 2 and parts[1] == ref:
return parts[0]
return None
def _read_commit_date_utc(git_dir: pathlib.Path, sha: str) -> str | None:
"""Parse a loose commit object and return committer date as YYYY-MM-DD (UTC)."""
obj_path = git_dir / "objects" / sha[:2] / sha[2:]
if not obj_path.exists():
return None # packed — skip; recent HEAD is usually loose
try:
raw = zlib.decompress(obj_path.read_bytes())
content = raw[raw.index(b"\0") + 1:].decode("utf-8", errors="replace")
for line in content.splitlines():
if line.startswith("committer "):
# "committer Name <email> <unix-ts> <tz>"
parts = line.rsplit(" ", 2)
if len(parts) == 3 and parts[1].lstrip("-").isdigit():
ts = int(parts[1])
return datetime.fromtimestamp(ts, tz=timezone.utc).strftime("%Y-%m-%d")
return None
except (OSError, ValueError, zlib.error):
return None
def _compute_version() -> str: def _compute_version() -> str:
"""Return `git describe --tags --always --dirty`, with a pure-Python """Return '<YYYY-MM-DD> <short-sha>', with a pure-Python fallback for
fallback for environments where the `git` binary isn't on PATH environments where the `git` binary isn't on PATH (e.g. minimal
(e.g. minimal systemd service environments on NixOS). systemd service environments on NixOS).
""" """
repo_root = pathlib.Path(__file__).parent repo_root = pathlib.Path(__file__).parent
# Preferred: git describe (picks up tags + dirty state). # Preferred: git (%cs = committer date short, %h = short SHA).
try: try:
out = subprocess.run( out = subprocess.run(
["git", "describe", "--tags", "--always", "--dirty"], ["git", "log", "-1", "--format=%cs %h"],
cwd=repo_root, cwd=repo_root,
capture_output=True, text=True, timeout=2, check=True, capture_output=True, text=True, timeout=2, check=True,
) )
@ -46,14 +84,18 @@ def _compute_version() -> str:
except (subprocess.SubprocessError, FileNotFoundError, OSError): except (subprocess.SubprocessError, FileNotFoundError, OSError):
pass pass
# Fallback: resolve HEAD ourselves. No tags, no dirty detection, just SHA. # Fallback: resolve HEAD + read commit object for the date.
try: try:
head = (repo_root / ".git" / "HEAD").read_text().strip() git_dir = repo_root / ".git"
if head.startswith("ref: "): if not git_dir.is_dir():
sha = (repo_root / ".git" / head[5:]).read_text().strip() return "unknown" # worktree or unusual layout
else: head = (git_dir / "HEAD").read_text().strip()
sha = head sha = _resolve_ref(git_dir, head[5:]) if head.startswith("ref: ") else head
return sha[:7] if sha else "unknown" if not sha:
return "unknown"
short_sha = sha[:7]
date = _read_commit_date_utc(git_dir, sha)
return f"{date} {short_sha}" if date else short_sha
except OSError: except OSError:
return "unknown" return "unknown"