$ sudo teach IT

Каждый разработчик хотя бы раз сталкивался с ситуацией, когда изменения нужно откатить назад:

  • случайно удалён важный кусок кода,
  • коммит сделан слишком рано,
  • или же эксперимент оказался неудачным.

Git даёт возможность «вернуться во времени» и восстановить проект до нужного состояния. Это как кнопка «Отменить» в текстовом редакторе, только с гораздо более гибким управлением.

В этом уроке вы научитесь:

  • отменять изменения в рабочих файлах,
  • убирать файлы из индекса,
  • откатывать коммиты, сохраняя или удаляя изменения,
  • использовать команды checkout, restore, reset и revert в реальных ситуациях.

Что вы узнаете в рамках статьи

  • Как отменять изменения в рабочей директории.
  • Чем отличается git checkout от git restore.
  • Как убрать файлы из индекса, не потеряв правки.
  • Как отменить последний коммит разными способами (-soft и -hard).
  • Когда стоит использовать git reset, а когда — git revert.

Ключевые термины

  • Рабочая директория — место, где вы редактируете файлы проекта.
  • Индекс (staging area) — область подготовки файлов перед коммитом.
  • Коммит — зафиксированное состояние проекта.
  • Отмена изменений — возврат файлов или проекта к предыдущему состоянию.
  • git checkout / git restore — команды для восстановления файлов из истории.
  • git reset — команда для отмены добавления файлов в индекс или отката коммитов.
  • git revert — команда для создания нового коммита, который отменяет изменения предыдущего.

Отмена изменений в рабочей директории

Когда вы работаете с проектом, правки в файлах происходят постоянно: что-то добавили, что-то переписали, удалили лишнее. Но не все изменения оказываются удачными. Иногда хочется вернуться к исходному состоянию и «откатить» то, что только что сделали.

Здесь на помощь приходят команды git checkout (в старых версиях Git) и git restore (в новых). Они позволяют вернуть файл или даже все файлы в том виде, в котором они находились в последнем коммите.

Как это работает

Представьте, что у вас есть сейф с копиями (репозиторий), и каждая копия хранит состояние проекта в момент коммита. Когда вы используете git restore, Git просто достаёт последнюю сохранённую копию файла из сейфа и заменяет текущую версию в вашей рабочей директории.

Пример команды:

git checkout index.html
# или в новых версиях:
git restore index.html

После этого файл index.html вернётся в то состояние, в котором был после последнего коммита. Все несохранённые правки исчезнут.

Пример из практики

Вы редактируете файл style.css, пробуете новые стили, но понимаете, что результат вас не устраивает. Вместо того чтобы вручную отменять каждое изменение, достаточно выполнить:

git restore style.css

и файл снова будет таким, каким он был в последнем зафиксированном состоянии.

Важный момент

  • Эти изменения нельзя вернуть. Если вы не сохранили их где-то ещё (например, во временной ветке или через git stash), они будут утеряны навсегда.
  • Если хотите отменить изменения сразу во всех файлах проекта, можно использовать:
git restore .

или в старой версии Git:

git checkout .

Отмена подготовленных к коммиту файлов

Очень часто при работе с Git мы добавляем файлы в индекс командой git add, чтобы подготовить их к коммиту. Но бывает ситуация: файл добавлен по ошибке, изменения ещё не готовы или в коммит попал лишний код. Создавать новый коммит ради этого не нужно — достаточно убрать файл из индекса.

Как это работает

В Git есть рабочая директория (где мы редактируем файлы) и индекс (staging area) — «корзина для покупок», в которую мы складываем изменения перед тем, как их «оплатить» (зафиксировать коммитом).

Если вы поняли, что положили в корзину что-то лишнее, можно просто вынуть это обратно — изменения останутся в рабочей директории, но они не будут включены в коммит.

Основные команды

Через git reset:

git reset style.css

Эта команда уберёт файл style.css из индекса, оставив все правки в рабочей директории.

Через более новую и интуитивную команду git restore --staged:

git restore --staged style.css

Фактически она делает то же самое, но название команды более понятно: «убрать из staged-области».

Пример из практики

Представьте, что вы правите два файла: index.html и config.js. Вы сделали git add ., но вдруг понимаете, что config.js ещё «сырой» и его не стоит фиксировать.

Решение:

git restore --staged config.js

Теперь:

  • index.html останется в индексе, и его можно закоммитить;
  • config.js выйдет из индекса, но ваши изменения в файле никуда не денутся.

Важный момент

  • Сами изменения не пропадают: они всё ещё лежат в рабочей директории.
  • Вы можете доработать файл и снова добавить его в индекс, когда он будет готов.

Отмена последнего коммита

Иногда мы торопимся и делаем коммит слишком рано: забыли добавить важный файл, написали неудачное сообщение или просто хотим начать заново. В Git есть инструменты, которые позволяют «откатить» последний коммит и решить, что делать с его изменениями.

Два варианта отката

Сохранить изменения, но убрать коммит

git reset --soft HEAD~1

Что происходит:

  • сам коммит исчезает,
  • все изменения из него остаются в индексе (staged),
  • вы можете отредактировать файлы или просто сделать новый коммит с другим сообщением.

Пример: Вы закоммитили с сообщением "Update" (слишком общее). Выполнив git reset --soft HEAD~1, вы возвращаете изменения в staged-область и создаёте новый, более осмысленный коммит:

git commit -m "Добавлен модуль аутентификации"

Удалить коммит и все изменения в нём

git reset --hard HEAD~1

Что происходит:

  • коммит исчезает,
  • изменения полностью стираются из индекса и рабочей директории.

Пример: Вы случайно закоммитили файл с паролями или ненужный экспериментальный код. В этом случае --hard поможет избавиться от него раз и навсегда.

⚠ Осторожно: этот способ удаляет изменения без возможности восстановить их (если не использовать дополнительные команды вроде git reflog).

Как понять, какой вариант выбрать?

  • Используйте -soft, если код нужен, но вы хотите переписать коммит.
  • Используйте -hard, если изменения были ошибкой и от них нужно избавиться полностью.

Когда использовать git revert

Иногда ошибка уже попала в общий репозиторий: вы запушили коммит, коллеги успели забрать изменения, поверх них появились новые коммиты. В такой ситуации переписывать историю (reset, rebase и т. п.) опасно: у каждого участника уже своя версия истории, и вы спровоцируете конфликт при синхронизации. Безопасный способ — создать новый «отменяющий» коммит:

git revert <hash_коммита>

revert не стирает прошлое, а добавляет поверх новый коммит, который «аннулирует» изменения указанного. История остаётся прозрачной и последовательной, а у коллег не возникает расхождений.

Когда выбирать revert, а не reset

  • Коммит уже опубликован (запушен) и кто-то мог на него опереться.
  • Нужна полная трассируемость: в истории должно быть видно, что было сделано и как это отменили.
  • Вы работаете в защищённых ветках (например, main), где политика репозитория запрещает force-push.

Коротко: reset переписывает историю, revert — добавляет новый коммит, который логично «откручивает» изменения.

Базовый сценарий

Отменить один ошибочный коммит:

git revert a1b2c3d
# при необходимости поправьте конфликт(ы), затем:
git commit    # если Git не сделал коммит автоматически
git push

Отмена нескольких коммитов

Последовательный откат нескольких хэшей по одному:

git revert a1b2c3d e4f5g6h 9i0j1k2

Диапазон (сначала выясните порядок в git log --oneline):

# отменить всё, начиная от OLDER..NEWER (OLDER не включается)
git revert OLDER..NEWER

Если коммитов много и ожидаются конфликты, удобнее откатывать партиями.

Особый случай: откат merge-коммита

Merge-коммит объединяет две истории, и Git не знает, какое родительское направление считать «истинным». Поэтому при откате merge-коммита нужно указать номер родителя:

git revert -m 1 <hash_merge>

  • -m 1 — оставить как базовую «первую» родительскую ветку (обычно это main).
  • Если указать неправильного родителя, получите «открутку» не в ту сторону.

Подсказка: посмотреть родителей merge-коммита можно через git show <hash_merge>.

Что делать при конфликтах во время revert

revert — это фактически патч, который применяется поверх текущего состояния. Если код успел измениться, возможны конфликты.

Шаги:

  1. Откройте конфликтные файлы, разрешите пометки <<<<<<<, =======, >>>>>>>.

  2. Добавьте исправленные файлы:

    git add <file1> <file2>
    
    
  3. Завершите процесс:

    git revert --continue
    
    

    (или обычный git commit, если Git не ведёт вас интерактивно)

Частые сценарии использования

  • Откат неудачного хотфикса в проде: быстро, без переписывания истории.
  • Возврат к рабочему состоянию после проблемного PR, пока готовится исправление.
  • Командные репозитории с политикой «никаких force-push»: revert — единственный допустимый способ.

git reflog — «машина времени» в Git

Представьте, что вы сделали неосторожный git reset --hard и потеряли несколько часов работы. Паника, мысли вроде «всё пропало», желание закрыть ноутбук и пойти заваривать чай. Но на самом деле Git предусмотрел такую ситуацию: у него есть «чёрный ящик» — журнал перемещений HEAD.

Этот журнал хранится в фоне и фиксирует всё: куда вы прыгали, что коммитили, какие ветки переключали. И даже если коммит вроде бы исчез из истории, в reflog он останется жить ещё какое-то время.

Открыть этот журнал можно командой:

git reflog

Как это выглядит на практике

Вы видите историю всех своих действий с репозиторием:

a1b2c3d HEAD@{0}: reset: moving to HEAD~1
9f8e7d6 HEAD@{1}: commit: Добавлен модуль оплаты
5a4b3c2 HEAD@{2}: commit: Исправлен баг с авторизацией

  • HEAD@{0} — текущее состояние (последнее действие, например reset).
  • HEAD@{1} — то, что было прямо перед этим: в нашем случае — потерянный коммит с оплатой.
  • HEAD@{2} — ещё один коммит назад.

То есть даже если вы «снесли» коммит, его хэш (9f8e7d6) всё ещё можно достать и использовать.

🔄 Как восстановить «утерянное»

Предположим, вы случайно удалили коммит, но хотите его вернуть:

git checkout 9f8e7d6

Теперь HEAD указывает на этот коммит, и вы можете:

  • либо создать ветку для сохранения:
git branch restore-branch 9f8e7d6

  • либо просто перенести изменения обратно в нужное место.

Таким образом, даже самые «потерянные» коммиты можно оживить.

📚 Когда вспоминать про reflog

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

💡 Важные нюансы

  • reflog хранит записи ограниченное время (по умолчанию 90 дней для unreachable-коммитов, 30 дней для остальных).
  • Это журнал локальный: на сервер (GitHub, GitLab) он не уходит. То, что хранится в вашем reflog, видно только вам.
  • Чем раньше вы вспомните про него — тем выше шанс вернуть всё без боли.

📖 Аналогия: reflog — это «чёрный ящик самолёта». Даже если кажется, что проект разбился об reset --hard, записи там останутся. А значит, у вас есть шанс восстановить полёт.

Наиболее частые ошибки

  1. Использование reset --hard без проверки.
    Многие новички теряют код, не понимая, что команда удаляет изменения окончательно.
  2. Попытка переписать историю в общем репозитории.
    reset на сервере ломает работу команды. Здесь нужно использовать revert.
  3. Забыли про изменения в рабочей директории.
    После reset --soft файлы остаются изменёнными, и можно случайно закоммитить их снова.

Наиболее частые вопросы

1. Можно ли отменить изменения в нескольких файлах сразу?
Да, через git checkout . или git restore ..

2. Что делать, если я случайно удалил важный коммит?
Используйте git reflog, чтобы найти его и восстановить.

3. В чём разница между reset и revert?

  • reset переписывает историю, «откатываясь» назад.
  • revert создаёт новый коммит, который отменяет изменения, сохраняя историю.

Итоги

Сегодня вы узнали, как работать с отменой изменений в Git:

  • возвращать файлы к последнему коммиту,
  • убирать их из индекса,
  • откатывать коммиты разными способами,
  • безопасно использовать revert в командной работе.

Эти инструменты помогут держать проект под контролем, исправлять ошибки без паники и поддерживать порядок в истории.

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