
Привет! Я старший fullstack-разработчик в крупной b2b-команде, где мы активно развиваем IT турпродукты и сопровождаем легаси-проекты. Недавно мне довелось временно заменить тимлида — он ушёл в отпуск, оставив напоследок фразу: «Ты не будешь деплоить».
Спойлер: деплоил. И не просто деплоил, а чуть не похоронил релиз из-за одного неосторожного git reset --hard
. К счастью, всё закончилось хорошо — но пришлось восстановливать ветки из GitLab’а, бороться с удалённой историей и вручную черри-пикать задачи.
Рассказываю, как всё было, какие выводы сделал и чего теперь точно делать не буду. Надеюсь, кому-то это сэкономит пару нервных клеток.
? Контекст и старт релиза
Понедельник. Утро. Просыпаюсь в нормальном настроении, пью кофе, открываю ноутбук и захожу на утренний дейлик. Тимлид ушёл в отпуск, оставив мне временно исполнять обязанности старшего — я и так senior fullstack, но теперь фактически главный по команде.
Перед уходом он, с лёгкой иронией, сказал:
«Ты не будешь деплоить».
Как показало утро — деплоить как раз мне и придётся.
? Что происходит
На дейлике быстро становится ясно: релиз всё-таки на мне.
Это не что-то экстраординарное — раньше я релизил в прод, но уже больше года этим не занимался. За это время команда, процессы и репозиторий изменились.
Я смотрю на текущую релизную ветку, которую успел собрать тимлид, и понимаю — её нужно разделить на две части:
Релиз с накопившимися небольшими задачами, не входящими в эпики — чтобы быстрее выгрузить то, что уже протестировано и готово.
Отдельный релиз, содержащий только один эпик — он всё ещё нестабилен, требует доработок и не должен попадать на прод случайно.
Обсуждаю идею с командой — получаю согласие. План простой:
откатить релизную ветку до нужного состояния;
по частям пересобрать два релиза;
залить по очереди.
Выглядит буднично и несложно. И я, не особенно напрягаясь, приступаю.
? Начало проблем
В какой-то момент — почти на автопилоте — я выполняю команду:
git reset --hard origin/master
Моя мысль была простая: «обнулю релизную ветку, и соберу всё по новой, руками доберу нужные задачи».
Но спустя минуту я начинаю искать те самые ветки, из которых пришли задачи, чтобы пересобрать релиз...
…а их нет.
? Почему нет веток?
Тут я вспоминаю один спор с тимлидом. Мы долго обсуждали, стоит ли удалять ветки после мержа. В итоге пришли к соглашению: если задача смержена в релизную ветку — удаляем ветку.
В прод это попадёт из релиза, а значит, в git
у нас не будет лишнего хлама.
Но: релизная ветка — это не master
, это временная сборка, которая может быть переписана.
А я только что затёр её состояние хард-ресетом.
? Осознание
Истории изменений нет.
Оригинальные ветки удалены.
git log
ничего не показывает — релизная ветка теперь просто копияmaster
.git reflog
— локальная история, но она не спасает, если ветка была перезаписана и пушнута с--force
.
Всё, что было смержено и ещё не ушло на прод — оказалось уничтожено.
А продакшен-готовый релиз — должен быть сегодня.
Паника? Немного. Но я начинаю думать, где остались следы.
? Поиск решений и восстановление: 3 пути, которые я попробовал
В голове быстро оформляется три возможных способа хоть как-то восстановить утраченные изменения:
✅ 1. Стенд и тестировщики — может, там остались живые ветки
Проверяю, не остались ли локальные копии удалённых веток:
на QA-стенде (вдруг туда накатывался конкретный коммит),
у тестировщиков (бывает, у них что-то закэшено в локальных репах).
Ответ — нет. Никто не держит эти ветки у себя, и на стенд накатывали уже собранный релиз. Следов не осталось.
✅ 2. Разработчики — возможно, у кого-то локально остались ветки
Следующий ход — найти разработчика, который делал задачи. Возможно, он не успел удалить у себя локальную ветку, даже если она была удалена из репозитория.
Я проверяю MR и вижу, кто автор. Минус: он в отпуске.
Нехорошо тревожить по пустякам (хотя это не пустяк, конечно).
К счастью, над задачей работали вдвоём. Пишу второму разработчику.
Ответ: «Я сейчас у врача, смогу посмотреть через пару часов».
На этом моменте я ставлю вторую попытку на паузу и переключаюсь на оставшиеся части релиза.
✅ 3. GitLab — последнее убежище истории
Остаётся последний шанс: Merge Request в GitLab.
Да, ветки удалены. Да, в Git их уже нет.
Но GitLab умеет сохранять сравнение веток, даже если сами ветки удалены.
Я открываю MR на удалённую ветку — и вижу:
diff между веткой и целевой (
master
) живойвсе изменения, коммиты, комментарии — доступны
Это значит, что при желании, я могу вручную собрать эти коммиты, просто копируя diff кусками.
Это не идеально, но это шанс спасти релиз.
В это время поступает ответ от разработчика: одна из веток — сохранилась у него локально. Ура.
Но вторая — уже точно утрачена. Её нет ни у кого.
GitLab остаётся единственным источником для её восстановления.
? Параллельно: фронтовая релизная ветка
Пока я разбирался с бэком, вспоминаю: есть ещё фронтовая релизная ветка.
К счастью, там я не делал reset --hard
.
Но проблема с удалёнными ветками — та же. Задачи были смержены, ветки удалены, история отсутствует.
Решение:
вручную пересоздаю нужные ветки
поднимаю дифф из старых задач
cherry-pick'аю изменения обратно
одна из задач оказалась зависима от изменений в эпике (которые в другом релизе!) — пришлось вручную вырезать эту зависимость
Ещё одна задача попала в релиз недоделанной. Её убираю из релизной ветки и отправляю обратно на доработку.
В итоге мне удалось:
разделить фронт на два релиза
собрать релиз с «малыми» задачами
отложить эпик на следующий виток
? Восстановление бэковского релиза вручную через GitLab
На этом этапе часть задач по фронту уже спасена. Осталась проблема: бэковый релиз, в который я сделал git reset --hard
и уронил всю историю. Напомню:
ветки с задачами были удалены сразу после мержа в релизную ветку;
один разработчик был в отпуске, второй смог восстановить только одну задачу;
вторую ветку не удалось найти ни у кого — она ушла в небытие.
? GitLab — последняя надежда
Проверяю Merge Request, который когда-то мержил вторую задачу.
Сами ветки уже удалены, но GitLab по-прежнему показывает:
diff между удалённой веткой и
master
,все изменённые файлы,
старый коммит с идентификатором,
комментарии к MR.
Это значит, что все изменения технически доступны, пусть и не в виде ветки.
? Вручную собираю изменения
Вариантов немного:
Создаю новую ветку от
master
:git checkout -b restore-lost-task
Перехожу в MR, открываю вкладку "Changes".
-
Файл за файлом, правка за правкой — воссоздаю изменения вручную:
копирую код из GitLab,
вставляю в IDE,
коммитю по логике задачи.
Да, это долго. Да, это неудобно.
Но когда речь идёт о продакшен-релизе, у которого сегодня дедлайн, — считаешь каждую строчку.
✅ Результат: задача восстановлена, релиз собран
После ручной реконструкции:
проверяю локально, что поведение корректно;
прокидываю ветку в релизную;
уведомляю команду и QA: задача восстановлена, можно тестировать.
Спустя несколько итераций и сверок, релиз бэка собран заново — уже без потерь.
⚖️ Выводы и личные уроки
Этот релиз стал для меня хорошим напоминанием:
?
git reset --hard
на релизной ветке — очень плохая идея, особенно если ветки уже удалены.? Удаление веток после мержа — удобно, но опасно, если прод ещё не обновился.
-
? GitLab — это не только CI/CD, но и ценнейшее хранилище истории:
diff’ы остаются даже после удаления веток;
коммиты видны;
можно восстановить почти всё руками.
? Не стоит стесняться пинговать коллег, даже если они «в отпуске» — но лучше иметь бэкапы по процессу.
❓ Вопрос к читателям
Мне интересно — как бы вы поступили в такой ситуации?
Можно ли было восстановить ветку через
reflog
,gitlab API
или ещё как-то, о чём я не знал?Может, вы используете другие подходы к управлению релизными ветками?
Стоит ли автоматизировать часть процессов (например, авто-бэкап перед
reset
/merge
)?
Если у вас есть опыт с подобными граблями — напишите, будет круто сравнить.
Комментарии (18)
MEGA_Nexus
22.06.2025 18:25"Мне интересно — как бы вы поступили в такой ситуации? "
1. Делал бы бекапы Git-а. Тогда можно было бы откатиться на ночной бэкап, потеряв дельту данных за утро.
2. Делал бы снапшот виртуалки с git перед любыми серьёзными изменениями.
3. Перестал бы использовать команду git reset --hard и навёл бы порядок с ветками в Git. Если у вас ещё не стабильный EPIC, то что он делает в ветке мастер (Prod), а не в ветке dev (DEV). В прод должны попадать только стабильные изменения. Также почитал бы, что такое включение функционала через флаги, тогда нестабильный EPIC был бы спрятан от пользователей и никак на них не влиял, пока бы этот EPIC не довели до стабильного состояния и не включили этот функционал специальным флагом.
4. Слушал бы, что говорят старшие товарищи и почему именно тимлид, попросил тебя не лезть в релиз.
igoryan20 Автор
22.06.2025 18:25Спасибо, что так подробно расписал.
Много здравых мыслей, особенно про фичефлаги и стабильность изменений в релизных ветках — это как раз те моменты, которые хотелось бы довести до ума.
Согласен, что подход с dev → master и более чистой изоляцией эпиков с флагами — то, к чему надо стремиться, особенно если хочешь безопасных релизов.
Отдельное спасибо за мысль про снапшоты и бэкапы Git — мы как раз обсуждаем сейчас, как улучшить процессы на случай таких вот сбоев.
Про «слушать старших товарищей» — тоже услышал, без сарказма. Ошибся, сделал выводы.
vitaliy0000
22.06.2025 18:25В ситуации, как у автора,
git reflog
(для HEAD) илиgit reflog BRANCH_NAME
позволяют восстановить гораздо быстрее и удобнее, чем описано в статье.reflog
— работает не только в рамках какой-то ветки, но в рамках всех веток, в том числе HEAD,которая, конечно же, была на нужном commit-е перед тем, как был сделанgit reset --hard
.Надо было найти запись перед той, на которую встал HEAD после
--reset
, и остальная часть статьи просто не понадобилась бы.igoryan20 Автор
22.06.2025 18:25Спасибо, что написал! Про
reflog
слышал, но в реальных ситуациях почти не пользовался, так что упустил этот вариант.Будет повод разобраться глубже — выглядит действительно удобнее, чем тот путь, которым пошёл я.
gun_dose
22.06.2025 18:25Я вообще никогда не использую reset --hard. Если надо откатить локальные изменения, то использую git checkout + git clean -f. Если нужно стянуть ветку, которую удалённо ребэйсили, то надо свою локальную удалить, а удалённую стянуть через fetch. Про reset --hard я почему-то всегда слышу только в контексте стрёмных историй вроде тех, что случилась у автора.
Sly_tom_cat
22.06.2025 18:25git reset --hard - вполне рабочая команда. Но да прежде чем ее делать нужно четко понимать что именно ты хочешь сделать.... собственно так со многими командами в гите.
Сам тоже умудрялся "намудриь" в гите и пушнуть с форсом, но у меня хотя бы все коммиты локально были и git reflog спас. А так то да, автор, явно где-то поторопился..... бывает и на старуху проруха, что уж там... Я после последнего своего факапа стараюсь отставлять бекапные ветки, когда в гите всякие неоднозначные манипуляции делаю типа сквошей, ребейзов и ресетов. Удалить то их потом, когда все сделал и убедился, что все получилось как надо - не сложно.igoryan20 Автор
22.06.2025 18:25Спасибо, за понимающий комментарий. Да, reset --hard — не зло само по себе, просто требует осторожности и ясной головы.
В моём случае как раз её не хватило :) Поторопился, не оставил ни бэкапной ветки, ни чёткого плана отката — и вот результат.
Теперь точно буду аккуратнее и заведу привычку перед неоднозначными действиями делать временные ветки.
MonkAlex
Релизный цикл непонятный, проблемы непонятные, что происходит - лучше не знать. Ужас какой.
UPD: так и не понял, почему сброс ветки до создания результирующей был в принципе выполнен. Все изменения можно сделать локально, без форс пуша. И только потом, когда локально всё точно хорошо - сделать пуш.
А в целом почему форс пуш релизной ветки был посчитан хорошей идеей - даже думать не хочу.
UPD2: гугл говорит, что в гитлабе можно восстановить ветку, см https://stackoverflow.com/a/69763946
igoryan20 Автор
Да, решение с форс-пушем явно было так себе. По факту всё можно было сделать локально и пушнуть уже чисто.
Почему сделал именно так — на тот момент показалось проще, но сейчас понимаю, что зря. Буду аккуратнее с релизными ветками, вывод сделал.
Спасибо за ссылку, полезная штука.