Merge conflicts look alarming the first time, but they are just Git's way of saying "two changes touched the same lines and I need a human to decide the final version." The markers are structured; once you read them fluently, resolving a conflict is a five-minute task.
What changed in 2026
diff3 is now the recommended merge style — Git 2.35+ accepts git config --global merge.conflictstyle diff3, and most teams have adopted it because it shows the common ancestor.
- VS Code and JetBrains ship excellent three-way merge editors — the days of editing raw conflict markers in a terminal are optional.
- GitHub and GitLab both offer browser-based conflict resolution for simple conflicts — fine for one-line changes, insufficient for logic conflicts.
git rerere is underused — it records your resolution and replays it automatically if the same conflict appears again (useful on long-lived feature branches).
Understanding conflict markers
<<<<<<< HEAD
const timeout = 5000;
||||||| common ancestor (only with diff3)
const timeout = 3000;
=======
const timeout = 10000;
>>>>>>> feature/longer-timeouts
<<<<<<< HEAD — your current branch (what you have locally).
||||||| common ancestor — the original line both branches started from (only in diff3 style).
======= — separator between the two versions.
>>>>>>> feature/longer-timeouts — the incoming branch you are merging in.
The common ancestor (||||||| ...) is the crucial part: it tells you what both sides were trying to change from, so you can understand intent, not just final values.
Enable diff3 (do this now, globally)
git config --global merge.conflictstyle diff3
Or in .gitconfig:
[merge]
conflictstyle = diff3
Step-by-step conflict resolution
# 1. Attempt the merge
git merge feature/longer-timeouts
# 2. See which files have conflicts
git status
# both modified: src/config.ts
# 3. Open each conflicted file and resolve
# (edit in VS Code, IntelliJ, or your editor)
# 4. After resolving, stage the file
git add src/config.ts
# 5. Continue the merge
git merge --continue
# (or git commit if --continue is not available)
For rebase conflicts the flow is the same but uses git rebase --continue after each resolved commit.
Resolving in VS Code
VS Code detects conflict markers and shows inline buttons:
- Accept Current Change — keep HEAD (yours)
- Accept Incoming Change — keep the incoming branch
- Accept Both Changes — append both (rarely correct; review carefully)
- Compare Changes — opens a side-by-side diff
Use "Open Merge Editor" for a proper three-panel view: current, incoming, and the result you are editing.
Resolving with git mergetool
git config --global merge.tool vimdiff # or nvimdiff, meld, kdiff3, etc.
git mergetool
vimdiff opens three panes: LOCAL (yours), BASE (common ancestor), REMOTE (incoming), and the merged result below. Navigate with [c / ]c; use :diffget LO or :diffget RE to pull from one side.
Shortcuts for obvious resolutions
# Take the entire file from our branch
git checkout --ours src/generated-lockfile.json
# Take the entire file from the incoming branch
git checkout --theirs src/generated-lockfile.json
# Stage after
git add src/generated-lockfile.json
Use --ours / --theirs only when you know the entire file should come from one side — common for lock files and generated files.
Common conflict scenarios
| Conflict type |
Best approach |
| Two developers edited the same function |
Read both, write the merged intent manually |
| Renamed variable in one branch |
Accept the rename, propagate it to the other side |
| Lock file (package-lock.json) |
--theirs then run npm install to regenerate |
| Generated code (protos, GraphQL types) |
Regenerate from source; do not hand-edit |
| CSS/formatting only |
Accept either side; run formatter after |
Common mistakes
Staging without resolving. git add conflicted-file.js when the file still contains <<<<<<< markers. Git will let you commit it; your CI will catch the syntax error — but later than ideal. Some editors warn; configure your linter to flag conflict markers.
Using --theirs everywhere to "win fast". You silently discard your own changes. Read both sides.
Not running tests after resolving. A syntactically correct merge can introduce a logical error — e.g., two developers each added an if branch with slightly different behavior; the merged result keeps both but the interaction breaks something.
Resolving a rebase conflict and forgetting --continue. After git add, you must run git rebase --continue; if you run git commit instead you create an extra commit.
What to skip
- Browser-based conflict resolution for logic changes — it is fine for a one-word rename but inadequate for multi-line function changes.
- Deleting conflict markers by hand without reading both sides — you pick one version at random.
- Resolving conflicts in a shared branch by force-pushing — it rewrites history for everyone on that branch.
FAQ
How do I abort a merge and go back to before it started?
Run git merge --abort. This resets the working tree to the state before git merge was run.
What is the difference between merge and rebase conflicts?
A merge conflict happens once at the merge commit. A rebase conflict can happen once per commit being replayed — a rebase of 10 commits might trigger 10 separate conflicts if the changes overlap each time.
How do I prevent conflicts in the first place?
Short-lived branches (< 2 days), small PRs, and pulling from the base branch frequently reduce conflict frequency. Feature flags let you merge incomplete work without conflicts in visible behavior.
What is git rerere and should I use it?
git rerere (REuse REcorded REsolution) records how you resolve a conflict and replays it automatically next time. Enable with git config --global rerere.enabled true. Useful on rebased feature branches that repeatedly conflict with main.
Where to go next