Git undo last commit: откат коммита без потери изменений

Отменить последний коммит без потерь: сохранить изменения через reset --soft, снять стейджинг через --mixed, запушенный откатить через revert — точные команды.

HUMANавтор @mrsaynothing53d5 мин чтения

Remove, delete, cancel или undo последнего коммита — какой бы глагол вы ни искали, команда, сохраняющая изменения, одна и та же. TL;DR: чтобы отменить последний коммит и сохранить изменения, выполните git reset --soft HEAD~1 (изменения останутся в стейджинге) или git reset HEAD~1 (изменения останутся в рабочей директории). Если коммит уже запушен, вместо этого выполните git revert HEAD — он создаёт новый коммит, отменяющий старый, без переписывания истории. Эти три команды покрывают почти каждый момент «ой, я закоммитил слишком рано». Дальше разберём каждый случай с командами под копипаст, разницу между --soft, --mixed и --hard и что делать, если что-то пошло не так. Все команды ниже работают на любом свежем Git под Linux, macOS или Windows.

Как отменить последний коммит, но сохранить изменения?

Самая частая ситуация: вы закоммитили, потом заметили опечатку или недостающий файл, либо поняли, что изменение нужно в другой коммит. Пока ничего не запушено. Отменяем коммит и возвращаем всё как было:

# Коммит исчезает из истории — изменения возвращаются в стейджинг
git reset --soft HEAD~1

# Изменения возвращаются в рабочую директорию (незастейдженные)
git reset HEAD~1

HEAD~1 означает «на один коммит раньше, чем там, где HEAD сейчас». После любой из команд файлы на диске не тронуты — сдвинулся только указатель ветки. Проверьте через git status: с --soft изменения застейджены и готовы к повторному коммиту с уже внесёнными правками; без флага они не застейджены, и можно сначала свободно редактировать.

Более безопасная привычка, когда вы просто хотите добавить файлы в последний коммит, — вообще его не отменять:

git add forgotten-file.txt
git commit --amend --no-edit

--amend заменяет последний коммит на месте (здесь — без изменения сообщения). Учтите: amend уже запушенного коммита переписывает историю — об этом ниже.

В чём разница между --soft, --mixed и --hard?

Вот это стоит запомнить: именно флаг решает, где окажутся ваши изменения — и можно ли их потерять:

Флаг Коммит отменён? Изменения на диске В стейджинге? Типичное применение
--soft Да Сохранены Да Перекоммитить с мелкими правками
--mixed (по умолчанию) Да Сохранены Нет Перегруппировать изменения, застейджить выборочно
--hard Да Удалены — Выбросить работу целиком
git revert Нет (новый коммит) Сохранены — Отменить уже запушенный коммит

Опасен только --hard: он отбрасывает и коммит, и изменения. Перед любым reset --hard спрячьте или забранчьте то, что есть:

git branch backup-before-reset   # дешёвая страховка
git reset --hard HEAD~1          # коммит + изменения исчезли

Если опасная версия уже выполнена — ещё не всё потеряно: git reflog помнит, где HEAD бывал:

git reflog                       # найдите хеш потерянного коммита
git reset --hard HEAD@{1}        # или: git reset --hard <hash>

По умолчанию reflog хранит висячие коммиты около 90 дней, так что «я случайно сделал hard reset» — ситуация почти всегда обратимая, если успеть до garbage collection.

Что делать, если коммит уже запушен?

Если коммит лежит в общей ветке (всё, кроме вашей личной фиче-ветки), не переписывайте историю. Используйте git revert — он вычисляет противоположное изменение и коммитит его:

git revert HEAD
git push

Все, кто сделает pull, просто получат новый коммит, убирающий изменения старого. Ни force-push, ни сломанные коллеги. Если нужно отменить серию коммитов, ревертните диапазон: git revert --no-commit HEAD~3..HEAD && git commit.

Альтернатива — git reset --hard HEAD~1 && git push --force-with-lease — допустима только на ветке, на которую никто не опирается, и --force-with-lease (никогда голый --force) — единственная безопасная форма: она откажется перезаписывать remote, если кто-то успел запушить до вас. Force-push в общие ветки — это то, как команды теряют коммиты, а CI-логи перестают совпадать с чекаутом кого бы то ни было.

Как отменить последний коммит, но оставить его на потом?

Иногда коммит — хорошая работа по неправильному адресу: не та ветка или слишком рано. Вместо отмены переместите его:

git branch stash-commit          # припарковать коммит на новой ветке
git reset --hard HEAD~1          # затем почистить текущую ветку

Или забрать только коммит в другую ветку, не трогая текущую:

git cherry-pick <hash>           # находясь на целевой ветке

Между reset --soft, cherry-pick и revert не существует коммита, который нельзя перенести или обезвредить — трюк в том, чтобы выбрать «переместить» или «отменить» ещё до того, как рука потянется к флагу.

Undo, revert, reset, remove, cancel — у каждого слова своя команда

Половина путаницы — словарная: шесть глаголов, которые люди вбивают в поиск, и три команды, которые существуют на самом деле. Сопоставьте слово в голове с флагом, который его исполняет:

Что вбили Что вы хотите Команда Изменения сохранятся?
undo / uncommitкоммит ушёл, работа вернуласьgit reset --soft HEAD~1да — в индексе
undo, без индексакоммит ушёл, работа россыпьюgit reset HEAD~1да — mixed
remove / dropкоммит и правки удаленыgit reset --hard HEAD~1нет — reflog ~90 дней
revert / cancel, запушенопубличная отмена, история целаgit revert HEADда — новым коммитом

Эти глаголы в Git не синонимы: revert никогда не трогает прошлое, reset его переписывает. Скажете коллеге не то слово — получите не ту команду.

Какую отмену выбрать? Краткий ориентир

  1. Не запушено, нужно поправить и перезакоммитить → git reset --soft HEAD~1
  2. Не запушено, нужно застейджить выборочно → git reset HEAD~1 (mixed)
  3. Изменения не нужны совсем → git reset --hard HEAD~1 (reflog вспомнит, если пожалеете)
  4. Уже запушено в общую ветку → git revert HEAD
  5. Коммит место в другой ветке → cherry-pick, а не отмена

Хотите, чтобы решение приняли за вас? Генератор git undo задаёт эти три вопроса и печатает готовую команду.

Последний операционный совет: если плохой коммит всё же добрался до сервера — сломанный deploy-хук, взбесившийся после force-push CI-раннер, — следующий адрес для поиска не Git, а логи машины. На любом systemd-боксе journalctl -u <service> -n 100 показывает, что и когда выполнялось; готовые паттерны собраны в нашей шпаргалке по journalctl. А если в вашем воркфлоу есть локальные LLM для ревью диффов, два главных варианта мы сравнили в Ollama vs LM Studio.