fix(file-length): grandfather от merge-base, а не от HEAD #15
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "fix/grandfather-from-ref"
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?
Проблема
check-file-lengthв CI-инвокации отдавалPassed, ничего не проверив.Grandfather-baseline считался как
git show HEAD:${file}. В настоящем gitpre-commit-хуке это верно: новый коммит ещё не создан,
HEAD:file— версия доправки. Но в CI (
pre-commit run --from-ref X --to-ref Yпосле чекаута накоммит MR)
HEAD— это сам проверяемый коммит,oldвсегда равенlines,условие
lines <= oldвыполняется всегда, и grandfather срабатывает на чёмугодно.
Тихо не ловились ровно те две ситуации, ради которых хук ставили:
Обе воспроизведены на реальном пайплайне в
volody/dispatcher#186:
новый файл на 528 строк (
src fail= 500) иmain.py4864 → 4896 — обе джобызелёные.
Это не ложное срабатывание, а обратное: гейт молча не делает то, ради чего
стоит, и при этом создаёт ощущение защиты.
Решение
База берётся оттуда же, откуда pre-commit берёт список файлов:
HEAD--from-ref X --to-ref YXиY--all-filesHEAD(режим аудита, гейта нет)PRE_COMMIT_FROM_REF/PRE_COMMIT_TO_REFpre-commit выставляет ровно вCI-режиме — проверено эмпирически на pre-commit 4.6.0, в
--all-filesи вобычном коммите их нет.
Merge-base, а не вершина from-ref. Иначе split, приехавший в целевую ветку
после ветвления, засчитается текущему MR как рост: файл в целевой сжали до 520,
у нас он остался 600 — сравнение с вершиной даёт «600 > 520, рост», хотя ветка
файл не трогала.
Не резолвится merge-base — откат на сам from-ref; не резолвится и он —
git showпромахивается,old= 0, большой файл падает. Ошибка в сторону FAIL, а нев сторону зелёного.
Тест
fixtures/check_grandfather_baseline.sh— отдельный гейт, а не кейс вrun_smoke.sh, и это принципиально:run_smoke.shдёргает хук напрямую спискомфайлов, где
HEADи так «старая» версия, — в такой инвокации дыры не видновообще. Гейт поднимает настоящий репозиторий и зовёт
pre-commit run --from-ref/--to-ref, то есть ровно то, что делает CI консьюмеров.Восемь кейсов: рост god-файла, новый файл сверх порога, правка без роста, сжатие,
нетронутый маленький файл, локальный commit-хук, а также два на выбор именно
merge-base — целевая ветка сжала файл, а наша ветка его либо не растит (зелёный),
либо растит (красный).
Проверено:
действительно закреплён тестом, а не задекларирован;
run_smoke.sh— 94 OK, регрессий нет;check_wheel_matches_sources.py— зелёный.Что дальше, вне этого PR
Консьюмерам нужен bump
rev:—jamzap/backend,jamzap/frontend,jamzap/mobileиvolody/dispatcherвсе зовут хук через--from-ref/--to-refв CI, то есть защита от роста у всех четырёх сейчасфиктивна. В
.gitlab-ci.ymlфронта и мобилки лежит комментарий, прямоутверждающий обратное («grandfather для check-file-length берётся от
merge-base») — его тоже надо править, иначе заблуждение поедет дальше.
Тот же класс бага в неслитом
#7
(
check_max_filename_words.py,git cat-file -e HEAD:path) — в CI новый файлс длинным именем всегда «уже был в HEAD». Отмечу там, чтобы не уехало в релиз.
Версия 0.13.0. Номер пересекается с открытым
#13 — этот PR
срочнее и идёт первым, #13 после мержа перенумерую в 0.14.0.
Ревью — результат
Статус: Пройдено.
Вердикт субагента: APPROVE_WITH_NITS
Итерация: 1
Commit на момент ревью:
9a76735Ревьюер не ограничился чтением: прогнал гейт против старого кода (валится ровно на двух кейсах из issue), проверил каждый из восьми кейсов восемью мутациями самого хука и построил матрицу «какая мутация валит какой кейс» — greenwash-кейсов нет, каждый убивается хотя бы одной мутацией, а кейс с merge-base ловится только мутацией «merge-base → сам from-ref». Отдельно перепроверил обе моих ссылки на поведение pre-commit по его исходникам (
commands/run.py,git.py) и прошёлся по восьми крайним случаям — не-git,gitвне PATH, кириллица с пробелом в пути, shallow-клон,FROM_REFбезTO_REF, несуществующий ref, файл без хвостового перевода строки, переименование. Режима, где фикс молча пропускает нарушение, не нашёл: все ветки деградации ведут вold=0→ FAIL.Что поправлено по нитам (commit
b5c128a)rev: v0.12.0— ту самую версию, где гейт в CI не работает, при том что абзацем выше написано «сv0.13.0». Бампнут.--depth 1→ merge-base не резолвится → откат на вершинуfrom_ref→ невинная правка получает FAIL за чужой split. Добавлен абзац; заодно проверено, что все четыре консьюмера ставятGIT_DEPTH: "0", то есть их это не заденет.old= 0 по новому пути). Проверил сам: старый хук наgit mvбез правки содержимого давалPassed, новый даётFAIL. Это восстановление паритета с commit-хуком, где rename падал всегда, но для консьюмеров при бампеrev:— новый способ покраснеть, поэтому вынесено в README отдельным пунктом «что ужесточилось».Ниты Н3 (
pre-commit==4.6.1в CI против 4.6.0 локально — CI на нём зелёный) и Н6 (set -uo pipefailбез-e— осознанно, скрипт ловит коды возврата) оставлены как есть.Не чинится этим PR и починено быть не может
--all-filesостаётся режимом без гейта:PRE_COMMIT_ALL_FILESв pre-commit не существует, отличить аудит от commit-хука по окружению нечем. Новый файл на 700 строк там по-прежнему даётPassed. Это задокументировано в README и важно для хендовера: бампаrev:консьюмеру мало, его джоба обязана звать хук через--from-ref/--to-ref. У всех четырёх наших это так — проверено.CI зелёный на
b5c128a(run 91). Мержу.