Перейти к содержанию

5. Отмена, история и восстановление

Цель темы: уметь откатывать коммиты и изменения осознанно: различать reset и revert, понимать, когда что безопасно; использовать reflog для восстановления «потерянных» коммитов и веток.


Определения терминов

Reset (сброс)

Reset — перемещение текущей ветки (указателя HEAD) на другой коммит. В зависимости от режима (--soft, --mixed, --hard) откатываются коммит, индекс и/или рабочая директория. История переписывается: коммиты «выше» целевого перестают быть на ветке (но какое-то время остаются доступны через reflog).

Revert (отмена коммита новым коммитом)

Revert — создание нового коммита, который отменяет изменения указанного коммита. История не переписывается: старый коммит остаётся, добавляется коммит-«противоположность». Безопасно для общих веток, потому что не требует force push.

Reflog (журнал ссылок)

Reflog — журнал перемещений HEAD и веток: на какой коммит указывала ветка после каждой операции (commit, checkout, reset, merge и т.д.). Хранится в .git/logs/. Позволяет «найти» коммит после случайного reset --hard или удаления ветки, пока объекты не удалены сборщиком мусора (обычно 30–90 дней).

HEAD~n

HEAD~1 — один коммит назад от текущего (родитель). HEAD~2 — два коммита назад. Удобно для reset: git reset --hard HEAD~1 откатывает ветку на предыдущий коммит.


Reset: три режима

Общий вид:

git reset [--soft | --mixed | --hard] <коммит>

По умолчанию используется --mixed, если не указано иное.

Таблица: что откатывается

Режим Коммит (ветка) Индекс (staging) Рабочая директория
--soft откат остаётся остаётся
--mixed откат очищен остаётся
--hard откат очищен удалены

--soft: откатить коммит, оставить изменения в индексе

git reset --soft HEAD~1

Ветка указывает на предыдущий коммит; изменения из «отменённого» коммита остаются в индексе (staged). Удобно, чтобы переделать сообщение или добавить ещё файлы и закоммитить заново.

--mixed (по умолчанию): откатить коммит и индекс

git reset HEAD~1
# то же: git reset --mixed HEAD~1

Ветка и индекс откатываются; изменения остаются в рабочей директории как unstaged. Можно заново выбрать, что коммитить.

--hard: полный откат (осторожно)

git reset --hard HEAD~1

Ветка, индекс и рабочая директория приводятся к состоянию указанного коммита. Все незакоммиченные и «откатанные» изменения в рабочей копии теряются.

Внимание

git reset --hard необратимо уничтожает локальные изменения в файлах. Убедитесь, что не откатываете то, что нужно. При необходимости коммит можно попытаться восстановить через git reflog (см. ниже).


Revert: отмена коммита без переписывания истории

Обычный коммит

git revert HEAD
git revert abc123

Создаётся новый коммит, который отменяет изменения в указанном. Сообщение по умолчанию — «Revert "исходное сообщение"».

Merge-коммит

У merge-коммита два родителя. Нужно указать, какую «линию» оставить основной (обычно первую — ту, в которую вливали):

git revert -m 1 abc123

-m 1 — первый родитель (например, main); изменения второй ветки (влитой) отменяются.

Revert и push

После revert достаточно обычного push — история не переписывалась:

git revert HEAD
git push origin main

Production

На общих ветках (main, develop) для отката уже запушенного коммита используйте revert, а не reset + force push. Так не ломается история у остальных и у CI.


Revert vs Reset для запушенных коммитов

Действие Revert Reset + force push
История Не переписывается Переписывается
На общих ветках Безопасно Опасно, ломает клоны/CI
Когда уместно Откат коммита в main Личная ветка, по договорённости

Правило: в main (и других общих ветках) откатывать уже запушенные коммиты только через revert.


Reflog: как найти «потерянный» коммит

Просмотр reflog

git reflog
# abc1234 HEAD@{0}: commit: Add feature
# def5678 HEAD@{1}: commit: WIP
# 9ab0123 HEAD@{2}: checkout: moving from main to feature

Показаны последние перемещения HEAD с хешами коммитов. Аналогично для веток: git reflog main.

Восстановление после reset --hard

Сделали git reset --hard HEAD~1, потом поняли, что коммит нужен:

git reflog
# находите хеш нужного коммита (например abc1234)
git reset --hard abc1234
# или создать новую ветку на этом коммите:
git branch recovered abc1234

Восстановление удалённой ветки

Ветку удалили локально (git branch -D) или на remote — коммиты ещё есть в reflog или в копии у кого-то:

git reflog
# найти последний коммит удалённой ветки по сообщению или хешу
git branch restored-branch abc1234

Паттерны использования

Паттерн Описание
На общих ветках — только revert Откат запушенного коммита в main делайте через git revert, затем push.
Reset — только локально Используйте reset для «ещё не запушенных» коммитов или в личных ветках.
Перед reset --hard — проверить статус Убедиться, что не теряете нужные изменения; при сомнении сделать stash или временную ветку.
Знать про reflog После случайного reset или удаления ветки первым делом смотреть git reflog.

Антипаттерны

Антипаттерн Почему плохо Что делать
Reset --hard на общих ветках Потеря истории и рассинхрон с remote у всей команды. На main не делать reset --hard запушенных коммитов; использовать revert.
Force push после reset в main Переписывание истории на remote; у коллег и CI сломанная история. В общих ветках не делать force push; откат через revert.
Игнорировать reflog при «потере» коммита Коммит ещё есть в объектной базе; без reflog сложнее найти хеш. Сразу выполнять git reflog и восстанавливать ветку или reset на нужный хеш.
Revert без проверки Revert может конфликтовать с последующими правками; нужен тест после revert. После revert проверять сборку и тесты, при конфликтах разрешать и коммитить.

Примеры из production

Откат продакшен-коммита

В main попал коммит с багом. Уже запушен, деплой прошёл. Откат делают через revert, чтобы не трогать историю и не ломать остальных:

git revert HEAD -m 1   # если это merge; иначе git revert HEAD
git push origin main

Деплой по новому коммиту (revert) убирает проблемные изменения.

Аудит и расследование

Через reflog можно посмотреть, когда и на какой коммит переключались, что помогает при расследовании инцидентов («кто и когда откатил ветку»). Логи хранятся в .git/logs/.

Восстановление ветки после случайного удаления

Разработчик удалил ветку git branch -D feature/x. Пока объекты не почищены, ветку можно восстановить по reflog: найти последний коммит этой ветки и создать ветку заново: git branch feature/x <hash>.


Дополнительные материалы