You ran git pull (or git merge) and Git stopped before touching a single file:
$ git pull --no-rebase origin main
From github.com:acme/shop-api
* branch main -> FETCH_HEAD
fatal: refusing to merge unrelated historiesThe output above is from Git 2.54.0, reproduced with throwaway repositories (remote path replaced with a GitHub URL), and the wording has been the same since Git 2.9 introduced the check. It means the two branches you asked Git to merge do not share any commit. Git refuses because a merge without a common ancestor is usually a mistake: the wrong remote, a re-created repository, or two separate git init runs for what you thought was one project.
Quick fix checklist
- Confirm the remote is the repository you think it is:
git remote -v. - Check that there really is no shared commit:
git merge-base HEAD origin/mainprints nothing and exits with status 1. - Brand-new GitHub repo with a README plus local
git init:git pull --no-rebase origin main --allow-unrelated-histories, resolve the README conflict, commit, push. - Prefer a linear result?
git pull --rebase origin mainreplays your local commits on top of the remote and does not need the flag. - Remote history was rewritten or the repo re-created: clone fresh and move your work across instead of merging.
- Never add the flag to a script or alias by default; it disables a safety check for every future merge.
Before you start
You should know what a commit, a branch and a remote are, and how git pull is git fetch followed by a merge or rebase. Make sure your working tree is clean (git status) so a conflicted merge does not mix with unrelated edits. If unsure, copy the project folder first and retry from the copy.
Why it happens
Every commit records its parent commits, so a branch is a chain that ends at a root commit (a commit with no parent). Two branches are related if their chains meet somewhere. A normal three-way merge needs that meeting point, the merge base, to decide which side changed each line: if the base said timeout: 30 and only one side changed it, that side wins without a conflict.
When you create a repository on GitHub with “Add a README file” ticked, GitHub makes a root commit containing README.md (and maybe LICENSE or .gitignore). If you then run git init in an existing local folder and commit, you make a second root commit. Neither chain contains the other’s root, so git merge-base finds nothing:
$ git merge-base HEAD origin/main; echo "exit $?"
exit 1Without a base, Git would treat every file as added by both sides. Since Git 2.9, git merge refuses unless you pass --allow-unrelated-histories, because the same situation also arises when merging is clearly wrong: pulling from a different project, or from a repository whose history was rewritten (for example with git filter-repo), giving every commit a new ID.
You often meet a different error first. A plain git push to that new GitHub repository is rejected with ! [rejected] main -> main (fetch first), because the remote has a commit you do not have. A plain git pull on Git 2.33.1 or later then stops with fatal: Need to specify how to reconcile divergent branches. Only when you choose to merge does the unrelated-histories check fire.
Step-by-step walkthrough
Step 1: Reproduce and confirm the cause
Recreate the situation in a scratch directory with a local bare repository standing in for GitHub:
git init -q --bare -b main remote.git
git init -q -b main gh && cd gh
echo "# shop-api" > README.md && echo "MIT" > LICENSE
git add . && git commit -qm "Initial commit"
git remote add origin ../remote.git && git push -q origin main && cd ..
git init -q -b main shop-api && cd shop-api
echo "console.log('hi')" > index.js && echo "# shop-api local" > README.md
git add . && git commit -qm "Start shop-api"
git remote add origin ../remote.git
git pull --no-rebase origin mainThe last command prints fatal: refusing to merge unrelated histories. Run git log --oneline --all --graph after a git fetch and you will see two separate chains with nothing joining them.
Step 2: Decide whether joining the histories is what you want
Ask one question: should these two histories be one project?
- Yes, they are the same project started in two places (new GitHub repo plus local
git init). Joining is correct. - Yes, you are deliberately combining two projects, for example moving a small library into a monorepo. Joining is correct, and you may want to move one project into a subdirectory first so files do not collide.
- No, the remote is wrong, or the remote was force-pushed with rewritten history. Do not join. Fix the remote URL or re-clone.
Check git remote -v and git log --oneline origin/main. If you do not recognise those commits, stop.
Step 3: Merge with –allow-unrelated-histories
For the README case, merge and expect an add/add conflict on any file both sides created:
$ git merge origin/main --allow-unrelated-histories -m "Merge GitHub initial commit"
Auto-merging README.md
CONFLICT (add/add): Merge conflict in README.md
Automatic merge failed; fix conflicts and then commit the result.git status lists LICENSE as a new file ready to commit and README.md under “Unmerged paths” as both added. Edit README.md, keep the content you want, remove the conflict markers, then finish:
git add README.md
git commit --no-edit
git push origin mainThe graph now has a merge commit with two parents, one from each root, and git merge-base HEAD origin/main prints the GitHub commit’s ID. Later pulls behave normally.
Step 4: Or rebase your commits on top instead
If you would rather avoid a merge commit in a history only minutes old, rebase. It replays your commits one by one onto the remote tip, so it needs no flag.
$ git pull --rebase origin main
From github.com:acme/shop-api
* branch main -> FETCH_HEAD
* [new branch] main -> origin/main
Successfully rebased and updated refs/heads/main.The result is one straight line starting at GitHub’s “Initial commit”. If both sides added the same file, you resolve the conflict per replayed commit and run git rebase --continue.
Step 5: Clone fresh and move your work (the safest alternative)
When the remote is the source of truth and your local history is not worth keeping, skip the merge entirely:
git clone git@github.com:acme/shop-api.git shop-api-clean
cp -R shop-api/src shop-api/package.json shop-api-clean/
cd shop-api-clean
git add . && git commit -m "Import existing shop-api code"
git pushYou lose your local commit history but keep one clean root. This is also the right answer after the remote was re-created or rewritten.
Worked scenario
Maya creates acme/shop-api on GitHub with a README, a LICENSE and a Node .gitignore. Her laptop already has a working project, so she runs git init, commits everything, adds the remote and pushes. The push is rejected with fetch first. She runs git pull, gets the “divergent branches” hint, then git pull --no-rebase origin main, and finally sees refusing to merge unrelated histories.
Diagnosis: git remote -v shows the right URL, git log origin/main shows one commit called “Initial commit” by GitHub, and git merge-base HEAD origin/main prints nothing. Same project, two roots: joining is the right call.
Fix:
git pull --no-rebase origin main --allow-unrelated-histories
# resolve both-added files (README.md, .gitignore)
git add README.md .gitignore
git commit --no-edit
git push -u origin mainShe combines both .gitignore files by hand, keeping node_modules/ from GitHub’s template and .env from her own. The -u sets the upstream so later pulls and pushes need no arguments.
Common mistake
Adding the flag everywhere. People hit the error once, then put --allow-unrelated-histories into a shell alias or CI script. The check exists to catch a pull from the wrong repository. With the flag baked in, a mistyped remote silently merges a whole foreign project into yours, and undoing it means resetting a merge that may already be pushed.
Force-pushing over GitHub’s initial commit. git push --force makes the error go away by deleting the remote README commit. It is harmless for a repository created a minute ago that only you use, but it is the wrong reflex in general: on a shared repository it discards other people’s commits. If you do overwrite, use --force-with-lease and do it deliberately.
Merging after a history rewrite. If a teammate rewrote history to remove a leaked secret, merging your old clone back in doubles every commit and restores the secret. Re-clone instead.
Verify the behavior
After the merge or rebase, these three checks should pass:
git merge-base HEAD origin/main # prints a commit ID, exit 0
git status -sb | head -1 # ## main...origin/main (no ahead/behind after push)
git log --oneline --graph | tail -3 # GitHub's "Initial commit" is an ancestorFinally, git pull with no flags should report Already up to date. The histories are now related, so the unrelated-histories check never fires again for this pair.
Interview exercise
“A colleague says --allow-unrelated-histories is harmless because Git will show conflicts if anything goes wrong. Do you agree? When would you use it, and what would you do instead?”
Answer and reasoning
Partly. Conflicts only appear where both sides added the same path; every other file from the foreign history merges in silently. So the flag is not dangerous in the “data loss” sense, but it can quietly import an entire wrong project, its history and any secrets in it, into yours. That is why Git made it opt-in.
I would use it when joining the histories is the intent: a GitHub repository initialised with a README alongside a local git init, or deliberately combining two repositories into a monorepo (ideally after moving one into a subdirectory with git mv so paths do not collide). I would first confirm the remote with git remote -v, inspect git log origin/main, and verify with git merge-base that there truly is no shared commit.
I would not use it when the history was rewritten or the remote is wrong: I would fix the remote URL, or re-clone and re-apply my work with git cherry-pick. For a repository only seconds old, git pull --rebase gives a cleaner linear history.