fix(version): a checkout past a CalVer-only release reports that release's version
Every release so far is tagged vYYYY.M.D only, and STABLE_TAG_RE rejects a 4-digit major on purpose, so a git checkout had no base and reported "unknown" (displayed git.<sha>). requires_hermes then compared plugins against "unknown" and let everything load. With no semver tag reachable, take the nearest CalVer tag (git describe) and the version its pyproject shipped, counting distance from that tag. A semver tag still wins when one exists; source stamps inherit this through _git_version_info.
This commit is contained in:
@@ -13,6 +13,7 @@ from __future__ import annotations
|
||||
|
||||
import json
|
||||
import subprocess
|
||||
import tomllib
|
||||
from dataclasses import dataclass
|
||||
from pathlib import Path
|
||||
from typing import Literal, cast
|
||||
@@ -82,6 +83,29 @@ def _parse_nonnegative(value: str | None) -> int | None:
|
||||
return parsed if parsed >= 0 else None
|
||||
|
||||
|
||||
def _calver_release_version(repo_dir: Path) -> tuple[str, int] | None:
|
||||
"""The version the nearest CalVer release shipped, and the commits since it.
|
||||
|
||||
Releases before semver tags existed are tagged ``vYYYY.M.D`` only; the
|
||||
version users actually run is in that tag's pyproject. Without this, a
|
||||
checkout past such a release would compare as "unknown" against plugins'
|
||||
``requires_hermes``.
|
||||
"""
|
||||
described = _run_git(repo_dir, "describe", "--tags", "--long", "--match", "v2[0-9][0-9][0-9].*", "HEAD")
|
||||
if not described:
|
||||
return None
|
||||
tag, count, _ = described.rsplit("-", 2)
|
||||
distance = _parse_nonnegative(count)
|
||||
try:
|
||||
project = tomllib.loads(_run_git(repo_dir, "show", f"{tag}:pyproject.toml") or "").get("project", {})
|
||||
except tomllib.TOMLDecodeError:
|
||||
return None
|
||||
version = project.get("version")
|
||||
if distance is None or not isinstance(version, str) or not STABLE_TAG_RE.fullmatch(f"v{version}"):
|
||||
return None
|
||||
return version, distance
|
||||
|
||||
|
||||
# --- Install stamp reader ---------------------------------------------------
|
||||
|
||||
def _resolve_stamp_file() -> Path | None:
|
||||
@@ -212,6 +236,8 @@ def _git_version_info(repo_dir: Path, *, include_untracked: bool = False) -> Ver
|
||||
distance = _parse_nonnegative(
|
||||
_run_git(repo_dir, "rev-list", "--count", f"v{base_version}..HEAD")
|
||||
) if releases else None
|
||||
if not releases:
|
||||
base_version, distance = _calver_release_version(repo_dir) or ("unknown", None)
|
||||
short_commit = _run_git(repo_dir, "rev-parse", "--short=7", "HEAD")
|
||||
if base_version == "unknown" and short_commit:
|
||||
display_version = f"git.{short_commit}{'.dirty' if dirty else ''}"
|
||||
|
||||
@@ -150,6 +150,38 @@ def test_get_version_info_derives_identity_from_reachable_release_tag(tmp_path,
|
||||
assert info.source == "git"
|
||||
|
||||
|
||||
def test_get_version_info_takes_the_version_a_calver_only_release_shipped(tmp_path, monkeypatch):
|
||||
"""Releases tagged only vYYYY.M.D resolve to their pyproject version, not "unknown"."""
|
||||
repo = tmp_path / "repo"
|
||||
repo.mkdir()
|
||||
|
||||
def git(*args: str) -> str:
|
||||
result = subprocess.run(
|
||||
["git", *args], cwd=repo, text=True, capture_output=True, check=True,
|
||||
env={"HOME": str(tmp_path), "PATH": __import__("os").environ["PATH"]},
|
||||
)
|
||||
return result.stdout.strip()
|
||||
|
||||
git("init", "-q")
|
||||
git("config", "user.name", "Hermes Test")
|
||||
git("config", "user.email", "hermes@example.invalid")
|
||||
(repo / "pyproject.toml").write_text('[project]\nname = "hermes-agent"\nversion = "0.21.4"\n', encoding="utf-8")
|
||||
git("add", "pyproject.toml")
|
||||
git("commit", "-qm", "release")
|
||||
git("tag", "v2026.9.21")
|
||||
(repo / "pyproject.toml").write_text('[project]\nname = "hermes-agent"\nversion = "0.0.0"\n', encoding="utf-8")
|
||||
git("commit", "-qam", "next")
|
||||
|
||||
monkeypatch.setattr("hermes_cli.version_info._resolve_stamp_file", lambda: None)
|
||||
monkeypatch.setattr("hermes_cli.version_info._resolve_repo_dir", lambda: repo)
|
||||
|
||||
info = get_version_info()
|
||||
|
||||
assert info.base_version == "0.21.4"
|
||||
assert info.distance == 1
|
||||
assert info.derived_version == f"0.21.4+1.g{git('rev-parse', '--short=7', 'HEAD')}"
|
||||
|
||||
|
||||
def test_resolve_stamp_file_honors_install_root(tmp_path, monkeypatch):
|
||||
"""Sealed installs (the Nix wrapper) point HERMES_INSTALL_ROOT at the stamp dir."""
|
||||
stamp = {"commit": "e" * 40, "source": "nix", "distribution": "nix", "updateMechanism": "external"}
|
||||
|
||||
Reference in New Issue
Block a user