Код: Выделить всё
git reset --hard HEAD~3Код: Выделить всё
HEAD~1Код: Выделить всё
git logТак вот: они НЕ исчезли. Почти наверняка они лежат целыми на месте, и через двадцать секунд ты вернёшь всё до байта. Спасает тебя git reflog - локальная летопись, которая помнит каждое движение твоего HEAD и веток, даже то, что ты считаешь "удалённым" и "потерянным". Это страховка номер один в Git, и сегодня мы разберём её по косточкам: как устроена, где живёт, сколько помнит, и как ею реально вытаскивать код из небытия.
Что такое reflog и почему он вообще существует
Чтобы понять reflog git, надо вспомнить одну вещь из устройства Git. Ветка - это просто файл с одним SHA внутри.
Код: Выделить всё
refs/heads/mainИ вот ключевая идея: Git не хочет, чтобы ты терял старые значения указателей по неосторожности. Поэтому при КАЖДОМ движении ссылки он дописывает строчку в специальный журнал - reflog (reference log, журнал ссылок). Физически это обычные текстовые файлы:
Код: Выделить всё
.git/logs/HEAD # журнал движений HEAD
.git/logs/refs/heads/main # журнал движений ветки main
.git/logs/refs/heads/feature
Код: Выделить всё
$ cat .git/logs/HEAD
0000000... 9f2a1c3 Ann <ann@x> 1718450000 +0300 commit (initial): init
9f2a1c3 4b7d8e2 Ann <ann@x> 1718450100 +0300 commit: add parser
4b7d8e2 c1a9f05 Ann <ann@x> 1718450200 +0300 commit: add tests
c1a9f05 9f2a1c3 Ann <ann@x> 1718450300 +0300 reset: moving to HEAD~2
Код: Выделить всё
c1a9f05Код: Выделить всё
9f2a1c3Код: Выделить всё
c1a9f05Теперь главное свойство, которое надо вбить гвоздём: reflog абсолютно локальный. Эти файлы лежат в твоём
Код: Выделить всё
.gitКод: Выделить всё
pushКод: Выделить всё
cloneКод: Выделить всё
fetch
Читаем reflog: HEAD@{N}, branch@{1} и время
Базовая команда:
Код: Выделить всё
$ git reflog
c1a9f05 (HEAD -> main) HEAD@{0}: reset: moving to HEAD~2
9f2a1c3 HEAD@{1}: commit: add tests
4b7d8e2 HEAD@{2}: commit: add parser
0000000 HEAD@{3}: commit (initial): init
Код: Выделить всё
git reflogКод: Выделить всё
git reflog show HEADКод: Выделить всё
HEAD@{0}Код: Выделить всё
HEAD@{1}Код: Выделить всё
HEAD@{2}Код: Выделить всё
HEAD~2Код: Выделить всё
HEAD~2Код: Выделить всё
HEAD@{2}Этот синтаксис - полноценная ссылка, её можно совать почти в любую команду:
Код: Выделить всё
$ git show HEAD@{2} # показать коммит, где HEAD был 2 шага назад
$ git diff HEAD@{0} HEAD@{3}
$ git log feature@{1} # где была ветка feature одно движение назад
Код: Выделить всё
git reflog show featureКод: Выделить всё
feature@{1}Git понимает и временные координаты внутри фигурных скобок:
Код: Выделить всё
$ git show HEAD@{yesterday}
$ git log main@{2.days.ago}
$ git show 'HEAD@{1 hour ago}'
Код: Выделить всё
HEAD@{5}Код: Выделить всё
HEAD@{5.minutes.ago}Код: Выделить всё
HEAD@{yesterday}Ещё одно различие:
Код: Выделить всё
git reflogКод: Выделить всё
git log -gКод: Выделить всё
git log --walk-reflogsРеальные спасения: восстановить после reset hard, rebase и не только
Теперь то, ради чего всё затевалось. Общий алгоритм спасения всегда один: открыть reflog -> глазами найти SHA "хорошего" состояния -> вернуться на него через reset/checkout/branch. Разберём боевые кейсы.
Кейс 1. Восстановить после reset hard. Тот самый сценарий из вступления. Снёс три коммита.
Код: Выделить всё
$ git reflog
9f2a1c3 (HEAD -> main) HEAD@{0}: reset: moving to HEAD~3
a8c41d0 HEAD@{1}: commit: финальные правки
7e2b9f1 HEAD@{2}: commit: рефакторинг
3d5a8c2 HEAD@{3}: commit: новая фича
Код: Выделить всё
HEAD@{1}Код: Выделить всё
a8c41d0Код: Выделить всё
$ git reset --hard a8c41d0
HEAD is now at a8c41d0 финальные правки
Код: Выделить всё
git reset --hard HEAD@{1}Кейс 2. Испорченный rebase. Интерактивный rebase - чемпион по "ой, что я наделал": сквошнул не то, дропнул нужный коммит, разрулил конфликт неправильно. Лечится одной строкой. Перед любым rebase Git ставит в журнал маркер:
Код: Выделить всё
$ git reflog
b41f7a2 (HEAD -> main) HEAD@{0}: rebase (finish): returning to refs/heads/main
b41f7a2 HEAD@{1}: rebase (pick): apply hotfix
8c0d3e9 HEAD@{2}: rebase (start): checkout main~4
e7f9a51 HEAD@{3}: commit: важный коммит который я случайно дропнул
2a6b8c4 HEAD@{4}: checkout: moving to feature
Код: Выделить всё
e7f9a51Код: Выделить всё
HEAD@{3}Код: Выделить всё
$ git reset --hard HEAD@{3}
Кейс 3. Вернуть удаленную ветку git. Снёс ветку
Код: Выделить всё
git branch -D experimentКод: Выделить всё
git branchКод: Выделить всё
git logКод: Выделить всё
$ git reflog | grep experiment
$ git log -g --grep experiment # или так, аккуратнее
Код: Выделить всё
checkout: moving to experimentКод: Выделить всё
d9a3f17Код: Выделить всё
$ git branch experiment d9a3f17
Кейс 4. Detached HEAD и потерянные коммиты. Самый коварный. Ты сделал
Код: Выделить всё
git checkout <sha>Код: Выделить всё
$ git reflog
4f8e1a0 (HEAD -> main) HEAD@{0}: checkout: moving from 91b2c5d to main
91b2c5d HEAD@{1}: commit: эксперимент в detached HEAD
77a0e3b HEAD@{2}: checkout: moving from main to 77a0e3b
Код: Выделить всё
91b2c5dКод: Выделить всё
$ git branch rescue 91b2c5d
$ git switch rescue
У страховки есть срок годности, и его надо знать, чтобы не понадеяться на чудо через полгода. Записи reflog не вечны - их подчищает обслуживание репозитория (раньше это был
Код: Выделить всё
git gc- gc.reflogExpire - по умолчанию 90 дней. Применяется к записям, которые ведут к ДОСТИЖИМЫМ коммитам (на них всё ещё можно дойти от какой-нибудь ветки или тега).
- gc.reflogExpireUnreachable - по умолчанию 30 дней. Применяется к записям про НЕДОСТИЖИМЫЕ коммиты - те самые "потерянные" после reset --hard, которые ни одна ветка больше не держит.
Посмотреть и подкрутить:
Код: Выделить всё
$ git config gc.reflogExpire
$ git config gc.reflogExpire 180.days # хранить достижимое полгода
$ git config gc.reflogExpireUnreachable never # вообще не чистить (для паранойи)
Код: Выделить всё
.gitТипичные грабли
- Путать HEAD~N и HEAD@{N}. Первое - родитель коммита (по графу), второе - позиция в журнале (по времени твоих действий). и
Код: Выделить всё
git reset HEAD@{1}почти всегда дают РАЗНЫЙ результат. После reset тебе почти всегда нуженКод: Выделить всё
git reset HEAD~1.Код: Выделить всё
HEAD@{1} - Делать ещё один reset --hard поверх паники. Не суетись. Сначала , глазами найди нужный SHA, и только потом действуй. Reflog никуда не денется за минуту размышлений.
Код: Выделить всё
git reflog - Надеяться на reflog после clone/на сервере. Он локальный. Нет твоей машины - нет твоего reflog. Для бэкапов это не замена.
- Думать, что reflog ловит ВСЁ. Он ловит движения ССЫЛОК. Изменения, которые ты не закоммитил (правки в рабочей директории, не добавленный в индекс файл), в reflog не попадают - там нечему двигаться. Reflog - страховка для коммитов, не для несохранённой работы.
- Тянуть с восстановлением. Помни про 30 дней на недостижимое. Запустилось обслуживание - и потерянный коммит может реально исчезнуть.
Сделай по шагам в пустой папке, это пять минут, но навык спасения останется в руках навсегда.
Код: Выделить всё
# 1. Готовим полигон
git init reflog-lab && cd reflog-lab
git branch -m main
git commit --allow-empty -m "c1: старт"
git commit --allow-empty -m "c2: фича"
git commit --allow-empty -m "c3: ещё фича"
# 2. Запоминаем, как сейчас выглядит история
git log --oneline
# 3. Имитируем катастрофу - сносим два коммита
git reset --hard HEAD~2
git log --oneline # видим только c1, c2 и c3 "пропали"
# 4. Открываем журнал и находим вершину ДО reset
git reflog # ищем строку "reset: moving to HEAD~2", над ней - спасение
# 5. Возвращаем всё
git reset --hard HEAD@{1}
git log --oneline # c2 и c3 на месте
# 6. Бонус: ловим коммит из detached HEAD
git checkout HEAD~1
git commit --allow-empty -m "secret: коммит в detached HEAD"
git switch main # Git предупредит про "leaving 1 commit behind"
git reflog | head -3 # находим SHA secret-коммита
git branch rescued <SHA-из-reflog>
git log --oneline rescued
Контрольные вопросы
- Чем отличается от
Код: Выделить всё
HEAD@{2}? В каком из спасательных кейсов нужен именно первый вариант?Код: Выделить всё
HEAD~2 - Ты сделал и потерял коммиты. Сколько примерно дней у тебя есть, чтобы их вернуть, прежде чем обслуживание их зачистит? Какая настройка за это отвечает?
Код: Выделить всё
git reset --hard - Почему после свежего в reflog почти ничего нет, и можно ли по чужому reflog узнать, куда двигался HEAD у предыдущего владельца репозитория?
Код: Выделить всё
git clone - Ты удалил ветку через . По каким двум зацепкам в reflog HEAD её можно найти и какой командой воскресить, не трогая текущую ветку?
Код: Выделить всё
git branch -D feature
Reflog - это локальный журнал движений HEAD и веток, который пишется при каждом коммите, reset, rebase, checkout, merge и хранит ПРЕДЫДУЩИЕ значения ссылок. Пока запись жива (90 дней для достижимого, 30 для потерянного), коммит можно вернуть: открываешь
Код: Выделить всё
git reflog