From 7f061c5b113bcf5b88de92fac115f8b5b271e452 Mon Sep 17 00:00:00 2001 From: Danny Date: Sat, 18 Apr 2026 19:31:30 +0200 Subject: [PATCH] feat(server): version badge now shows ISO date + short SHA Format: 'YYYY-MM-DD '. 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) --- server.py | 66 +++++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 54 insertions(+), 12 deletions(-) diff --git a/server.py b/server.py index 84b400a..bb65489 100644 --- a/server.py +++ b/server.py @@ -11,6 +11,8 @@ import logging import os import pathlib import subprocess +import zlib +from datetime import datetime, timezone from urllib.parse import parse_qs from aiohttp import web @@ -27,17 +29,53 @@ logger = logging.getLogger(__name__) # ── 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 " + 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: - """Return `git describe --tags --always --dirty`, with a pure-Python - fallback for environments where the `git` binary isn't on PATH - (e.g. minimal systemd service environments on NixOS). + """Return ' ', with a pure-Python fallback for + environments where the `git` binary isn't on PATH (e.g. minimal + systemd service environments on NixOS). """ repo_root = pathlib.Path(__file__).parent - # Preferred: git describe (picks up tags + dirty state). + # Preferred: git (%cs = committer date short, %h = short SHA). try: out = subprocess.run( - ["git", "describe", "--tags", "--always", "--dirty"], + ["git", "log", "-1", "--format=%cs %h"], cwd=repo_root, capture_output=True, text=True, timeout=2, check=True, ) @@ -46,14 +84,18 @@ def _compute_version() -> str: except (subprocess.SubprocessError, FileNotFoundError, OSError): pass - # Fallback: resolve HEAD ourselves. No tags, no dirty detection, just SHA. + # Fallback: resolve HEAD + read commit object for the date. try: - head = (repo_root / ".git" / "HEAD").read_text().strip() - if head.startswith("ref: "): - sha = (repo_root / ".git" / head[5:]).read_text().strip() - else: - sha = head - return sha[:7] if sha else "unknown" + git_dir = repo_root / ".git" + if not git_dir.is_dir(): + return "unknown" # worktree or unusual layout + head = (git_dir / "HEAD").read_text().strip() + sha = _resolve_ref(git_dir, head[5:]) if head.startswith("ref: ") else head + 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: return "unknown"