Git: отмена изменений, reset, reflog и восстановление
Четвёртая часть гида: как откатить изменения на любом уровне, переписать историю и вернуть «потерянные» коммиты
Где и как отменять
| Ситуация | Инструмент |
| Правка в рабочей папке, которую нужно выбросить | git restore файл |
| Файл добавлен в индекс по ошибке | git restore --staged файл |
| Последний коммит неудачный, он локальный | git reset --soft HEAD~1 |
| Коммит уже опубликован, нужно откатить эффект | git revert |
| Нужно вернуть «потерянный» коммит | git reflog |
restore и checkout для файлов
| Команда | Вариант | Что делает |
| git restore | git restore app.py | Вернуть файл к состоянию индекса (правки в рабочей папке теряются). |
| git restore --staged | git restore --staged app.py | Убрать из индекса, сохранив правки. |
| git restore --source | git restore --source=HEAD~3 config.yml | Взять версию файла из любого коммита. |
| git restore -p | git restore -p app.py | Отменить выборочно, по кускам. |
| git checkout -- | git checkout -- app.py | Старая форма того же: отменить правки файла. |
| git clean | git clean -n | Показать неотслеживаемые файлы, которые будут удалены (сухой запуск). |
| git clean -f | git clean -fd | Удалить неотслеживаемые файлы и папки (необратимо). |
| git clean -fx | git clean -fdx | Удалить и файлы, игнорируемые .gitignore (например, сборочные артефакты). |
| git clean -i | git clean -i | Интерактивный выбор файлов для удаления. |
reset: перемещение указателя ветки
Команда git reset двигает ветку на другой коммит и по-разному затрагивает индекс и рабочую папку.
| Режим | Пример | Что меняется |
| --soft | git reset --soft HEAD~1 | Ветка переходит назад, изменения остаются в индексе. Удобно, чтобы переписать коммит. |
| --mixed | git reset HEAD~1 | Режим по умолчанию: изменения остаются в рабочей папке, индекс очищается. |
| --hard | git reset --hard origin/main | Всё совпадает с указанным коммитом. Незакоммиченные правки удаляются. |
| --keep | git reset --keep HEAD~1 | Как hard, но не трогает незакоммиченные правки в файлах, которые меняются. |
| reset файл | git reset HEAD app.py | Убрать один файл из индекса (аналог restore --staged). |
git reset --hard безвозвратно удаляет незакоммиченные изменения. Перед ним посмотрите git status и при необходимости сохраните работу через git stash.
revert: откат через новый коммит
| Команда | Вариант | Что делает |
| git revert | git revert abc1234 | Создаёт новый коммит, который отменяет изменения указанного. История сохраняется. |
| git revert --no-edit | git revert --no-edit HEAD | Отменить последний коммит с сообщением по умолчанию. |
| git revert -n | git revert -n abc1234 def5678 | Отменить несколько коммитов, не создавая отдельные коммиты. |
| git revert -m | git revert -m 1 MERGE_COMMIT | Откат коммита слияния: указать родителя, чьи изменения сохранить. |
| git revert диапазон | git revert HEAD~3..HEAD | Отменить серию коммитов. |
| git revert --abort | git revert --abort | Прервать откат при конфликте. |
reflog: журнал движения указателей
Git хранит историю перемещений HEAD и веток. Даже после reset --hard или удаления ветки коммит обычно можно найти в reflog в течение срока хранения (по умолчанию около 90 дней).
| Команда | Вариант | Что делает |
| git reflog | git reflog | Все движения HEAD: коммиты, переключения, reset. |
| git reflog show | git reflog show feature/x | История конкретной ветки. |
| git reset --hard HEAD@{n} | git reset --hard HEAD@{2} | Вернуться к состоянию из reflog. |
| git branch | git branch recovered abc1234 | Восстановить потерянный коммит в новую ветку (безопаснее, чем reset). |
| git fsck --lost-found | git fsck --lost-found | Найти висящие коммиты, которых нет ни в одной ветке. |
Ошибочно удалили ветку с коммитами? Найдите её последний коммит через git reflog и создайте ветку на нём: git branch восстановленная abc1234. Так вы сохраните работу без лишнего риска.
Переписывание истории: rebase -i
| Команда | Вариант | Что делает |
| git rebase -i HEAD~n | git rebase -i HEAD~5 | Редактировать последние 5 коммитов. |
| git rebase -i root | git rebase -i --root | Переписать всю историю, включая первый коммит. |
| git commit --fixup | git commit --fixup=abc1234 | Создать исправление для конкретного коммита. |
| git rebase --autosquash | git rebase -i --autosquash main | Автоматически объединить fixup-коммиты. |
| git commit --amend | git commit --amend | Изменить последний коммит. |
Порядок строк в интерактивном rebase — от старых коммитов к новым. Изменяйте слово перед хэшем: pick, reword, edit, squash, fixup, drop.
Поиск источника проблемы: bisect
Если известно, что ошибка появилась между двумя коммитами, bisect быстро найдёт виновника двоичным поиском.
git bisect start
git bisect bad # текущий коммит сломан
git bisect good v1.3.0 # этот тег точно работал
# Git переключит на средний коммит. Проверьте и ответьте:
git bisect good # или git bisect bad
# ...повторять, пока Git не назовёт первый плохой коммит...
git bisect reset # вернуться к исходной ветке
# Автоматический режим, если есть скрипт проверки (код 0 — хорошо)
git bisect start HEAD v1.3.0
git bisect run ./test.sh
Анализ истории
| Команда | Вариант | Что делает |
| git blame -w | git blame -w -C app.py | Игнорировать пробелы и искать перенесённый код. |
| git log -G | git log -G "password" --oneline | Коммиты, где изменились строки с регулярным выражением. |
| git log --follow | git log --follow --oneline -- renamed.py | История файла, включая время до переименования. |
| git grep | git grep -n "TODO" $(git rev-list -n 1 v1.0) | Поиск в конкретном коммите или теге. |
| git cat-file | git cat-file -p HEAD^{tree} | Внутреннее содержимое объекта (для отладки). |
| git rev-list | git rev-list --count HEAD | Число коммитов в ветке. |
Пример: исправить последний коммит и отправить
# Ошибка в сообщении последнего коммита (ещё не отправлен)
git commit --amend -m "fix: корректный таймаут"
# Забыли файл в коммите
git add config/timeouts.yml
git commit --amend --no-edit
# Коммит уже отправлен, а содержимое неверно: откат новым коммитом
git revert HEAD
git push
# Хотим откатить только один файл к версии из предыдущего коммита
git restore --source=HEAD~1 -- docs/guide.md
git commit -m "docs: вернуть прежнюю версию guide.md"
Переписывание истории (rebase -i, reset, commit --amend, filter-repo) меняет хэши коммитов. Для опубликованных веток используйте revert, а переписывание оставляйте для локальной работы. git clean -fdx удаляет всё лишнее без возврата, включая локальные конфиги, не попавшие в git.
Перед рискованными операциями создайте резервную ветку: git branch backup-before-rebase. Если что-то пойдёт не так, вернуться можно одной командой: git reset --hard backup-before-rebase.