diff --git a/web/common.py b/web/common.py index 35d7e39..0c2372b 100644 --- a/web/common.py +++ b/web/common.py @@ -24,6 +24,7 @@ import notifications from apply_client import ApplyClient, _row_to_profile from auth import issue_csrf_token from settings import APPLY_FAILURE_THRESHOLD, GIT_COMMIT, INTERNAL_API_KEY +from version import commits_behind_main logger = logging.getLogger("web") @@ -108,6 +109,13 @@ def require_internal(x_internal_api_key: str | None = Header(default=None)) -> N def base_context(request: Request, user, active_tab: str) -> dict: + behind = commits_behind_main(GIT_COMMIT) + if behind is None: + git_status = "" + elif behind == 0: + git_status = "latest" + else: + git_status = f"{behind} behind" return { "request": request, "user": user, @@ -116,6 +124,7 @@ def base_context(request: Request, user, active_tab: str) -> dict: "is_admin": bool(user["is_admin"]), "git_commit": GIT_COMMIT, "git_commit_short": GIT_COMMIT[:7] if GIT_COMMIT and GIT_COMMIT != "dev" else GIT_COMMIT, + "git_status": git_status, } diff --git a/web/templates/_layout.html b/web/templates/_layout.html index f93a029..a01bd17 100644 --- a/web/templates/_layout.html +++ b/web/templates/_layout.html @@ -33,7 +33,7 @@ Mit ♥ programmiert für Annika {% if git_commit_short %} · - build {{ git_commit_short }} + build {{ git_commit_short }}{% if git_status %} ({{ git_status }}){% endif %} {% endif %} {% endblock %} diff --git a/web/version.py b/web/version.py new file mode 100644 index 0000000..5f7ccf8 --- /dev/null +++ b/web/version.py @@ -0,0 +1,37 @@ +"""How far behind origin/main the running build is. + +Hits Gitea's compare endpoint with a short per-SHA cache. Network errors +return None and the footer just hides the status — the SHA itself still +renders, so a flaky git host never breaks the page. +""" +import logging +import time + +import requests + + +_GITEA_REPO_API = "https://git.moritz.run/api/v1/repos/moritz/lazyflat" +_CACHE_TTL = 60.0 +_HTTP_TIMEOUT = 1.5 +_cache: dict[str, tuple[int, float]] = {} + +logger = logging.getLogger("web.version") + + +def commits_behind_main(sha: str) -> int | None: + """Return how many commits `sha` is behind origin/main, or None on error/dev.""" + if not sha or sha == "dev": + return None + now = time.monotonic() + cached = _cache.get(sha) + if cached and (now - cached[1]) < _CACHE_TTL: + return cached[0] + try: + r = requests.get(f"{_GITEA_REPO_API}/compare/{sha}...main", timeout=_HTTP_TIMEOUT) + r.raise_for_status() + behind = int(r.json().get("total_commits", 0)) + except (requests.RequestException, ValueError, KeyError) as e: + logger.debug("commits_behind_main fetch failed: %s", e) + return cached[0] if cached else None + _cache[sha] = (behind, now) + return behind