A merge conflict in git happens when two branches changed the same lines of the same file in ways git can't reconcile automatically. Git stops the merge, marks the conflicting hunks in your working tree, and waits for you to resolve them.
The same machinery surfaces during git merge, git rebase, git cherry-pick, git stash pop, and git revert. The recovery flow is mostly the same.
What a conflict looks like
A conflicting file has hunks like this:
<<<<<<< HEAD
const timeout = 30;
=======
const timeout = 60;
>>>>>>> feature/longer-timeout
- Everything between
<<<<<<<and=======is your side (HEAD, often called ours). - Everything between
=======and>>>>>>>is the incoming side (the branch you're merging in, often called theirs). - The label after
<<<<<<<and>>>>>>>tells you which ref is which.
A resolved hunk has neither markers nor leftover code — just the version you want to keep:
const timeout = 60;
The conflict workflow
git merge feature/x
# CONFLICT (content): Merge conflict in src/config.ts
# Automatic merge failed; fix conflicts and then commit the result.
-
See what's conflicted.
git statusFiles under "Unmerged paths" are the ones you need to fix.
-
Open each file and resolve the markers. Pick ours, theirs, both, or a hybrid. Delete every
<<<<<<<,=======, and>>>>>>>line. -
Stage the resolved files.
git add src/config.ts -
Finish the merge.
git commit # for merge git rebase --continue git cherry-pick --continueGit pre-fills the commit message for merge commits — usually accept it.
-
Or bail out.
git merge --abort git rebase --abort git cherry-pick --abortThis restores your branch to the state before the operation.
Useful inspection commands
git status # which files are conflicted
git diff # unresolved conflicts in the working tree
git diff --name-only --diff-filter=U # just the names of conflicted files
git log --merge --oneline # commits that touched the conflicted regions on either side
The git log --merge output is gold for deciding why the conflict is there. It shows the commits from both sides that affected the conflicting lines.
Choosing one side wholesale
Sometimes you know one side is the right answer for a whole file or hunk:
git checkout --ours path/to/file # keep your version
git checkout --theirs path/to/file # take the incoming version
git add path/to/file
During a rebase, the meaning of ours and theirs is reversed compared to a merge — ours refers to the commit being replayed, theirs refers to the branch you're rebasing onto. Read the message git prints; don't guess.
Use a three-way merge tool
For non-trivial conflicts, looking at three versions side-by-side (your side, their side, and the original common ancestor) makes the right resolution obvious. Configure once:
git config --global merge.tool meld
# or vimdiff, kdiff3, opendiff, code (VS Code)
Then on a conflict:
git mergetool
Most editors and IDEs (VS Code, JetBrains, etc.) ship a built-in three-way merge UI that handles this without mergetool.
Show the common ancestor in conflict markers
By default git shows two sides. You can ask for three (the merge base too):
git config --global merge.conflictStyle diff3
Conflicts now look like:
<<<<<<< HEAD
const timeout = 30;
||||||| merged common ancestors
const timeout = 15;
=======
const timeout = 60;
>>>>>>> feature/longer-timeout
Knowing the common ancestor (15) tells you that both sides changed the value, which is usually a stronger signal than just seeing the two endpoints. The newer zdiff3 style is even cleaner if your git is recent enough.
Conflicts during rebase
Rebases can produce one conflict per replayed commit. The rhythm is:
git rebase main
# conflict
# resolve, then:
git add path/to/file
git rebase --continue
# next conflict, repeat...
Useful escape hatches:
git rebase --skip # drop the current commit and move on
git rebase --abort # give up and return to the original branch state
Tip: turn on git rerere (git config --global rerere.enabled true). Git remembers conflict resolutions and replays them automatically when the same conflict shows up again — invaluable on long rebases.
Conflicts during cherry-pick, stash, revert
Same flow:
| Operation | Continue | Abort |
|---|---|---|
git cherry-pick |
--continue |
--abort |
git revert |
--continue |
--abort |
git stash pop |
resolve, git add, the stash stays on the stack until you drop it |
n/a — just git reset --hard if you want to bail |
Practical recipes
"I'm halfway through a merge — what was I doing?"
git status # shows merge in progress and conflicted files
cat .git/MERGE_MSG # the prepared merge commit message
git diff --name-only --diff-filter=U
"Take all of my changes, throw away theirs"
git merge -X ours feature/x
-X ours is a strategy option: in conflicts, prefer our side. Note this is different from git checkout --ours, which works on already-conflicted files.
"Delete vs modify" conflict
If one side deleted a file and the other modified it, git asks you to choose:
git rm path/to/file # accept the deletion
git add path/to/file # accept your modification
Binary file conflicts
Git can't merge binaries. Pick a side:
git checkout --ours path/to/image.png
git add path/to/image.png
Avoiding painful conflicts
- Pull / rebase often. A 1-commit-behind branch is cheap to resolve. A 200-commit-behind branch isn't.
- Keep branches small. A focused feature branch conflicts in fewer places.
- Use
git rerere. It pays for itself the first time you repeat a rebase. - Format / lint changes separately. A "ran prettier on the whole repo" commit is the worst possible thing to merge across — every other branch will conflict with it.
Summary
- A conflict is git asking you to pick a winner between two changes to the same lines.
- Open the file, resolve the markers,
git add, then--continueorcommit. git checkout --ours/--theirschooses one side wholesale.- Turn on
merge.conflictStyle = diff3andrerere.enabled = true— both make conflicts dramatically easier. - When in doubt,
--abortand try a smaller, less divergent merge.