mirror of
https://github.com/Matysh/houseplan-card
synced 2026-10-02 12:49:56 +00:00
ci: публикация ревью-дока не имеет права трогать ничего, кроме документа
28.08 коммитbb2919fуехал в dev с тридцатью файлами вместо одного markdown: откатил отревьюженную реализацию #359, вернул старые чанки, оставил в dist/ двойной набор. dev держал откаченное дерево три часа. Сообщение коммита было невинным, и от рутины инцидент отличался только диффом. Механизм воспроизведён локально, а не предположен. `git checkout -- .` восстанавливает рабочее дерево ИЗ ИНДЕКСА, `git clean -fd` убирает неотслеживаемое — ни то, ни другое индекс не трогает. Ревьюер работает с Bash и, проверяя «умеет ли тест падать», вполне может сделать git add; всё оставшееся у него в индексе прежняя уборка сохраняла, и следующий git commit забирал это вместе с документом. Отсюда три рубежа, каждый закрывает свой отрезок пути. База: reset --hard на свежий origin/$target снимает и индекс, и дерево разом. Терять нечего — документ приезжает из RUNNER_TEMP, а не из рабочей копии. Индексируется ровно один путь, а не каталог. Индекс: перед коммитом дифф проверяется allowlist'ом docs/reviews/. Диапазон: перед КАЖДЫМ push проверяется origin/$target...HEAD — то есть то, что пуш добавит в ветку. Проверок две, потому что push делается из двух мест, и второй путь срабатывает ровно тогда, когда dev ушёл вперёд — в тех самых условиях, при которых случилсяbb2919f. Пустой дифф — тоже отказ: публиковать нечего означает, что документа нет, а прежняя редакция шага выходила тут с нулём и оставляла вердикт без артефакта (#171). Сравнение по префиксу каталога, а не подстрокой: docs/reviews-old и docs/reviewsx разрешёнными не считаются. Форс-пуш отсутствует и закреплён тестом. Четыре мутанта проверены руками, два добавлены в реестр. Пятый — «убрать одну из двух проверок диапазона» — сначала выжил: тест требовал наличия, а не количества. Тест усилен до подсчёта, мутант убит. Issue: #365 User-Visible: no
This commit is contained in:
@@ -642,14 +642,31 @@ jobs:
|
||||
marker=CODE-REVIEW
|
||||
if [ "$STAGE" = "spec" ]; then marker=SPEC-REVIEW; fi
|
||||
doc="docs/reviews/${marker}-${NUM}-r${CYCLE}.md"
|
||||
# Рабочая копия отбрасывается ДО того, как документ попадёт в дерево:
|
||||
# ревьюер правит код, проверяя «умеет ли тест падать», и его правки
|
||||
# публиковаться не должны.
|
||||
git checkout -- . 2>/dev/null || true
|
||||
# docs/reviews исключён из уборки: ревьюер мог написать документ по
|
||||
# старому пути, и клин не должен его съесть до `git add` — ровно так
|
||||
# оба пути остаются работоспособными.
|
||||
git clean -fd -e docs/reviews -e node_modules >/dev/null 2>&1 || true
|
||||
# Документ спасается ПЕРВЫМ делом. Ревьюер мог написать его по старому
|
||||
# пути прямо в рабочую копию, а дальше эта копия будет отброшена
|
||||
# целиком — и вместе с ней пропал бы артефакт (#220).
|
||||
if [ ! -f "$SOURCE" ] && [ -f "$doc" ]; then
|
||||
cp "$doc" "$SOURCE"
|
||||
echo "документ найден в рабочей копии и сохранён в $SOURCE"
|
||||
fi
|
||||
# Reset, а не checkout+clean, и вот почему (#365).
|
||||
#
|
||||
# 28.08 коммит bb2919f уехал в dev с тридцатью файлами вместо одного
|
||||
# markdown: откатил отревьюженную реализацию #359, вернул старые чанки
|
||||
# и держал dev откаченным три часа. Механизм воспроизведён:
|
||||
# `git checkout -- .` восстанавливает рабочее дерево ИЗ ИНДЕКСА, а
|
||||
# `git clean -fd` убирает неотслеживаемое — ни то, ни другое индекс не
|
||||
# трогает. Ревьюер работает с Bash и в ходе проверки «умеет ли тест
|
||||
# падать» вполне может сделать `git add`; всё, что осталось у него в
|
||||
# индексе, прежняя уборка сохраняла, и следующий же `git commit`
|
||||
# забирал это вместе с документом. Сообщение при этом невинное, и от
|
||||
# рутины инцидент отличается только диффом.
|
||||
#
|
||||
# `reset --hard` снимает и индекс, и дерево разом. Терять нечего:
|
||||
# документ приезжает извне репозитория, из RUNNER_TEMP.
|
||||
git fetch -q origin "$target"
|
||||
git reset -q --hard "origin/$target"
|
||||
git clean -fdq -e node_modules >/dev/null 2>&1 || true
|
||||
# Документ приезжает извне репозитория (#220). Три раунда подряд он
|
||||
# терялся, пока лежал некоммитнутым файлом в том же дереве, которое
|
||||
# ревьюер мутирует и затем восстанавливает: `git checkout -- .` плюс
|
||||
@@ -661,11 +678,12 @@ jobs:
|
||||
cp "$SOURCE" "$doc"
|
||||
echo "документ взят из $SOURCE ($(wc -c < "$doc") байт)"
|
||||
else
|
||||
# Совместимость: ревьюер мог написать по старому пути, если промпт
|
||||
# ещё не обновился в этой ветке.
|
||||
echo "::warning::$SOURCE не найден — ищу документ в рабочей копии"
|
||||
echo "::warning::$SOURCE не найден — документа для публикации нет"
|
||||
fi
|
||||
git add docs/reviews 2>/dev/null || true
|
||||
# Индексируется ровно один путь, а не каталог: `git add docs/reviews`
|
||||
# забрал бы всё, что там окажется, а после reset там не должно быть
|
||||
# ничего постороннего — но полагаться на «не должно» здесь нельзя.
|
||||
git add -- "$doc" 2>/dev/null || true
|
||||
if git diff --cached --quiet; then
|
||||
# Пустая рабочая копия — ещё не провал: ревьюер иногда коммитит
|
||||
# документ сам, своим app-токеном мимо этого шага (CODE-REVIEW-150-r1,
|
||||
@@ -683,6 +701,8 @@ jobs:
|
||||
echo "::error::вердикт есть, а документа нет: ни $SOURCE, ни $doc в рабочей копии, ни $doc в $target — ревью без артефакта (#171, #220)"
|
||||
exit 1
|
||||
fi
|
||||
# Первый рубеж: что вообще проиндексировано.
|
||||
git diff --cached --name-only | node scripts/review-doc-guard.mjs
|
||||
git -c user.name="claude[bot]" \
|
||||
-c user.email="209825114+claude[bot]@users.noreply.github.com" \
|
||||
commit -q -F - <<EOF
|
||||
@@ -691,6 +711,10 @@ jobs:
|
||||
Issue: #$NUM
|
||||
User-Visible: no
|
||||
EOF
|
||||
# Второй рубеж, и он главный: что пуш ДОБАВИТ в целевую ветку. Первый
|
||||
# судит намерение шага, этот — результат, а расходились они именно
|
||||
# тогда, когда база оказывалась не той.
|
||||
git diff --name-only "origin/$target...HEAD" | node scripts/review-doc-guard.mjs
|
||||
# Публикация в dev идёт из детачнутого состояния поверх ветки задачи
|
||||
# либо dev, поэтому push нужен с явным перебазированием при гонке:
|
||||
# dev мог уйти вперёд, пока шло ревью — оно длится до 45 минут.
|
||||
@@ -705,6 +729,9 @@ jobs:
|
||||
echo "::error::документ ревью не удалось опубликовать в $target: конфликт (#171)"
|
||||
exit 1
|
||||
fi
|
||||
# После ребейза набор путей другой — проверяется заново. Форс здесь
|
||||
# запрещён и не появляется: ветка двигается только вперёд.
|
||||
git diff --name-only "origin/$target...HEAD" | node scripts/review-doc-guard.mjs
|
||||
git push -q "https://x-access-token:$TOKEN@github.com/${{ github.repository }}" \
|
||||
"HEAD:$target"
|
||||
fi
|
||||
|
||||
Reference in New Issue
Block a user