Git: отмена изменений, reset, reflog и восстановление

Четвёртая часть гида: как откатить изменения на любом уровне, переписать историю и вернуть «потерянные» коммиты

Где и как отменять

СитуацияИнструмент
Правка в рабочей папке, которую нужно выброситьgit restore файл
Файл добавлен в индекс по ошибкеgit restore --staged файл
Последний коммит неудачный, он локальныйgit reset --soft HEAD~1
Коммит уже опубликован, нужно откатить эффектgit revert
Нужно вернуть «потерянный» коммитgit reflog

restore и checkout для файлов

КомандаВариантЧто делает
git restoregit restore app.pyВернуть файл к состоянию индекса (правки в рабочей папке теряются).
git restore --stagedgit restore --staged app.pyУбрать из индекса, сохранив правки.
git restore --sourcegit restore --source=HEAD~3 config.ymlВзять версию файла из любого коммита.
git restore -pgit restore -p app.pyОтменить выборочно, по кускам.
git checkout --git checkout -- app.pyСтарая форма того же: отменить правки файла.
git cleangit clean -nПоказать неотслеживаемые файлы, которые будут удалены (сухой запуск).
git clean -fgit clean -fdУдалить неотслеживаемые файлы и папки (необратимо).
git clean -fxgit clean -fdxУдалить и файлы, игнорируемые .gitignore (например, сборочные артефакты).
git clean -igit clean -iИнтерактивный выбор файлов для удаления.

reset: перемещение указателя ветки

Команда git reset двигает ветку на другой коммит и по-разному затрагивает индекс и рабочую папку.

РежимПримерЧто меняется
--softgit reset --soft HEAD~1Ветка переходит назад, изменения остаются в индексе. Удобно, чтобы переписать коммит.
--mixedgit reset HEAD~1Режим по умолчанию: изменения остаются в рабочей папке, индекс очищается.
--hardgit reset --hard origin/mainВсё совпадает с указанным коммитом. Незакоммиченные правки удаляются.
--keepgit reset --keep HEAD~1Как hard, но не трогает незакоммиченные правки в файлах, которые меняются.
reset файлgit reset HEAD app.pyУбрать один файл из индекса (аналог restore --staged).
git reset --hard безвозвратно удаляет незакоммиченные изменения. Перед ним посмотрите git status и при необходимости сохраните работу через git stash.

revert: откат через новый коммит

КомандаВариантЧто делает
git revertgit revert abc1234Создаёт новый коммит, который отменяет изменения указанного. История сохраняется.
git revert --no-editgit revert --no-edit HEADОтменить последний коммит с сообщением по умолчанию.
git revert -ngit revert -n abc1234 def5678Отменить несколько коммитов, не создавая отдельные коммиты.
git revert -mgit revert -m 1 MERGE_COMMITОткат коммита слияния: указать родителя, чьи изменения сохранить.
git revert диапазонgit revert HEAD~3..HEADОтменить серию коммитов.
git revert --abortgit revert --abortПрервать откат при конфликте.

reflog: журнал движения указателей

Git хранит историю перемещений HEAD и веток. Даже после reset --hard или удаления ветки коммит обычно можно найти в reflog в течение срока хранения (по умолчанию около 90 дней).

КомандаВариантЧто делает
git refloggit reflogВсе движения HEAD: коммиты, переключения, reset.
git reflog showgit reflog show feature/xИстория конкретной ветки.
git reset --hard HEAD@{n}git reset --hard HEAD@{2}Вернуться к состоянию из reflog.
git branchgit branch recovered abc1234Восстановить потерянный коммит в новую ветку (безопаснее, чем reset).
git fsck --lost-foundgit fsck --lost-foundНайти висящие коммиты, которых нет ни в одной ветке.
Ошибочно удалили ветку с коммитами? Найдите её последний коммит через git reflog и создайте ветку на нём: git branch восстановленная abc1234. Так вы сохраните работу без лишнего риска.

Переписывание истории: rebase -i

КомандаВариантЧто делает
git rebase -i HEAD~ngit rebase -i HEAD~5Редактировать последние 5 коммитов.
git rebase -i rootgit rebase -i --rootПереписать всю историю, включая первый коммит.
git commit --fixupgit commit --fixup=abc1234Создать исправление для конкретного коммита.
git rebase --autosquashgit rebase -i --autosquash mainАвтоматически объединить fixup-коммиты.
git commit --amendgit 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 -wgit blame -w -C app.pyИгнорировать пробелы и искать перенесённый код.
git log -Ggit log -G "password" --onelineКоммиты, где изменились строки с регулярным выражением.
git log --followgit log --follow --oneline -- renamed.pyИстория файла, включая время до переименования.
git grepgit grep -n "TODO" $(git rev-list -n 1 v1.0)Поиск в конкретном коммите или теге.
git cat-filegit cat-file -p HEAD^{tree}Внутреннее содержимое объекта (для отладки).
git rev-listgit 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.