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 есть ветки — тема следующего модуля.