By Ryan Calloway. Updated May 2026.
The canonical Stack Overflow question on resolving Git merge conflicts has crossed 4.6 million views and 2,800 upvotes. The top answer is six steps long. The recurring r/vscode thread “Resolve merge conflicts in VS Code” compresses the same workflow into one reply: pull from main, get the conflict, click each file, take current or incoming, commit. The <<<<<<< markers look terrifying on first contact and are almost always a 90-second fix once you know what they mean. This walk-through is the four-step routine that resolves 95% of real conflicts on Git 2.45+ — how to read the markers, how to pick a strategy, how to use the three-way view, and how to bail out cleanly when you need to.
What you’ll fix
By the end of this tutorial you will:
- Read Git’s conflict markers with confidence and understand exactly what each section represents.
- Run the 4-step routine (
status, edit,add,commit) end to end on a real conflict. - Use a three-way merge tool (VS Code, neovim, Meld) to see the common ancestor — the third perspective that turns guessing into knowing.
- Pick the right resolution strategy: keep ours, keep theirs, or combine.
- Abort cleanly when the conflict is bigger than you thought.
- Recognise the four named conflict patterns (formatting, imports, lockfiles, renames) and resolve each mechanically.
Prerequisites
- Git 2.45 or newer (run
git --version; if you are on an older version, the merge UX is workable but missing the recent improvements togit rebase --mergereporting and--no-rebase-mergesdefaults). Git 2.50+ is current as of May 2026. - A repository with at least two branches that have diverged. The four steps below assume you have already run
git merge <branch>orgit rebase <branch>and Git has stopped on a conflict. - Optional but recommended: VS Code with the merge editor enabled, or your editor of choice configured as a mergetool.
- Comfort with reading and editing source files. No prior Git internals knowledge required.
Step 1: read the conflict markers
When Git cannot auto-merge a file, it writes both versions into the file with markers around each side:
<<<<<<< HEAD
const timeout = 5000;
=======
const timeout = 10000;
>>>>>>> feature/slow-network
Three markers, three sections to understand:
- Everything between
<<<<<<< HEADand=======is the version currently on your branch. Git calls this side “ours” during a merge. - Everything between
=======and>>>>>>> feature/slow-networkis the version from the incoming branch. Git calls this side “theirs” during a merge. - The branch label after
>>>>>>>tells you where “theirs” came from. During a rebase, the label is usually the commit hash being replayed.
Your job is to pick one side, combine both, or rewrite entirely, then delete all three marker lines. Git did not break anything; it intentionally left the file in a state no compiler can parse so you cannot miss the conflict. Resolve, save, move to the next step.
To enable a fourth marker showing the common ancestor, configure once:
git config --global merge.conflictstyle zdiff3
That adds a ||||||| merged common ancestors section between <<<<<<< HEAD and =======, showing the file before either side touched it. zdiff3 (added in Git 2.35, refined since) is the most useful merge style on the planet and it is not the default for historical reasons; turn it on now.
Step 2: run git status, then edit each conflicted file
List the conflicted files:
$ git status
On branch main
You have unmerged paths.
(fix conflicts and run "git commit")
Unmerged paths:
(use "git add <file>..." to mark resolution)
both modified: src/config.ts
both modified: tests/e2e/checkout.spec.ts
Open each file. The temptation is to pick “whichever compiles”. Do not. Read both sides and ask two questions:
- What was the intent of each change?
git log -p --follow <file>on each branch shows the commit history that produced each side. Twenty seconds of reading often reveals that the two changes are about different things. - Can both intentions survive together? Often yes, in different functions or different configs. Combine them.
The most common mistake is treating a value-conflict as a value-conflict when it is actually a design problem. Two engineers fighting over a timeout constant (5 vs 10 seconds) usually means the system needs a per-endpoint timeout, not a single number. Three minutes of reading both commits saves a two-week revert later.
Pick one of three strategies:
| Strategy | When | Command helper |
|---|---|---|
| Keep yours | Their change is wrong, outdated, or already superseded | git checkout --ours <file> |
| Keep theirs | Your change was temporary or reverted | git checkout --theirs <file> |
| Combine | Both changes are needed (most common) | edit by hand, delete markers |
Be careful with --ours and --theirs during a rebase: they are swapped relative to a merge, because during rebase your branch is replayed on top of the other one. The git-checkout docs have a four-line note explaining the flip. Save the file with no remaining marker lines.
Step 3: stage with git add
After saving each resolved file, mark it as resolved:
git add src/config.ts tests/e2e/checkout.spec.ts
You can git add . to stage everything, but the explicit form is better — it forces you to look at the list and notice if Git generated a file you did not intend to commit (a .orig backup, or a stray IDE settings file). Run git status again:
$ git status
On branch main
All conflicts fixed but you are still merging.
(use "git commit" to conclude merge)
Changes to be committed:
modified: src/config.ts
modified: tests/e2e/checkout.spec.ts
If git status still lists files under “Unmerged paths”, you missed a conflict marker somewhere. Run git diff --check to find leftover <<<<<<< or >>>>>>> lines that slipped through.
Step 4: finish the merge or rebase
For a merge:
git commit # opens an editor with a pre-filled message; save and quit
For a rebase:
git rebase --continue
That is the whole 4-step routine: status, edit, add, commit. If you find yourself reaching for git reset --hard and starting over, stop — you are throwing away someone’s work. Resolve the conflict, commit, move on.
If the conflict is bigger than you expected and you want your working tree back the way it was, both merge and rebase have an escape hatch:
git merge --abort # undoes the in-progress merge
git rebase --abort # undoes the in-progress rebase
Nothing is lost. You are back on your branch, your commits are still there, and you can re-attempt with a fresher pull or a smaller change. Use --abort freely — it is designed for exactly this.
Use a three-way merge tool for anything non-trivial
The <<<<<<< markers show your side and theirs. A three-way merge tool also shows the common ancestor (the file before either side touched it). That third perspective is the difference between guessing and knowing — Pro Git, chapter 7.8 walks through the three-way view with the same example.
git mergetool # opens the configured tool
git config --global merge.tool vscode # set VS Code as default
git config --global mergetool.vscode.cmd 'code --wait --merge $REMOTE $LOCAL $BASE $MERGED'
VS Code’s three-way merge editor ships an “Accept Incoming” / “Accept Current” / “Accept Combination” toolbar with a base-pane on the left so you can see the original. The neovim crowd in a 2024 thread agrees on the same shortlist: diffview.nvim for heavy conflicts, git-conflict.nvim for the VS Code-style flow, lazygit for everything else. For non-VS Code editors, Meld is the cross-platform standby and KDiff3 the Windows-friendly equivalent.
Common pitfalls
- Clicking “Accept Incoming” without reading. The merge tool’s one-click buttons are the single biggest cause of silent regressions in production code reviews. Use them only when you are certain the incoming change supersedes yours.
- Hand-merging a lockfile.
package-lock.json,poetry.lock,Cargo.lock,yarn.lock, migration files. Regenerate from scratch (rm package-lock.json && npm install), commit the regenerated file. Do not try to hand-merge a lockfile; you will produce an inconsistent dependency tree and not know it. - Resolving without running tests. Conflict resolution is a code change. Run the test suite before you commit; conflicts that compile but break behaviour are the worst kind.
- Assuming “ours” means yours during a rebase. During
git rebase, “ours” is the branch you are rebasing onto, and “theirs” is your branch being replayed. The flip catches everyone the first time. - Committing leftover marker lines. Run
git diff --checkbeforegit commit— it flags whitespace errors and leftover conflict markers in one pass. - Force-pushing after a merge to undo the conflict. If the merge commit is already pushed, force-pushing rewrites public history and breaks every collaborator. Revert with a follow-up commit instead, and rebase only on branches you alone use.
Prevent most of your conflicts with three habits
- Pull before you start work.
git fetch && git rebase origin/mainfirst thing in the morning. Conflicts are tiny when they are a day old; they are enormous when they are a month old. - Keep branches short-lived. Three-day branches conflict once; three-week branches conflict thirty times. If a feature needs three weeks, split it.
- Own narrow files. If two engineers need to edit the same 300-line config every day, split the config. The conflict was architectural, not Git.
Trunk-based development formalises the same advice: short-lived branches, frequent integration. The Google and Facebook engineering blogs cite the same pattern as the reason their monorepos do not implode under merge conflict load.
The four conflict patterns worth naming
- Formatting-only conflict. The content is identical but one side reformatted whitespace. Configure the same formatter (Prettier, Black, gofmt) on both sides via pre-commit and the conflict goes away.
- Import-order conflict. Two branches added imports in the same block. Run your IDE’s organize-imports action, commit once.
- Generated-file conflict. Lockfiles, migration files, OpenAPI generated clients. Regenerate from scratch on your side; commit the regenerated file. Do not hand-merge.
- Rename conflict. One branch renamed a file, the other edited it. Git marks both. Apply the edits to the renamed file and delete the old one with
git rm.
Once you name the pattern, the resolution is mechanical.
FAQ
How do I resolve a merge conflict from the command line?
Run git status to see conflicted files, edit each to remove the <<<<<<</=======/>>>>>>> markers while keeping the correct content, then git add <file> and git commit (for merge) or git rebase --continue (for rebase). The official GitHub docs walk through the same flow.
What is the difference between merge and rebase conflicts?
Mechanically, the markers look identical. Procedurally, you finish a merge with git commit and a rebase with git rebase --continue. “Ours” and “theirs” also swap meaning: during rebase, “theirs” is your branch being replayed and “ours” is the base you are rebasing onto.
Can I undo a resolved merge conflict?
Yes, until you push. git reset --hard HEAD@{1} rolls back to the state before the merge commit. After you push, you have to revert with a new commit instead, and you should talk to your team first if the branch is shared.
Should I use “Accept Incoming” or resolve manually?
Use “Accept Incoming” only when you are certain the incoming change supersedes yours and the diff is trivial. In every other case, read both sides and resolve by hand. The merge tool’s one-click buttons are the single biggest cause of silent regressions.
How do I resolve a conflict on GitHub directly?
If the conflict is small enough that GitHub’s web UI shows the inline conflict editor, you can resolve in the browser, click “Mark as resolved”, and commit the merge. The GitHub docs show the flow. For larger or multi-file conflicts, GitHub punts you back to the command line; the 4-step routine above is what you run.
How do I avoid merge conflicts entirely?
You cannot, on a team of more than one. You can cut their frequency hard: pull daily, keep branches short, split files people contend for, use the same formatter everywhere. See the three habits section above.
Why is zdiff3 not the default conflict style?
Backwards compatibility. diff3 shipped in Git 1.7; zdiff3 arrived in Git 2.35 (January 2022) but was not promoted to default to avoid changing existing scripts that grep for the marker shape. Set it globally and forget it — the common-ancestor section pays for itself within a week.
Sources and further reading
- Pro Git, chapter 7.8: Advanced Merging — the canonical deep dive.
- GitHub: resolving a merge conflict from the command line.
- GitHub: resolving a merge conflict in the web UI.
- VS Code: merge conflicts.
- git-checkout(1) docs —
--oursand--theirs. - Trunk-Based Development.
- The canonical Stack Overflow thread (4.6M+ views).
If conflicts keep happening on generated files, a CI job that regenerates and commits them solves it; the GitHub Actions CI/CD tutorial covers the setup. For the next layer of Git fluency, the Pro Git chapter on rewriting history is the most useful 30 minutes a working engineer can spend.