Fatal: Not a Git Repository? The 60-Second Fix
The four real causes of fatal: not a git repository, ranked plus the one command that tells you which one you have before you touch anything.
The fastest way to lose twenty minutes is typing git pull in the folder above your repo. Git answers fatal: not a git repository (or any of the parent directories): .git, and most answers online reply with git init — which is wrong for three of the four real causes.
The 60-second triage:
git rev-parse --show-toplevel || ls -a
--show-toplevel prints your repository root when one exists above you: wrong directory, case closed, cd there. When it fails too, the same line lists the folder's contents: no .git in that list means there was never a repo here to find. That single command separates the two causes behind most occurrences. The rest of this post is the smaller cases.
The 60-second triage
Match your symptom to a row — the fix is the third column.
| Symptom | Likely cause | Fix |
|---|---|---|
Error in one project, ls -a shows no .git |
ZIP download / copied files | git clone again |
| Error from a subfolder, root works | Standing outside the tree | cd "$(git rev-parse --show-toplevel)" |
| Error in every directory, even ones that worked | GIT_DIR exported |
unset GIT_DIR, purge it from the shell profile |
| Error inside a submodule folder | Submodule not initialized | git submodule update --init --recursive |
.git is a file, error persists |
Dangling worktree/submodule pointer | git worktree repair, else re-clone |
verify: git rev-parse --show-toplevel → the repo root path, no error
Why does Git say "fatal: not a git repository"?
Git finds a repository by walking up from the current directory, hunting for a .git entry, and stops at the first one. That walk breaks in exactly four ways: you stand outside the tree, the .git folder is gone, an environment variable redirects the search, or .git exists but points at nothing. Stack Overflow's 2022 Developer Survey put Git on roughly 94% of developer machines, so the error is a shared rite of passage. Learn the four cases once and they stop costing time.
(If your repo is fine and you actually came here to undo a commit, Undo Anything in Git collects every repair path on this site in one place — the long-form walk-through is git undo last commit.)
Case 1: you are in the wrong directory
The common one. Two shapes:
- You
cd'd into a subfolder (docs/,src/api/) and the shell habit from another project says the repo root is here. Git walks up, finds nothing, quits. - You cloned a repo and expected the files here, but
git clonealways creates a subdirectory named after the repo. The checkout sits one level below where you're standing.
git rev-parse --show-toplevel # prints the root when one exists
cd "$(git rev-parse --show-toplevel)" # jump there in one step
The second command is the permanent fix for the muscle-memory problem: it works from any depth inside the repo.
Case 2: there is no .git — the folder never was a repo
ls -a shows no .git. Usually one of:
- A GitHub ZIP download. Code → Download ZIP ships the files only: no
.git, no history, no remote. GitHub's own docs confirm the ZIP archive contains the source snapshot alone. - Files copied in from a machine or another project, without the hidden folder.
- A cleanup tool deleted
.gitas "hidden junk".
Fix: re-clone to recover the history.
cd ..
git clone https://github.com/<user>/<repo>.git
git init here starts a fresh repository; nothing is recovered. And the copied-files mess usually drags untracked strays with it; git remove untracked files covers the cleanup once the clone is in place.
git initdoesn't repair a repo — it creates an empty one where you stand.
Case 3: GIT_DIR is set, and breaks every repo
The tell: the error follows you into directories where Git worked an hour ago. An exported GIT_DIR tells Git to look at one fixed path instead of discovering the repo, so every "ordinary" directory becomes "not a repository".
env | grep GIT_DIR # GIT_DIR=/some/old/path
unset GIT_DIR
If it comes back every morning, a line in ~/.bashrc, ~/.zshrc, or a CI wrapper exports it.
CI systems set GIT_DIR on purpose
Inside a pipeline, GIT_DIR is correct and deliberate — the runner points Git at the checkout it prepared. Unsetting it there breaks the build; the landmine is only when that line leaks into your interactive shell profile. Fix the shell, leave the CI alone.
verify: env | grep GIT_DIR → empty
Case 4: the submodule was never initialized
You cd into vendor/libfoo/, and Git says the directory is not a repository. It isn't: until initialized, a submodule directory is an empty folder with an entry in .gitmodules and nothing checked out.
cat .gitmodules # lists the submodule paths
git submodule update --init --recursive
Run that from the repo root, not inside the submodule. The same walk-up rule applies, and the empty submodule has no root to walk to.
verify: git submodule status → no leading dash on the path (a dash = still uninitialized)
Case 5: .git is a file, and its target is gone
Worktrees and submodules make .git a one-line file: gitdir: /path/to/real/gitdir. When the real gitdir is deleted (a cleaned .git/modules, a moved drive), the pointer dangles and the walk-up finds a .git that answers nothing:
$ ls -la .git
-rw-r--r-- 1 you you 35 Sep 28 10:12 .git # a file, not a directory
$ cat .git
gitdir: ../.git/modules/libfoo
git worktree repair # re-links worktrees whose paths changed
If worktree repair can't find the target, the gitdir is gone for good. Re-clone. The history lives in the parent repo's .git/modules/<name> for submodules; check it exists before assuming the worst.
verify: cat .git → the gitdir: path exists on disk
So is git init ever the right fix?
Once: when ls -a shows no .git and you intend to start a brand-new repository here. Run it, and the next command is git add, not git log.
Everywhere else it's a trap with a signature. git init one folder too deep builds a nested repo with zero commits, and your next git log answers fatal: your current branch 'main' does not have any commits yet. That error means you just created a repository where you stood, and the real one is one level up. The cleanup is surgical:
cd the-nested-folder
rm -rf .git # removes only the accidental repo, files untouched
Double-check the prompt before that rm: it must run inside the nested folder, never at the project root. Then cd back to the real root and confirm with git rev-parse --show-toplevel that Git finds what it was always supposed to find.
verify: git log --oneline -1 → a commit, not the no-commits-yet error