ci: гейт «версия в pyproject.toml против тега релиза» #29
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "ci/version-tag-guard"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Закрывает #28.
Зачем
Версия живёт в
pyproject.toml, тег ставится руками, и разойтись они могут молча — что и произошло: девять версий (v0.10.0–v0.18.0) прожили невыпущенными, аrev:из README у консьюмера не резолвился вовсе. Дыру заметили только когда стали разбирать трекер. Проверять это должен CI, а не внимательность.Что проверяется
mainВторое — не теоретическое: за сегодня три PR подряд претендовали на соседние номера, и очередь версий приходилось держать в голове и пересчитывать при каждом мерже. Теперь это ловит CI.
fetch-depth: 0в checkout: при shallow-клоне тегов нет вовсе, и гейт падает с объяснением, а не отдаёт зелёный вхолостую.Тесты гейта — отдельным скриптом
fixtures/check_version_tag_cases.shподнимает временные репозитории с заданным набором тегов. На живом репо вердикт гейта меняется от коммита к коммиту (сегодня зелёный, завтра красный после бампа), поэтому кейсы вrun_smoke.shбыли бы нестабильны по построению.Восемь кейсов: тег есть / тега нет на
main; новая, занятая и отставшая версия на ветке; версионная сортировка (0.10.0больше0.9.0, а не наоборот — лексикографически было бы иначе); отсутствие тегов вовсе; отсутствие строкиversion.Мутации
Каждая роняет самопроверку: не требовать тега на
main, не сравнивать версию с последним тегом, лексикографическое сравнение вместо версионного.Отдельно стоит отметить одну: «не проверять занятость тега на ветке» вердикт не меняет — обе ветки логики дают FAIL на одном и том же входе. Но меняет диагноз, а от него зависит, что человек пойдёт чинить. Поэтому два кейса пиннят текст: «уже выпущена» против «не больше выпущенной».
По дороге в самом гейте нашлась подстановка команды: сообщение об ошибке содержало
`rev: ${tag}`в двойных кавычках, то есть bash пытался бы выполнитьrev: v0.21.0.Версия
0.21.0→0.22.0. Порядок выпуска описан в README рядом с политикой версионирования.e1a1252191089b9e24ffРевью (субагент, свежий контекст): блокер подтверждён и исправлен.
B1 — гейт красил бы собственный релизный тег. Воспроизвёл:
on: pushсрабатывает и на пуш тега,ref_nameтам — имя тега, гейт уходил в ветку «это фича-ветка», видел существующий тег и печатал «версия уже выпущена, поднимите номер». То есть корректный выпуск диагностировался как ошибка, и красным помечался ровно тот коммит, на который консьюмер ставитrev:. Ирония в том, что гейт против молчаливого расхождения сам был бы источником постоянного красного.Теперь пуш тега — отдельная ветка проверки: версия обязана совпадать с тегом. Кейс
release_tag_push_passes; мутация «убрать эту ветку» роняет самопроверку.B1-следствие (окно красного
main). Оно остаётся и остаётся намеренно — это буквальное требование приёмки #28:mainс версией без тега = релиз забыт. Но выхода из красного теперь нет только у прогона, отработавшего до тега; после пуша тега его можно перезапустить, и он зелёный. Порядок выпуска и это следствие дописаны в README.Ниты — взяты почти все:
git rev-parse --verify refs/tagsвсегда возвращал 1 (refs/tagsэто каталог, не ref), так что guard сводился к[[ -z "$(git tag)" ]]. Заменён на явную проверку «мы в git» плюсgit tag --list 'v*'; guard и выбор последнего тега теперь смотрят на одно множество — репозиторий с тегами не по маске больше не проходит guard с пустымlatest.mainони краснели по другой причине, то есть кейс пиннил не то, что заявлял), лексикографическийlatestловится кейсом сv0.9.0+v0.10.0.unset GIT_DIR GIT_WORK_TREE GIT_INDEX_FILE(под git-хуком они коммитили и тегали в посторонний репозиторий, печатая «пройден» вхолостую — спасибо за воспроизведение),init.templateDir=иcore.hooksPath=/dev/null,commit --no-verify,cd … || exit 1.run_smoke.sh, которого нет.mainпроверяется ещё и что тег стоит на коммите с той же версией. Тег на постороннем коммите — этоrev:, резолвящийся в чужой код, то есть исходная проблема закрывалась лишь наполовину. Кейсtag_points_to_other_version_fails.sort -Vставитrc1старше релиза), ветки поддержки не поддержаны, любой PR обязан бампать версию, и два PR на один номер остаются зелёными, пока первый не смержен и не протегирован.N12 (разбор
versionпервой подходящей строкой) оставляю как есть: сейчас вpyproject.tomlодна такая строка, а надёжный разбор TOML в bash-гейте — несоразмерная цена. Если появится вторая таблица сversion, кейс «версия не та» поймает это сразу, потому что тег не сойдётся.