Восстановление потерянных коммитов с помощью Git Reflog
Восстановите потерянные коммиты Git с reflog: git reflog находит осиротевшие коммиты, отменяет hard reset и rebase, и возвращает ветки.
Чтобы восстановить потерянный коммит, выполните git reflog, скопируйте хеш нужного коммита и восстановите его командой git checkout -b recovered <hash>.
Вы имели в виду HEAD~1, набрали HEAD~2 — и наблюдали, как три часа работы исчезают из git log. Несколько секунд между нажатием Enter и моментом, когда вы вспоминаете о существовании reflog, — худшие в Git. Когда вы выполняете git reset --hard, портите rebase или удаляете ветку, ваши коммиты почти никогда не уничтожаются: они просто теряют связь с ветками, а их хеши по-прежнему записаны в reflog. В этом руководстве приведён быстрый рецепт восстановления для трёх сценариев, вызывающих панику, а затем — жёсткие ограничения, о которых стоит знать, чтобы не потерять работу во второй раз. Все команды ниже можно копировать и вставлять.
Ключевые выводы
git reflogфиксирует каждое перемещениеHEADв вашем локальном репозитории (commit, checkout, reset, rebase, merge), поэтому «потерянный» коммит обычно находится в одномgit reset --hard <hash>от возвращения.- Самый безопасный способ восстановления —
git checkout -b recovered <hash>: восстановите коммит в новой ветке и изучите его, прежде чем трогать реальную ветку. - Reflog не может восстановить незакоммиченные изменения рабочего каталога, потому что Git изначально не фиксировал их ни в одной ссылке (ref).
- По умолчанию Git хранит достижимые записи reflog 90 дней, а недостижимые — 30 дней, поэтому восстанавливайте оперативно и не запускайте
git gc, пока не вернёте коммиты. - Reflog строго локален и никогда не отправляется на сервер, поэтому он способен спасти вашу собственную потерянную работу, но не неотправленные коммиты коллеги.
Как работает Git reflog?
Reflog в Git — это локальный журнал всех позиций, которые занимали вершины ваших ветвей и другие ссылки. Каждая операция commit, checkout, reset, rebase и merge перемещает HEAD и попадает в журнал. Поэтому когда коммит становится «потерянным» (то есть на него больше не указывает ни одна ветка или тег), он лишь остаётся без ссылки, но не удаляется, и его хеш всё ещё лежит в reflog, ожидая повторного присоединения.
Запустите команду без аргументов, чтобы увидеть историю HEAD:
git reflog
b58145c HEAD@{0}: reset: moving to HEAD~2
dd7c37e HEAD@{1}: commit: one more commit
b5b3286 HEAD@{2}: checkout: moving from main to feature
b58145c HEAD@{3}: commit: very important commit
Каждую строку следует читать так: короткий хеш, индекс HEAD@{n} (насколько ходов назад находится эта запись) и описание операции. В приведённом выше выводе HEAD@{0} — это только что выполненный деструктивный reset, а HEAD@{1} (dd7c37e) — коммит, от которого он ушёл, тот самый, который вы хотите вернуть. Под капотом git reflog show выполняет то же самое, что и git log -g --abbrev-commit --pretty=oneline, и принимает любую ссылку, так что git reflog show main покажет историю конкретной ветки.
Discover how at OpenReplay.com.
Восстановление после случайного hard reset
git reset --hard перемещает указатель ветки, но оставляет старый коммит нетронутым в хранилище объектов. Найдите его в reflog, а затем снова направьте на него ссылку. Потерянный коммит — это тот, что был непосредственно перед reset, обычно HEAD@{1}.
git reflog
# HEAD@{0}: reset: moving to HEAD~2
# HEAD@{1}: commit: one more commit <-- this hash
git checkout -b recovered dd7c37e
Самый безопасный путь восстановления — сначала проверить, а потом перезаписывать: выполните git checkout -b recovered <hash>, чтобы восстановить коммит в новой ветке, проверьте его с помощью git log и только затем решайте, стоит ли перемещать реальную ветку. Когда вы уверены, можно переставить ветку напрямую:
git reset --hard dd7c37e # or: git reset --hard HEAD@{1}
Предупреждение о двойном риске: git reset --hard отбрасывает любые текущие незакоммиченные изменения в рабочем дереве. Если дерево «грязное», сначала выполните git stash или воспользуйтесь вариантом с новой веткой. Иначе команда восстановления приведёт ко второй потере. Сразу после reset можно также использовать git reset --hard ORIG_HEAD, поскольку git reset записывает предыдущую вершину ветки в ORIG_HEAD перед перемещением.
Отмена неудачного rebase
Rebase перезаписывает историю, и неверно разрешённый конфликт может незаметно отбросить коммиты. Reflog хранит вершину ветки до rebase. После неудачного rebase команда git reset --hard ORIG_HEAD мгновенно возвращает ветку туда, откуда она начиналась. Но ORIG_HEAD перезаписывается следующей операцией reset, rebase или merge, поэтому, если вы уже успели выполнить одну из них, ищите вершину до rebase в git reflog.
git reflog
# HEAD@{0}: rebase (finish): returning to refs/heads/feature
# HEAD@{1}: rebase (pick): one more commit
# HEAD@{2}: rebase (start): checkout main
# HEAD@{3}: checkout: moving from main to feature <-- pre-rebase tip
git reset --hard HEAD@{3}
Ищите запись rebase (start); строка непосредственно перед ней — это состояние вашей ветки до rebase. Если вам нужны только отдельные коммиты, отброшенные rebase, а не отмена всей операции, возьмите их хеши из reflog и воспроизведите их:
git cherry-pick <hash>
Восстановление удалённой ветки
Удаление ветки убирает ссылку, но не коммиты. Её последний коммит по-прежнему присутствует в reflog HEAD (с момента, когда вы последний раз переключались на неё), поэтому найдите этот хеш и создайте ветку заново на нём.
git reflog
# HEAD@{0}: checkout: moving from example-branch to main
# HEAD@{1}: commit: very important commit <-- last commit on deleted branch
git checkout -b example-branch b5b3286 # or HEAD@{1}
git checkout -b <branch> <hash> создаёт ветку заново, указывающую на восстановленный коммит, с сохранением его истории. Если вы помните часть сообщения коммита, но не хеш, команда git log --oneline -g --grep='<fragment>' найдёт его в reflog. (git switch -c — современный аналог checkout -b; работают оба варианта, и checkout не считается устаревшим.)
Что Git reflog восстановить не может?
Reflog может восстановить всё, что перемещало HEAD или вершину ветки (коммиты, reset, rebase, удалённые ветки), но он не может восстановить незакоммиченные изменения рабочего каталога, потому что Git изначально не фиксировал их ни в одной ссылке. Если изменение никогда не было закоммичено, то нет и обновления ссылки, на которое мог бы указать reflog, поэтому поиск в reflog ничего не даст.
| Reflog МОЖЕТ восстановить | Reflog НЕ МОЖЕТ восстановить |
|---|---|
Коммиты, потерявшие ссылки из-за reset --hard | Незакоммиченные правки в рабочем дереве |
| Коммиты, отброшенные неудачным rebase | Проиндексированные, но не закоммиченные изменения |
| Удалённые локальные ветки (недавние) | Неотправленные коммиты коллеги |
| Ваше локальное представление удалённой ветки после force-push | Записи, срок которых истёк и которые собраны сборщиком мусора |
Восстановление регулируется ещё тремя ограничениями:
- Он локален и временен. Reflog существует только в вашем каталоге
.gitи никогда не отправляется на сервер, поэтому он спасает вашу собственную работу, но не неотправленные коммиты коллеги. - Записи истекают. По умолчанию Git хранит достижимые записи reflog 90 дней, а недостижимые — 30 дней, что задаётся параметрами
gc.reflogExpireиgc.reflogExpireUnreachable. Коммиты без ссылок существуют до истечения срока и последующей сборки мусора, поэтому восстанавливайте оперативно и не запускайтеgit gc, пока не закончите. - Сначала восстанавливайте в новую ветку. Сделайте
git checkout -b recovered <hash>действием по умолчанию, чтобы иметь возможность проверить результат до изменения общей ветки. И пишите содержательные сообщения коммитов: reflog показывает их, поэтому «Fix login bug» найти гораздо проще, чем что-то расплывчатое.
Бонус (после force-push): на удалённом сервере нет reflog, который вы могли бы прочитать, но он есть у вашей локальной отслеживающей ссылки. Чтобы восстановить коммиты, стёртые с удалённого репозитория force-push, проверьте свою локальную запись командой git reflog show origin/<branch>. Запись непосредственно перед force-push содержит старую вершину.
Заключение
Reflog — это локальная страховочная сетка Git, превращающая почти любой момент «я потерял свою работу» в исправление из двух команд: прочитать журнал и переставить ссылку на хеш. Единственное, что он не способен спасти, — работу, которую вы никогда не коммитили, поэтому главный устойчивый вывод: коммитьте рано и часто — это гарантирует, что запись в reflog всегда найдётся. В следующий раз, когда терминал заставит ваше сердце уйти в пятки, выполните git reflog прежде всего остального.
Часто задаваемые вопросы
В чём разница между git reflog и git log?
git log показывает историю коммитов, достижимую от вершины ветки, следуя указателям на родителей, поэтому коммиты без ссылок в ней никогда не появляются. git reflog показывает каждую позицию, которую занимал HEAD или другая ссылка в вашем локальном репозитории — коммиты, checkout, reset, rebase и merge — включая коммиты, на которые больше не указывает ни одна ветка. Именно поэтому reflog может найти коммит после hard reset, когда git log не может: коммит потерял ссылку, но обновление ссылки всё ещё записано в журнале.
Работает ли git reflog после клонирования репозитория?
Нет. Reflog хранится только в вашем локальном каталоге .git и никогда не отправляется и не загружается, поэтому свежий клон начинается с пустого reflog, охватывающего только действия, выполненные после клонирования. Он не может показать перемещения HEAD до клонирования или раскрыть локальную историю коллеги. Reflog спасает вашу собственную потерянную работу в вашем собственном репозитории; это не общий инструмент восстановления и не инструмент, поддерживаемый удалённым сервером.
Можно ли восстановить коммит после истечения срока записей git reflog?
Через сам reflog — нет. По умолчанию достижимые записи истекают через 90 дней, а недостижимые — через 30 дней, и как только сборка мусора удалит объекты без ссылок, они действительно потеряны. До этого момента иногда всё же можно найти хеш с помощью git fsck --lost-found, которая перечисляет висячие (dangling) коммиты в хранилище объектов. Надёжный подход — восстанавливать оперативно и не запускать git gc, пока коммиты не вернулись.
Как найти потерянный коммит, если я не знаю его хеш?
Ищите непосредственно в reflog. Выполните git log --oneline -g --grep='fragment', чтобы отфильтровать записи reflog по части сообщения коммита, или git reflog --date=relative, чтобы просмотреть записи по времени и найти операцию, которую нужно отменить. Для коммитов без ссылок, у которых нет записи в reflog, команда git fsck --lost-found перечислит висячие коммиты, которые можно изучить с помощью git show перед восстановлением в новую ветку командой git checkout -b recovered <hash>.