"""pre-commit и uv.lock держат ОДНУ версию ruff (#2864). Хук `ruff-pre-commit` пинился на v0.7.4, а CI и `uv run ruff format` брали ruff из lock-файлов (0.15.12 в backend/, 0.15.20 в tradein-mvp/). Старый и новый ruff по-разному форматируют длинные `assert ..., "сообщение"` — хук на каждом коммите переписывал нетронутые строки, следующий `uv run ruff format` возвращал как было. `ruff format --check` намеренно не в CI-гейте, поэтому дрейф нигде не краснел. Гейт читает три источника истины и требует равенства. Красный на origin/main по значению: rev v0.7.4 против 0.15.12 и 0.15.20. Бампить втроём: rev в .pre-commit-config.yaml + `ruff==X` в обоих pyproject.toml + `uv lock`. """ from __future__ import annotations import re from pathlib import Path КОРЕНЬ = Path(__file__).resolve().parents[2] _REV = re.compile( r"repo:\s*https://github\.com/astral-sh/ruff-pre-commit\s*\n\s*rev:\s*v?([\w.]+)", ) _LOCK_RUFF = re.compile(r'^name = "ruff"\nversion = "([\w.]+)"', re.MULTILINE) _PYPROJECT_RUFF = re.compile(r'"ruff([<>=!~]+[^"]*)"') def _precommit_rev() -> str: текст = (КОРЕНЬ / ".pre-commit-config.yaml").read_text(encoding="utf-8") m = _REV.search(текст) assert m, "в .pre-commit-config.yaml нет блока ruff-pre-commit с rev" return m.group(1) def _lock_version(lock: str) -> str: m = _LOCK_RUFF.search((КОРЕНЬ / lock).read_text(encoding="utf-8")) assert m, f"в {lock} нет пакета ruff" return m.group(1) def test_precommit_rev_matches_both_locks() -> None: """Головной: хук и оба lock'а — одна версия ruff.""" rev = _precommit_rev() locks = { "backend/uv.lock": _lock_version("backend/uv.lock"), "tradein-mvp/uv.lock": _lock_version("tradein-mvp/uv.lock"), } расхождения = {k: v for k, v in locks.items() if v != rev} assert not расхождения, ( f"pre-commit ruff rev=v{rev}, а в lock'ах {расхождения} — хук и " "`uv run ruff format` будут форматировать по-разному (#2864)" ) def test_pyproject_pins_exact_ruff() -> None: """Контроль от повторного дрейфа: `ruff==X` в обоих pyproject, X = rev. С `>=` любой `uv lock --upgrade` молча уводит lock от хука — ровно так и разъехались 0.7.4 / 0.15.12 / 0.15.20. """ rev = _precommit_rev() for f in ("backend/pyproject.toml", "tradein-mvp/backend/pyproject.toml"): m = _PYPROJECT_RUFF.search((КОРЕНЬ / f).read_text(encoding="utf-8")) assert m, f"в {f} нет зависимости ruff" assert m.group(1) == f"=={rev}", f"{f}: ruff{m.group(1)}, ожидался ruff=={rev}" def test_gate_reads_real_values() -> None: """Контроль самого гейта: парсеры возвращают версии, а не падают на пустом.""" assert re.fullmatch(r"\d+\.\d+\.\d+", _precommit_rev()) assert re.fullmatch(r"\d+\.\d+\.\d+", _lock_version("backend/uv.lock"))