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>.