Как отменить изменения в Git: reset, revert и restore
Практика3 минРедакция Innovialabs
Коротко
restore отменяет правки в файлах, reset двигает ветку назад в локальной истории, revert создает новый коммит-отмену для уже опубликованного кода, а reflog помогает вернуть «потерянное».
Короткий ответ
Выбор команды зависит от двух вопросов: что вы хотите отменить и опубликованы ли уже изменения.
| Ситуация | Команда |
|---|---|
Испортил файл, еще не делал git add | git restore <файл> |
| Добавил лишнее в индекс | git restore --staged <файл> |
| Последний коммит неудачный, не запушен | git reset --soft HEAD~1 |
| Нужно исправить сообщение последнего коммита | git commit --amend |
| Баг уже в общей ветке | git revert <хеш> |
Удалил ветку или сделал reset --hard по ошибке | git reflog + git branch / git reset |
Главное правило: не переписывайте историю, которую уже забрали другие. Для опубликованных коммитов используйте revert.
restore: отмена правок в файлах
git restore работает с файлами, а не с коммитами.
git restore src/app.js # вернуть файл к последнему коммиту
git restore --staged src/app.js # убрать из индекса, правки остаются
git restore --source=HEAD~2 src/app.js # взять версию двухкоммитной давности
Осторожно: git restore <файл> без --staged безвозвратно удаляет незакоммиченные правки в этом файле. Git их не сохранял — reflog не поможет.
reset: перенос ветки назад
git reset перемещает указатель текущей ветки на другой коммит. Три режима отличаются тем, что происходит с изменениями из «отброшенных» коммитов:
| Режим | Ветка | Индекс (staged) | Рабочие файлы |
|---|---|---|---|
--soft | сдвигается | изменения остаются в индексе | не трогаются |
--mixed (по умолчанию) | сдвигается | очищается | изменения остаются в файлах |
--hard | сдвигается | очищается | перезаписываются |
Практика:
git reset --soft HEAD~1— «разкоммитить», чтобы переделать коммит или объединить несколько в один.git reset HEAD~1— то же, но изменения нужно заново добавить черезgit add; удобно, чтобы разбить коммит на несколько.git reset --hard origin/main— выбросить все локальные изменения и привести ветку к удаленной. Незакоммиченная работа пропадет.
Если ветка уже запушена, после reset придется делать git push --force-with-lease. В личной ветке это допустимо, в main — почти никогда.
revert: безопасная отмена опубликованного
git revert не удаляет коммит, а создает новый коммит с обратными изменениями. История сохраняется, коллегам ничего не нужно чинить.
git revert a1b2c3d # отменить один коммит
git revert HEAD~3..HEAD # отменить диапазон из трех последних
git revert -m 1 <merge-хеш> # отменить merge-коммит
Для merge-коммита -m 1 указывает, какой родитель считать основным (обычно первый — ветка, в которую сливали). Учтите: если позже снова слить ту же ветку, Git посчитает, что ее изменения уже применялись, и придется «отменить отмену».
reflog: спасательный круг
Git записывает каждое перемещение HEAD в reflog. Это локальный журнал, и он спасает почти любую ошибку с коммитами.
git reflog
# 9f8e7d6 HEAD@{0}: reset: moving to HEAD~3
# 4c5d6e7 HEAD@{1}: commit: add payment form
# ...
git reset --hard 4c5d6e7 # вернуть ветку в прежнее состояние
git branch recovered 4c5d6e7 # или восстановить удаленную ветку
Ограничения: reflog хранится только у вас локально, записи со временем удаляются сборщиком мусора, и он не содержит того, что никогда не было закоммичено.
Частые ошибки
reset --hardпри незакоммиченной работе. Перед рискованной операцией сделайтеgit stashили временный коммит.- Force push в общую ветку. Ломает историю коллегам. Используйте
revert. - Путаница между
revertиreset. Revert добавляет коммит, reset двигает ветку. - Паника. Если что-то закоммичено хотя бы раз, оно почти наверняка восстанавливается через reflog.