Модуль 8: Решение сложных ситуаций и «спасение»
Разберём, как безопасно откатывать изменения и восстанавливать «потерянные» коммиты. В Git почти всё поправимо.
Принцип: в Git «по-настоящему» удаляется только то, что не было закоммичено или давно не достижимо и было сборщиком мусора удалено.
Важно не паниковать и двигаться по алгоритму: оценить текущее состояние, посмотреть историю и reflog, зафиксировать «здоровую» точку, а затем аккуратно вернуться к рабочему процессу. Спокойствие и методичность здесь решают всё.
8.1 Откат незакоммиченных изменений: restore
Самый частый случай — вы отредактировали файл и хотите вернуть его к состоянию последнего коммита.
# Откатить изменения в конкретном файле
git restore path/to/file
# Откатить все изменения в рабочей директории
git restore .
# Убрать файл из staging (unstage), но сохранить изменения
git restore --staged path/to/file
# Убрать из staging И откатить изменения
git restore --staged --worktree path/to/file
# Старый синтаксис (работает, но менее понятен):
# git checkout -- path/to/file
Важно: git restore без --staged безвозвратно удаляет незакоммиченные изменения. Если вы не уверены — сначала сделайте git stash.
8.2 Восстановление файла из прошлого коммита
Иногда нужно вернуть файл к состоянию из конкретного коммита, не откатывая всю ветку.
# Взять версию файла из конкретного коммита
git restore --source=abc1234 -- path/to/file
# Из предыдущего коммита
git restore --source=HEAD~1 -- path/to/file
# Из другой ветки
git restore --source=main -- path/to/file
# Файл появится в рабочей директории (и в staging)
# Проверить: git diff --cached
Это удобно, когда файл был сломан несколькими коммитами назад и нужна «здоровая» версия.
8.3 Отмена коммитов: reset vs revert
Это два принципиально разных подхода к отмене. Выбор зависит от того, опубликована ли история.
git reset — переписать историю
Три режима reset
- --soft — откатить только указатель HEAD. Изменения остаются в staging. Как будто вы «расконмитили».
- --mixed (по умолчанию) — откатить HEAD и unstage. Изменения в рабочей директории.
- --hard — откатить всё: HEAD, staging, рабочую директорию. Опасно!
# --soft: «расконмитить» последний коммит (изменения в staging)
git reset --soft HEAD~1
# Полезно: хотите переделать коммит — изменения уже в staging
# --mixed: убрать из staging + из истории (изменения в рабочей директории)
git reset HEAD~1
# Полезно: хотите перегруппировать изменения по коммитам
# --hard: полный откат (ОСТОРОЖНО — незакоммиченное теряется!)
git reset --hard HEAD~1
# Полезно: последний коммит полностью мусор
# Откатить на конкретный коммит
git reset --hard abc1234
git revert — безопасный откат
Revert создаёт новый коммит, который инвертирует изменения указанного коммита. История не переписывается — все видят и исходный коммит, и его отмену.
# Откатить конкретный коммит
git revert abc1234
# Откатить несколько коммитов подряд
git revert abc1234..def5678
# Откатить без автоматического коммита (сначала посмотреть)
git revert --no-commit abc1234
# Проверить изменения, потом: git commit
# Откат merge-коммита (нужно указать родителя)
git revert -m 1 <merge-commit-hash>
Когда что использовать
- reset — для локальных, неопубликованных коммитов. Можно переписать историю.
- revert — для публичных веток (main, develop). Безопасно, не ломает историю коллег.
8.4 Поиск «потерянного»: git reflog
Reflog — ваша страховочная сеть. Он записывает каждое перемещение HEAD, даже после reset --hard. Если вы «потеряли» коммит — скорее всего, его можно найти здесь.
# Посмотреть reflog
git reflog
# abc1234 HEAD@{0}: reset: moving to HEAD~3
# def5678 HEAD@{1}: commit: feat: добавить авторизацию
# ghi9012 HEAD@{2}: commit: fix: поправить валидацию
# ← Вот они, «потерянные» коммиты!
# Вернуться к «потерянному» состоянию
git reset --hard HEAD@{1}
# Или создать ветку (безопаснее)
git branch rescue/auth def5678
git switch rescue/auth
# Reflog для конкретной ветки
git reflog show main
8.5 Алгоритм «если всё сломали»
Пошаговая инструкция для паники:
- Не паникуйте. В Git почти всё поправимо.
- Не удаляйте ничего. Не делайте
rm -rf .git. - Оцените ситуацию:
# Что сейчас происходит?
git status
# Как выглядит история?
git log --oneline --graph --decorate --all
# Reflog — что делали последнее время?
git reflog
- Найдите «здоровую» точку в reflog
- Создайте спасательную ветку:
# Спасательная ветка от найденного хеша
git branch rescue/2025-02-17 abc1234
git switch rescue/2025-02-17
# Проверить, что всё ок
git log --oneline -5
# Если всё хорошо — продолжить работу
Совет: Если вы в середине merge/rebase и что-то пошло не так — всегда есть git merge --abort или git rebase --abort для безопасного отката.
8.6 Сроки хранения reflog
Reflog — локальный журнал. Записи хранятся ограниченное время:
# Проверить текущие настройки
git config gc.reflogExpire # по умолчанию 90 дней
git config gc.reflogExpireUnreachable # по умолчанию 30 дней
# Изменить срок хранения
git config --global gc.reflogExpire "180 days"
После истечения срока записи удаляются при git gc. Для важных спасательных операций действуйте оперативно.
8.7 Garbage collection и packfiles
Git автоматически оптимизирует хранилище, упаковывая объекты в packfiles.
# Запустить сборку мусора вручную
git gc
# Агрессивная оптимизация (медленно, но максимально сжимает)
git gc --aggressive
# Проверить размер репозитория
git count-objects -vH
8.8 Проверка целостности: git fsck
# Проверить целостность графа объектов
git fsck
# Найти «висячие» объекты (не привязаны ни к чему)
git fsck --unreachable
# Найти потерянные коммиты
git fsck --lost-found
# Результаты сохраняются в .git/lost-found/
8.9 Revert merge-коммита
Merge-коммит имеет двух родителей. При revert нужно указать, какой родитель считать «основным»:
# -m 1 = первый родитель (обычно main)
git revert -m 1 <merge-commit-hash>
# -m 2 = второй родитель (вливаемая ветка)
# Используется редко
Осторожно: после revert merge-коммита повторный merge этой ветки не подтянет уже «отменённые» изменения. Нужен revert revert'а или cherry-pick.
8.10 Глубже про reset: три дерева Git
Чтобы понять reset, нужно понимать «три дерева» Git:
Три дерева и три режима reset
- HEAD (текущий коммит) —
--softперемещает только его - Index (staging area) —
--mixedсбрасывает и HEAD, и index - Working Directory (файлы) —
--hardсбрасывает всё
# Пример: вы сделали 3 коммита и хотите объединить в один
# --soft: коммиты «раскоммичены», но всё в staging
git reset --soft HEAD~3
git commit -m "feat: объединённый коммит"
# --mixed: коммиты раскоммичены, изменения в рабочей директории
git reset HEAD~3
git add .
git commit -m "feat: объединённый коммит"
# --hard: всё стёрто, как будто 3 коммитов не было
git reset --hard HEAD~3
Практическое задание
Задание: сценарий спасения
- Создайте репозиторий с 5 коммитами
- Выполните
git reset --hard HEAD~3— «потеряйте» 3 коммита - Убедитесь:
git log --onelineпоказывает только 2 коммита - Найдите потерянные коммиты через
git reflog - Восстановите:
git reset --hard HEAD@{1}или создайте ветку от хеша - Проверьте: все 5 коммитов на месте