See git-tag pins in Python requirements; cut crdt-sync 0.9.1, gatelock 0.7.2
Some checks are pending
crdt-sync pre-commit / pre-commit (push) Waiting to run
crdt-sync tests / test (3.10) (push) Waiting to run
crdt-sync tests / test (3.11) (push) Waiting to run
crdt-sync tests / test (3.12) (push) Waiting to run
dependency freshness / dependencies on newest stable (push) Waiting to run
file length / file-length (push) Waiting to run
gatelock pre-commit / pre-commit (push) Waiting to run
gatelock tests / test (3.10) (push) Waiting to run
gatelock tests / test (3.11) (push) Waiting to run
gatelock tests / test (3.12) (push) Waiting to run
md-naming / markdown naming convention (push) Waiting to run
palette drift / palette (push) Waiting to run
shared gate tests / gate unit tests (push) Waiting to run

**The gate could not see the shared Python libraries at all.** They are
consumed as PEP 508 direct references --
`crdt-sync @ git+https://github.com/kuhyx/utils@crdt-sync-v0.5.1#subdirectory=crdt-sync`
-- which parsed as an unpinned PyPI package that does not exist on PyPI. That
answer is undeterminable, not stale, so wake-alarm sat on gatelock-v0.4.1
against v0.7.1 and crdt-sync-v0.5.1 against v0.9.0 with a green gate. They are
git-tag dependencies now, judged the same way the Dart libraries are.

A ref that is a branch or a SHA still parses as before: there is no version
in it to compare.

The line is found textually when the key indexer cannot help. A TOML
dependency array holds bare strings with no `key =`, so these findings would
otherwise report line 0 and make the fix a search.

Both libraries also had their dependency upgrades land after their newest tag
(81d2493), so anything moving to "latest tag" would have taken the pre-upgrade
constraints. Cut 0.9.1 and 0.7.2; version bump only.

315 tests, 100% branch coverage.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CRykabMwPgDzFHcARoqpaK
This commit is contained in:
Krzysztof Rudnicki 2026-08-28 14:52:19 +02:00
parent 2e1f3da6cf
commit 7a682a46f3
4 changed files with 123 additions and 4 deletions

View File

@ -1,6 +1,6 @@
[project]
name = "crdt-sync"
version = "0.9.0"
version = "0.9.1"
description = "Shared CRDT merge scheme + GitHub-Contents-API sync transport for cross-device apps"
requires-python = ">=3.10"
dependencies = ["requests==2.34.2"]

View File

@ -12,7 +12,7 @@ from pathlib import Path
import re
import tomllib
from dep_freshness._tables import PYPI, TOOLCHAIN
from dep_freshness._tables import GITTAG, PYPI, TOOLCHAIN
from dep_freshness.models import Dep
from dep_freshness.parsers._lines import index
from dep_freshness.versions import exact_pin
@ -24,6 +24,49 @@ _REQ = re.compile(
)
_SKIP_PREFIXES = ("-", "#", "git+", "http://", "https://", ".", "/")
# PEP 508 direct reference: `crdt-sync @ git+https://.../utils@crdt-sync-v0.9.0`
# The shared Python libraries are consumed this way, so "latest" for them is a
# git tag, exactly as for the Dart libraries. Without this the requirement
# parses as an unpinned PyPI package that does not exist on PyPI, the answer
# comes back undeterminable, and four minor versions of drift stay invisible --
# which is how wake-alarm sat on gatelock-v0.4.1 against v0.7.1 and
# crdt-sync-v0.5.1 against v0.9.0 with a green gate.
_DIRECT = re.compile(
r"^\s*(?P<name>[A-Za-z0-9][A-Za-z0-9._-]*)"
r"(?:\[[^\]]*\])?"
r"\s*@\s*git\+\S+?@(?P<ref>[A-Za-z0-9_.-]+-v[0-9][^\s#]*)"
)
def _direct_reference(requirement: str, path: Path, line: int) -> Dep | None:
"""A `<name> @ git+<url>@<pkg>-vX.Y.Z` requirement as a git-tag dep."""
match = _DIRECT.match(requirement.split(";", 1)[0].strip())
if not match:
return None
ref = match.group("ref")
package, _, version = ref.rpartition("-v")
return Dep(
ecosystem=GITTAG, name=package or match.group("name"), constraint=ref,
path=path, line=line or _ref_line(path, ref), pinned=version or None,
)
def _ref_line(path: Path, ref: str) -> int:
"""Locate `ref` textually. A TOML dependency list has no `key =` to index.
`"gatelock @ git+...@gatelock-v0.7.1"` is a bare string inside an array,
so the shared key indexer finds nothing and the finding would report
line 0 -- a fix the reader has to go looking for.
"""
try:
text = path.read_text(encoding="utf-8", errors="replace")
except OSError:
return 0
for number, line in enumerate(text.splitlines(), start=1):
if ref in line:
return number
return 0
def _split(requirement: str) -> tuple[str, str] | None:
"""`('ruff', '==0.16.5')` from a PEP 508 requirement string."""
@ -51,6 +94,10 @@ def parse_requirements(path: Path) -> list[Dep]:
return []
deps: list[Dep] = []
for number, raw in enumerate(text.splitlines(), start=1):
direct = _direct_reference(raw, path, number)
if direct is not None:
deps.append(direct)
continue
split = _split(raw)
if split:
deps.append(_dep(split[0], split[1], path, number))
@ -81,7 +128,14 @@ def parse_pyproject(path: Path) -> list[Dep]:
for group in groups:
for requirement in group:
split = _split(str(requirement))
text = str(requirement)
name = _REQ.match(text.strip())
line = lines.get(name.group("name"), 0) if name else 0
direct = _direct_reference(text, path, line)
if direct is not None:
deps.append(direct)
continue
split = _split(text)
if split:
deps.append(_dep(split[0], split[1], path, lines.get(split[0], 0)))
return deps

View File

@ -140,3 +140,68 @@ dependencies:
for d in pubspec.parse(write(tmp_path, "pubspec.yaml", body))
}
assert deps["dart"].line == 3
DIRECT_REQ = """\
ruff==0.16.5
crdt-sync @ git+https://github.com/kuhyx/utils@crdt-sync-v0.9.0#subdirectory=crdt-sync
gatelock @ git+https://github.com/kuhyx/utils@gatelock-v0.7.1#subdirectory=gatelock
"""
def test_a_git_direct_reference_is_a_git_tag_dependency(tmp_path):
"""The shared Python libs are consumed by git tag, like the Dart ones.
Parsed as an ordinary requirement this is an unpinned PyPI package that
does not exist on PyPI, so the answer is undeterminable and the drift is
invisible -- wake-alarm sat four minor versions behind on both with a
green gate.
"""
deps = {
d.name: d
for d in python.parse_requirements(
write(tmp_path, "requirements.txt", DIRECT_REQ)
)
}
assert deps["crdt-sync"].ecosystem == "gittag"
assert deps["crdt-sync"].pinned == "0.9.0"
assert deps["crdt-sync"].line == 2
assert deps["gatelock"].pinned == "0.7.1"
assert deps["ruff"].ecosystem == "pypi"
def test_a_pyproject_git_direct_reference_is_a_git_tag_dependency(tmp_path):
body = """\
[project]
name = "demo"
requires-python = ">=3.13"
dependencies = [
"gatelock @ git+https://github.com/kuhyx/utils@gatelock-v0.7.1#subdirectory=gatelock",
]
"""
deps = {
d.name: d
for d in python.parse_pyproject(write(tmp_path, "pyproject.toml", body))
}
assert deps["gatelock"].ecosystem == "gittag"
assert deps["gatelock"].pinned == "0.7.1"
assert deps["gatelock"].line == 5
def test_a_plain_git_url_without_a_version_tag_is_still_skipped(tmp_path):
"""A branch or SHA ref has no version to compare, so it stays unparsed."""
body = "thing @ git+https://github.com/kuhyx/utils@main#subdirectory=thing\n"
assert python.parse_requirements(
write(tmp_path, "requirements.txt", body)
)[0].ecosystem == "pypi"
def test_an_unreadable_manifest_gives_the_direct_reference_no_line(tmp_path):
"""`_ref_line` degrades to 0 rather than raising, like the key indexer."""
missing = tmp_path / "gone.txt"
assert python._ref_line(missing, "gatelock-v0.7.1") == 0
def test_a_direct_reference_the_file_does_not_contain_has_no_line(tmp_path):
path = write(tmp_path, "requirements.txt", "ruff==0.16.5\n")
assert python._ref_line(path, "gatelock-v0.7.1") == 0

View File

@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "gatelock"
version = "0.7.1"
version = "0.7.2"
description = "Shared per-output lock-window + HMAC log-integrity backend for blocking-overlay apps"
requires-python = ">=3.10"
dependencies = [] # pure stdlib — tkinter is bundled with Python