A collision is two writers changing the same branch independently: two
machines, two people, or you and a tool or bot. Neither side sees the
other’s commits until someone fetches or pushes. Placeholders below:
<remote> (usually origin) and <branch>.
For the basic pull --rebase vs pull --no-rebase choice, see
Resolve Divergent Branches. This page
covers working out what happened, preventing it, and recovering when the
simple fix has already gone wrong.
1. Identify it
Common symptoms
You see
It means
! [rejected] ... (fetch first)
Remote has commits you don’t. Fetch and integrate.
! [rejected] ... (non-fast-forward)
Your branch and the remote have diverged, or the remote was rewritten.
Your branch and '<remote>/<branch>' have diverged
Both sides have unique commits.
fatal: refusing to merge unrelated histories
The two sides share no common commit.
rebase in progress / You are in the middle of a merge
A previous integration attempt was never finished.
A commit you made isn’t on the remote or on another machine
It’s unpushed, or on a different branch.
Look before you act
git fetch only updates your remote-tracking refs. It never touches your
branches or working tree, so it is always safe to run first.
git fetch <remote>git status -sb # ahead/behind countsgit log --oneline <remote>/<branch>..HEAD # yours, not on remotegit log --oneline HEAD..<remote>/<branch> # remote's, not yoursgit log --left-right --oneline HEAD...<remote>/<branch> # both, < yours > theirsgit log --graph --oneline --decorate --all -20 # the whole picturegit branch -vv # local branches and their upstreamsgit branch -r # what exists on the remote
Related, or unrelated?
git merge-base HEAD <remote>/<branch> # prints a commit; nothing = unrelatedgit rev-list --max-parents=0 HEAD # root commit(s) of each side
No merge-base means the histories are unrelated: a branch was re-created or
the remote history rewritten. That is a different problem from a normal
divergence. Don’t rebase across it; decide which side is authoritative
first.
What state am I in right now?
git status # names any in-progress rebase / merge / cherry-pick / bisectgit branch --show-current # empty output = detached HEAD
While rebasing, HEAD is detached and, in conflict markers, “HEAD” /
“ours” is the branch you’re rebasing onto; “theirs” is your own commit
being replayed. It is backwards from a merge, and a common source of
resolving a conflict the wrong way round.
”Where did my commit go?”
git log --all --oneline -10 # every branch, local and remote-trackinggit reflog -20 # every place HEAD has been, even commits no # branch points to any moregit branch --contains <sha> # which branches include a given commit
A commit absent from every listing is usually unpushed on another machine or
clone: run the same commands there.
2. Avoid it
Sync first. Before editing on any machine or session:
git fetch <remote> && git status -sb
Integrate if behind and your tree is clean.
One writer per branch. Give each person, machine, or tool its own
branch and merge deliberately.
Push early and often. An unpushed commit is invisible to everyone
else, and a small commit collides smaller.
Make git pull unambiguous. Pick a default so a pull never
improvises:
git config --global pull.rebase true # replay local commits on top# or: git config --global pull.ff only # refuse anything but fast-forward
Stage files by name rather than git add -A/git add ., so scratch
files and unrelated edits don’t ride into a commit.
Finish what you start. Don’t leave a rebase or merge half done; run
git status when you return to a repo.
3. Clean it up
Step 0: snapshot first
A branch is only a pointer. Add a second one and the commits can’t be
lost whatever you do next.
git branch backup-<branch>
Copy out any untracked or uncommitted files you care about too, or
git stash push -u them.
Pick a strategy
Situation
Strategy
Few local commits, touching different files from the remote’s
Rebase (A)
You want to keep both histories visible
Merge (B)
Small local change, lots of conflicting remote change
Reset and re-apply (C)
Local work is disposable, remote is right
Reset to remote (D)
Remote is wrong, yours is right, and you’re allowed to overwrite it
Force with lease (E)
A. Rebase: replay your commits on top
git pull --rebase <remote> <branch># on conflict: fix the files, remove every marker, thengit add <file>git rebase --continuegit rebase --skip # drop the current commitgit rebase --abort # give up, back to where you started
Expect to resolve conflicts once per replayed commit.
B. Merge
git pull --no-rebase <remote> <branch># on conflict: fix files, thengit add <file>git commitgit merge --abort # give up, back to where you started
One resolution in total, at the cost of a merge commit.
C. Reset to the remote and re-apply by hand
git rebase --abort # or: git merge --abortgit diff <old-base> backup-<branch> -- <file> # what you actually changedgit reset --hard <remote>/<branch> # branch now matches the remotegit checkout backup-<branch> -- <paths-of-new-files># redo the edits by hand, skipping anything now obsolete, thengit add <files> && git commit -m "Re-apply local changes"git push <remote> <branch>
git reset --hard throws away uncommitted changes to tracked files and
moves the branch pointer. It is safe here only because step 0 preserved the
old commits.
Only when you are sure the remote’s commits are the ones to lose, and
nobody else builds on them:
git push --force-with-lease <remote> <branch>
--force-with-lease refuses if the remote moved since your last fetch.
Plain --force doesn’t. Avoid either on shared or auto-deployed branches.
Unrelated histories
If merge-base found nothing, choose a side:
Remote is canonical: git reset --hard <remote>/<branch> (after the
backup), then re-apply any commits you need with
git cherry-pick <sha>.
You really want both: git merge --allow-unrelated-histories <remote>/<branch>,
and expect conflicts in nearly every shared file.
Pulling single commits across
git cherry-pick <sha> # one commitgit cherry-pick <sha1>..<sha2> # a range, excluding sha1
If rebase --abort or a checkout refuses
error: The following untracked working tree files would be overwritten
Leftover untracked files collide with tracked ones. Copy them elsewhere,
delete them from the working tree, and retry.
Check you didn’t commit conflict markers
git add doesn’t check whether a conflict is truly resolved. It will stage
<<<<<<< lines happily.
git diff --check # flags leftover markersgit grep -nE '^(<<<<<<<|=======|>>>>>>>)' # search the tree
Tell-tale in a commit summary: an insertion count equal to your lines
plus 3 (one line each for <<<<<<<, =======, >>>>>>>). Inspect the
file as committed with git show <sha>:<file>.
Recover something that seems lost
git reflog # find the shagit branch rescue <sha> # pin it to a branch
Reflog entries are kept for weeks by default, so a “lost” commit is almost
always still recoverable. Garbage collection only removes it after that.
Verify before pushing
git log --oneline <remote>/<branch>..HEAD # only the commits you intendgit diff <remote>/<branch> --stat # only the files you intendgit diff --check # no stray markers or whitespace errors
Then git push. Once pushed and confirmed, the backup branch can go:
git branch -d backup-<branch> (use -D only if you’re sure its commits
are safely elsewhere).
Shell differences
Git commands are identical on every platform. Only the surrounding file
operations differ: