mirror of
https://github.com/razzant/ouroboros.git
synced 2026-10-03 04:07:04 +00:00
178 lines
6.1 KiB
Python
178 lines
6.1 KiB
Python
"""Official managed-update source selection and bounded git network calls."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import os
|
|
import pathlib
|
|
import re
|
|
import subprocess
|
|
from typing import Callable, List, Optional, Tuple
|
|
|
|
|
|
FETCH_TIMEOUT_RC = 124
|
|
MANAGED_TAG_NAMESPACE = "refs/ouroboros-managed/tags"
|
|
_RELEASE_TAG_RE = re.compile(r"^v(\d+)\.(\d+)\.(\d+)$")
|
|
|
|
|
|
def official_ref_has_constitution(
|
|
ref: str, *, repo_dir: os.PathLike[str] | str | None = None
|
|
) -> bool:
|
|
"""Whether *ref* contains the non-empty regular ``BIBLE.md`` required by P4."""
|
|
if repo_dir is None:
|
|
from supervisor import git_ops as _g
|
|
|
|
repo_dir = _g.REPO_DIR
|
|
try:
|
|
result = subprocess.run(
|
|
["git", "ls-tree", "-l", ref, "--", "BIBLE.md"],
|
|
cwd=str(repo_dir),
|
|
capture_output=True,
|
|
text=True,
|
|
check=False,
|
|
)
|
|
except OSError:
|
|
return False
|
|
fields = (result.stdout or "").strip().split(maxsplit=4)
|
|
return bool(
|
|
result.returncode == 0
|
|
and len(fields) == 5
|
|
and fields[0] in {"100644", "100755"}
|
|
and fields[1] == "blob"
|
|
and fields[3].isdigit()
|
|
and int(fields[3]) > 0
|
|
and fields[4] == "BIBLE.md"
|
|
)
|
|
|
|
|
|
def _git_network_bounded(
|
|
args: List[str],
|
|
*,
|
|
timeout: Optional[float] = None,
|
|
cwd: os.PathLike[str] | str | None = None,
|
|
) -> Tuple[int, str, str]:
|
|
"""Run one non-interactive git network command under the shared ceiling.
|
|
|
|
``cwd`` selects the repository the command runs in; the default remains the
|
|
managed system repository. An explicit ``cwd`` must be an existing
|
|
directory — network commands against a caller-selected repository must
|
|
never silently fall back to the system repository.
|
|
"""
|
|
from ouroboros.update_channels import get_managed_update_fetch_timeout_sec
|
|
from supervisor import git_ops as _g
|
|
|
|
if cwd is None:
|
|
repo_dir = _g.REPO_DIR
|
|
else:
|
|
repo_dir = pathlib.Path(cwd)
|
|
if not repo_dir.is_dir():
|
|
return 127, "", f"git cwd is not a directory: {repo_dir}"
|
|
limit = float(timeout or get_managed_update_fetch_timeout_sec())
|
|
env = {**os.environ, "LC_ALL": "C", "LANG": "C", "GIT_TERMINAL_PROMPT": "0"}
|
|
rc, out, err = _g._run_git_process_bounded(
|
|
["git", "-c", "http.lowSpeedLimit=1024", "-c", "http.lowSpeedTime=30", *args],
|
|
timeout=limit,
|
|
cwd=repo_dir,
|
|
env=env,
|
|
text=True,
|
|
)
|
|
if rc == FETCH_TIMEOUT_RC:
|
|
return rc, "", f"git {' '.join(args[:2])} exceeded {limit:.0f}s and was terminated"
|
|
return rc, str(out or "").strip(), str(err or "").strip()
|
|
|
|
|
|
def git_fetch_bounded(remote_name: str, *, timeout: Optional[float] = None) -> Tuple[int, str, str]:
|
|
"""Fetch the official remote under one cross-platform wall-clock ceiling."""
|
|
return _git_network_bounded(
|
|
[
|
|
"fetch",
|
|
"--quiet",
|
|
remote_name,
|
|
f"+refs/heads/*:refs/remotes/{remote_name}/*",
|
|
f"+refs/tags/*:{MANAGED_TAG_NAMESPACE}/*",
|
|
],
|
|
timeout=timeout,
|
|
)
|
|
|
|
|
|
def _managed_update_target() -> Tuple[str, str, str]:
|
|
"""Return the runtime-selected official ref, independent from launcher provenance."""
|
|
from supervisor import git_ops as _g
|
|
|
|
managed_meta = _g._read_managed_repo_meta()
|
|
if not managed_meta:
|
|
return "", "", ""
|
|
from ouroboros.update_channels import get_update_branch
|
|
|
|
remote_name = _g._managed_remote_name(managed_meta)
|
|
remote_branch = get_update_branch()
|
|
target_ref = f"{remote_name}/{remote_branch}" if remote_name and remote_branch else ""
|
|
return remote_name, remote_branch, target_ref
|
|
|
|
|
|
def resolve_managed_update_target(
|
|
remote_name: str,
|
|
remote_branch: str,
|
|
branch_ref: str,
|
|
*,
|
|
update_channel: str,
|
|
capture: Callable[[List[str]], Tuple[int, str, str]],
|
|
) -> Tuple[str, str, str]:
|
|
"""Resolve the selected feed to one immutable commit.
|
|
|
|
QA and Development follow their branch tips. Stable follows the newest plain
|
|
``vX.Y.Z`` release whose commit is reachable from both ``main`` and
|
|
``ouroboros-stable``. This keeps an untagged main commit out of the stable
|
|
feed without creating a second channel-specific updater.
|
|
"""
|
|
if not remote_name or not remote_branch or not branch_ref:
|
|
return "", "", "managed update target is unavailable"
|
|
if str(update_channel or "") != "stable":
|
|
rc, sha, error = capture(
|
|
["git", "rev-parse", "--verify", f"{branch_ref}^{{commit}}"]
|
|
)
|
|
return (branch_ref, sha, "") if rc == 0 and sha else ("", "", error or branch_ref)
|
|
|
|
main_ref = f"{remote_name}/main"
|
|
qa_ref = f"{remote_name}/ouroboros-stable"
|
|
for required_ref in (main_ref, qa_ref):
|
|
rc, _sha, error = capture(
|
|
["git", "rev-parse", "--verify", f"{required_ref}^{{commit}}"]
|
|
)
|
|
if rc != 0:
|
|
return "", "", error or f"stable release branch is unavailable: {required_ref}"
|
|
|
|
rc, raw_tags, error = capture(
|
|
[
|
|
"git",
|
|
"for-each-ref",
|
|
"--format=%(refname:strip=3)",
|
|
f"{MANAGED_TAG_NAMESPACE}/v*",
|
|
]
|
|
)
|
|
if rc != 0:
|
|
return "", "", error or "could not list release tags"
|
|
candidates = []
|
|
for tag in raw_tags.splitlines():
|
|
match = _RELEASE_TAG_RE.fullmatch(tag.strip())
|
|
if match:
|
|
candidates.append((tuple(int(part) for part in match.groups()), tag.strip()))
|
|
candidates.sort(reverse=True)
|
|
|
|
for _version, tag in candidates:
|
|
tag_ref = f"{MANAGED_TAG_NAMESPACE}/{tag}"
|
|
rc, sha, _error = capture(
|
|
["git", "rev-parse", "--verify", f"{tag_ref}^{{commit}}"]
|
|
)
|
|
if rc != 0 or not sha:
|
|
continue
|
|
present_in_both = True
|
|
for release_branch_ref in (main_ref, qa_ref):
|
|
ancestor_rc, _out, _ancestor_error = capture(
|
|
["git", "merge-base", "--is-ancestor", sha, release_branch_ref]
|
|
)
|
|
if ancestor_rc != 0:
|
|
present_in_both = False
|
|
break
|
|
if present_in_both:
|
|
return tag_ref, sha, ""
|
|
return "", "", "no shared stable vX.Y.Z release exists on main and ouroboros-stable"
|