Восстановление утерянных коммитов, веток и файлов

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

Восстановление утерянных коммитов, веток и файлов

Сообщение 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 почти ничего не теряет сразу

Сценарий, который ловит каждого. Ты сделал git reset --hard, и три часа работы испарились. Или удалил ветку git branch -D feature, а она была не смержена. Или git checkout затёр незакоммиченную правку в файле. Желудок проваливается, в голове крутится "всё пропало". Так вот: в 90 процентах случаев ничего не пропало. Git - это база контентно-адресуемых объектов, и при обычных операциях он не стирает данные сразу. Он лишь перестаёт на них ссылаться. Физически коммит, дерево и блоб лежат в .git/objects ещё две недели (по умолчанию), пока их не подберёт сборщик мусора.

В прошлых уроках мы разбирали reflog - журнал, где Git помнит, куда указывал HEAD и каждая ветка в последние 90 дней. Reflog - первый инструмент спасателя, и в большинстве случаев его хватает. Но бывает, что reflog не помогает: запись истекла, ты на другой машине, reflog отключён в bare-репозитории, или коммит вообще никогда не был в HEAD (например, он создан внутри прерванного rebase). Вот тогда в дело идёт тяжёлая артиллерия - git fsck, dangling-объекты и прямое чтение базы. Этот урок про то, как восстановить коммит git, как восстановить удаленную ветку git и как вытащить любой файл из любого коммита, даже когда reflog молчит. И главное - в каком порядке это делать, чтобы не сделать хуже.

Изображение

Где Git физически держит мусор до сборки

Чтобы спасать осознанно, надо понимать, куда смотреть. Любой коммит, дерево или блоб - это объект с именем-хешем. Пока на объект кто-то ссылается (ветка, тег, HEAD, reflog, индекс), он "живой". Как только последняя ссылка пропала, объект становится unreachable (недостижимым). Если на него вдобавок не ссылается ни один другой объект - он dangling (висячий). Висячий коммит - типичный итог reset, оборванной ветки или прерванного rebase: верхушка цепочки, на которую больше ничто не указывает.

Недостижимые объекты не исчезают мгновенно. Раньше они валялись россыпью loose-файлов в .git/objects/xx/. С Git 2.5x обслуживание поумнело: одиночные объекты-сироты собираются в cruft pack - отдельный пакет с файлом .mtimes, где у каждого объекта записано время последнего касания. Это аккуратнее, чем тысячи мелких файлов, и позволяет точно отсчитывать "возраст" мусора. Удаляет его только сборка - и тут важная развилка. С Git 2.54 дефолтом ручного обслуживания стала geometric-стратегия (git maintenance), а классический git gc - легаси-путь. Но суть для спасателя одна:
Пока ты не запустил git gc или git maintenance run с прунингом - твои потерянные коммиты ещё в репозитории. Первое правило спасателя: не запускай сборку мусора, пока не вытащил всё нужное.
Сроки по умолчанию: недостижимое с reflog-следом держится 90 дней (gc.reflogExpire), совсем недостижимое без ссылок - 14 дней (gc.pruneExpire). Это твоё окно.

git fsck: рентген объектной базы

git fsck (file system check) проверяет связность и целостность всех объектов. Для спасения нас интересует не проверка, а его побочный талант - он показывает то, на что больше никто не ссылается. Запускаем так:

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

$ git fsck --full --no-reflogs
Checking object directories: 100% (256/256), done.
Checking objects: 100% (1843/1843), done.
dangling commit 9f3c1a7b8e2d4c6f0a1b2c3d4e5f60718293a4b5
dangling blob   1d8e7f6a5b4c3d2e1f0091a2b3c4d5e6f7081920
dangling tree   c4b5a6978d0e1f2a3b4c5d6e7f80910a1b2c3d4e
Разбираем вывод построчно. dangling commit - это и есть оборванная верхушка: скорее всего, твой потерянный коммит. dangling blob - содержимое файла, которое было в индексе (например, ты сделал git add, а потом снёс изменения, не закоммитив). dangling tree - снимок каталога. Зачем --no-reflogs? Без него fsck считает reflog за валидную ссылку, и реально потерянное скрывается. Мы хотим видеть сирот, которых не держит даже журнал.

Когда висячих коммитов много, удобнее, чтобы Git сам разложил их по полочкам - для этого есть git fsck --lost-found. Это и есть классический git lost found из старых мануалов:

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

$ git fsck --lost-found
dangling commit 9f3c1a7b8e2d4c6f0a1b2c3d4e5f60718293a4b5
dangling blob   1d8e7f6a5b4c3d2e1f0091a2b3c4d5e6f7081920

$ ls .git/lost-found/commit/
9f3c1a7b8e2d4c6f0a1b2c3d4e5f60718293a4b5
$ ls .git/lost-found/other/
1d8e7f6a5b4c3d2e1f0091a2b3c4d5e6f7081920
Каждый висячий коммит получает файл-ссылку в .git/lost-found/commit/, остальное (блобы, деревья) - в .git/lost-found/other/. Теперь по этим хешам можно бить git show и искать нужное. Это спасает, когда reflog пуст: например, репозиторий клонировали bare, или ты накосячил внутри cherry-pick, который не оставил следов в журнале HEAD.

Опознаём нужный объект: cat-file и show

fsck выдал десяток хешей - который твой? Не угадывай, читай. Любой объект можно вскрыть напрямую. git cat-file -p печатает содержимое объекта (-p значит pretty - распарсить по типу), git cat-file -t говорит тип:

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

$ git cat-file -t 9f3c1a7
commit
$ git cat-file -p 9f3c1a7
tree 8a2b...e1f
parent 4d5e6f7...
author Anton <a@cyberlake.ru> 1749900000 +0300
committer Anton <a@cyberlake.ru> 1749900000 +0300

feat: парсер конфигов, который я случайно снёс ресетом
Видишь сообщение и автора - это он. Чтобы глянуть, что внутри коммита было по содержанию, не создавая ветку, удобнее git show: он покажет дифф и метаданные. Для блоба show просто выведет текст файла. Когда нужен конкретный файл из конкретного коммита, работает мощнейший синтаксис revision:path:

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

$ git show 9f3c1a7:src/config/parser.php
<?php
namespace App\Config;
final class Parser { /* ... тот самый код ... */ }
Двоеточие между ссылкой и путём - это "достань этот файл в состоянии этого коммита". Можно перенаправить в файл и всё, контент спасён: git show 9f3c1a7:src/config/parser.php > parser.php. Это git восстановить файл в чистом виде - без создания веток, без переключений, просто чтение базы. Путь всегда от корня репозитория, не от текущего каталога (краевой случай, на котором спотыкаются).

Восстановление удалённой ветки

Снёс ветку: git branch -D feature. Ветка в Git - это просто файл в .git/refs/heads/ с одним хешем верхушки. Удалив ветку, ты убрал ссылку, но цепочка коммитов цела. Найти верхушку - две дороги.

Дорога первая, быстрая: reflog ветки HEAD ещё помнит, на чём ты стоял. Команда git reflog show ничего не даст по удалённой ветке (её reflog тоже снесён), а вот общий журнал HEAD - да:

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

$ git reflog
a1b2c3d (HEAD -> main) HEAD@{0}: checkout: moving from feature to main
9f3c1a7 HEAD@{1}: commit: feat: парсер конфигов
4d5e6f7 HEAD@{2}: checkout: moving from main to feature
Вот он, 9f3c1a7 - последний коммит на feature перед уходом. Восстановить удаленную ветку git - одной строкой, пересоздав ссылку на этот хеш:

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

$ git branch feature 9f3c1a7
$ git log --oneline -1 feature
9f3c1a7 feat: парсер конфигов
git branch имя SHA - это просто "создай ref refs/heads/имя, указывающий сюда". Дорога вторая, если reflog не помог (другая машина, истёкший журнал): тот самый git fsck --no-reflogs, ищем dangling commit, опознаём через git show и так же делаем git branch feature <SHA>. Так восстановить коммит git и ветку получается даже из пустыни без журнала.

Восстановление файла: удалённого и закоммиченного

Случай мягче: файл цел в истории, но ты его удалил и закоммитил удаление, либо затёр локально. Тут не нужен fsck - файл достижим, ищем его в нужном коммите.

Современный путь - git restore (стабилен с Git 2.51, учим его вместо checkout). Восстановить файл из конкретного коммита в рабочее дерево:

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

$ git restore --source=HEAD~1 -- src/config/parser.php
--source указывает, откуда брать содержимое, -- отделяет пути. Тонкость: если ты восстанавливаешь файл из того самого коммита, что его удалил, этого коммита уже нет файла - бери HEAD~1 (коммит до удаления). Чтобы найти, в каком коммите файл жил последний раз:

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

$ git log --oneline -- src/config/parser.php
7e8f9a0 chore: убрал старый парсер
9f3c1a7 feat: парсер конфигов
$ git restore --source=9f3c1a7 -- src/config/parser.php
Старый, но живучий способ делает то же: git checkout 9f3c1a7 -- src/config/parser.php. Он повсюду в интернете, знать надо, но restore семантически чище и не путается с переключением веток. Если файл ты только что грохнул локально и ещё не закоммитил - вообще git restore src/config/parser.php (без --source), он вернёт версию из индекса/HEAD.

Отдельная боль - восстановление из stash. Сделал git stash, потом git stash drop или git stash clear - и заначка исчезла из списка. Но коммиты stash - тоже объекты, и они становятся dangling:

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

$ git fsck --no-reflogs | grep commit
dangling commit b7c8d9e0f1a2b3c4d5e6f7081920a3b4c5d6e7f8
$ git stash apply b7c8d9e0
git stash хранит заначку как обычный коммит (с особыми родителями), поэтому git stash apply <SHA> поднимает её прямо по хешу из fsck. С Git 2.51 появились git stash export и git stash import - можно вынести заначки в объект и перенести между репозиториями, что раньше было невозможно.

Восстановление после reset, rebase, checkout

Три классических способа "потерять" работу - и все обратимы.

После git reset --hard. Ресет двигает ветку и затирает рабочее дерево, но старая верхушка остаётся в reflog:

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

$ git reset --hard HEAD~3      # ой, снёс три коммита
$ git reflog
d4e5f60 HEAD@{0}: reset: moving to HEAD~3
a1b2c3d HEAD@{1}: commit: третий коммит, который мне нужен
$ git reset --hard a1b2c3d     # вернулись точно как было
После rebase. Rebase переписывает коммиты (создаёт новые), старые становятся висячими. В reflog ищи метку rebase (finish) или ORIG_HEAD - Git сам кладёт туда верхушку до операции: git reset --hard ORIG_HEAD откатывает неудачный rebase целиком.

После checkout/switch, затёршего файл. Если незакоммиченную правку затёрло переключением - её, увы, не было в объектной базе, reflog не спасёт. Но если ты делал git add перед этим, содержимое осело блобом в индексе и всплывёт в git fsck --lost-found как dangling blob. Поэтому привычка git add перед опасными операциями - дешёвая страховка.

Восстановление целого репозитория

Худший случай: снёс или повредил .git целиком. Git распределённый - почти наверняка полная история есть у коллеги или на сервере. Клонируй заново (git clone) - получишь все коммиты. Если уникальные локальные коммиты были только у тебя и .git частично жив, иногда вытягивает git reflog по веткам плюс fsck по уцелевшим объектам. Если .git мёртв, а рабочее дерево цело - это уже не Git-история, а просто файлы: делай git init, main вручную (init.defaultBranch main или git branch -m main - в ванильном Git на 2026 по умолчанию всё ещё создаётся master с подсказкой), и коммить заново. Полноценную историю в этом случае не вернуть - вот зачем нужен push на remote.

Типичные грабли
  • Запустил git gc до спасения - и прунинг снёс висячие объекты навсегда. Сначала вытащи, потом убирайся.
  • Забыл --no-reflogs в fsck - реально потерянное прячется за reflog-ссылками и не показывается как dangling.
  • Путь в git show <commit>:path указал относительно текущего каталога, а не корня репо - получил "does not exist". Путь всегда от корня.
  • Восстанавливаешь файл из коммита-удаления, а не из предыдущего - бери коммит ДО удаления (parent).
  • Снёс ветку на удалённом сервере и думаешь, что fsck локально поможет - нет, ищи в своём клоне или в reflog того, кто пушил.
  • Перепутал git branch <имя> <SHA> (создаёт) с git branch -f (двигает существующую) - в спасении нужен первый.
Мини-лаба: потерять и вернуть руками

Повтори по шагам в тестовом репо, ничем не рискуя:

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

mkdir rescue && cd rescue && git init -b main
echo "v1" > app.txt && git add . && git commit -m "first"
echo "ценный код" > app.txt && git add . && git commit -m "valuable"
git log --oneline                 # запомни хеш второго коммита
git reset --hard HEAD~1           # ПОТЕРЯЛИ valuable
cat app.txt                       # снова v1, паника
git reflog                        # видим valuable в HEAD@{1}
git reset --hard HEAD@{1}         # ВЕРНУЛИ, проверь cat app.txt
git branch -D main 2>/dev/null; git checkout -b tmp  # снесём ветку-имитацию
git fsck --lost-found --no-reflogs                   # найди dangling commit
git show <SHA-из-вывода>:app.txt                      # прочти файл из сироты
Прогони и убедись: данные возвращаются. Это снимает половину страха перед reset и rebase.

Чек-лист спасателя
  • СТОП. Не запускай git gc, git maintenance run, git prune. Заморозь окно.
  • Что потеряно - коммит, ветка, файл, stash? От этого зависит инструмент.
  • Сначала git reflog - в большинстве случаев верхушка там. Нашёл - git reset --hard <SHA> или git branch <имя> <SHA>.
  • Reflog пуст - git fsck --lost-found --no-reflogs, разбирай dangling commit/blob/tree.
  • Опознай объект: git cat-file -t/-p, git show <SHA>. Не угадывай по хешу.
  • Достань: ветку - git branch <имя> <SHA>; файл - git restore --source=<SHA> -- path или git show <SHA>:path > file; stash - git stash apply <SHA>.
  • Репозиторий целиком - клонируй с remote или у коллеги.
  • После спасения - закоммить, запушь, и только потом думай об уборке.
Итог

Git проектировался так, чтобы данные было трудно потерять навсегда. Контентно-адресуемая база не стирает объекты при reset, rebase или удалении ветки - она лишь убирает ссылки, а сами объекты ждут в .git/objects недели до сборки мусора. Твоя лестница спасения: reflog как первый и самый частый инструмент, git fsck --lost-found как тяжёлая артиллерия для висячих сирот, git cat-file и git show <commit>:path для опознания и извлечения, git branch <имя> <SHA> для воскрешения ветки, git restore --source для файла. Запомни одно правило крепче остальных: в момент паники не запускай сборку мусора - дай себе время вытащить потерянное. А лучшая страховка от всего - регулярный git push: распределённость работает только когда у истории есть копия.
👍4 ❤️3 🔥1 😄 🤔3
Аватара пользователя
terraformdev
Сообщения: 1
Зарегистрирован: 13 май 2026, 01:40

Re: Восстановление утерянных коммитов, веток и файлов

Сообщение terraformdev »

Сделал reset --hard и реально думал что три дня работы в трубу. reflog показал HEAD@{1}, вернул одной строкой. Спасибо, теперь не боюсь ресета так панически.
👍 ❤️ 🔥 😄 🤔3
Аватара пользователя
thecommander
Сообщения: 1
Зарегистрирован: 26 май 2026, 14:29

Re: Восстановление утерянных коммитов, веток и файлов

Сообщение thecommander »

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

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

Поделиться темой: ✈ Telegram VK
Похожие запросы: .gitignore: как игнорировать файлыGit LFS: большие файлы в репозиторииgit commit: фиксация измененийgit tag: теги и релизыgit blame: кто изменил строкуПодпись коммитов в Git (SSH, GPG)

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

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

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