git reset откатывает ветку на указанный коммит. Вопрос всегда один: что при этом произойдёт с твоими правками — они вернутся в индекс, вернутся в рабочий каталог или исчезнут совсем. Ответ и задаёт флаг.
Здесь пригодится модель из разбора основных команд: у Git три места, где живёт код. reset отматывает назад первое, первые два или все три.
Режим определяет, насколько далеко пойдёт reset
--hard. Но --mixed тоже сбрасывает индекс: отдельная версия, сохранённая только через git add, перестанет быть в нём доступна.Один и тот же откат, три результата
Три одинаковых репозитория, в каждом три коммита по строке в notes.txt, и в каждом — HEAD~1, то есть «отмотать на один коммит назад».
git reset --soft HEAD~1 # сам reset ничего не печатает
git status -s
M notes.txt
git reset --mixed HEAD~1 # то же, что просто git reset HEAD~1
Unstaged changes after reset:
M notes.txt
git status -s
M notes.txt
git reset --hard HEAD~1
HEAD is now at bbba675 коммит 2
А git status -s после него не печатает ничего: откатывать больше нечего.
Во всех трёх случаях git log теперь показывает два коммита вместо трёх. Разница — в файле и в том, где лежат его изменения:
| Режим | git status -s | notes.txt на диске |
|---|---|---|
--soft | M␣notes.txt — изменения в индексе | три строки, всё на месте |
--mixed | ␣M notes.txt — изменения в рабочем каталоге | три строки, всё на месте |
--hard | пусто | две строки — третья исчезла |
Позиция буквы M — та же нотация, что в разборе основных команд: левый столбец про индекс, правый про рабочий каталог. По ней сразу видно, куда именно откат положил твою работу.
Отсюда самый частый рабочий сценарий: git reset --soft HEAD~1 — «переделать последний коммит». Коммит исчез, всё его содержимое лежит в индексе готовым, поправил и закоммитил заново.
Что можно вернуть, а что нет
--hard перезаписывает рабочий каталог. --mixed его не трогает, но сбрасывает индекс: если после git add файл меняли снова, отдельная проиндексированная версия тоже может потребовать восстановления. Перед экспериментом надёжнее сделать коммит, stash или отдельную копию.
Вернуть можно лишь то, что Git успел сохранить
Коммит возвращается
git reflog
7eb8148 HEAD@{0}: reset: moving to HEAD~1
28aef82 HEAD@{1}: commit: коммит 3
7eb8148 HEAD@{2}: commit: коммит 2
acbb610 HEAD@{3}: commit (initial): коммит 1
Reflog — журнал всех перемещений HEAD, включая сам reset. Отброшенный коммит виден в строке HEAD@{1}, и возврат к нему — тот же reset:
git reset --hard HEAD@{1}
HEAD is now at 28aef82 коммит 3
Файл снова с тремя строками. Не запускай такой --hard поверх новых несохранённых правок: сначала можно закрепить найденный коммит командой git branch rescue 28aef82. Стандартные сроки очистки reflog — 90 дней для обычных записей и 30 для записей, недостижимых из текущей вершины. Настройки и сборка мусора меняют сроки; гарантированного окна восстановления нет.
Незакоммиченная правка не возвращается
echo "важная работа, которую не закоммитили" >> notes.txt
git reset --hard HEAD
Строки в файле больше нет, и в git reflog про неё ничего — там только коммиты. Никакой команды, возвращающей её, не существует: Git этих байтов никогда не видел.
--hard обычно оставляет посторонние неотслеживаемые файлы, но удаляет или перезаписывает те, которые мешают восстановить отслеживаемые пути. Это касается и файлов, исключённых через .gitignore: правило игнорирования не защищает от такого удаления. Отдельную массовую очистку неотслеживаемых и игнорируемых файлов делает git clean -fdx; обе команды требуют заранее сохранить нужные данные.
А вот заиндексированная — уцелеет
Если перед --hard успел сделать git add, содержимое попало в базу объектов и переживёт откат — потеряется только имя:
git fsck --lost-found
dangling blob 5ec200bf8b1a23de52b6cf2c94c100cfcc321ea2
git cat-file -p 5ec200bf
полузащищённая работа
git add повышает шанс восстановления, но не заменяет резервную копию: недостижимые объекты могут быть удалены сборщиком мусора. Перед опасным экспериментом сохрани работу коммитом, через stash или вне репозитория.
reset, revert и restore — три разные команды
- reset — переписать историю у себяОтматывает ветку назад: коммитов становится меньше. Годится, пока эти коммиты никуда не отправлены.
- revert — отменить, не переписываяСоздаёт НОВЫЙ коммит, отменяющий указанный. История растёт, ничего не пропадает. Единственный безопасный способ отменить то, что уже видели другие.
- restore — вернуть файл, не трогая историю
git restore файлоткатывает файл к индексу,git restore --staged файлубирает из индекса. Коммиты при этом не двигаются вообще.
Разница между reset и revert видна в логе. После git revert HEAD содержимое файла такое же, как после reset --hard HEAD~1, но история противоположная:
git revert --no-edit HEAD
git log --oneline
b1fe329 Revert "коммит 3"
b56f660 коммит 3
a471d5a коммит 2
6f8ec68 коммит 1
Коммит 3 на месте, к нему добавлен отменяющий. Ничего не потеряно, и у коллег, которые уже забрали коммит 3, ничего не разъедется.
Почему нельзя откатывать отправленное
Отправишь ветку, откатишь её через reset и попробуешь запушить снова — Git откажет:
! [rejected] main -> main (non-fast-forward)
error: failed to push some refs to 'origin'
hint: Updates were rejected because the tip of your current branch is behind
hint: its remote counterpart.
Обойти это можно только --force, и цена известна: у всех, кто успел забрать старую историю, при следующем pull начнётся расхождение, а чужие коммиты поверх отброшенных могут пропасть. Правило простое: отправленное отменяют через revert, не отправленное — через reset.
Если форсить всё же приходится, --force-with-lease безопаснее голого --force: он откажется затирать чужую работу, появившуюся после твоего последнего fetch.
Частые вопросы
Что делает git reset без флагов
Без флага режима используется --mixed. Команда git reset HEAD~1 сдвигает ветку на родительский коммит и сбрасывает индекс, не меняя рабочий каталог. А просто git reset, без указания коммита, означает git reset --mixed HEAD: снимает изменения с индекса, но не отменяет последний коммит.
Как отменить последний коммит, но сохранить изменения
git reset --soft HEAD~1 — коммит исчезнет, всё его содержимое останется в индексе. Если нужно только поправить сообщение, есть более прямой путь: git commit --amend.
Чем HEAD~1 отличается от HEAD^
Ничем, в том числе у коммитов слияния: обе записи означают первого родителя. Разница появляется с другими числами: HEAD^2 — второй родитель самого HEAD, а HEAD~2 — два шага по цепочке первых родителей.
Что такое git reset —hard origin/main
Приравнивает локальную ветку к удалённой, выбрасывая все свои коммиты и незакоммиченные правки. Способ сказать «забудь всё, что я тут делал». Перед ним стоит убедиться, что терять действительно нечего — незакоммиченное не вернётся.
Правда ли, что reflog спасёт в любом случае
Нет. reflog помнит перемещения HEAD, то есть коммиты. Правки в рабочем каталоге, которые не были закоммичены, в нём не отражаются вовсе и после --hard теряются насовсем.
Где потренироваться
Заведи пустую папку, сделай git init, три коммита подряд — и повтори все три режима, сверяясь с git status -s и git log --oneline после каждого. Ошибиться там нечем: репозиторий одноразовый, а привычка проверять status после отката останется. Команды, на которых всё это стоит, разобраны в основных командах Git.