
Сначала определите, что именно нужно откатить
Под «откатом на предыдущий коммит» обычно понимают одну из трёх задач: убрать последний коммит, но сохранить правки; вернуть ветку к более старому состоянию вместе с файлами; отменить изменения только в одном файле. Команды для них разные, и путаница между ними — частая причина потерянных изменений.
Второй критерий — опубликован ли коммит. Пока ветка существует только локально, историю можно переписать через git reset. Если коммит уже ушёл в общий репозиторий, переписывание создаст проблемы коллегам: им придётся заново синхронизировать ветку. В таком случае используют git revert, который добавляет новый коммит с обратными изменениями.
| Задача | Команда | Результат |
|---|---|---|
| Убрать последний коммит, оставить изменения в индексе | git reset --soft HEAD~1 | HEAD сдвигается, файлы и индекс не меняются |
| Убрать коммит, оставить изменения в файлах | git reset --mixed HEAD~1 | HEAD и индекс сбрасываются, рабочая директория не меняется |
| Полностью вернуть ветку к предыдущему коммиту | git reset --hard HEAD~1 | HEAD, индекс и отслеживаемые файлы приводятся к HEAD~1 |
| Отменить опубликованный коммит | git revert HEAD | Создаётся новый коммит с обратными изменениями |
| Вернуть один файл из предыдущего коммита | git restore --source=HEAD~1 -- file.txt | Файл в рабочей директории меняется, история не трогается |
git reset: локальный откат ветки
HEAD~1 означает коммит, который был до текущего. HEAD~2 — двумя шагами раньше, HEAD^ — первый родитель текущего коммита. В линейной истории HEAD~1 и HEAD^ совпадают, но для merge-коммита ^2 указывает на второго родителя.
Режим --soft двигает только указатель ветки. Все изменения последнего коммита остаются в индексе, и их можно сразу закоммитить заново или объединить с другими правками. --mixed (режим по умолчанию) дополнительно сбрасывает индекс: файлы остаются изменёнными, но не подготовленными к коммиту. --hard возвращает и файлы, поэтому незакоммиченные правки в отслеживаемых файлах исчезнут.
git reset --soft HEAD~1
git status
Передgit reset --hardпроверьтеgit status. Если в рабочей директории есть нужные, но ещё не закоммиченные изменения, сохраните их черезgit stashили отдельный коммит: reset --hard перезапишет отслеживаемые файлы. Неотслеживаемые файлы команда не удаляет.
Главное ограничение reset — он переписывает историю ветки. Если вы уже сделали git push, обычный push после reset будет отклонён, а git push --force перезапишет ветку в удалённом репозитории. Для личной ветки это допустимо, для общей — нет. Если коммит опубликован и с ним уже работают другие, используйте revert.
git revert: отмена в опубликованной ветке
git revert HEAD не удаляет коммит из истории. Он создаёт новый коммит, который отменяет изменения указанного коммита. Ветка остаётся совместимой с удалённой, и коллегам не нужно пересоздавать локальные копии.
git revert HEAD
git revert a1b2c3d
Если изменения конфликтуют с более поздними коммитами, Git остановит операцию и попросит разрешить конфликт. После правки файлов выполните git revert --continue; чтобы отказаться — git revert --abort. Для отмены merge-коммита нужен родитель: git revert -m 1 <hash>, где 1 — основная ветка, к состоянию которой вы возвращаетесь.
У revert есть цена: история становится длиннее, а отменённые изменения остаются в старых коммитах. Если в коммите были пароли или ключи, revert не удалит их из истории — потребуется очистка через git filter-repo или аналогичные инструменты, и это отдельная операция с переписыванием истории.
Откат файла, старый коммит и восстановление
Для одного файла не нужно двигать всю ветку. Команда git restore --source=HEAD~1 -- path/to/file берёт версию файла из предыдущего коммита и кладёт её в рабочую директорию. История и индекс остаются как были, если не добавить --staged. Старый вариант — git checkout HEAD~1 -- path/to/file; он затрагивает и индекс.
git restore --source=HEAD~1 -- src/app.js
git diff -- src/app.js
Чтобы посмотреть старое состояние без изменения файлов, используйте git show HEAD~1:src/app.js. Для перехода на старый коммит целиком есть detached HEAD: git switch --detach <hash>. В этом режиме можно смотреть файлы и запускать тесты, но новые коммиты не привязаны к ветке. Вернуться — git switch -. Минус detached HEAD в том, что случайно созданный коммит легко потерять; если он нужен, сразу создайте ветку: git switch -c fix-from-old-commit.
Если после reset --hard пропали коммиты, их обычно можно найти через git reflog. Журнал показывает перемещения HEAD, включая сброшенные состояния. Найдите строку перед неудачной командой и вернитесь к ней.
git reflog
git reset --hard HEAD@{1}
Типичные ошибки: делать reset --hard при незакоммиченных правках; применять reset к общей ветке и затем делать force push; пытаться откатить один файл через reset всей ветки; отменять merge без указания родителя. Коммиты после неудачного reset восстановимы через reflog, а незакоммиченные изменения в отслеживаемых файлах — нет. git restore тоже перезаписывает текущую версию файла, поэтому перед ним проверяйте diff.
Частые вопросы
Можно ли откатиться на предыдущий коммит, не потеряв изменения? Да. git reset --soft HEAD~1 оставит изменения в индексе, git reset --mixed HEAD~1 — в рабочей директории. Затем их можно закоммитить заново.
Что делать, если после reset --hard пропали файлы? Сначала проверьте git reflog и вернитесь на нужный коммит. Если файлы не были закоммичены, Git их не хранит; помогут только внешние копии или резервные копии.
Чем опасен force push после reset? Он заменяет историю ветки в удалённом репозитории. У коллег, которые уже получили старые коммиты, возникнут расхождения; их локальные коммиты могут потеряться при синхронизации. Для общей ветки безопаснее git revert.
Если коммит ещё не опубликован, начните с git reset --soft HEAD~1 — он сохраняет правки и позволяет собрать коммит заново. Для общей ветки используйте git revert, а для одного файла — git restore --source=HEAD~1. Перед любой командой с --hard проверьте git status и сохраните нужные изменения.