fatal: refusing to merge unrelated histories

Git is protecting you from combining two repositories that share no common ancestor. Sometimes that is exactly what you want.

Share
fatal: refusing to merge unrelated histories. Abstract tooling illustration in orange and dark grey on debugly.dev

The short answer

fatal: refusing to merge unrelated histories

The two branches have no common ancestor commit. Git will not merge them by default because the result is almost always a mistake.

If you genuinely want to combine two separate histories:

git merge --allow-unrelated-histories origin/main

Before you do that, work out why they are unrelated, because in most cases the answer is that you are on the wrong repository or you re-initialised one by accident.

Tested on git 2.47.

What "unrelated" means

Git history is a directed graph. A merge works by finding the most recent commit reachable from both branches, the merge base, and computing what changed on each side since then.

With no common ancestor there is no merge base. Git cannot tell what changed, so every file on both sides looks like a new file, and every file present in both looks like a conflict.

The flag exists because there are legitimate cases. The default is a refusal because the common case is a mistake.

Why it happened

You initialised a local repository and then added a remote that already had commits

The most frequent cause.

mkdir project && cd project
git init
# write code, commit
git remote add origin [email protected]:me/project.git
git pull origin main        # remote has a README from the web UI

Your local history starts at your first commit. The remote's starts at the README commit GitHub created. Two roots, no relationship.

If your local work is the real content and the remote only has a README or a licence:

git pull origin main --allow-unrelated-histories
# resolve conflicts, usually keeping both

Or if you do not care about the remote's initial commit:

git push --force-with-lease origin main

--force-with-lease rather than --force. It refuses if someone else pushed since you last fetched, which is the difference between overwriting your own history and overwriting a colleague's.

You re-ran git init

Deleting .git and running git init again creates a completely new history. The files are identical, the commits are not.

Usually done to escape a confusing state. Check for the old repository before assuming it is gone:

ls -la .git
git reflog                  # if .git still exists, your commits are probably there

Nine times out of ten the original history is recoverable and re-initialising was the wrong move. The reflog holds a lot more than people expect.

You are on the wrong remote

git remote -v

Fetching from a fork you did not intend, or a repository with a similar name. The histories genuinely are unrelated because they are different projects.

A squashed or rewritten history on the remote

Someone ran a history rewrite, git filter-repo, or force pushed a rebased branch. Every commit sha changed, so your local commits no longer share ancestry with the new remote state.

Confirm:

git log --oneline origin/main | tail -5
git log --oneline main | tail -5

Different root commits means the history was rewritten. Coordinate with whoever did it rather than merging, because merging produces a duplicated history that is genuinely unpleasant to untangle.

The legitimate cases

Merging one repository into another as a subdirectory

You have two projects and want to combine them, preserving both histories.

git remote add other ../other-project
git fetch other
git merge --allow-unrelated-histories other/main

That merges the other project's files into your root, which is usually not what you want. To put it in a subdirectory with its history intact, use a subtree merge:

git remote add other ../other-project
git fetch other
git merge -s ours --no-commit --allow-unrelated-histories other/main
git read-tree --prefix=packages/other/ -u other/main
git commit -m "merge other-project into packages/other"

-s ours takes your tree and records the other branch as a parent, then read-tree places its files under a prefix. History from both sides is reachable, and git log --follow works on the moved files.

For a monorepo migration, git subtree add does the same thing with a friendlier interface:

git subtree add --prefix=packages/other ../other-project main

Adopting an orphan branch

Documentation sites frequently live on an orphan branch:

git checkout --orphan gh-pages
git rm -rf .
# add site files
git commit -m "initial site"

An orphan branch has no parent by design. If you later want to merge it into main, you need the flag.

Combining a template with an existing project

You started from a template repository and now want to pull in upstream template changes. The histories diverged at the point the template was copied rather than forked.

What the merge looks like

Prepare for a lot of conflicts. Every file present in both trees conflicts, because git has no base to compare against.

git merge --allow-unrelated-histories other/main
# CONFLICT (add/add): Merge conflict in README.md
# CONFLICT (add/add): Merge conflict in .gitignore
# CONFLICT (add/add): Merge conflict in package.json

add/add is the signature. Both sides added the file, git has no idea which is correct.

To resolve wholesale in one direction:

git checkout --ours README.md      # keep your version
git checkout --theirs LICENSE      # keep theirs
git add README.md LICENSE

Note that --ours and --theirs mean the opposite of what you might expect during a rebase, where the sides are swapped. During a merge, ours is the branch you are on.

For package.json and lockfiles, resolve the manifest by hand and then regenerate the lockfile rather than merging it:

rm package-lock.json
npm install

Merging lockfiles by hand produces dependency trees that satisfy neither side, and it is a reliable way to create a build that works locally and fails in CI.

Backing out

If the merge goes badly:

git merge --abort

If you already committed:

git reset --hard ORIG_HEAD

ORIG_HEAD points at where you were before the last merge or rebase, which makes this the fastest recovery available.

Preventing it

Clone rather than init when the remote exists.

git clone [email protected]:me/project.git

Obvious, and the mistake happens because people create the repository on GitHub with a README while also having local work.

When creating a repository on GitHub for existing local work, create it empty. No README, no licence, no gitignore. Then git remote add and push cleanly.

Check before you pull into a fresh repository:

git log --oneline origin/main -1
git log --oneline main -1

Two different root commits is your warning.

Do not delete .git to fix a problem. Almost every git state is recoverable, and re-initialising throws away the recovery path along with the problem.