Откат опасных операций: reset --hard, сломанный rebase, плохой merge

Рейтинг: 66.7% · 13 голосов
Самый подробный курс по Git - от первого коммита до объектной базы и packfiles. Терминал и первый репозиторий, ветки и слияние, конфликты, удалённая работа и форджи (GitHub, GitLab), rebase и переписывание истории, спасение кода через reflog и fsck, stash и worktree, bisect и blame, подмодули и монорепо, хуки и подпись коммитов, внутреннее устройство и масштаб. Для новичков и профи. Актуально на 2026 (Git 2.54, дорожная карта 3.0).
Ответить
Аватара пользователя
Pavel_Git
Сообщения: 52
Зарегистрирован: 11 май 2026, 05:31

Откат опасных операций: reset --hard, сломанный rebase, плохой merge

Сообщение Pavel_Git »

Оглавление курса (52)
  1. Что такое контроль версий и почему победил Git
  2. Установка Git на Windows, macOS и Linux и первая настройка
  3. Терминал и как спрашивать у Git: выживание в командной строке
  4. Ментальная модель Git: три состояния, коммит-снимок и граф
  5. Первый репозиторий: init, add, commit, status
  6. Просмотр изменений: status, diff и индексация по частям
  7. История проекта: git log, диапазоны и поиск
  8. .gitignore: что не должно попасть в репозиторий
  9. .gitattributes: окончания строк, бинарники и атрибуты
  10. Ветки как указатели: branch, switch и detached HEAD
  11. Слияние веток: fast-forward против трёхстороннего merge
  12. Конфликты слияния: анатомия и уверенное разрешение
  13. Практикум: твой первый день с Git от нуля до push
  14. Удалённые репозитории: remote, fetch, базовый pull и refspec
  15. git push: безопасная отправка и защита от перезаписи
  16. Клонирование: shallow, partial clone, bundle и backfill
  17. GitHub, GitLab и форки: pull request, rulesets и защита веток
  18. Рабочие процессы команды: GitFlow, GitHub Flow, trunk-based
  19. Merge queue и merge trains: безопасное вливание в trunk
  20. Культура code review и Conventional Commits
  21. git reset: три дерева и отмена индексации и коммитов
  22. git revert: безопасная отмена в общей истории
  23. Правка последнего коммита: git commit --amend
  24. git rebase: пересадка истории и золотое правило
  25. Интерактивный rebase: переписать серию коммитов
  26. git cherry-pick: перенос отдельных коммитов
  27. Stacked diffs: цепочка зависимых PR
  28. Массовое переписывание истории: git filter-repo и git replace
  29. git reflog: журнал, который помнит всё
  30. Восстановление утерянных коммитов, веток и файлов
  31. Откат опасных операций: reset --hard, сломанный rebase, плохой merge (вы здесь)
  32. git stash: тайник для незавершённой работы
  33. git worktree: несколько рабочих деревьев одного репозитория
  34. git bisect: бинарный поиск регрессии
  35. git blame, mailmap и археология кода
  36. Теги, релизы, archive и bundle
  37. Подмодули: внешние репозитории внутри проекта
  38. Subtree, монорепозитории и sparse-checkout
  39. Git LFS и большие файлы
  40. Вспомогательные команды: clean, mv, rm, restore
  41. Псевдонимы и кастомизация .gitconfig
  42. Хуки Git: автоматизация на клиенте и сервере
  43. Подпись коммитов и тегов: SSH, GPG и Sigstore
  44. Внутреннее устройство I: объектная база Git
  45. Внутреннее устройство II: ссылки, HEAD, notes и reftable
  46. Внутреннее устройство III: индекс, packfiles, gc и обслуживание
  47. От SHA-1 к SHA-256: переход на новый хеш
  48. Огромные репозитории: partial clone, FSMonitor, Scalar
  49. Git в CI/CD: shallow, кеш, merge_group и безопасность
  50. Куда движется Git: дорожная карта 3.0 и экосистема
  51. Профессиональный setup: собираем рабочее окружение Git
  52. 30+ типичных ошибок Git и как из них выбираться
Паника - худший советчик

Сценарий знаком каждому. Ты делаешь

Код: Выделить всё

git reset --hard
, жмёшь Enter и через полсекунды осознаёшь, что снёс полдня работы. Или прогоняешь rebase на двадцать коммитов, что-то идёт не так, и история превращается в кашу. Или делаешь force-push и ровно в этот момент понимаешь, что затёр чужую ветку. Сердце ухает в пятки, ладони потеют, рука тянется паниковать.

Так вот, главное правило этого урока, и запомни его раньше всех команд: не паникуй и ничего больше не трогай. Git - не текстовый редактор, где Ctrl+Z имеет глубину в пару шагов. Под капотом это база данных с журналом всех движений, и почти всё, что ты сделал, ещё лежит на диске. Большинство катастроф, которые новички считают фатальными, на деле откатываются одной-двумя командами. Опасны не сами операции - опасны хаотичные движения после них. Человек, который снёс reset-ом коммит, а потом в панике начал делать новые коммиты, checkout-ы и ещё один reset, своими руками затаптывает следы, которые Git бережно сохранил.

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

Изображение

Reflog - чёрный ящик твоего репозитория

Чтобы

Код: Выделить всё

git reset hard отменить
, надо понять, куда вообще делся "потерянный" коммит. А он никуда не делся. Когда ты двигаешь ветку или HEAD - коммитом, reset-ом, rebase, checkout, merge - Git дописывает строчку в reflog (reference log, журнал ссылок). Лежит он в

Код: Выделить всё

.git/logs/HEAD
и

Код: Выделить всё

.git/logs/refs/heads/<ветка>
. Это не магия, это обычный текстовый файл, где каждая строка - "откуда -> куда, кто, когда, каким действием".

Ключевая идея: коммит в Git живёт, пока на него есть хоть какая-то ссылка. Ветка - ссылка. HEAD - ссылка. И reflog тоже держит ссылки. Поэтому даже если ни одна ветка больше не указывает на твой коммит, на него ещё месяца три указывает запись в reflog, и сборщик мусора его не тронет. Дефолт - 90 дней для достижимых записей и 30 дней для недостижимых (

Код: Выделить всё

gc.reflogExpire
и

Код: Выделить всё

gc.reflogExpireUnreachable
).

Смотрим журнал:

Код: Выделить всё

$ git reflog
8f3a1c2 (HEAD -> main) HEAD@{0}: reset: moving to HEAD~3
a91be07 HEAD@{1}: commit: add cache layer
4d2f8aa HEAD@{2}: commit: wire up redis client
1c7e0b9 HEAD@{3}: commit: draft config
8f3a1c2 (HEAD -> main) HEAD@{4}: commit: base scaffolding
Читаем сверху вниз - это история движений HEAD, свежее наверху. Видишь:

Код: Выделить всё

HEAD@{0}
- это где мы сейчас (после злополучного reset на HEAD~3). А вот

Код: Выделить всё

HEAD@{1}
, коммит

Код: Выделить всё

a91be07 "add cache layer"
- это то состояние, которое было до reset. Три коммита, которые я "потерял", вот они, целые: a91be07, 4d2f8aa, 1c7e0b9. Синтаксис

Код: Выделить всё

HEAD@{N}
означает "где HEAD был N движений назад". Это полноценный указатель, его можно скармливать любой команде.

Возврат - один reset обратно:

Код: Выделить всё

$ git reset --hard a91be07
HEAD is now at a91be07 add cache layer
Или эквивалентно, не выписывая хеш:

Код: Выделить всё

git reset --hard HEAD@{1}
. Всё, три коммита вернулись на ветку, как будто ничего не было. Заметь тонкость: reset нас сюда же и притащил, так что отменяем reset тем же reset-ом, просто в обратную сторону. Никакой отдельной команды undo в Git нет - и она не нужна, когда есть reflog.

Важная оговорка про

Код: Выделить всё

reset --hard
. Он восстанавливает коммиты. Если у тебя были несохранённые правки в рабочем каталоге, которые ты никогда не коммитил и не клал в stash, - вот их reflog не вернёт, потому что их вообще никогда не было в объектной базе. Git хранит снимки, а не нажатия клавиш. Мораль на будущее: коммить чаще, хоть мусорными "wip"-коммитами. Закоммиченное практически невозможно потерять, незакоммиченное - в один

Код: Выделить всё

reset --hard
.

ORIG_HEAD, MERGE_HEAD, FETCH_HEAD - спец-указатели на крайний случай

Git заранее знает, что reset, merge и rebase опасны, поэтому перед такими операциями он сам сохраняет точку возврата. Это

Код: Выделить всё

git orig_head
- специальная ссылка

Код: Выделить всё

.git/ORIG_HEAD
, куда Git кладёт хеш HEAD прямо перед операцией, которая массово двигает историю: перед reset, перед merge, перед rebase, перед pull. По сути это автоматическая закладка "где я был секунду назад".

Разберём три брата, чтобы не путать:
  • ORIG_HEAD - где был HEAD до последней крупной операции (reset/merge/rebase/pull). Твоя кнопка "отмена" для одного шага назад.
  • MERGE_HEAD - существует только во время незавершённого merge. Указывает на вершину ветки, которую ты вливаешь. Видишь его в - значит, ты в середине слияния (например, застрял в конфликте).
  • FETCH_HEAD - что в последний раз притащил

    Код: Выделить всё

    git fetch
    с удалёнки. На него ссылается

    Код: Выделить всё

    git merge FETCH_HEAD
    , который скрыто и делает

    Код: Выделить всё

    git pull
    .
Самое частое применение - откатить только что сделанную операцию, даже не лазая в reflog:

Код: Выделить всё

$ git merge feature/payments
# понял, что слил не то и не туда
$ git reset --hard ORIG_HEAD
HEAD is now at 8f3a1c2 base scaffolding
Один нюанс, на котором горят: ORIG_HEAD хранит только одну точку - от самой последней операции. Сделал reset, потом ещё merge - ORIG_HEAD уже показывает на состояние перед merge, а точка перед reset затёрта. Поэтому ORIG_HEAD удобен для немедленного "ой, отмотай", а для более глубокого отката - reflog, он помнит всю цепочку.

Откатить rebase: ORIG_HEAD, --abort и reflog

Rebase - самая частая причина запросов "

Код: Выделить всё

откатить rebase git
". И тут есть две принципиально разные ситуации.

Ситуация А: rebase ещё идёт и встал на конфликте или ты понял, что всё пошло не так. Пока процесс не завершён, существует папка состояния (например,

Код: Выделить всё

.git/rebase-merge/
) и спасение элементарное:

Код: Выделить всё

$ git rebase --abort
Эта команда телепортирует тебя ровно в то состояние, что было до начала rebase, как будто ты его не запускал. Рабочий каталог, ветка - всё как было. Это самый чистый и безопасный выход, пока rebase не закончился. У merge и cherry-pick ровно такие же кнопки экстренного торможения:

Код: Выделить всё

git merge --abort
и

Код: Выделить всё

git cherry-pick --abort
. Запомни их как единый рефлекс: "пошло не так в середине - значит --abort".

Ситуация Б: rebase уже завершился, и только теперь ты видишь, что история - каша. тут не сработает, прерывать нечего. Зато сработают наши знакомые:

Код: Выделить всё

$ git reset --hard ORIG_HEAD
потому что rebase любезно положил в ORIG_HEAD вершину ветки до своего запуска. Если же между rebase и осознанием ты успел ещё что-то наделать и ORIG_HEAD уже не тот - идём в reflog и ищем последнюю запись перед началом rebase:

Код: Выделить всё

$ git reflog
2b9f4e1 (HEAD -> main) HEAD@{0}: rebase (finish): returning to refs/heads/main
2b9f4e1 HEAD@{1}: rebase (pick): add cache layer
7c01aa3 HEAD@{2}: rebase (pick): wire up redis client
e44d9c0 HEAD@{3}: rebase (start): checkout main~5
a91be07 HEAD@{4}: commit: add cache layer
Видишь маркеры

Код: Выделить всё

rebase (start)
и

Код: Выделить всё

rebase (finish)
- вся операция как на ладони. Состояние до rebase - это

Код: Выделить всё

HEAD@{4}
, последняя запись перед

Код: Выделить всё

rebase (start)
. Возврат:

Код: Выделить всё

$ git reset --hard HEAD@{4}
И ветка снова такая, какой была до твоих rebase-экспериментов. Reflog буквально показывает путь - твоё дело прочитать его и ткнуть пальцем в нужную строку.

Восстановиться после force-push

Теперь страшное -

Код: Выделить всё

восстановить после force push
. Ты переписал историю локально, сделал

Код: Выделить всё

git push --force
, и удалёнка приняла новую ветку, а старые коммиты на сервере "осиротели". Хуже того, ты мог затереть чужие коммиты, которых у тебя локально и не было.

Спокойно, восстановить почти всегда реально, и вот откуда брать данные.
  • Твой локальный reflog. Force-push не трогает локальный репозиторий - там вся старая история цела. Находишь нужный коммит в

    Код: Выделить всё

    git reflog
    (или у тебя ещё жив

    Код: Выделить всё

    refs/remotes/origin/<ветка>
    на старое значение), сбрасываешь ветку на него и пушишь обратно.
  • Чужой клон. Если коллега не делал fetch после твоего форс-пуша, у него на машине лежит старое

    Код: Выделить всё

    origin/main
    . Он делает

    Код: Выделить всё

    git fetch
    ... стоп, не делает - сначала смотрит

    Код: Выделить всё

    git reflog --all
    или

    Код: Выделить всё

    git log origin/main
    , достаёт хеш потерянной вершины и отдаёт тебе. Чужая машина - тоже резервная копия истории.
  • Сервер. На GitHub/GitLab осиротевшие коммиты живут какое-то время и достаются через reflog сервера, события API или интерфейс ("Activity"/восстановление ветки). На своём bare-репозитории - тот же

    Код: Выделить всё

    git reflog
    , если на сервере включён

    Код: Выделить всё

    core.logAllRefUpdates
    .
Но лучшее лечение - профилактика. Никогда не делай голый [/b]. Делай

Код: Выделить всё

--force-with-lease
:

Код: Выделить всё

$ git push --force-with-lease origin main
Разница принципиальная. Обычный говорит "затри удалёнку чем бы там ни было, мне плевать". А

Код: Выделить всё

--force-with-lease
говорит "затри, только если удалёнка всё ещё на том коммите, который я видел в прошлый fetch". Если кто-то успел запушить между твоим fetch и push, lease обломится, и Git откажет:

Код: Выделить всё

$ git push --force-with-lease origin main
 ! [rejected]        main -> main (stale info)
error: failed to push some refs to 'origin'

Код: Выделить всё

stale info
- это не ошибка, это твой спасательный круг. Он только что не дал тебе затереть чужую работу. Делай fetch, разбирайся, что там появилось, и только потом форсь. Возьми за правило: force-with-lease всегда, голый force - почти никогда.

Удалённый stash и затёртый файл - тоже спасаемо

Случайно дропнул stash.

Код: Выделить всё

git stash drop
или

Код: Выделить всё

git stash pop
(который при успехе сам дропает запись) - и заначка пропала из списка. Но коммит-объект stash ещё висит в базе как недостижимый. Достаём через fsck:

Код: Выделить всё

$ git fsck --no-reflogs | grep commit
dangling commit 3e7af19c0b4a8d2e5f6071c9d8a4b3c2e1f09a8b
$ git stash store -m "rescued" 3e7af19c
$ git stash list
stash@{0}: rescued

Код: Выделить всё

git fsck
обходит всю объектную базу и докладывает о "висячих" (dangling) объектах - тех, на кого никто не ссылается. Среди них и будет твой stash, у него характерная структура коммита с двумя-тремя родителями. Командой

Код: Выделить всё

git stash store
возвращаем его в список заначек.

Затёр файл в рабочем каталоге - например, неудачным checkout или reset. Если версия файла была в любом коммите, восстановить точечно:

Код: Выделить всё

$ git restore --source=HEAD@{2} -- config/redis.yml
С Git 2.51

Код: Выделить всё

git restore
стабилен (вышел из экспериментального статуса) - это специальный инструмент именно для восстановления файлов рабочего дерева, в отличие от перегруженного checkout. Источником может быть любая ревизия, в том числе запись reflog.

Плохой merge: revert -m или reset до ORIG_HEAD

Влил ветку, а merge оказался плохим - принёс баги или вообще не то. Тут развилка зависит от того, ушёл ли merge на общий сервер.

Merge ещё локальный, никто не видел. Просто перепиши историю, как будто слияния не было:

Код: Выделить всё

$ git reset --hard ORIG_HEAD
или

Код: Выделить всё

git merge --abort
, если слияние ещё не завершено (застрял в конфликте). Чисто и просто, потому что историю никто не успел утащить.

Merge уже опубликован, коллеги его подтянули. Тут делать нельзя - перепишешь общую историю и поломаешь всем жизнь. Нужен , который не стирает merge, а добавляет новый коммит, отменяющий его эффект. Но у merge два родителя, и Git не знает, к какому из них "откатывать", - надо указать явно флагом :

Код: Выделить всё

$ git revert -m 1 9c4f0a2
значит "оставь первого родителя", то есть основную ветку, куда мы вливали (mainline), и выкини изменения второго родителя - влитой фичи. Номер родителя смотри в

Код: Выделить всё

git show 9c4f0a2
, строка

Код: Выделить всё

Merge: <parent1> <parent2>
: первый - mainline, второй - то, что влилось. Перепутаешь номер - откатишь не ту сторону.

Большие грабли с revert merge: revert убирает изменения фичи, но в графе слияние остаётся. Если потом захочешь влить ту же фичу заново, Git решит, что она уже была, и не принесёт коммиты. Лечится revert-ом самого revert-а перед повторным слиянием. Но это уже тема отдельного разговора - просто знай, что revert merge оставляет такой шлейф.

Свод: если случилось X - делай Y

Вот та самая таблица "

Код: Выделить всё

отменить опасную операцию git
", ради которой стоит вернуться к уроку в момент паники:

Код: Выделить всё

КАТАСТРОФА                          ->  ЛЕЧЕНИЕ
reset --hard снёс коммиты           ->  git reflog; git reset --hard HEAD@{1}
                                        (или git reset --hard ORIG_HEAD)
rebase идёт и сломался              ->  git rebase --abort
rebase завершился, история каша     ->  git reset --hard ORIG_HEAD
                                        (или HEAD@{N} до 'rebase (start)')
merge в процессе (конфликт)         ->  git merge --abort
плохой merge, локальный            ->  git reset --hard ORIG_HEAD
плохой merge, уже запушен           ->  git revert -m 1 <хеш merge>
cherry-pick встал на конфликте      ->  git cherry-pick --abort
ошибочный force-push                ->  reflog/чужой клон -> reset -> push
                                        --force-with-lease; профилактика: lease
дропнут stash                       ->  git fsck --no-reflogs; git stash store
затёрт файл                         ->  git restore --source=<rev> -- путь
Один паттерн пронизывает всю таблицу: операция в процессе -> --abort, операция завершена -> reset/revert через ORIG_HEAD или reflog. Держи это в голове, и почти любая git-катастрофа из конца света превращается в минутную процедуру.

Мини-лаба: устрой катастрофу и спасись

Лучший способ перестать бояться - сломать всё в песочнице и починить руками.
Прогони это один раз спокойно - и в реальный момент паники руки сами вспомнят.

Контрольные вопросы
  • Чем отличается выход из rebase командой от отката завершённого rebase через

    Код: Выделить всё

    reset --hard ORIG_HEAD
    ? Когда работает только один из вариантов?
  • Что именно хранит ORIG_HEAD и почему на него нельзя полагаться, если после ошибочной операции ты сделал ещё одну крупную операцию?
  • Почему голый опасен, а

    Код: Выделить всё

    --force-with-lease
    нет? Что значит ответ сервера

    Код: Выделить всё

    stale info
    ?
  • Ты случайно сделал

    Код: Выделить всё

    reset --hard
    и потерял незакоммиченные правки в файле, которого не было ни в одном коммите. Вернёт ли их reflog и почему?
Итог

Опасные операции Git опасны ровно до тех пор, пока ты не знаешь про reflog и ORIG_HEAD. Закоммиченное практически невозможно потерять: Git ведёт журнал всех движений HEAD и веток, и любой "потерянный" коммит достаётся одной строкой из

Код: Выделить всё

git reflog
. Незавершённую операцию гаси через , завершённую откатывай через

Код: Выделить всё

reset --hard ORIG_HEAD
(локально) или

Код: Выделить всё

revert -m
(если уже опубликовал). Форсь только с

Код: Выделить всё

--force-with-lease
. И главное - не паникуй и не трать первые секунды на хаотичные движения: именно они затаптывают следы, которые Git для тебя сохранил. Reflog покажет путь, твоё дело - спокойно его прочитать.
👍7 ❤️3 🔥 😄 🤔2
Аватара пользователя
grpcninja
Сообщения: 1
Зарегистрирован: 24 май 2026, 20:57

Re: Откат опасных операций: reset --hard, сломанный rebase, плохой merge

Сообщение grpcninja »

Вчера форс-пушнул и думал что убил ветку коллеги. Оказалось у него локально старый origin/main лежал, достали хеш через git log origin/main и я запушил обратно. Теперь только force-with-lease, спасибо за урок.
👍1 ❤️2 🔥 😄 🤔
Аватара пользователя
zhuravljevan
Сообщения: 1
Зарегистрирован: 02 июн 2026, 16:43

Re: Откат опасных операций: reset --hard, сломанный rebase, плохой merge

Сообщение zhuravljevan »

Вопрос: а если я reset --hard сделал неделю назад и только сейчас спохватился, коммит ещё достанется через reflog или его уже gc съел? И как проверить не запуская восстановление вслепую?
👍1 ❤️3 🔥 😄 🤔
Ответить
← Предыдущая глава
Восстановление утерянных коммитов, веток и файлов
Следующая глава →
git stash: тайник для незавершённой работы

Все главы курса «Git профессионально: от первого коммита до внутреннего устройства»

Поделиться темой: ✈ Telegram VK
  • Похожие темы
Похожие запросы: git reflog: восстановить потерянный кодРазрешение конфликтов слияния в Gitgit merge или rebase: что выбратьgit merge: слияние ветокgit rebase: перебазированиеgit reset: отменить коммит и изменения

Вернуться в «Git профессионально: от первого коммита до внутреннего устройства»

Кто сейчас на конференции

Сейчас этот форум просматривают: нет зарегистрированных пользователей и 2 гостя