Рано или поздно ты увидишь красную строку в терминале и сердце ёкнет. Закоммитил не туда. Сделал reset --hard и работа исчезла. Запушил .env с боевым паролем. Git ответил fatal каким то загадочным текстом. В эти секунды важно не паниковать и не делать резких движений, потому что почти всё в Git обратимо - данные живут в .git/objects и в reflog ещё недели после того, как ты думаешь, что их уже нет.
Этот урок - финальный troubleshooting-компендиум курса. Формат каждого пункта жёсткий и одинаковый: симптом - причина - диагностика - лечение - профилактика. Это и шпаргалка под рукой, и материал, который удобно кинуть джуну в чат, когда он прибежал с горящими глазами. Здесь собраны самые частые ошибки git и проблемы git с решением, с реальными fatal-сообщениями, чтобы ты гуглил не текст ошибки, а сразу делал. Команды выверены под современный Git серии 2.5x (актуальная линия к середине 2026 - около 2.54). Где надо, отмечу нюансы свежих версий.
Один принцип перед стартом, выучи его как мантру: сначала reflog, потом думаем. git reflog - это журнал, куда Git пишет каждое перемещение HEAD и веток: коммит, reset, rebase, checkout, merge. Пока объект упомянут в reflog, сборщик мусора его не трогает. Поэтому 90 процентов "я всё потерял" лечится одной командой просмотра журнала и одним reset обратно.

Потеря работы: reset, rebase, force-push, stash
X1. reset --hard съел незакоммиченное и закоммиченное.
Симптом: сделал git reset --hard, рабочая копия откатилась, нужных коммитов нет.
Причина: --hard двигает ветку и затирает рабочее дерево под новый коммит. Закоммиченное при этом не удаляется физически - на него просто перестаёт указывать ветка.
Диагностика и лечение:
Код: Выделить всё
$ git reflog
a1b2c3d (HEAD -> main) HEAD@{0}: reset: moving to HEAD~3
9f8e7d6 HEAD@{1}: commit: add payment retry
3c2b1a0 HEAD@{2}: commit: wip invoice parser
$ git reset --hard 9f8e7d6
HEAD is now at 9f8e7d6 add payment retry
X2. Сломал rebase, конфликты, всё перепуталось.
Симптом: посреди git rebase каша из конфликтов, непонятно где ты.
Причина: rebase переписывает коммиты по одному поверх новой базы, на каждом может встать конфликт.
Лечение - аварийный выход:
Код: Выделить всё
$ git rebase --abort
Код: Выделить всё
$ git reset --hard ORIG_HEAD
X3. Случайный force-push снёс чужие коммиты на сервере.
Симптом: запушил с --force, у коллег пропали коммиты на удалёнке.
Причина: git push --force затирает удалённую ветку безусловно, даже если кто то успел запушить после тебя.
Лечение: коммиты не исчезли - они есть в reflog у того, чей push ты затёр, и в любом свежем клоне. Восстанови SHA у коллеги (git reflog или git log на его машине) и запушь правильное состояние. На сервере с включённым reflog поможет и серверный журнал.
Профилактика: никогда не пуш --force вслепую. Используй:
Код: Выделить всё
$ git push --force-with-lease
X4. Потерял stash.
Симптом: сделал git stash pop, словил конфликт или случайно git stash drop, заначка пропала из git stash list.
Причина: stash - это объекты-коммиты, на которые после drop никто не ссылается по имени.
Диагностика:
Код: Выделить всё
$ git fsck --no-reflogs --unreachable | grep commit
unreachable commit 5d41402abc4b2a76b9719d911017c592...
$ git show 5d41402
$ git stash apply 5d41402
Не туда закоммитил, не туда запушил
X5. Закоммитил в неправильную ветку.
Симптом: сделал коммит в main, а хотел в feature.
Причина: забыл переключиться перед commit.
Лечение (коммит ещё не запушен):
Код: Выделить всё
$ git switch -c feature # создаём целевую ветку от текущего состояния
$ git switch main
$ git reset --hard HEAD~1 # откатываем main на один коммит назад
Код: Выделить всё
$ git switch feature
$ git cherry-pick a1b2c3d # переносим нужный коммит на feature
$ git switch main
$ git reset --hard HEAD~1
X6. Запушил секрет (пароль, токен, ключ).
Это самая болезненная git push ошибка. Действуй по порядку и не пропускай шаги.
Симптом: в коммите уехал .env, id_rsa или AWS-ключ.
Причина: файл не был в .gitignore, попал в индекс и улетел на сервер.
Лечение:
1. Сначала ротация. Считай секрет скомпрометированным в ту же секунду. Отзови токен, смени пароль, перевыпусти ключ. Переписывание истории НЕ отменяет того, что секрет уже прочитали боты, сканирующие GitHub в реальном времени.
2. Вычисти из всей истории. Рекомендованный инструмент 2026 - git filter-repo (сторонний, ставится отдельно), не legacy filter-branch:
Код: Выделить всё
$ git filter-repo --invert-paths --path .env
Код: Выделить всё
$ git push --force-with-lease --all
$ git push --force-with-lease --tags
Профилактика: pre-commit-хук с gitleaks/trufflehog, .gitignore на секреты с первого дня, push protection на платформе.
Состояние HEAD и отклонённый push
X7. Detached HEAD - паника.
Симптом:
Код: Выделить всё
$ git checkout a1b2c3d
You are in 'detached HEAD' state. ...
HEAD detached at a1b2c3d
Лечение - просто уйти обратно:
Код: Выделить всё
$ git switch -
Код: Выделить всё
$ git switch -c rescue-branch
X8. push отклонён: rejected non-fast-forward.
Самый частый git push rejected.
Симптом:
Код: Выделить всё
! [rejected] main -> main (non-fast-forward)
error: failed to push some refs to 'origin'
hint: Updates were rejected because the remote contains work that you do
hint: not have locally.
Лечение - подтянуть и переналожить своё:
Код: Выделить всё
$ git fetch origin
$ git rebase origin/main # или git pull --rebase
... разруливаешь конфликты, если есть ...
$ git push
X9. Перепутал merge и rebase.
Симптом: хотел линейную историю, а получил merge-коммит "Merge branch main"; или наоборот - переписал общую ветку rebase'ом и всем поломал.
Причина: merge сохраняет обе линии и создаёт коммит слияния, rebase переписывает твои коммиты поверх чужих (новые SHA).
Правило: rebase - только для своих ещё не запушенных или личных веток. Общую ветку, которую тянут другие, не rebase'ь. Откатить ошибочный merge до push:
Код: Выделить всё
$ git reset --hard ORIG_HEAD
X10. fatal: not a git repository.
Симптом:
Код: Выделить всё
$ git status
fatal: not a git repository (or any of the parent directories): .git
Лечение: проверь, где ты, и поднимись/спустись в нужную папку. Если репо реально новый - инициализируй. На 2026 в ванильном Git ветка по умолчанию всё ещё master с подсказкой, поэтому main ставим руками:
Код: Выделить всё
$ git rev-parse --show-toplevel # покажет корень репо, если ты внутри
$ git init -b main # если репо нужно создать
X11. Конфликт в бинарном файле.
Симптом: при merge конфликт в .png, .pdf, собранном артефакте - руками такое не сольёшь.
Причина: Git мержит построчно, бинарь строк не имеет.
Лечение - выбрать одну из версий целиком:
Код: Выделить всё
$ git checkout --ours assets/logo.png # моя версия (текущая ветка)
$ git checkout --theirs assets/logo.png # их версия (входящая)
$ git add assets/logo.png
X12. Огромный файл попал в историю, репо распух.
Симптом: клон тянется минутами, .git весит гигабайты из за дампа базы или видео.
Причина: большой blob закоммичен и теперь сидит в каждом срезе истории.
Лечение - вырезать из истории или перенести в LFS:
Код: Выделить всё
$ git filter-repo --strip-blobs-bigger-than 50M
# либо перенос в LFS:
$ git lfs migrate import --include="*.psd,*.mp4"
X13. Слетели окончания строк (CRLF/LF), весь файл в diff.
Симптом: git diff показывает, что изменён весь файл, хотя ты правил одну строку; в команде Windows плюс Linux/Mac вечные конфликты переносов.
Причина: разные ОС пишут конец строки по разному (CRLF против LF), а в репо нет единого правила.
Лечение - зафиксировать политику и перенормализовать:
Код: Выделить всё
$ printf '* text=auto\n' > .gitattributes
$ git add --renormalize .
$ git commit -m "normalize line endings"
X14. Отвалилась аутентификация при push/clone.
Симптом:
Код: Выделить всё
remote: Support for password authentication was removed.
fatal: Authentication failed for 'https://github.com/...'
# либо по SSH:
git@github.com: Permission denied (publickey).
Лечение:
Код: Выделить всё
$ ssh -T git@github.com # проверка SSH-доступа
$ ssh-add ~/.ssh/id_ed25519 # подгрузить ключ в агент
# для HTTPS - вместо пароля ввести PAT, а лучше сохранить:
$ git config --global credential.helper store # или osxkeychain / manager
Игнор, амменд и ещё пачка быстрых рецептов
X15. Забыл .gitignore - файл уже отслеживается.
Симптом: добавил node_modules/ в .gitignore, а Git всё равно его видит в изменениях.
Причина: ignore работает только для НЕотслеживаемых файлов. Что уже в индексе - игнор не выкинет.
Лечение:
Код: Выделить всё
$ git rm -r --cached node_modules
$ git commit -m "stop tracking node_modules"
X16. Опечатка в последнем коммите / забыл файл.
Код: Выделить всё
$ git add forgotten_file.py
$ git commit --amend --no-edit # дописать файл в последний коммит
$ git commit --amend # поправить сообщение
X17. fatal: refusing to merge unrelated histories.
Причина: сливаешь две истории без общего предка (например, локальный init и удалённый репо с README).
Лечение: git pull origin main --allow-unrelated-histories. Профилактика: клонируй существующий репо, а не делай init поверх.
X18. error: Your local changes would be overwritten by checkout.
Причина: незакоммиченные правки мешают переключению. Лечение: git stash, переключись, git stash pop. Либо git switch -m для слияния правок в новую ветку.
X19. Удалил ветку, а в ней были коммиты.
git reflog покажет последний SHA удалённой ветки, git branch имя SHA воссоздаст её.
X20-X30 кратко (симптом - лечение):
- fatal: remote origin already exists - git remote set-url origin URL.
- detached после git pull в пустой ветке - git switch -c.
- "nothing to commit" хотя файлы менялись - они в .gitignore, проверь git check-ignore -v файл.
- Случайно git add лишнего - git restore --staged файл (убрать из индекса, правки сохранить).
- Хочу выкинуть локальные правки в файле - git restore файл.
- Тег создал не на том коммите - git tag -d имя, потом git tag имя SHA.
- Завис git из за крупного коммита - git maintenance run (с 2.54 геометрическая стратегия по умолчанию, gc - легаси).
- "fatal: pathspec did not match any files" - опечатка в имени файла или он не в индексе.
- Откатить уже запушенный коммит без переписывания истории - git revert SHA (создаёт обратный коммит).
- Большой репо медленно тянется - partial clone: git clone --filter=blob:none URL.
- index.lock мешает - оборвался прошлый процесс; убедись что git не запущен и удали .git/index.lock.
Повтори руками, 10 минут, на тестовом репо:
- 1. git init -b main, сделай три коммита (a, b, c).
- 2. git reset --hard HEAD~2 - "потеряй" b и c.
- 3. git reflog, найди SHA коммита c, git reset --hard на него - верни всё. Прочувствуй, что reset --hard обратим.
- 4. git switch -c feature, накоммить, вернись git switch main, сделай git checkout SHA - поймай detached HEAD и выйди git switch -.
- 5. Создай файл secret.txt с фейковым токеном, закоммить. Вычисти git filter-repo --invert-paths --path secret.txt и проверь git log -- secret.txt - история пуста.
- 6. Положи в .gitignore слово build, но сначала закоммить build/ - убедись, что игнор не работает, и почини через git rm -r --cached build.
- 1. Ты сделал git reset --hard и потерял два коммита. Какая команда покажет, куда вернуться, и почему данные ещё живы?
- 2. В ответ на "rejected non-fast-forward" коллега советует git push --force. Почему это опасно и что сделать вместо?
- 3. В историю уехал боевой токен. Перечисли шаги по порядку - что делать ПЕРВЫМ и почему одного filter-repo мало?
- 4. Чем --force-with-lease безопаснее обычного --force?
Запомни три якоря, и большинство аварий перестанет быть страшными: reflog возвращает почти любой коммит, ORIG_HEAD откатывает rebase/merge/reset, force-with-lease защищает удалёнку от слепого затирания. Текст fatal-сообщения - это не приговор, а подсказка: not a git repository значит "ты не там", non-fast-forward значит "сначала подтяни чужое". И главное правило при любой git ошибке: не делай резких --force, пока не посмотрел reflog. Сохрани этот компендиум - он экономит часы и нервы.