Сравнение

git reset и git revert: как отменить коммит и не сломать историю команды

Обновлено: · Skillaria.Top

git reset переносит указатель ветки на другой коммит и переписывает историю, поэтому подходит только для локальных, ещё не отправленных коммитов. git revert создаёт новый коммит с обратными изменениями и не трогает историю, поэтому это безопасный способ отменить коммит, который уже есть на сервере.

Коротко: git reset отматывает ветку назад и делает вид, что лишних коммитов не было. git revert оставляет историю как есть и добавляет новый коммит, который отменяет изменения старого. Первое годится для локальной работы, второе — для всего, что уже ушло в общий репозиторий. Если запомнить только это правило, вы избежите большинства аварий с Git.

Ниже разберём, как устроены обе команды, чем отличаются режимы reset, как отменить коммит после push, как вернуть «потерянные» коммиты и при чём тут git clean.

Три уровня, которые меняют команды отмены

Чтобы понять разницу, вспомните три области Git: рабочая папка (файлы на диске), индекс (staging area, то, что попадёт в следующий коммит) и история (коммиты, на которые указывает ветка и HEAD). Каждая команда отмены работает со своим набором этих областей.

КомандаИсторияИндексРабочая папкаБезопасна для общей ветки
git restore файлнетнетдада
git restore --staged файлнетданетда
git reset --softданетнетнет
git reset --mixed (по умолчанию)даданетнет
git reset --hardдададанет
git revertдобавляет коммит——да
git cleanнетнетудаляет неотслеживаемые файлыда, но необратимо

Как работает git reset

Команда git reset <коммит> переносит указатель текущей ветки (и HEAD вместе с ним) на указанный коммит. Коммиты «после» него не удаляются физически, но перестают быть видны в ветке. Режим определяет, что произойдёт с индексом и файлами.

git reset --soft: отменить коммит, оставив изменения в индексе

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

git reset --soft HEAD~3
git commit -m "feat: форма регистрации"

Три последних коммита превратились в один, содержимое файлов не изменилось.

git reset --mixed: отменить коммит и индекс

Режим по умолчанию: git reset HEAD~1 равносилен git reset --mixed HEAD~1. Ветка сдвигается, индекс сбрасывается, а изменения остаются в рабочей папке как неподготовленные. Удобно, когда вы закоммитили лишнее и хотите заново решить, что и какими порциями добавлять.

git reset HEAD~1
git status
# Changes not staged for commit:
#   modified:   app.py
#   modified:   config.yml

git reset --hard: откатиться полностью

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

git reset --hard HEAD~1          # выбросить последний коммит и все правки
git reset --hard origin/main     # привести ветку к состоянию сервера

Перед --hard полезно выполнить git status и git stash, если есть что-то ценное. Закоммиченное вернуть можно (об этом ниже), незакоммиченное — нет.

git reset для одного файла

С путём reset не двигает ветку, а только меняет индекс: git reset app.py убирает файл из подготовленных. Современный эквивалент — git restore --staged app.py, он нагляднее. Флаги --soft и --hard с путями не работают.

Как работает git revert

git revert <коммит> вычисляет изменения указанного коммита, применяет их в обратную сторону и создаёт новый коммит. Добавленные строки удаляются, удалённые — возвращаются. История растёт вперёд, ничего не переписывается, поэтому коллегам не нужно ничего чинить у себя.

git log --oneline
# a3f9c21 (HEAD -> main) docs: README
# 7be41d0 feat: кэширование ответов
# 1c02e8f fix: таймаут запросов

git revert 7be41d0
# [main 5d8e2aa] Revert "feat: кэширование ответов"
#  2 files changed, 3 insertions(+), 41 deletions(-)

Git откроет редактор с сообщением вида Revert "..." и строкой This reverts commit 7be41d0.... Хорошая практика — дописать, почему изменение отменено.

Полезные флаги revert

  • --no-edit — принять стандартное сообщение без редактора.
  • -n (--no-commit) — применить обратные изменения в индекс, не создавая коммит. Так можно отменить несколько коммитов одним коммитом.
  • git revert A..B — отменить диапазон коммитов (каждый отдельным revert-коммитом, от новых к старым).
  • -m 1 — обязателен для merge-коммита: указывает, какой родитель считать основной линией.
git revert --no-commit HEAD~3..HEAD
git commit -m "Revert: откат трёх коммитов с экспериментом"

Revert merge-коммита

У слияния два родителя, и Git не знает, относительно какого считать «обратные» изменения, поэтому без -m выдаст ошибку is a merge but no -m option was given. Обычно основная ветка — первый родитель:

git revert -m 1 9f1e2ab

Подвох: если потом вы захотите снова влить ту же ветку, Git посчитает её коммиты уже слитыми и не принесёт изменения. Решение — сначала сделать revert самого revert-коммита, а затем доливать новые правки.

git reset vs git revert: что выбрать

Критерийgit resetgit revert
Что делает с историейпереписывает (коммиты исчезают из ветки)добавляет новый коммит
Коммит уже запушеннужен push --force, ломает работу коллегобычный git push
Отмена коммита из середины историитолько вместе со всеми следующимиможно точечно
След в историине остаётсяостаётся, видно кто и зачем откатил
Типичный сценарийпочистить локальные коммиты перед pushоткатить сломавший прод коммит в main

Простое правило: если коммит видел кто-то кроме вас — revert, если нет — reset. Исключение — ваша личная feature-ветка, которую никто не забирал: там переписывать историю и делать git push --force-with-lease допустимо.

Почему reset --hard на общей ветке — плохая идея

Представьте: вы сделали git reset --hard HEAD~2 в main и git push --force. У коллег эти два коммита остались. При следующем git pull Git попытается слить их ветку с вашей, и «удалённые» коммиты вернутся обратно, только уже с мусорным merge-коммитом. Хуже, если кто-то успел запушить свои коммиты поверх: force push их просто затрёт.

Поэтому на защищённых ветках (main, release) force push обычно запрещают настройками хостинга. Если без переписывания не обойтись, используйте git push --force-with-lease: он откажет, если на сервере появились коммиты, которых вы не видели.

Типичные сценарии отмены

Как отменить последний коммит, который ещё не запушен

git reset --soft HEAD~1   # изменения в индексе, можно перекоммитить
git reset HEAD~1          # изменения в файлах, индекс пуст
git reset --hard HEAD~1   # изменения выброшены

Если нужно лишь поправить сообщение или добавить забытый файл, reset не нужен: git add забытый.txt и git commit --amend.

Как отменить коммит после push

git revert <хеш>
git push

Это и есть ответ на частый запрос «git reverse commit»: отдельной команды git reverse в Git нет, «обратный» коммит создаёт именно git revert.

Как вернуть ветку к состоянию сервера

git fetch origin
git reset --hard origin/main

Полезно, когда локальная ветка запуталась после неудачного rebase или экспериментов. Все локальные коммиты, которых нет на сервере, исчезнут из ветки.

Как восстановить коммиты после неудачного reset

Git записывает каждое перемещение HEAD в журнал reflog. Коммиты после reset остаются в базе объектов как минимум несколько недель (по умолчанию недостижимые записи reflog хранятся 30 дней).

git reflog
# 1c02e8f HEAD@{0}: reset: moving to HEAD~2
# a3f9c21 HEAD@{1}: commit: docs: README
# 7be41d0 HEAD@{2}: commit: feat: кэширование ответов

git reset --hard a3f9c21   # или HEAD@{1}

Reflog локальный: он есть только в вашем клоне и не передаётся на сервер.

git clean: удалить неотслеживаемые файлы

git reset --hard не трогает файлы, которые Git не отслеживает: новые несохранённые в индекс файлы, сборочные артефакты, логи. Для них есть git clean. Команда необратима — удалённые ею файлы не попадают ни в reflog, ни в корзину.

git clean -n      # dry run: показать, что будет удалено
git clean -f      # удалить неотслеживаемые файлы
git clean -fd     # то же плюс неотслеживаемые папки
git clean -fdx    # плюс игнорируемые файлы (.gitignore): node_modules, dist
git clean -i      # интерактивный выбор

Без -f Git по умолчанию откажется работать (настройка clean.requireForce). Всегда начинайте с -n, особенно с -x: он сотрёт и локальный .env, если тот в игноре.

Связка для полного сброса к последнему коммиту: git reset --hard && git clean -fd.

Типичные ошибки

  • reset --hard вместо --soft. Закоммиченное спасает reflog, незакоммиченное потеряно. Привычка делать git stash перед экспериментами решает проблему.
  • revert без понимания зависимостей. Если более поздние коммиты меняли те же строки, revert даст конфликт. Разрешайте его как обычный: правите файлы, git add, git revert --continue; передумали — git revert --abort.
  • Путаница restore и reset. Чтобы откатить правки в одном файле, не нужен reset всей ветки: git restore файл.
  • force push в main. Лучше revert и обычный push, даже если история станет чуть длиннее.

Итог

git reset — инструмент для собственной, ещё не опубликованной истории: --soft сохраняет изменения в индексе, --mixed в файлах, --hard выбрасывает всё. git revert — безопасная отмена в общих ветках, которая оставляет честный след в истории. git clean дочищает неотслеживаемые файлы, но работает без права на ошибку. Сценарии отмены с restore, reset и revert разобраны на практике в курсе по Git.

Частые вопросы

Удаляет ли git reset коммиты навсегда?

Нет, коммиты остаются в базе объектов и доступны через git reflog, пока их не удалит сборщик мусора — по умолчанию через 30 дней для недостижимых записей.

Можно ли отменить git revert?

Да, revert-коммит — обычный коммит, его можно отменить ещё одним git revert или, если он не запушен, убрать через git reset.

Чем git reset отличается от git checkout?

git reset двигает указатель текущей ветки, а git checkout (или git switch) переключает HEAD на другую ветку или коммит, не меняя саму ветку.

Что значит HEAD~1 и HEAD^?

Оба обозначают родителя текущего коммита; HEAD~3 — третий предок по первым родителям, а HEAD^2 — второй родитель merge-коммита.

Как отменить git clean?

Средствами Git никак: удалённые неотслеживаемые файлы не хранились в репозитории, поэтому всегда запускайте сначала git clean -n.

Разберите тему на видео

Эта тема подробно разобрана с практикой в курсе «Git с нуля до уверенного разработчика».

Перейти к курсу