mirror of
https://github.com/kuhyx/utils.git
synced 2026-11-13 00:10:59 +01:00
dep_freshness: a commit pin is current at any branch head, not only the default one
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
930888bc15
commit
94f6873148
@ -25,19 +25,24 @@ from dep_freshness.versions import behind
|
||||
_SHORT_SHA = 10
|
||||
|
||||
|
||||
def _commit(dep: Dep, head: str) -> Finding | None:
|
||||
"""A hash pin is current when the default branch's HEAD still starts with it."""
|
||||
short = head[:_SHORT_SHA]
|
||||
def _commit(dep: Dep, heads: str) -> Finding | None:
|
||||
"""A hash pin is current while SOME branch head still starts with it.
|
||||
|
||||
`heads` is the registry's space-joined list, default branch first; the
|
||||
default head is what a stale finding proposes.
|
||||
"""
|
||||
short = heads.split()[0][:_SHORT_SHA]
|
||||
if not dep.pinned:
|
||||
return Finding(dep, Severity.UNPINNED, short, detail=f"pin a commit ({short})")
|
||||
if head.lower().startswith(dep.pinned.lower()):
|
||||
pin = dep.pinned.lower()
|
||||
if any(head.lower().startswith(pin) for head in heads.split()):
|
||||
return None
|
||||
return Finding(
|
||||
dep,
|
||||
Severity.STALE,
|
||||
short,
|
||||
detail="the source repository's default branch has moved on; a commit "
|
||||
"pin has no release to wait for",
|
||||
detail="no branch of the source repository points at this commit any "
|
||||
"more; a commit pin has no release to wait for",
|
||||
)
|
||||
|
||||
|
||||
|
||||
@ -6,9 +6,10 @@ version, so the gate's question becomes the only one a commit pin admits:
|
||||
is the pinned commit still what the source repository's default branch
|
||||
points at? (kuhy, 2026-09-12: chosen over never-expiring allowlist entries.)
|
||||
|
||||
The answer is the full HEAD sha; `evaluate` compares by prefix so a 7-, 10-
|
||||
or 40-character pin all work, and reports the first ten characters, which
|
||||
is what JitPack accepts as a version.
|
||||
The answer is every branch head's full sha, default branch first; `evaluate`
|
||||
compares by prefix so a 7-, 10- or 40-character pin all work, and reports
|
||||
the default head's first ten characters, which is what JitPack accepts as
|
||||
a version.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
@ -36,12 +37,27 @@ def source_repo(name: str) -> str | None:
|
||||
|
||||
|
||||
def latest(name: str) -> str | None:
|
||||
"""The default branch's HEAD sha, or None when `name` is not a JitPack pin."""
|
||||
"""Every branch head of the source repo, default branch first, space-joined.
|
||||
|
||||
A pin is current when it is SOME branch's head, not only the default one:
|
||||
Mihon pins `deadlock-fix` of sqldelight-androidx-driver and Tachiyomi
|
||||
pins `fix-build2` of FlexibleAdapter, both side branches whose heads are
|
||||
exactly the pinned commits. Comparing those against `master` alone
|
||||
called two current pins stale. None when `name` is not a JitPack pin.
|
||||
"""
|
||||
repo = source_repo(name)
|
||||
if repo is None:
|
||||
return None
|
||||
for line in ls_remote(GITHUB_REMOTE.format(repo=repo), "HEAD"):
|
||||
default: str | None = None
|
||||
heads: list[str] = []
|
||||
# No `--heads`: that filter drops the symbolic HEAD line, which is the
|
||||
# only way to learn WHICH head is the default branch's.
|
||||
for line in ls_remote(GITHUB_REMOTE.format(repo=repo)):
|
||||
sha, _, ref = line.partition("\t")
|
||||
if ref.strip() == "HEAD" and sha:
|
||||
return sha.strip()
|
||||
return None
|
||||
sha, ref = sha.strip(), ref.strip()
|
||||
if ref == "HEAD":
|
||||
default = sha
|
||||
elif ref.startswith("refs/heads/"):
|
||||
heads.append(sha)
|
||||
ordered = ([default] if default else []) + [h for h in heads if h != default]
|
||||
return " ".join(ordered) or None
|
||||
|
||||
@ -22,6 +22,7 @@ from dep_freshness.resolve import _LOOKUP, Answer
|
||||
from dep_freshness.tests.conftest import write
|
||||
|
||||
HEAD = "844a07002c3d2f2b68a6b26a25e8e401777c73e7"
|
||||
SIDE = "c80135339b6f4c2d8e1a7b3f9d0e5c6a2b4d8f10"
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
@ -60,19 +61,25 @@ def test_the_github_repo_behind_a_jitpack_coordinate(name, expected):
|
||||
assert gitcommit.source_repo(name) == expected
|
||||
|
||||
|
||||
def test_latest_is_the_default_branch_head(ls_remote):
|
||||
calls = ls_remote(f"{HEAD}\tHEAD\n{HEAD}\trefs/heads/master\n")
|
||||
assert gitcommit.latest("com.github.arkon.FlexibleAdapter:flexible-adapter") == HEAD
|
||||
assert calls[-1] == [
|
||||
"git",
|
||||
"ls-remote",
|
||||
"https://github.com/arkon/FlexibleAdapter",
|
||||
"HEAD",
|
||||
]
|
||||
def test_latest_lists_every_branch_head_default_first(ls_remote):
|
||||
calls = ls_remote(
|
||||
f"{SIDE}\trefs/heads/fix-build2\n{HEAD}\tHEAD\n{HEAD}\trefs/heads/master\n"
|
||||
f"{SIDE}\trefs/pull/1/head\n"
|
||||
)
|
||||
assert (
|
||||
gitcommit.latest("com.github.arkon.FlexibleAdapter:flexible-adapter")
|
||||
== f"{HEAD} {SIDE}"
|
||||
)
|
||||
assert calls[-1] == ["git", "ls-remote", "https://github.com/arkon/FlexibleAdapter"]
|
||||
|
||||
|
||||
def test_a_repo_with_no_default_head_still_lists_its_branches(ls_remote):
|
||||
ls_remote(f"{SIDE}\trefs/heads/dev\n")
|
||||
assert gitcommit.latest("com.github.mihonapp:image-decoder") == SIDE
|
||||
|
||||
|
||||
def test_no_head_line_means_no_answer(ls_remote):
|
||||
ls_remote("\n \nabc\trefs/heads/dev\n")
|
||||
ls_remote("\n \nabc\trefs/tags/v1\n")
|
||||
assert gitcommit.latest("com.github.mihonapp:image-decoder") is None
|
||||
|
||||
|
||||
@ -144,17 +151,19 @@ def dep(pinned: str | None = "844a07002c") -> Dep:
|
||||
)
|
||||
|
||||
|
||||
def test_a_pin_at_head_passes_whatever_its_length_or_case():
|
||||
def test_a_pin_at_any_branch_head_passes_whatever_its_length_or_case():
|
||||
assert judge(dep("844a07002c"), Answer(HEAD)) is None
|
||||
assert judge(dep("844A070"), Answer(HEAD)) is None
|
||||
assert judge(dep(HEAD), Answer(HEAD)) is None
|
||||
# fix-build2's head, not master's: still current.
|
||||
assert judge(dep("c8013533"), Answer(f"{HEAD} {SIDE}")) is None
|
||||
|
||||
|
||||
def test_a_pin_behind_head_is_stale_and_reports_the_short_sha():
|
||||
finding = judge(dep("c8013533"), Answer(HEAD))
|
||||
def test_a_pin_on_no_branch_head_is_stale_and_proposes_the_default_head():
|
||||
finding = judge(dep("0123abc"), Answer(f"{HEAD} {SIDE}"))
|
||||
assert finding.severity is Severity.STALE
|
||||
assert finding.latest == "844a07002c"
|
||||
assert "default branch has moved" in finding.detail
|
||||
assert "no branch" in finding.detail
|
||||
|
||||
|
||||
def test_a_missing_pin_is_reported_unpinned_with_head_to_use():
|
||||
|
||||
Loading…
Reference in New Issue
Block a user