Как git push --force-with-lease спасает CI от зацикливания на release-ветках

Работаем над genkit — платформой для AI-агентов от Google. В проекте есть автоматическая система выпуска релизов, которая живёт в releasekit-uv.yml и должна была работать как часы. Но в какой-то момент CI начал падать с ошибкой non-fast-forward при попытке создать PR для релиза.
Проблема: ветка, которая не отпускает
Корень зла оказался простым, но коварным. Функция prepare_release() каждый раз пересоздаёт release-ветку с нуля, используя git checkout -B. Это нормально, если ветка только локальная. Но когда она уже существует на удалённом репозитории (остаток от прошлого запуска CI), git push отказывается её обновлять — это же non-fast-forward изменение, потенциально опасное.
Ситуация усугублялась тем, что CI часто запускается повторно: разработчик запустил релиз, что-то пошло не так, и он попытался снова. На втором прогоне releasekit уже видит старую ветку на origin и падает.
Решение: force с умом
Мы добавили параметр force: bool = False в протокол VCS — это общий интерфейс, который поддерживают и Git, и Mercurial. В реализации для Git выбрали --force-with-lease вместо обычного --force.
Почему именно --force-with-lease? Потому что это безопаснее. Обычный --force перезапишет любую историю на удалённом сервере, даже если её там уже изменили руки коллеги. --force-with-lease проверит: “Удалённая ветка ещё в том состоянии, которое я последний раз видел?” Если нет — откажет. Это защита от случайного стирания чужой работы.
В prepare.py теперь вызываем:
vcs.push(force=True)
И выполненных тестов говорят, что всё работает: ruff check, py type check, pyrefly check — все зелёные.
Заодно навели чистоту
Улучшили обработку ошибок в cli.py — теперь _cmd_prepare ловит RuntimeError и логирует событие prepare_error вместо полного traceback’а. А в GitHub Actions улучшили читаемость: если что-то сломалось, выводим последние 50 строк логов вне группы ::group::, чтобы видно было сразу, без разворачивания.
Бонус: переписали скрипт setup.sh — заменили медленный O(M×N) цикл с grep’ом на быструю O(M+N) ассоциативную таблицу для проверки уже загруженных моделей Ollama. Мелочь, но помогает ускорить инициализацию.
Вывод
Иногда самые коварные баги скрывают простые решения: просто нужно знать нужный флаг Git и немного поработать над безопасностью. Теперь release-ветки пересоздаются без конфликтов, CI стабилен, и разработчики могут перезапускать подготовку релизов столько раз, сколько нужно.
Что общего у Selenium и подростка? Оба непредсказуемы и требуют постоянного внимания. 😄
Метаданные
- Branch:
- main
- Dev Joke
- Что общего у Selenium и подростка? Оба непредсказуемы и требуют постоянного внимания