$ sudo teach IT
Модуль 3 · Отмена и .gitignore · Урок 3.4

Git reflog

Вы научитесь находить «потерянные» коммиты в журнале перемещений HEAD и возвращать их.

Теория + практика~35 минутНовичокgit reflogспасение коммитов

Git почти ничего не забывает

Git reflog — это журнал, в который Git записывает каждое перемещение HEAD: новый коммит, reset, переключение, amend. Если вы случайно потеряли коммиты после git reset --hard, команда git reflog покажет, где они лежали, и вы вернёте их одной командой. Вы нарочно сломаете проект и почините его, а в конце пройдёте мини-практикум из четырёх аварий.

В прошлом уроке мы сказали, что reset --hard опасен и что «пропавшие» коммиты можно найти. Пришло время проверить: без этой страховки экспериментировать страшно, а с ней — нет.

Простыми словами

Это как история посещений в браузере: даже если вы закрыли страницу и не добавили в закладки, адрес в истории остался. Reflog — такая «история посещений» для HEAD: каждый шаг в ней записан.

Официальный термин и пример

Reflog (reference log, «журнал ссылок») ведётся автоматически и только у вас на компьютере. Прочитать его можно командой git reflog. Вот журнал после экспериментов с reset из прошлого урока (в вашем будут другие хеши, а набор строк может быть другим):

Терминал
$ git reflog -8
5acc5ae (HEAD -> main) HEAD@{0}: reset: moving to HEAD~1
54a20bb HEAD@{1}: commit: Увеличить отступ заголовка
5acc5ae HEAD@{2}: reset: moving to HEAD~1
54a20bb HEAD@{3}: commit: Увеличить отступ заголовка
5acc5ae HEAD@{4}: reset: moving to HEAD~1
54a20bb HEAD@{5}: commit: Увеличить отступ заголовка
5acc5ae HEAD@{6}: revert: Revert "Поменять цвет фона"
875cb0a HEAD@{7}: commit: Поменять цвет фона

Разбор по строкам

  • -8 — показать только 8 последних записей. Без числа Git выведет весь журнал; длинный вывод закрывается клавишей q.
  • Первое поле, 5acc5ae — хеш коммита, на котором стоял HEAD после этого действия.
  • HEAD@{0}, HEAD@{1}, … — номер шага назад: HEAD@{0} — где HEAD сейчас, HEAD@{1} — где был перед последним действием, и так далее. Такой записью можно пользоваться вместо хеша.
  • reset: moving to HEAD~1, commit: Увеличить отступ заголовка, revert: … — что именно произошло. Каждая команда, двигавшая HEAD, оставила здесь след.

Частая путаница: log и reflog

git log показывает историю коммитов: что в проекте сейчас. git reflog показывает историю ваших действий: где побывал HEAD. После reset сброшенных коммитов в git log нет, а в git reflog они остаются. Заметьте также: даже git switch записывается в журнале как checkout. Это нормально, команды делают одно и то же.

Спасаем после reset --hard

Допустим, вы хотели убрать последний коммит, но вместо этого сбросили проект на коммит «Добавить описание сайта» и потеряли сразу два: «Поменять цвет фона» и его отмену. Выполните это нарочно — именно ради спасения (подставьте хеш своего коммита «Добавить описание сайта»):

Терминал
$ git reset --hard 746ac3d
HEAD is now at 746ac3d Добавить описание сайта
$ git log --oneline
746ac3d (HEAD -> main) Добавить описание сайта
12d081f Добавить .gitignore
284ecf7 Исправить опечатку в заголовке
...

Двух коммитов в истории нет. Смотрим журнал:

Терминал
$ git reflog -4
746ac3d (HEAD -> main) HEAD@{0}: reset: moving to 746ac3d
5acc5ae HEAD@{1}: reset: moving to HEAD~1
54a20bb HEAD@{2}: commit: Увеличить отступ заголовка
5acc5ae HEAD@{3}: reset: moving to HEAD~1

Строка HEAD@{0} — наш неудачный reset. Строка HEAD@{1} с хешем 5acc5ae показывает, где стоял HEAD до него, то есть последний из потерянных коммитов («Revert "Поменять цвет фона"»). Возвращаем проект туда:

Терминал
$ git reset --hard 5acc5ae
HEAD is now at 5acc5ae Revert "Поменять цвет фона"
$ git log --oneline
5acc5ae (HEAD -> main) Revert "Поменять цвет фона"
875cb0a Поменять цвет фона
746ac3d Добавить описание сайта
...

Разбор по строкам

  • git reset --hard 5acc5ae — та же команда, что сломала проект, но теперь с нужным хешем: история и файлы возвращаются на тот коммит.
  • То же самое можно сделать короче: git reset --hard HEAD@{1}. Но так работает только сразу после аварии, пока между ней и спасением нет других действий — номера в журнале сдвигаются. Надёжнее смотреть хеш в git reflog и использовать его.
  • В git log снова оба коммита. Ничего не потеряно.

Reflog выручает и после amend

Помните git commit --amend? Старый коммит с опечаткой «описанеи» никуда не делся: в журнале он значится строкой commit: Добавить описанеи сайта, и его можно достать так же. Поэтому при ошибке просто смотрите в git reflog.

Хеш из reflog выбирайте внимательно

Журнал полон похожих строк. Прежде чем делать reset --hard на найденный хеш, сверьте подпись коммита в той же строке (commit: …) с тем, что ищете. Ошибётесь — ничего страшного, просто повторите поиск, ведь журнал уже запомнил и это действие.

Чего reflog не спасёт

  • Правки, которые никогда не коммитили. Reflog записывает перемещения HEAD, а до коммита ваши правки в историю не входят. Отброшенное через git restore или потерянное при reset --hard вместе с несохранёнными правками не вернуть. Привычка: перед рискованной командой сделайте коммит, любой, потом исправите.
  • Чужой компьютер и GitHub. Журнал локальный: при push и clone он не копируется. Своя копия проекта на другом компьютере имеет свой, совершенно другой журнал.
  • Старые записи. Reflog хранит записи ограниченное время: по умолчанию 90 дней, а записи о коммитах, до которых нельзя добраться из текущей истории, — 30 дней (настройки gc.reflogExpire и gc.reflogExpireUnreachable). Потом Git при уборке удаляет их. Не откладывайте спасение на месяц.

Подробно — в документации: git reflog.

Практикум: сломать и починить

Теперь четыре мини-аварии на визитке. В каждой сначала вы создаёте проблему по описанию, а потом сами выбираете инструмент из модуля: restore, restore --staged, reset, revert, reflog. Ответы ниже, но сначала попробуйте сами. Старт: git status чистый, в git log --oneline девять коммитов, последний — «Revert "Поменять цвет фона"».

Авария 1. Исчез файл

Удалите файл стилей: rm style.css. Откройте git status. Задача: вернуть файл, не создавая коммитов.

Авария 2. Плохой коммит

Дополните заголовок в index.html словом TEST (<h1>Алекс Иванов TEST</h1>), выполните git add index.html и git commit -m "Сломать заголовок". Задача: убрать этот коммит из истории вместе с правкой, ведь вы им ещё ни с кем не делились.

Авария 3. Неудачный reset

Сделайте два коммита подряд: допишите в конец README.md строку Пробная строка 1 и закоммитьте с подписью «Проба 1», затем строку Пробная строка 2 с подписью «Проба 2». Затем «по ошибке» выполните git reset --hard HEAD~2. Задача: вернуть оба коммита; убедившись, что они на месте, сбросьте их ещё раз (теперь намеренно), чтобы проект снова остался в исходном состоянии.

Авария 4. Мусор в индексе

Создайте пустой файл командой touch draft.txt, затем выполните git add . и посмотрите git status. Задача: убрать файл из индекса и из папки, ничего не коммитя.

Ответы

Авария 1. Файл удалён, но удаление не закоммичено, поэтому достаточно git restore style.css. Статус снова чистый.

Авария 2. Коммит свой и не опубликован, поэтому подходит git reset --hard HEAD~1. Ответ HEAD is now at … Revert "Поменять цвет фона", в заголовке снова нет TEST. Вариант git revert HEAD тоже верен, но оставил бы в истории лишнюю пару коммитов.

Авария 3. Выполните git reflog -4: HEAD@{1} — это «Проба 2». Затем git reset --hard HEAD@{1} (или хеш из журнала). После проверки git log --oneline верните исходное состояние: git reset --hard HEAD~2.

Терминал
$ git reflog -4
5acc5ae (HEAD -> main) HEAD@{0}: reset: moving to HEAD~2
160a483 HEAD@{1}: commit: Проба 2
1cc963a HEAD@{2}: commit: Проба 1
5acc5ae HEAD@{3}: reset: moving to HEAD~1
$ git reset --hard HEAD@{1}
HEAD is now at 160a483 Проба 2

Авария 4. Подсказка в git status уже называет команду: git restore --staged draft.txt, после чего файл снова неотслеживаемый (?? в git status --short). Из папки его удаляет обычный rm draft.txt.

Проверьте себя

  • git log --oneline показывает те же девять коммитов, что и до практикума; git status чистый.
  • Вы можете объяснить, чем git reflog отличается от git log.
  • Вы знаете, что делать, если после reset --hard пропали коммиты: git reflog, найти хеш, git reset --hard <хеш>.
  • Вы помните, что не закоммиченные правки reflog вернуть не может.

Частые ошибки

В reflog нет нужного коммита. Либо прошло много времени (записи удалены при уборке), либо вы работаете в другой копии проекта: журнал у каждой свой. Либо правка не была закоммичена.

Вернулись не на тот коммит. Хеш взяли из соседней строки. Откройте git reflog снова: новое действие тоже записано, и вы выберете нужную строку.

Терминал ругается на HEAD@{1}. В некоторых оболочках фигурные скобки нужно взять в кавычки: "HEAD@{1}". Или просто используйте хеш.

Что важно запомнить

  • git reflog — локальный журнал всех перемещений HEAD. Отличается от git log тем, что помнит и сброшенные коммиты.
  • HEAD@{n} — положение HEAD на n шагов назад.
  • Спасение после reset --hard: git reflog, находите хеш до аварии, git reset --hard <хеш>.
  • Reflog не спасёт незакоммиченные правки, не едет с проектом на GitHub и стирает записи через 30–90 дней.
  • Рискуете — сначала коммитьте.

Теперь вы умеете отменять и возвращать почти что угодно. Но экспериментировать прямо в рабочей версии проекта всё равно неуютно: каждая проба оставляет следы в истории. Лучше пробовать новое отдельно от основной линии, и именно для этого в Git есть ветки — тема следующего модуля.

На следующий урок