~/tutorials/how-to-resolve-a-git-merge-conflict-the-4-step-routine
§ POST · MAY 15, 2026 v1.0

How to resolve a Git merge conflict: the 4-step routine

The 4-step routine for resolving any Git merge conflict: read the markers, pick a side or merge by hand, mark resolved, commit. With VS Code screenshots.
Ryan CallowayStaff contributor
  10 min read

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:

Prerequisites

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:

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:

  1. 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.
  2. 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

Prevent most of your conflicts with three habits

  1. Pull before you start work. git fetch && git rebase origin/main first thing in the morning. Conflicts are tiny when they are a day old; they are enormous when they are a month old.
  2. Keep branches short-lived. Three-day branches conflict once; three-week branches conflict thirty times. If a feature needs three weeks, split it.
  3. 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

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

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.

esc