Упс... Один git push --force, и полгода работы команды испарились за секунду
Есть команды, которые надо вводить с холодной головой и полным осознанием последствий. rm -rf / — очевидный пример. Но среди разработчиков и DevOps есть своя версия этой русской рулетки — git push --force.
Я работал тогда в продуктовой компании — делали SaaS-платформу для управления проектами. Небольшая команда, человек двенадцать, хороший продукт, живые клиенты, нормальный процесс разработки. Мы использовали GitHub, feature-ветки, pull requests, code review — всё как у взрослых.
Репозиторий был защищён: ветка main заблокирована от прямых пушей, обязательный PR с одобрением от двух ревьюеров. Всё серьёзно. Но была одна маленькая деталь, которую мы упустили: ветка develop — наша основная рабочая ветка, куда мерджились все фичи перед выходом в main — была защищена только от обычного push. Не от push --force.
Это упущение жило полтора года. До того утра.
Антон — старший разработчик, умный, опытный, пять лет в команде — в среду утром пришёл на работу в слегка раздражённом состоянии. Накануне вечером работал из дома, что-то переделывал в своей feature-ветке, несколько раз rebase'ил на свежий develop, история коммитов запуталась. Он хотел сделать красиво — squash коммиты, причесать историю, залить обратно.
Он сделал git rebase -i HEAD~15. Причесал. Сделал git push origin feature/my-branch. Получил ошибку — ветка расходилась с remote из-за rebase. Это нормально. Написал git push --force origin feature/my-branch.
И тут автодополнение в его терминале сыграло злую шутку.
Он не заметил. Нажал Enter. Терминал отработал молниеносно.
git push --force origin develop
Ветка develop теперь содержала только его пятнадцать причёсанных коммитов. Полгода работы двенадцати разработчиков — восемьсот с лишним коммитов, десятки смёрджённых feature-веток — перестали существовать в remote.
Первым заметил Кирилл — он в это время делал git pull origin develop и увидел странное сообщение о том, что его локальная ветка «впереди» remote на 847 коммитов. Написал в слак: «ребят, что-то странное с develop».
Я в это время пил кофе. Зашёл в GitHub. Посмотрел на историю develop. Увидел пятнадцать коммитов за подписью Антона.
Поставил кружку. Написал в общий чат: «стоп, никто не делайте git pull и не трогайте develop».
Антон в этот момент только что дошёл до своего рабочего места — потому что через тридцать секунд в чате появилось его сообщение: «ребята, кажется, я что-то сломал».
Восстановление заняло примерно двадцать минут и было почти триумфальным.
Git — распределённая система. Это означает, что у каждого разработчика в локальном репозитории была актуальная копия develop на момент последнего git fetch. У Кирилла — который как раз делал git pull в момент инцидента — локальная ветка содержала все 847 коммитов.
# На машине Кирилла:
git log origin/develop..develop --oneline | wc -l
# 847
# Пришлось временно снять все защиты с ветки в GitHub
# и залить обратно правильную историю
git push --force origin develop
Через двадцать минут develop снова содержал все 847 коммитов. Все локальные репозитории сделали git fetch. Ничего не потерялось.
Антон написал мне в личку: «Максим, я понимаю что случилось. Хочу объяснить». Мы созвонились. Он рассказал про автодополнение, про то, как перепутал ветки. Голос у него был как у человека, который ещё не понял, уволен он или нет.
Я сказал ему три вещи. Первое: никто ничего не потерял, всё восстановлено, все живы. Второе: это системная ошибка, не личная — защита ветки была настроена неправильно, и это моя ответственность как DevOps. Третье: мы немедленно это исправим.
В тот же день мы включили защиту от force push на develop. Добавили в onboarding раздел «Никогда не используй git push --force без явного указания feature-ветки». Везде заменили --force на --force-with-lease — он проверяет что remote не изменился с момента вашего последнего fetch, и отказывает если кто-то уже запушил.
Антон остался в команде. Ещё через полгода стал тимлидом.
Самый важный вывод из этой истории не технический. Технический прост: защищайте все ветки от force push, используйте --force-with-lease, настройте алиасы.
Важный вывод другой: реакция команды на инцидент определяет культуру команды. Можно было устроить показательную порку. Вместо этого мы исправили систему — и получили разработчика, который с тех пор параноидально аккуратен с git и научил этому ещё троих новых.
Ошибки надо исправлять в системе. Не в людях.
А git push --force без явного указания ветки — это как ходить с заряженным пистолетом без предохранителя. Рано или поздно что-то нажмётся не то.
Recommended Comments