Restore и reset
git restore отменяет правки в рабочем дереве или снимает файлы со стейджа; git reset двигает HEAD и может сбросить индекс и рабочее дерево. Три режима — soft, mixed, hard — отличаются глубиной сброса, и именно --hard навсегда уничтожает незакоммиченную работу.
Ты застейджил не тот файл — или потратил час на эксперимент, который никуда не привёл, и просто хочешь вернуть файл в прежний вид. У git есть два инструмента для «отменить это»: git restore для повседневной отмены на уровне файла и git reset для перемещения указателя ветки и индекса. Они достаточно пересекаются, чтобы путать людей, и один режим reset может молча и навсегда удалить твою незакоммиченную работу.
К концу этого урока ты сможешь выбрать правильную команду, чтобы отменить правки, снять файл со стейджа или отвести HEAD на коммит назад, — и будешь точно знать, какая из этих операций разрушительна.
После этого урока ты сможешь использовать git restore, чтобы отменить или снять со стейджа изменения, объяснить, как git reset двигает HEAD относительно рабочего дерева, индекса и HEAD, и точно сказать, что именно затрагивает каждый из --soft, --mixed и --hard — и какой из них теряет незакоммиченную работу.
У git есть три места, где может жить изменение: рабочее дерево, индекс и HEAD. Рабочее дерево — это файлы, которые ты правишь на диске. Индекс (он же область подготовки, staging area) — это то, что ты отметил через git add, готовое к следующему коммиту. HEAD — это последний коммит на текущей ветке. Любая команда отмены в этом уроке на самом деле сводится к вопросу, какое из этих трёх ты хочешь отмотать назад и насколько далеко. Держи эту модель в голове — и флаги перестанут казаться произвольными.
git restore — это современная отмена в рамках файла. Две повседневные задачи:
# Выбросить незакоммиченные правки в рабочем дереве, вернуть файл из HEAD
git restore src/app.js
# Снять файл со стейджа (убрать из индекса), но СОХРАНИТЬ правки на диске
git restore --staged src/app.jsПервая форма разрушительна для правок в рабочем дереве — файл на диске возвращается к закоммиченному содержимому, а твои несохранённые изменения исчезают. Вторая форма трогает только индекс; твои правки остаются, они просто больше не застейджены. restore выделили из перегруженного git checkout именно для того, чтобы эти два намерения перестали быть двусмысленными.
git reset двигает указатель текущей ветки (HEAD) на другой коммит. Там, где restore работает с файлами, reset работает с позицией в истории. Самая частая форма отматывает на один коммит:
git reset HEAD~1 # отвести HEAD на один коммит назадЧто произойдёт с индексом и рабочим деревом после этого, целиком зависит от флага режима. По умолчанию (без флага) это --mixed. Коммит, который ты «убрал», не исчез — HEAD просто больше на него не указывает, — но содержавшиеся в нём изменения снова всплывают как незакоммиченная работа, ровно настолько, насколько это позволяет режим.
Три режима отличаются лишь тем, насколько далеко достаёт сброс. Один и тот же целевой коммит, три глубины:
git reset --soft HEAD~1 # двигает только HEAD; индекс + рабочее дерево не тронуты
git reset --mixed HEAD~1 # двигает HEAD + сбрасывает индекс; рабочее дерево не тронуто (по умолчанию)
git reset --hard HEAD~1 # двигает HEAD + сбрасывает индекс + рабочее дерево (РАЗРУШИТЕЛЬНО)
| Режим | Двигает HEAD | Сбрасывает индекс | Сбрасывает рабочее дерево | Незакоммиченная работа |
|---|---|---|---|---|
--soft | да | нет | нет | сохранена, всё ещё застейджена |
--mixed (по умолчанию) | да | да | нет | сохранена, теперь не застейджена |
--hard | да | да | да | уничтожена навсегда |
Читай это как лестницу: --soft отматывает меньше всего, --hard — больше всего. --soft HEAD~1 — это классический приём «раскоммитить, но оставить всё застейдженным», идеален для переделки сообщения коммита или разбиения коммита.
▸Частая ошибка
git reset --hard — это самый распространённый способ, которым разработчики уничтожают работу, которую хотели сохранить. Он перезаписывает рабочее дерево, поэтому любая незакоммиченная правка — застейдженная или нет — исчезает, и restore/reset её не вернут, потому что она никогда не была закоммичена. Прежде чем запускать, спроси: «Всё, что мне дорого, либо закоммичено, либо в стэше?» Если нет — сначала запусти git stash. (Закоммиченную работу, которую он убирает, всё ещё можно вернуть через git reflog — это в следующем уроке, — но незакоммиченную нельзя.)
Раскоммитить слишком большой коммит, сохранив работу.
Ты закоммитил всё разом, но понял, что это должно было быть два коммита. Ты хочешь отменить коммит, не потеряв ни одной правки.
git reset --soft HEAD~1
git status--soft отводит HEAD на один коммит назад, но оставляет индекс и рабочее дерево ровно такими, какими они были, — поэтому git status показывает все твои файлы по-прежнему застейдженными, готовыми к повторному коммиту меньшими порциями. Теперь можно снять со стейджа одну группу через git restore --staged tests/ и закоммитить остальное:
git restore --staged tests/
git commit -m "Add parser"
git add tests/
git commit -m "Add parser tests"Если бы ты вместо этого набрал git reset --hard HEAD~1, тот же первый шаг выбросил бы каждую правку на диске — ровно противоположное тому, чего ты хотел. Один флаг — это разница между переформированием коммита и потерей целого дня.
▸Почему это работает
Почему --mixed сбрасывает индекс, но не рабочее дерево? Потому что самая частая причина отменить коммит — пересобрать его изменения иначе: посмотреть дифф, разбить его, отбросить часть. Оставив рабочее дерево нетронутым, ты сохраняешь реальные правки в безопасности, очищая при этом область подготовки, чтобы начать пересборку с чистого листа. Это безопасное умолчание именно потому, что оно никогда не трогает байты на диске.
Ты запускаешь `git reset --soft HEAD~1`, когда застейджено несколько файлов. Каким будет состояние после?
Думай в трёх слоях: рабочее дерево, индекс, HEAD. git restore <file> отменяет правки рабочего дерева; git restore --staged <file> снимает со стейджа, сохраняя твои правки. git reset двигает HEAD, а его режим решает глубину: --soft двигает только HEAD (работа остаётся застейдженной), --mixed ещё и очищает индекс (работа остаётся на диске, не застейджена), а --hard ещё и перезаписывает рабочее дерево, навсегда уничтожая незакоммиченную работу. Безопасный инстинкт — застэшить перед любым --hard. Дальше ты встретишь git revert — отмену, которая добавляет новый коммит вместо отматывания, поэтому безопасна даже на общих ветках.
Практика
Начни сверху. Задачи идут от простого к сложному: вспомнить факт, применить к случаю, затем senior-уровень. Открой, попробуй, потом открой ответ.
Что-то непонятно?
Задай вопрос по этому уроку. Вопросы анонимны и попадают напрямую автору — урок станет лучше.