Git

Модуль 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 Алгоритм «если всё сломали»

Пошаговая инструкция для паники:

  1. Не паникуйте. В Git почти всё поправимо.
  2. Не удаляйте ничего. Не делайте rm -rf .git.
  3. Оцените ситуацию:
# Что сейчас происходит?
git status

# Как выглядит история?
git log --oneline --graph --decorate --all

# Reflog — что делали последнее время?
git reflog
  1. Найдите «здоровую» точку в reflog
  2. Создайте спасательную ветку:
# Спасательная ветка от найденного хеша
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

Практическое задание

Задание: сценарий спасения

  1. Создайте репозиторий с 5 коммитами
  2. Выполните git reset --hard HEAD~3 — «потеряйте» 3 коммита
  3. Убедитесь: git log --oneline показывает только 2 коммита
  4. Найдите потерянные коммиты через git reflog
  5. Восстановите: git reset --hard HEAD@{1} или создайте ветку от хеша
  6. Проверьте: все 5 коммитов на месте

Настройки

Цветовая схема

Тема