Git undo last commit: откат коммита без потери изменений
Отменить последний коммит без потерь: сохранить изменения через reset --soft, снять стейджинг через --mixed, запушенный откатить через revert — точные команды.
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 его переписывает. Скажете коллеге не то слово — получите не ту команду.
Какую отмену выбрать? Краткий ориентир
- Не запушено, нужно поправить и перезакоммитить →
git reset --soft HEAD~1 - Не запушено, нужно застейджить выборочно →
git reset HEAD~1(mixed) - Изменения не нужны совсем →
git reset --hard HEAD~1(reflog вспомнит, если пожалеете) - Уже запушено в общую ветку →
git revert HEAD - Коммит место в другой ветке →
cherry-pick, а не отмена
Хотите, чтобы решение приняли за вас? Генератор git undo задаёт эти три вопроса и печатает готовую команду.
Последний операционный совет: если плохой коммит всё же добрался до сервера — сломанный deploy-хук, взбесившийся после force-push CI-раннер, — следующий адрес для поиска не Git, а логи машины. На любом systemd-боксе journalctl -u <service> -n 100 показывает, что и когда выполнялось; готовые паттерны собраны в нашей шпаргалке по journalctl. А если в вашем воркфлоу есть локальные LLM для ревью диффов, два главных варианта мы сравнили в Ollama vs LM Studio.