git reflog: как восстановить потерянные коммиты и ветки
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 не на тот коммит, и работа «пропала». Коммиты на самом деле никуда не делись — их просто не видно из веток.
- Откройте журнал:
git reflog. - Найдите строку прямо перед
reset: moving to ...— это состояние до ошибки. В примере выше —HEAD@{1}с хешем9a8b7c6. - Проверьте, что это то самое:
git log --oneline -5 HEAD@{1}. - Верните ветку:
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.reflogExpire | 90 дней | Срок для записей с коммитами, достижимыми из текущих веток |
gc.reflogExpireUnreachable | 30 дней | Срок для записей с коммитами, недостижимыми ни из одной ветки — как раз «потерянных» |
gc.pruneExpire | 2 недели | Через сколько удаляются сами недостижимые объекты, на которые не ссылается уже ничего |
Практический вывод: потерянный коммит гарантированно доступен через 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 с нуля до уверенного разработчика».
Перейти к курсу