Инструкция

git reflog: как восстановить потерянные коммиты и ветки

Обновлено: · Skillaria.Top

git reflog — локальный журнал, куда Git записывает каждое перемещение HEAD и веток: коммиты, reset, checkout, rebase, merge. Найдите в git reflog запись до ошибки, например HEAD@{2}, и верните состояние командой git reset --hard HEAD@{2} или создайте ветку git branch rescue HEAD@{2}.

git reflog — это журнал ссылок (reference log): Git записывает в него каждое перемещение HEAD и концов веток. Сделали коммит, переключились на ветку, выполнили reset, rebase или merge — в reflog появилась строка с хешем, куда указывала ссылка после операции. Поэтому даже коммит, на который больше не ссылается ни одна ветка, можно найти и вернуть.

Главное, что нужно знать: reflog локальный. Он есть только в вашем клоне, не отправляется на сервер и не приходит при git clone. Зато в своём репозитории он спасает почти от любой ошибки с историей.

Как читать вывод git reflog

git reflog
3c4d5e6 (HEAD -> main) HEAD@{0}: reset: moving to HEAD~2
9a8b7c6 HEAD@{1}: commit: Добавить валидацию e-mail
1f2e3d4 HEAD@{2}: commit: Вынести конфиг в отдельный файл
3c4d5e6 (HEAD -> main) HEAD@{3}: checkout: moving from feature to main
7b6a5c4 (feature) HEAD@{4}: commit: Черновик формы входа

Каждая строка — одно состояние HEAD, сверху самое свежее:

  • хеш — коммит, на который указывал HEAD после операции;
  • HEAD@{n} — «где был HEAD n перемещений назад»; это полноценная ссылка, её можно передавать в любые команды;
  • описание — тип операции (commit, reset, checkout, rebase, merge, pull, cherry-pick, commit (amend)) и детали.

В примере видно: два коммита — «Вынести конфиг» и «Добавить валидацию» — были сделаны, а затем reset HEAD~2 откатил ветку. В git log их больше нет, но в reflog они на месте.

Reflog конкретной ветки

У каждой ветки свой журнал. Он короче и полезнее, когда вы много переключались между ветками:

git reflog show feature
git reflog show origin/main   # как менялась удалённая ветка при fetch

Ссылки по времени

Вместо номера можно указать момент времени — Git найдёт, куда указывала ссылка тогда:

git show main@{yesterday}
git diff main@{"2 hours ago"} main
git log -g --since="1 day ago" --oneline

Флаг -g (--walk-reflogs) у git log проходит по reflog вместо родительских связей и позволяет использовать привычные форматы и фильтры. Для вывода дат удобно git reflog --date=iso.

Как восстановить коммиты после git reset --hard

Самая частая ситуация: вы выполнили git reset --hard не на тот коммит, и работа «пропала». Коммиты на самом деле никуда не делись — их просто не видно из веток.

  1. Откройте журнал: git reflog.
  2. Найдите строку прямо перед reset: moving to ... — это состояние до ошибки. В примере выше — HEAD@{1} с хешем 9a8b7c6.
  3. Проверьте, что это то самое: git log --oneline -5 HEAD@{1}.
  4. Верните ветку: git reset --hard HEAD@{1}.
git reset --hard HEAD@{1}
HEAD is now at 9a8b7c6 Добавить валидацию e-mail

Будьте внимательны: после каждой операции номера сдвигаются. То, что было HEAD@{1}, после reset станет HEAD@{2}. Поэтому надёжнее копировать хеш, а не номер.

Важное ограничение: reflog хранит только закоммиченное. Незакоммиченные изменения, стёртые reset --hard или git checkout -- файл, через reflog не вернуть. Немного надежды остаётся, если файлы были добавлены в индекс — об этом ниже.

Как восстановить удалённую ветку

Вы удалили ветку командой git branch -D feature, а в ней были незалитые коммиты. Журнал самой ветки удаляется вместе с ней, но журнал HEAD остаётся — а HEAD побывал на её коммитах, когда вы на ней работали.

git reflog | grep feature
7b6a5c4 HEAD@{4}: commit: Черновик формы входа
3c4d5e6 HEAD@{3}: checkout: moving from feature to main

# Последний коммит ветки — тот, с которого с неё ушли
git branch feature 7b6a5c4

Ищите строку checkout: moving from feature to ...: хеш в предыдущей записи (ниже по списку) — последнее состояние ветки. Кстати, при удалении Git сам печатает хеш: Deleted branch feature (was 7b6a5c4). — если терминал ещё открыт, хватит его.

Как откатить неудачный rebase

Rebase переписывает коммиты, и если в процессе вы неправильно разрешили конфликты, результат может оказаться хуже исходного. Reflog хранит состояние до начала rebase.

git reflog
e1f2a3b HEAD@{0}: rebase (finish): returning to refs/heads/feature
e1f2a3b HEAD@{1}: rebase (pick): Черновик формы входа
c4d5e6f HEAD@{2}: rebase (start): checkout main
7b6a5c4 HEAD@{3}: commit: Черновик формы входа

git reset --hard HEAD@{3}

Нужна запись перед rebase (start). Есть и короткий путь: перед опасными операциями (rebase, merge, reset) Git сохраняет прежнее положение в ссылку ORIG_HEAD, поэтому сразу после rebase сработает git reset --hard ORIG_HEAD. Но ORIG_HEAD перезаписывается следующей такой операцией, а reflog хранит всю историю.

Как вернуть коммит до git commit --amend

--amend не изменяет коммит, а создаёт новый на его месте. Старый остаётся в reflog:

git reflog
5a6b7c8 HEAD@{0}: commit (amend): Добавить валидацию e-mail
9a8b7c6 HEAD@{1}: commit: Добавить валидацию e-mail

git diff HEAD@{1} HEAD          # что изменил amend
git reset --soft HEAD@{1}       # вернуть старый коммит, изменения amend останутся в индексе

Безопасное восстановление: ветка вместо reset

Если не уверены, какой из коммитов нужен, не делайте reset --hard вслепую. Создайте спасательную ветку — она ничего не ломает и защищает найденный коммит от удаления:

git branch rescue HEAD@{5}
git log --oneline rescue
git switch rescue

Дальше коммиты из rescue можно перенести через cherry-pick или слить, а ветку удалить, когда всё вернётся на место.

Сколько хранится reflog

Записи не вечны. Их чистит git gc, который Git периодически запускает сам. Сроки задают настройки:

НастройкаПо умолчаниюЧто означает
gc.reflogExpire90 днейСрок для записей с коммитами, достижимыми из текущих веток
gc.reflogExpireUnreachable30 днейСрок для записей с коммитами, недостижимыми ни из одной ветки — как раз «потерянных»
gc.pruneExpire2 неделиЧерез сколько удаляются сами недостижимые объекты, на которые не ссылается уже ничего

Практический вывод: потерянный коммит гарантированно доступен через reflog около месяца. Не откладывайте восстановление и не запускайте вручную git gc --prune=now или git reflog expire --expire=now --all, пока не вернули нужное.

Если reflog не помог: git fsck

Бывают случаи, когда коммита в reflog нет: например, его создал git stash, а потом stash удалили через git stash drop, или изменения были только в индексе. Тогда ищите «висячие» объекты:

git fsck --lost-found
dangling commit 2b3c4d5e6f...
dangling blob 8e9f0a1b2c...

git show 2b3c4d5e6f            # посмотреть содержимое
git branch recovered 2b3c4d5e6f

Висячие коммиты из удалённого stash содержат сообщение «WIP on ...». Висячие blob — это содержимое файлов, которые когда-то были добавлены через git add, но не закоммичены. Имени файла у них нет, придётся опознавать по содержимому, зато так иногда удаётся спасти работу после reset --hard. С флагом --lost-found Git ещё и копирует их в .git/lost-found/.

Чего reflog не умеет

  • Не работает между машинами. Если коллега сделал force push и затёр ваши коммиты на сервере, ищите их в reflog того, у кого они были локально, — например, git reflog show origin/main у любого, кто делал fetch до затирания.
  • Не видит незакоммиченное. Поэтому лучшая страховка перед рискованной операцией — коммит или git stash.
  • Не хранит историю вечно. Сроки выше, плюс свежий клон начинает с пустого журнала.

Практика: страховка перед опасной операцией

Reflog спасает, но восстановление по нему требует внимательности: нужно найти правильную запись среди десятков похожих. Гораздо проще заранее поставить метку. Перед rebase, большим reset или экспериментами с историей создайте временную ветку или тег:

git branch backup/before-rebase
git rebase -i main
# что-то пошло не так
git reset --hard backup/before-rebase
# всё получилось
git branch -D backup/before-rebase

Это стоит одну команду и избавляет от поиска хеша. Reflog остаётся запасным вариантом на случай, когда о страховке вспомнили слишком поздно.

Как быстро найти нужную запись

В длинном журнале помогает фильтрация. Ищите по типу операции или тексту сообщения коммита, а в сомнительных случаях смотрите содержимое:

git reflog | grep -E "reset|rebase \(start\)"
git log -g --grep="валидац" --oneline
git show --stat HEAD@{7}

Команда git log -g с --grep ищет по сообщениям коммитов из reflog, а git show --stat показывает, какие файлы затронуты, — по ним легко опознать нужный коммит, даже если сообщение было неинформативным вроде «wip».

Итог

git reflog — страховочная сетка Git: всё, что было закоммичено в вашем репозитории за последние недели, можно найти и вернуть. Порядок действий всегда одинаков: открыть журнал, найти запись до ошибки, проверить её через git log или git show, а затем создать ветку или сделать reset --hard. Работа с restore, reset, revert и восстановлением после ошибок разобрана на практике в курсе по Git.

Частые вопросы

Чем git reflog отличается от git log?

git log идёт по родительским связям коммитов и показывает историю ветки. git reflog показывает, куда перемещались HEAD и ветки в вашем репозитории, включая коммиты, которых в истории веток уже нет.

Попадает ли reflog на GitHub или GitLab?

Нет, reflog хранится только в локальной папке .git/logs и не передаётся при push и clone.

Можно ли очистить reflog?

Да, командой git reflog expire --expire=now --all с последующим git gc --prune=now, но после этого потерянные коммиты восстановить уже не получится.

Где физически лежит reflog?

В текстовых файлах .git/logs/HEAD и .git/logs/refs/heads/имя-ветки, по строке на каждое перемещение.

Работает ли reflog в bare-репозитории на сервере?

По умолчанию в bare-репозиториях reflog выключен (core.logAllRefUpdates), поэтому на сервере на него рассчитывать нельзя.

Разберите тему на видео

Эта тема подробно разобрана с практикой в курсе «Git с нуля до уверенного разработчика».

Перейти к курсу