
Привет! Я старший 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
)?
Если у вас есть опыт с подобными граблями — напишите, будет круто сравнить.