fatal: not a git repository — как починить за 60 секунд

Четыре реальные причины fatal: not a git repository по убыванию вероятности — и одна команда, которая назовёт вашу до того, как вы что-то тронете.

HUMANавтор @mrsaynothing28d4 мин чтения

Быстрейший способ потерять двадцать минут — набрать git pull в папке над вашим репозиторием. Git отвечает fatal: not a git repository (or any of the parent directories): .git, и большинство ответов в сети советует git init — что неверно для трёх из четырёх реальных причин.

Триаж за 60 секунд:

git rev-parse --show-toplevel || ls -a

Если репозиторий существует выше, --show-toplevel печатает его корень — не та папка, дело закрыто, cd туда. Если падает и он, та же команда покажет содержимое папки: нет .git в списке — репозитория здесь никогда и не было. Одна команда отделяет две причины, за которыми большинство случаев. Остальная часть поста — про более редкие.

Почему Git пишет «fatal: not a git repository»?

Git находит репозиторий, поднимаясь от текущего каталога в поисках записи .git, и останавливается на первой. Этот подъём ломается ровно четырьмя способами: вы снаружи дерева, каталога .git больше нет, переменная окружения увела поиск в сторону, либо .git есть, но указывает в никуда. По опросу Stack Overflow 2022 года Git стоит примерно на 94% машин разработчиков — ошибка эта общий обряд посвящения. Выучите четыре случая один раз, и перестанут тратиться время и нервы.

(Если с репозиторием всё в порядке и вы пришли отменить коммит, страница Undo Anything in Git собирает все способы починки с этого сайта в одном месте.)

Случай 1: вы не в том каталоге

Самый частый. Две формы:

  1. Вы сделали cd в подкаталог — docs/, src/api/ — а привычка с другого проекта подсказывает, что корень здесь. Git поднимается, не находит ничего, сдаётся.
  2. Вы склонировали репозиторий и ждали файлы здесь, но git clone всегда создаёт подкаталог с именем репозитория. Checkout лежит уровнем ниже.
git rev-parse --show-toplevel   # печатает корень, когда он есть
cd "$(git rev-parse --show-toplevel)"  # прыжок туда одной командой

Вторая команда навсегда лечит проблему мышечной памяти: работает с любой глубины внутри репозитория.

Случай 2: нет .git — папка никогда не была репозиторием

ls -a не показывает .git. Обычно это:

  • ZIP с GitHub. Code → Download ZIP отдаёт только файлы: без .git, без истории, без remote. Документация GitHub это подтверждает: ZIP-архив содержит лишь снимок кода.
  • Файлы, скопированные с другой машины или из другого проекта, без скрытого каталога.
  • Чистильщик удалил .git как «бесполезную скрытую папку».

Починка: клонировать заново, история вернётся.

cd ..
git clone https://github.com/<user>/<repo>.git

git init здесь начинает совершенно новый репозиторий; ничего не возвращается. А куча скопированных файлов обычно тянет за собой неотслеживаемые; git remove untracked files прикроет уборку, когда клон встанет на место.

git init не чинит репозиторий — он создаёт пустой там, где вы стоите.

Случай 3: GIT_DIR экспортирован и ломает все репозитории

Примета: ошибка следует за вами в каталоги, где час назад всё работало. Экспортированный GIT_DIR велит Git смотреть на один фиксированный путь вместо поиска репозитория, и каждый «обычный» каталог становится «not a repository».

env | grep GIT_DIR     # GIT_DIR=/какой-то/старый/путь
unset GIT_DIR

Если возвращается каждое утро — строка в ~/.bashrc, ~/.zshrc или CI-обёртке его экспортирует. Системы CI ставят GIT_DIR намеренно: внутри пайплайна это правильно, в интерактивном shell — мина.

Случай 4: submodule не инициализирован

Вы заходите в vendor/libfoo/, и Git говорит, что каталог не репозиторий. Он прав: до инициализации каталог submodule — пустая папка со строкой в .gitmodules и без единого checkout.

cat .gitmodules        # перечисляет пути submodule
git submodule update --init --recursive

Запускайте из корня репозитория, не изнутри submodule — правило подъёма действует и тут, а пустому submodule некуда подниматься.

Случай 5: .git — файл, и его цель исчезла

Worktree и submodule делают .git однострочным файлом: gitdir: /путь/к/настоящему/gitdir. Когда настоящий gitdir удалён (чистка .git/modules, переезд диска), указатель висит, и подъём находит .git, который не отвечает:

$ ls -la .git
-rw-r--r-- 1 you you 35 Sep 28 10:12 .git     # файл, а не каталог
$ cat .git
gitdir: ../.git/modules/libfoo
git worktree repair    # перевязывает worktree со сменившимися путями

Если worktree repair не находит цель — gitdir потерян. История submodule живёт в .git/modules/<имя> родительского репозитория; проверьте, что она там, прежде чем хоронить всё.

Симптом → причина → починка

Симптом Вероятная причина Починка
Ошибка в одном проекте, ls -a без .git ZIP / скопированные файлы заново git clone
Ошибка из подкаталога, корень работает Вы снаружи дерева cd "$(git rev-parse --show-toplevel)"
Ошибка везде, даже там, где работало Экспортирован GIT_DIR unset GIT_DIR, вычистить из профиля shell
Ошибка внутри каталога submodule submodule не инициализирован git submodule update --init --recursive
.git — файл, ошибка остаётся Висячий указатель worktree/submodule git worktree repair, иначе заново клонировать

Так когда git init всё-таки верное решение?

Один раз: когда ls -a не показывает .git, а вы и собирались начать здесь новый репозиторий. Запускайте — и следующая команда git add, а не git log.

Во всех остальных случаях это ловушка с подписью. git init на каталог глубже строит вложенный репозиторий без коммитов — и следующий git log ответит fatal: your current branch 'main' does not have any commits yet. Эта ошибка значит: вы только что создали репозиторий там, где стояли, а настоящий — уровнем выше. Уборка хирургическая:

cd вложенный-каталог
rm -rf .git    # удаляет только случайный репозиторий, файлы целы

Проверьте строку приглашения перед этим rm: он должен выполняться внутри вложенного каталога, никогда — в корне проекта. Затем cd в настоящий корень и подтвердите через git rev-parse --show-toplevel, что Git находит то, что должен был найти с самого начала.