linuxlab.io
Учебники▾
  • Линукс и сети
    Файловая система, процессы, TCP/IP, BGP и OSPF
    →
  • Terraform и IaC
    HCL, state, plan/apply на sandbox LocalStack
    →
  • Git и GitHub
    Объектная модель, plumbing, ветвление, GitHub Actions
    →
Все учебники →
ЦеныО платформеВойтиСоздать аккаунт
/
Intro
Lessons
Footer
linuxlab-УчебникиЦеныО платформеКонфиденциальность и куки
Copyright © 2026 LinuxLab. Все права защищены.
linuxlab.io
Учебники▾
  • Линукс и сети
    Файловая система, процессы, TCP/IP, BGP и OSPF
    →
  • Terraform и IaC
    HCL, state, plan/apply на sandbox LocalStack
    →
  • Git и GitHub
    Объектная модель, plumbing, ветвление, GitHub Actions
    →
Все учебники →
ЦеныО платформеВойтиСоздать аккаунт
/
  • Введение
  • Главы
  • How it works
  • Уроки
  • База знаний
  • Собеседование
Часть III — Ежедневный Git

$ глава 9 · 50 минут

Отмена без паники

Главное, что нужно знать про отмену в Git: почти ничего нельзя потерять навсегда. Закоммиченное живёт в .git/objects/ ещё тридцать дней после того, как ты «удалил» это. Reflog хранит все движения HEAD за тот же срок. Если знаешь, что нажимать, - паника редко обоснована.

Эта глава - короткий справочник по командам отмены и одна фундаментальная идея: пока коммит существует, его можно вернуть. Незакоммиченные правки - другое дело, их Git не сторожит.

9.1 Что вообще можно отменить

Любая отмена в Git - это попытка ответить на один из вопросов:

  1. «Я ещё не закоммитил - как откатить незакоммиченные правки?»
  2. «Я закоммитил, но не запушил - как откатить локальный коммит?»
  3. «Я запушил - как откатить публичный коммит, не сломав другим?»
  4. «Я случайно стёр данные - как их вернуть?»

Под каждый вопрос своя команда. Перепутать их - и можно потерять работу, хотя сам Git к этому почти не подталкивает.

Что отменитьКоманда
Правку в файле, ещё не застейдженнуюgit restore <file>
Стейдж файлаgit restore --staged <file>
Все правки в рабочем дереве (опасно)git reset --hard HEAD
Последний коммит, оставив правки в индексеgit reset --soft HEAD~1
Последний коммит, оставив правки в рабочем деревеgit reset --mixed HEAD~1
Последний коммит со всеми правкамиgit reset --hard HEAD~1
Любой коммит в публичной историиgit revert <sha>
Слишком резкий reset (вернуть состояние)git reflog + git reset
Удалённую ветку с несмерженными коммитамиgit reflog + git branch <name> <sha>

Дальше - детали по каждой команде.

9.2 git reset: три режима, три зоны

Главу 5 мы уже разбирали reset через призму трёх зон Git. Повторим, потому что это самая опасная и самая часто встречающаяся команда отмены.

git reset <commit> двигает текущую ветку на <commit>. Что произойдёт с индексом и рабочим деревом - зависит от флага.

ФлагWorking treeIndexHEAD
--softне трогаетне трогаетдвигает
--mixed (по умолчанию)не трогаетпереписываетдвигает
--hardпереписываетпереписываетдвигает

--soft - самый безопасный. Двигает только указатель ветки. Все правки, которые были после <commit>, оказываются в индексе - готовы к новому коммиту. Используется для «хочу переделать последний коммит, сообщение или содержимое»:

bash
git reset --soft HEAD~1
# ... поправь файлы или сообщение ...
git commit -m "новое сообщение"

--mixed (по умолчанию) - двигает ветку, переписывает индекс на состояние из <commit>, рабочее дерево не трогает. Все правки, которые были, оказываются как unstaged. Используется для «отменить последние N коммитов, но не выкидывать правки»:

bash
git reset HEAD~3            # без флага = --mixed
git status                  # увидишь три коммита правок как modified

--hard - переписывает всё. Все правки после <commit> исчезают из рабочего дерева. Используется для «полностью откатить состояние к этому коммиту, потери не страшны»:

bash
git reset --hard HEAD       # выкинуть всё незакоммиченное
git reset --hard origin/main # синхронизироваться с remote

--hard стирает незакоммиченные правки в tracked-файлах безвозвратно. Untracked-файлы при этом остаются на месте - reset их не видит; вычищаются они git clean -fd. Если не уверен - сначала git stash или git tag backup как страховка. Сам коммит, на который ветка указывала до reset, остаётся в reflog. Незакоммиченные правки - нет, их никогда не существовало как объектов Git.

9.3 git revert: отмена для публичной истории

git reset подходит для своей локальной истории. Для публичной

  • нет: после reset нужно force-push, а force-push на общую ветку запрещён (см. главу 8).

Для публичной истории есть git revert <sha>. Эта команда не удаляет коммит, а создаёт новый коммит, который вносит обратные правки. Старый коммит остаётся в истории, к нему просто добавляется коммит-отменитель.

bash
git revert a1b2c3d

Git откроет редактор для сообщения (по умолчанию Revert "..."), сохрани и выходи. История получит новый коммит:

до:    A → B → C → D (HEAD)
после: A → B → C → D → D'   ← D' - это revert D

Содержимое после revert идентично содержимому до коммита D. Эта команда безопасна для публичной истории: ни один SHA не переписывается, force-push не нужен.

Полезные опции:

bash
git revert <sha1>..<sha3>   # отменить серию коммитов
git revert --no-commit <sha> # применить отмену, но не коммитить
                             # (можно собрать с другими в один)
git revert -m 1 <merge-sha>  # отменить merge-коммит (см. ниже)

Отмена merge-коммита

Merge-коммит имеет двух родителей, и Git не знает, к какому «возвращаться». Опция -m <N> указывает «оставайся на стороне родителя N»:

bash
git revert -m 1 <merge-sha>

-m 1 - оставайся на стороне первого родителя (того, который был на main до merge). -m 2 - на стороне второго (из ветки). В 95% случаев нужен -m 1 - «откатить merge, как будто фичу не вливали».

Подвох: после revert merge-коммита та фича считается уже смерженной, и при повторной попытке merge той же ветки Git решит, что вливать нечего. Нужно либо revert revert'а, либо rebase ветки на свежий main.

9.4 git restore: точечная отмена файлов

Главу 6 мы уже видели git restore. Тут - про роль команды в «отмене».

Сценарии:

bash
# Откатить незакоммиченные правки в файле
git restore file.txt
# Откатить незакоммиченные правки во всём дереве
git restore .
# Убрать файл из стейджа, оставить правки в рабочем дереве
git restore --staged file.txt
# Достать версию файла из старого коммита (текущий не двигается)
git restore --source=HEAD~3 file.txt
git restore --source=v1.0 README.md
# Достать в рабочее дерево И в индекс
git restore --source=HEAD~3 --staged --worktree file.txt

restore отличается от reset тем, что не двигает ветку. Это команда «верни мне эту версию файла», а не «верни мне состояние репозитория».

Все варианты restore для незакоммиченных правок - необратимы. Файл в рабочем дереве перезаписывается без подтверждения. Если боишься - git stash сначала.

9.5 git stash: отложить незавершённое

Stash - это специальная зона, куда можно временно отправить незакоммиченные правки. Используется, когда:

  • нужно срочно переключиться на другую ветку, а у тебя на текущей грязное рабочее дерево;
  • попробовал что-то, не понравилось, не хочется коммитить;
  • нужно git pull, но pull отказывается из-за локальных правок.
bash
git stash                   # отправить правки в stash
# ... переключиться, поработать, вернуться ...
git stash pop               # вытащить и применить, удалить из stash

По умолчанию stash берёт только отслеживаемые файлы. Untracked не попадут, и можно потом удивиться, что они никуда не делись и продолжают мешать. Решение - флаг -u:

bash
git stash -u                # включая untracked
git stash -a                # включая ещё и игнорируемые (.gitignore)

Полезные команды:

bash
git stash list              # список всех stash
# stash@{0}: WIP on main: a1b2c3d initial commit
# stash@{1}: WIP on feat: e4f5a6b add login
git stash show              # diff верхнего stash
git stash show stash@{1}    # конкретный stash
git stash show -p stash@{0} # с полным патчем
git stash apply             # применить, НЕ удалять из stash
git stash pop               # применить И удалить
git stash drop stash@{1}    # удалить, не применяя
git stash clear             # удалить все stash
git stash branch feat-recovery stash@{0}
                            # создать новую ветку из stash

apply vs pop - частая путаница. pop равен apply + drop. Если боишься, что применение пойдёт не так - используй apply и drop отдельно после проверки.

Физически stash - это два-три обычных коммита (один для index, один для working tree, один для untracked если -u), привязанных к специальной ссылке refs/stash. Поэтому stash переживает gc и хранится столько, сколько ты ему позволишь.

См. stash.

9.6 Reflog: спасательный круг

Reflog - самая недооценённая команда Git. Это журнал всех движений HEAD: записи для достижимых состояний по умолчанию хранятся 90 дней, для недостижимых - 30 (см. подробности ниже). Каждый раз, когда что-то двигает HEAD (commit, checkout, reset, merge, rebase, pull), Git добавляет запись.

bash
git reflog
# a1b2c3d HEAD@{0}: reset: moving to HEAD~3
# 4d5e6f7 HEAD@{1}: commit: feat: add login
# 8a9b0c1 HEAD@{2}: commit: docs: update README
# ...

Каждая строка - предыдущее состояние HEAD, со SHA коммита и причиной перехода. Это значит, что любое состояние, в котором ты был за последний месяц, восстановимо.

Главный сценарий - после катастрофического reset:

bash
git reset --hard HEAD~10    # ой, не туда
git reflog
# вверху - текущее состояние (после reset)
# дальше - состояния до. Найди SHA правильного
git reset --hard <sha-правильного>
# вернулся

Reflog работает для:

  • reset --hard - самый частый случай. SHA состояния до reset в reflog`е первой строкой.
    • commit --amend - старый коммит остался; HEAD@{1} обычно указывает на него.
    • rebase - длинная серия записей с подробностями. До и после.
    • merge - HEAD@{0} после, HEAD@{1} до.
    • Удалённая ветка через branch -D - её последний SHA тоже в reflog (потому что когда-то ты на ней был).
    • Случайный коммит в detached HEAD - отмеченный как checkout: moving to ... и commit:.

Конкретные синтаксы:

bash
git reflog show feature           # reflog конкретной ветки
git reflog show HEAD              # reflog HEAD (то же что просто reflog)
git reset --hard HEAD@{2}         # вернуться к третьему состоянию назад
git checkout HEAD@{1}             # просто посмотреть

Reflog хранится в .git/logs/. Срок хранения по умолчанию - 90 дней для достижимых коммитов и 30 для недостижимых. Если хочешь изменить:

bash
git config --global gc.reflogExpire "180 days"
git config --global gc.reflogExpireUnreachable "90 days"

См. reflog.

9.6.1 Подводный камень: когда reflog не поможет

Reflog хранит только локальные движения HEAD на твоей машине. На коллегиной машине у его reflog'а свои записи - твои действия там не видны.

Что reflog не покрывает:

  • Незакоммиченные правки в рабочем дереве. Если ты сделал git reset --hard HEAD или git restore file.txt, некоммиченные правки потеряны. Они никогда не существовали как Git-объекты, reflog их не помнит.
  • Untracked-файлы, которые попали под git clean -fd. Та же история.
  • Состояния ветки на чужой машине. Если коллега force-push'нул и переписал твою публичную ветку - его reflog'е есть твои старые коммиты, в твоём - нет (если ты не клонил после force-push).
  • Состояния старше срока reflog'а. По умолчанию 30 дней для недостижимых веток. После этого git gc подметает.

Также reflog очищается явными командами:

bash
git reflog expire --all --expire=now
git gc --prune=now

Не запускай это без острой нужды - это сжигание спасательного круга. Случаев, когда reflog реально мешает, мало (диск кончается на больших репах, и то лучше через git repack).

Правило к запоминанию: gc --prune=now через несколько минут после катастрофы - это всё. Если что-то пошло не так

  • не запускай gc, сначала разберись.

Уроки в sandbox

lab-9.1. Случайный reset --hard и восстановление через reflog

Цель - спровоцировать «катастрофическую» потерю коммитов и восстановить их через reflog. После лабы становится понятно, что reset --hard - не «удаление навсегда», а «двинул указатель ветки».

  1. Создай репозиторий, сделай пять коммитов: mkdir undo-lab && cd undo-lab && git init && for i in 1 2 3 4 5; do echo "line $i" >> file.txt && git add file.txt && git commit -m "commit $i"; done.

  2. Посмотри историю: git log --oneline. Должно быть 5 коммитов. Запомни SHA верхнего (HEAD).

  3. Сделай «катастрофу»: git reset --hard HEAD~3. Это сбросит ветку на третий коммит снизу. Проверь git log --oneline - два верхних коммита исчезли. Проверь file.txt - там только 3 строки.

  4. Посмотри reflog: git reflog. Увидишь запись о reset: ... HEAD@{0}: reset: moving to HEAD~3 и до неё - запись о последнем commit с SHA, который ты «потерял».

  5. Восстанови: возьми SHA из HEAD@{1} (или явный SHA из reflog) и сделай git reset --hard HEAD@{1} (или git reset --hard <sha>). Проверь git log --oneline - все 5 коммитов снова на месте.

  6. Эксперимент с untracked: создай temp.txt (echo "draft" > temp.txt), НЕ делай git add. Запусти git clean -fd. Файл удалён. Запусти git reflog - там его нет, его никогда не было в reflog. Untracked, не закоммиченное, через clean удалённое - это потеряно. Урок: коммить чаще, даже если не уверен.

  7. Эксперимент с amend: сделай коммит (echo "line 6" >> file.txt && git add file.txt && git commit -m "line 6"). Потом git commit --amend -m "line 6 (corrected)". Запусти git reflog - увидишь обе версии: одну как commit (amend):, другую как commit:. Если хочется откатить amend - git reset --hard HEAD@{1}.

  8. Эксперимент с удалением ветки: создай feature (git switch -c feature && echo "feat" >> feat.txt && git add feat.txt && git commit -m "feature work"), запомни SHA, переключись на main и удали: git switch main && git branch -D feature. Запусти git reflog - последний SHA feature там есть. Восстанови ветку: git branch feature <sha>.

sandbox с автопроверкой - открыть в песочнице

Резюме

  • Перед любой опасной операцией думай: «что я отменяю - незакоммиченное или закоммиченное?». Закоммиченное почти всегда восстановимо. Незакоммиченное - нет.
  • `git reset` двигает ветку. Три флага: `--soft` (только HEAD), `--mixed` (HEAD+index, по умолчанию), `--hard` (всё). `--hard` стирает незакоммиченные правки.
  • `git revert <sha>` - создаёт новый коммит с обратными правками. Безопасно для публичной истории, force-push не нужен. Merge-коммит - `revert -m 1 <sha>`.
  • `git restore file.txt` - откатить файл к версии из индекса/HEAD. `--staged` - убрать из стейджа. `--source=<commit>` - достать версию из коммита.
  • `git stash` - отложить незакоммиченные правки в специальную зону. `-u` для untracked, `pop` чтобы применить и удалить, `apply`+`drop` если хочется проверить перед удалением.
  • Reflog - журнал движений HEAD за последние 30+ дней. Главный инструмент восстановления после `reset --hard`, `--amend`, `rebase`, удаления ветки.
  • Reflog не покрывает: незакоммиченные правки, untracked после `clean`, состояния старше срока, чужие машины. Не запускай `git gc --prune=now` сразу после катастрофы.

Контрольные вопросы

  1. Я случайно сделал `git reset --hard HEAD~5` и хочу вернуть последние 5 коммитов. Что делать?

    Показать ответ

    Спокойно. Запусти git reflog - первая или вторая строка будет HEAD@{1}: commit: ... с SHA состояния до reset. Скопируй SHA, сделай git reset --hard <тот-SHA> (или эквивалент git reset --hard HEAD@{1}). Все 5 коммитов снова на месте, ветка восстановлена. Reflog хранит записи 30 дней, так что времени достаточно - главное, не запускай git gc --prune=now до восстановления. Имей в виду: если незакоммиченные правки были в рабочем дереве на момент reset - они потеряны, reflog их не помнит.

  2. В чём принципиальная разница между `git reset` и `git revert`?

    Показать ответ

    reset двигает указатель ветки назад - то есть «делает вид, что последних коммитов не было». Это переписывает историю. Безопасно только на личных ветках до push. revert создаёт новый коммит, который вносит обратные правки. История остаётся как была, но добавляется коммит-отменитель. Безопасно в любом контексте, потому что не переписывает существующие SHA. Правило: до push - reset (если хочется чистой истории), после push - revert (чтобы не ломать другим работу).

  3. Сделал `git stash`, потом сделал ещё несколько правок и коммитов. Хочу вернуть тот stash. Как?

    Показать ответ

    Stash не «привязан» к ветке или коммиту - он живёт отдельно в refs/stash. Запусти git stash list - увидишь свой stash как stash@{0} (или дальше, если делал несколько stash). Чтобы применить - git stash apply stash@{0} (или pop, если хочешь сразу удалить). Если в текущем рабочем дереве есть правки, пересекающиеся со stash, может возникнуть конфликт - разруливай как обычный конфликт через git add после правки. На случай «слишком много стало stash'ей» - git stash list покажет их все с описанием, какой когда был сделан и на какой ветке.

  4. У меня в команде кто-то запушил плохой merge на main. Как откатить, чтобы не ломать всем?

    Показать ответ

    git revert -m 1 <sha-merge-коммита>. Это создаст новый коммит, откатывающий весь merge, оставаясь на стороне main (родитель 1). Запушить - обычным git push, никаких force не нужно. После revert все, кто склонит main, естественно получат это исправление. Подвох: если потом понадобится снова влить ту же feature, простым git merge feat не получится - Git решит, что ветка уже смержена. Нужен либо revert revert'а, либо rebase feature на новый main с пересборкой коммитов.

  5. Что произойдёт с моими коммитами на удалённой ветке, если я её удалю через `git branch -D`?

    Показать ответ

    Локально ветка пропадёт из .git/refs/heads/. Коммиты, на которые она указывала, останутся в .git/objects/. Если ни одна другая ветка/тег/HEAD на них не указывают - они станут недостижимыми. Reflog при этом запомнит SHA вершины ветки в течение 30 дней. Восстановить - git branch <name> <sha-из-reflog>. Если же ветка была запушена в origin - на сервере она остаётся, пока не удалишь её явно через git push origin --delete <name>. Так что «полное» удаление - это два шага: локально и на remote.

← Предыдущая08-merge-vs-rebaseСледующая →10-history
Footer
linuxlab-
Copyright © 2026 LinuxLab. Все права защищены.
Учебники
Цены
О платформе
Конфиденциальность и куки