git reflog: журнал, который помнит всё

Рейтинг: 64.6% · 12 голосов
Самый подробный курс по Git - от первого коммита до объектной базы и packfiles. Терминал и первый репозиторий, ветки и слияние, конфликты, удалённая работа и форджи (GitHub, GitLab), rebase и переписывание истории, спасение кода через reflog и fsck, stash и worktree, bisect и blame, подмодули и монорепо, хуки и подпись коммитов, внутреннее устройство и масштаб. Для новичков и профи. Актуально на 2026 (Git 2.54, дорожная карта 3.0).
Ответить
Аватара пользователя
Pavel_Git
Сообщения: 52
Зарегистрирован: 11 май 2026, 05:31

git reflog: журнал, который помнит всё

Сообщение Pavel_Git »

Оглавление курса (52)
  1. Что такое контроль версий и почему победил Git
  2. Установка Git на Windows, macOS и Linux и первая настройка
  3. Терминал и как спрашивать у Git: выживание в командной строке
  4. Ментальная модель Git: три состояния, коммит-снимок и граф
  5. Первый репозиторий: init, add, commit, status
  6. Просмотр изменений: status, diff и индексация по частям
  7. История проекта: git log, диапазоны и поиск
  8. .gitignore: что не должно попасть в репозиторий
  9. .gitattributes: окончания строк, бинарники и атрибуты
  10. Ветки как указатели: branch, switch и detached HEAD
  11. Слияние веток: fast-forward против трёхстороннего merge
  12. Конфликты слияния: анатомия и уверенное разрешение
  13. Практикум: твой первый день с Git от нуля до push
  14. Удалённые репозитории: remote, fetch, базовый pull и refspec
  15. git push: безопасная отправка и защита от перезаписи
  16. Клонирование: shallow, partial clone, bundle и backfill
  17. GitHub, GitLab и форки: pull request, rulesets и защита веток
  18. Рабочие процессы команды: GitFlow, GitHub Flow, trunk-based
  19. Merge queue и merge trains: безопасное вливание в trunk
  20. Культура code review и Conventional Commits
  21. git reset: три дерева и отмена индексации и коммитов
  22. git revert: безопасная отмена в общей истории
  23. Правка последнего коммита: git commit --amend
  24. git rebase: пересадка истории и золотое правило
  25. Интерактивный rebase: переписать серию коммитов
  26. git cherry-pick: перенос отдельных коммитов
  27. Stacked diffs: цепочка зависимых PR
  28. Массовое переписывание истории: git filter-repo и git replace
  29. git reflog: журнал, который помнит всё (вы здесь)
  30. Восстановление утерянных коммитов, веток и файлов
  31. Откат опасных операций: reset --hard, сломанный rebase, плохой merge
  32. git stash: тайник для незавершённой работы
  33. git worktree: несколько рабочих деревьев одного репозитория
  34. git bisect: бинарный поиск регрессии
  35. git blame, mailmap и археология кода
  36. Теги, релизы, archive и bundle
  37. Подмодули: внешние репозитории внутри проекта
  38. Subtree, монорепозитории и sparse-checkout
  39. Git LFS и большие файлы
  40. Вспомогательные команды: clean, mv, rm, restore
  41. Псевдонимы и кастомизация .gitconfig
  42. Хуки Git: автоматизация на клиенте и сервере
  43. Подпись коммитов и тегов: SSH, GPG и Sigstore
  44. Внутреннее устройство I: объектная база Git
  45. Внутреннее устройство II: ссылки, HEAD, notes и reftable
  46. Внутреннее устройство III: индекс, packfiles, gc и обслуживание
  47. От SHA-1 к SHA-256: переход на новый хеш
  48. Огромные репозитории: partial clone, FSMonitor, Scalar
  49. Git в CI/CD: shallow, кеш, merge_group и безопасность
  50. Куда движется Git: дорожная карта 3.0 и экосистема
  51. Профессиональный setup: собираем рабочее окружение Git
  52. 30+ типичных ошибок Git и как из них выбираться
Представь: ты сидишь на ветке feature, у тебя три часа работы в коммитах, и ты решаешь "переписать историю". Жмёшь

Код: Выделить всё

git reset --hard HEAD~3
, потому что хотел , но пальцы соврали. Терминал молча возвращает приглашение. Три коммита исчезли. В логе их нет, в нет, в ветке нет. Холодок по спине.

Так вот: они НЕ исчезли. Почти наверняка они лежат целыми на месте, и через двадцать секунд ты вернёшь всё до байта. Спасает тебя git reflog - локальная летопись, которая помнит каждое движение твоего HEAD и веток, даже то, что ты считаешь "удалённым" и "потерянным". Это страховка номер один в Git, и сегодня мы разберём её по косточкам: как устроена, где живёт, сколько помнит, и как ею реально вытаскивать код из небытия.

Что такое reflog и почему он вообще существует

Чтобы понять reflog git, надо вспомнить одну вещь из устройства Git. Ветка - это просто файл с одним SHA внутри.

Код: Выделить всё

refs/heads/main
- это текстовый файл, в котором лежит хеш последнего коммита. Когда ты делаешь коммит, reset, merge, rebase, checkout - ты двигаешь этот указатель: меняешь SHA в файле. Старое значение при этом затирается.

И вот ключевая идея: 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
Каждая строка: старый SHA, новый SHA, кто, когда, что за операция. Последняя строка - это и есть наш фатальный reset: HEAD прыгнул с обратно на . Но СТАРОЕ значение навсегда записано тут, слева. Вот за эту "левую колонку" мы и будем цепляться, когда надо восстановить коммит git.

Теперь главное свойство, которое надо вбить гвоздём: reflog абсолютно локальный. Эти файлы лежат в твоём и больше нигде. Они НЕ передаются при , НЕ скачиваются при , НЕ ходят через . Reflog описывает историю твоих личных действий в этом конкретном клоне. У коллеги свой reflog, на сервере reflog обычно вообще выключен. Отсюда два следствия: во-первых, твой reflog - это только твоя страховка, на чужую машину он не приедет; во-вторых, свежесклонированный репозиторий reflog почти пустой - там одна запись о клоне, и спасать ранние "потерянные" коммиты в нём нечем.

Изображение

Читаем 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
. Слева укороченный SHA, справа - синтаксис HEAD@{N}. Это координата во времени:

Код: Выделить всё

HEAD@{0}
- где HEAD сейчас,

Код: Выделить всё

HEAD@{1}
- где он был ОДНО движение назад,

Код: Выделить всё

HEAD@{2}
- два движения назад. Это не "коммит-родитель" (то пишется ), а "предыдущая ПОЗИЦИЯ указателя в журнале". Разница принципиальная: идёт по родителям коммита,

Код: Выделить всё

HEAD@{2}
идёт по строкам твоего журнала.

Этот синтаксис - полноценная ссылка, её можно совать почти в любую команду:

Код: Выделить всё

$ git show HEAD@{2}        # показать коммит, где HEAD был 2 шага назад
$ git diff HEAD@{0} HEAD@{3}
$ git log feature@{1}      # где была ветка feature одно движение назад
Журнал есть не только у HEAD, но и у каждой ветки.

Код: Выделить всё

git reflog show feature
покажет движения именно этой ветки, а

Код: Выделить всё

feature@{1}
- её предыдущее положение. Это золото, когда ты сломал конкретную ветку, а не просто HEAD.

Git понимает и временные координаты внутри фигурных скобок:

Код: Выделить всё

$ git show HEAD@{yesterday}
$ git log main@{2.days.ago}
$ git show 'HEAD@{1 hour ago}'
Тут важная тонкость, на которой спотыкаются.

Код: Выделить всё

HEAD@{5}
(просто число) - это пятая запись в reflog, чисто по порядку. А

Код: Выделить всё

HEAD@{5.minutes.ago}
- это позиция HEAD по ВРЕМЕНИ, Git ищет в журнале запись, актуальную на тот момент. Если репозиторий молодой и записей за вчера нет, на

Код: Выделить всё

HEAD@{yesterday}
Git вернёт ближайшую и честно предупредит "log only goes back to ...". Не пугайся этого ворнинга, это не ошибка.

Ещё одно различие:

Код: Выделить всё

git reflog
- компактный однострочный формат, заточенный под "найти SHA". А

Код: Выделить всё

git log -g
(он же

Код: Выделить всё

git log --walk-reflogs
) показывает то же самое, но полным форматом log, с датой и телом сообщения - удобно, когда надо вчитаться.

Реальные спасения: восстановить после 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}
с SHA - это вершина ДО reset, со всеми тремя коммитами. Возвращаемся:

Код: Выделить всё

$ git reset --hard a8c41d0
HEAD is now at a8c41d0 финальные правки
Всё. Три коммита на месте. Можно было написать и

Код: Выделить всё

git reset --hard HEAD@{1}
- то же самое. Git reflog восстановить состояние позволяет именно так: reflog дал тебе SHA, reset перевёл ветку на него.

Кейс 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
Ищем запись rebase (start) и берём то, что было НЕПОСРЕДСТВЕННО перед ней, - это вершина ветки до начала rebase, на

Код: Выделить всё

HEAD@{3}
:

Код: Выделить всё

$ git reset --hard HEAD@{3}
Ветка как до rebase, целиком, со всеми коммитами. Часто проще не выцеплять SHA глазами, а сразу прыгнуть на состояние "до того как я всё начал ломать".

Кейс 3. Вернуть удаленную ветку git. Снёс ветку

Код: Выделить всё

git branch -D experiment
, а она была нужна. В

Код: Выделить всё

git branch
её нет, в нет. Но при удалении Git тоже пишет строку в reflog HEAD - там остался последний коммит этой ветки. Ищем по имени:

Код: Выделить всё

$ git reflog | grep experiment
$ git log -g --grep experiment   # или так, аккуратнее
Или вспоминаем, где была ветка, когда ты на неё последний раз переключался:

Код: Выделить всё

checkout: moving to experiment
. Нашли SHA - воскрешаем ветку прямо на нём:

Код: Выделить всё

$ git branch experiment d9a3f17
Ветка вернулась, указывая на тот же коммит. Заметь: тут мы создаём НОВУЮ ветку на старом SHA, а не двигаем текущую. Это аккуратнее, чем reset, - ничего своего не трогаем.

Кейс 4. Detached HEAD и потерянные коммиты. Самый коварный. Ты сделал

Код: Выделить всё

git checkout <sha>
, оказался в detached HEAD (HEAD смотрит не на ветку, а прямо на коммит), накоммитил, потом переключился на main - и Git выдал предупреждение про "warning: you are leaving N commits behind". Эти коммиты не висят ни на одной ветке. Кажется, всё пропало:

Код: Выделить всё

$ 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
Коммит - вот он, цел. Поднимаем на него ветку, чтобы больше не потерять:

Код: Выделить всё

$ git branch rescue 91b2c5d
$ git switch rescue
Сколько reflog всё это помнит: сроки жизни записей

У страховки есть срок годности, и его надо знать, чтобы не понадеяться на чудо через полгода. Записи reflog не вечны - их подчищает обслуживание репозитория (раньше это был , с Git 2.54 ручное обслуживание по умолчанию идёт геометрической стратегией). Сроки задаются двумя настройками:
  • gc.reflogExpire - по умолчанию 90 дней. Применяется к записям, которые ведут к ДОСТИЖИМЫМ коммитам (на них всё ещё можно дойти от какой-нибудь ветки или тега).
  • gc.reflogExpireUnreachable - по умолчанию 30 дней. Применяется к записям про НЕДОСТИЖИМЫЕ коммиты - те самые "потерянные" после reset --hard, которые ни одна ветка больше не держит.
Почему недостижимые чистятся агрессивнее? Логика такая: достижимое состояние - это нормальная история, её не жалко хранить три месяца. А недостижимое - это обычно мусор от твоих экспериментов, его держат месяц как страховочную сетку и убирают. Грубое практическое правило: если ты потерял коммит - спасай его в течение ближайших недель, а не "когда-нибудь". Через 30 дней потерянный после reset коммит реально может уйти под нож при упаковке.

Посмотреть и подкрутить:

Код: Выделить всё

$ git config gc.reflogExpire
$ git config gc.reflogExpire 180.days          # хранить достижимое полгода
$ git config gc.reflogExpireUnreachable never  # вообще не чистить (для паранойи)
И отдельно: на свежем клоне reflog пустой не потому, что он "сломан", а потому, что он локальный и начинает писаться только с момента клона. Старую историю восстановить можно по обычным коммитам (они скачались), а вот "куда двигался HEAD у бывшего владельца" - нет, эти движения остались в его .

Типичные грабли
  • Путать HEAD~N и HEAD@{N}. Первое - родитель коммита (по графу), второе - позиция в журнале (по времени твоих действий).

    Код: Выделить всё

    git reset HEAD@{1}
    и

    Код: Выделить всё

    git reset HEAD~1
    почти всегда дают РАЗНЫЙ результат. После reset тебе почти всегда нужен

    Код: Выделить всё

    HEAD@{1}
    .
  • Делать ещё один reset --hard поверх паники. Не суетись. Сначала

    Код: Выделить всё

    git reflog
    , глазами найди нужный SHA, и только потом действуй. 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
Если на шаге 5 история вернулась полностью - ты только что освоил главный приём модуля спасения.

Контрольные вопросы
  • Чем отличается

    Код: Выделить всё

    HEAD@{2}
    от ? В каком из спасательных кейсов нужен именно первый вариант?
  • Ты сделал

    Код: Выделить всё

    git reset --hard
    и потерял коммиты. Сколько примерно дней у тебя есть, чтобы их вернуть, прежде чем обслуживание их зачистит? Какая настройка за это отвечает?
  • Почему после свежего

    Код: Выделить всё

    git clone
    в reflog почти ничего нет, и можно ли по чужому reflog узнать, куда двигался HEAD у предыдущего владельца репозитория?
  • Ты удалил ветку через

    Код: Выделить всё

    git branch -D feature
    . По каким двум зацепкам в reflog HEAD её можно найти и какой командой воскресить, не трогая текущую ветку?
Итог

Reflog - это локальный журнал движений HEAD и веток, который пишется при каждом коммите, reset, rebase, checkout, merge и хранит ПРЕДЫДУЩИЕ значения ссылок. Пока запись жива (90 дней для достижимого, 30 для потерянного), коммит можно вернуть: открываешь

Код: Выделить всё

git reflog
, находишь SHA, делаешь reset/checkout/branch. Запомни три вещи. Reflog локальный - он только на твоей машине. Reflog про ССЫЛКИ - несохранённую работу он не спасёт. И reflog не вечен - спасай сразу. С этим инструментом фраза "я потерял код в Git" почти всегда означает лишь "я ещё не открыл reflog".
👍3 ❤️3 🔥1 😄 🤔1
Аватара пользователя
ElixirSmith
Сообщения: 2
Зарегистрирован: 12 май 2026, 04:17

Re: git reflog: журнал, который помнит всё

Сообщение ElixirSmith »

Сидел в панике после reset --hard, думал три часа работы в трубу. Открыл reflog, нашёл HEAD@{1}, всё вернулось за минуту. Теперь это первая команда, которую я набираю когда что-то сломал.
👍1 ❤️ 🔥1 😄 🤔2
Аватара пользователя
freako
Сообщения: 1
Зарегистрирован: 12 май 2026, 09:35

Re: git reflog: журнал, который помнит всё

Сообщение freako »

А можно уточнить: если я сделал rebase на 10 коммитов и накосячил, мне reset на rebase (start) или на строку перед ним делать? Всё время путаюсь где именно вершина ветки до начала.
👍1 ❤️2 🔥 😄 🤔
Ответить
← Предыдущая глава
Массовое переписывание истории: git filter-repo и git replace
Следующая глава →
Восстановление утерянных коммитов, веток и файлов

Все главы курса «Git профессионально: от первого коммита до внутреннего устройства»

Поделиться темой: ✈ Telegram VK
Похожие запросы: git reflog: восстановить потерянный код

Вернуться в «Git профессионально: от первого коммита до внутреннего устройства»

Кто сейчас на конференции

Сейчас этот форум просматривают: нет зарегистрированных пользователей и 1 гость