Menu

Git Merge Conflicts Explained: Real Examples & Every Command You Need πŸ”€

Dark-theme code editor showing HEAD and incoming branch changes marked with red and blue Git merge conflict markers

We've all felt that little drop in the stomach when "CONFLICT (content): Merge conflict in..." shows up in the terminal. Here's the thing though: a Git conflict isn't your repo breaking. It's Git politely saying "I can't decide this one for you." In this guide, we'll cover why conflicts happen, walk through 7 real-world scenarios you'll actually run into, and give you a command for every situation.

main feature/timeout ⚑ conflict

When two branches change the same spot in different ways, Git stops and asks a human to decide.

1. Why Do Git Conflicts Happen? πŸ€”

Git merges using what's called a 3-way merge. It compares three versions: the common ancestor (base) before the branches diverged, your current branch (HEAD), and the branch being merged in (incoming). If the two branches touched different parts of a file, Git merges them automatically without asking. But if both sides changed the same lines differently, Git says "I genuinely can't tell which version is correct" and hands the decision back to you. That's a conflict.

Conflicts can happen during git merge, git pull (which is fetch + merge under the hood), git rebase, and even git cherry-pick. There are three common root causes.

  • Same lines edited on both sides: The most common pattern by far.
  • Delete vs. modify: One branch deletes a file while the other edits it.
  • Add/Add: Both branches independently create a new file with the same name.
"A conflict isn't Git failing β€” it's a request to review two overlapping intentions." – a saying common in developer communities

2. Conflict Markers, Fully Explained

When a conflict happens, Git leaves special markers inside the file, like the example below. It looks cryptic at first, but the rules are actually simple.

HEAD (your branch) Common ancestor (diff3 only) incoming (the branch being merged in)
<<<<<<< HEAD
const timeout = 5000; // value on main
=======
const timeout = 10000; // value on my feature branch
>>>>>>> feature/increase-timeout

Everything from <<<<<<< HEAD to ======= is your currently checked-out branch's content, and everything from ======= to >>>>>>> branch-name is the incoming content. During a rebase, though, the meaning flips slightly. HEAD represents the branch you're rebasing onto, and the bottom section is your own commit being replayed on top of it. This trips up a lot of developers the first time.

Plain markers don't show you what the code originally looked like, which can make conflicts harder to reason about. Turning on diff3, or the newer and smarter zdiff3, adds the common ancestor back in so it's clearer who changed what.

# Switch conflict display to zdiff3 style (Git 2.35+)
git config --global merge.conflictStyle zdiff3
<<<<<<< HEAD
const timeout = 5000;
||||||| base
const timeout = 3000;
=======
const timeout = 10000;
>>>>>>> feature/increase-timeout

Key Takeaway: Use rerere to Stop Repeating Yourself

If you're resolving the same conflict over and over during a long rebase, turn on git config --global rerere.enabled true β€” short for "Reuse Recorded Resolution." Git remembers how you resolved a conflict once, and automatically applies the same fix the next time it sees an identical conflict.

3. Real-World Conflict Scenarios & Fixes πŸ› οΈ

Almost every conflict boils down to one of four options: revert everything (cancel the whole operation), keep the remote side (accept the incoming branch), keep the local side (accept your own branch), or merge manually (combine both). Let's walk through each scenario using these four options, with real commands and examples.

Resolution When to use it Typical command
πŸ”„ Revert everythingYou want to cancel the whole thing and rethink your approachgit merge --abort / git rebase --abort
🌐 Keep remoteYou're confident the incoming/remote change is correctgit checkout --theirs <file>
πŸ’» Keep localYou want to preserve your own branch's changegit checkout --ours <file>
✍️ Merge manuallyYou need to preserve both changesOpen the file and combine them by hand

Heads up: --ours/--theirs mean the opposite thing during merge versus rebase. During a merge, --ours is your branch and --theirs is the incoming one. During a rebase, --ours is the branch you're rebasing onto (usually remote main), and --theirs is your own commit being replayed. When in doubt, run git status first to double-check.

β‘  Same Lines Edited on Both Sides (Content Conflict)

The classic case. Say main set the timeout to 5000, while your feature branch set it to 10000. You'll see the same markers we covered earlier. Pick whichever option fits the situation.

πŸ”„ Revert everythingIf this isn't the right time to merge, cancel it and come back to it later.

git merge --abort     # goes back to exactly how things were before the merge

🌐 Keep remoteIf the incoming branch's 10000 is the right value, take it as-is.

git checkout --theirs src/config.js
git add src/config.js
git commit

πŸ’» Keep localOr, if you need to keep your own branch's 5000:

git checkout --ours src/config.js
git add src/config.js
git commit

✍️ Merge manuallyHonestly, a lot of value conflicts like this are best solved by keeping both β€” for example, by pulling the value out into an environment variable. Once the markers are gone, the file looks like this.

βœ… After resolving (markers fully removed)
const timeout = process.env.TIMEOUT || 10000; // defaults to the feature branch's value, adjustable via env var
git add src/config.js
git commit

β‘‘ Delete vs. Modify Conflict

One branch deleted a file while the other edited it. git status shows this as "deleted by us" or "deleted by them." Here, you're really just choosing between deleting or keeping the file.

πŸ”„ Revert everything

git merge --abort

Whether you delete or keep the file comes down to a single filename.

# Confirm the deletion
git rm src/legacy.js
git commit

# Or keep the modified version instead
git add src/legacy.js
git commit

β‘’ Both Branches Create the Same New File (Add/Add Conflict)

Two branches independently create a file with the same name, so Git has no way to know which content is correct. The same four options from scenario β‘  apply here too.

git status                   # look for "both added:"

# πŸ”„ Revert everything
git merge --abort

# 🌐 Keep remote (accept the other branch's version)
git checkout --theirs src/utils/format.js && git add src/utils/format.js

# πŸ’» Keep local (accept your own version)
git checkout --ours src/utils/format.js && git add src/utils/format.js

# ✍️ Merge manually (open the file and combine both versions)
git add src/utils/format.js
git commit

β‘£ The Same Conflict Keeps Coming Back During a Rebase

A rebase replays your commits one at a time, so the same part of the same file can conflict repeatedly, once per commit. This is exactly where rerere, mentioned earlier, really pays off β€” and it's also the scenario where the --ours/--theirs flip from the warning box above trips people up the most.

git rebase main
# conflict happens -> fix the file, then
git add <conflicted-file>
git rebase --continue     # move on to the next commit

# πŸ”„ Revert everything
git rebase --abort        # cancel and go back to how things were before the rebase

# To skip this commit entirely
git rebase --skip

# During a rebase, --ours = main (the branch you're rebasing onto), --theirs = your replayed commit
git checkout --ours  src/config.js   # keep main's version
git checkout --theirs src/config.js  # keep your commit's version

β‘€ Rename Conflict

One branch renamed a file while the other edited its contents. When Git can't auto-detect the rename cleanly, a visual merge tool makes this much easier.

git status                 # both modified: old-name.js -> new-name.js

# πŸ”„ Revert everything
git merge --abort

# Resolve visually (recommended)
git mergetool

# Or finalize the filename/content manually, then
git add new-name.js
git commit

β‘₯ Binary File Conflict (Images, Fonts, etc.)

Binary files like images or fonts can't be merged line by line, so "merge manually" isn't an option here β€” you're choosing between the other three.

# πŸ”„ Revert everything
git merge --abort

# πŸ’» Keep local (your branch's version)
git checkout --ours  path/to/logo.png
git add path/to/logo.png
git commit

# 🌐 Keep remote (the incoming version)
git checkout --theirs path/to/logo.png
git add path/to/logo.png
git commit
# on newer Git, "restore" works the same way as "checkout" here
# git restore --ours/--theirs path/to/logo.png

⑦ Auto-Generated File Conflicts (package-lock.json, yarn.lock, etc.)

Files like package-lock.json or yarn.lock aren't meant to be hand-edited, yet they conflict surprisingly often β€” usually because both branches installed different packages around the same time. The right move isn't to untangle the markers by hand; it's to delete the file and let the tool regenerate it.

git status                      # confirm package-lock.json is conflicted

# πŸ”„ Revert everything
git merge --abort

# ✍️ Recommended fix: don't hand-edit the markers, just regenerate
git checkout --ours package-lock.json   # pick either side just to clear the conflict
npm install                              # regenerates the lock file from package.json
git add package-lock.json
git commit

yarn.lock and pnpm-lock.yaml work the same way β€” just run yarn install or pnpm install instead. If package.json itself has a conflict, that's a normal hand-maintained text file, so treat it like scenario β‘  and merge it manually.

It's worth noting that developer communities never seem to run out of jokes about merge conflicts β€” from two AI-generated implementations clashing and nobody being sure which logic to keep, to only half-joking suggestions to just start a fresh repo rather than untangle the markers. It's a shared, mildly universal stress, and it shows in how often it comes up. On the tooling side, things have genuinely gotten better: VS Code's Merge Editor (with Accept Current/Incoming/Both buttons), GitKraken's GitLens (which shows inline blame and commit context right at the conflict), and structural merge tools like Mergiraf, which parse code at the syntax-tree level and silently auto-resolve conflicts that are really just independent, non-overlapping changes before you ever see them.

4. Command Cheat Sheet by Situation πŸ“‹

For when you need an answer fast, here's every command from this guide organized into one table. Bookmark it for the next time you're stuck.

Situation Command What it does
List conflicted filesgit statusShows which files are in a conflicted state
See only your changesgit diff --ours <file>Shows your branch's pre-merge changes
See only incoming changesgit diff --theirs <file>Shows the other branch's pre-merge changes
Keep your versiongit checkout --ours <file>Keeps the entire file as your branch's version
Keep the incoming versiongit checkout --theirs <file>Keeps the entire file as the other branch's version
Mark as resolvedgit add <file>Tells Git you've manually resolved the conflict
Finish the mergegit commitCreates the merge commit and completes the merge
Cancel the merge entirelygit merge --abortReverts to the state before the merge started
Continue a rebasegit rebase --continueMoves to the next commit after resolving this one
Skip a rebase commitgit rebase --skipSkips this commit entirely and continues
Cancel a rebase entirelygit rebase --abortReverts to the state before the rebase started
Open a visual merge toolgit mergetoolOpens conflicts in a configured tool like VS Code or KDiff3
Auto-reuse past resolutionsgit config --global rerere.enabled trueRemembers and reapplies previously resolved conflict patterns
Improve marker readabilitygit config --global merge.conflictStyle zdiff3Adds the common ancestor content to conflict markers

5. Habits & Tools That Cut Down on Conflicts

You can't eliminate conflicts entirely, but you can absolutely reduce how often and how bad they get. Pulling frequently with git pull --rebase, keeping commits and PRs small, and simply telling your teammates when you're about to touch a shared file for a while all noticeably cut down on conflicts.

And when a conflict does show up, there's no need to panic anymore. Once you know how to read the markers and have the right command ready for the situation, a conflict is just Git politely asking you to pause and double-check something. Bookmark today's cheat sheet, and go into your next conflict with a little more confidence πŸ™Œ

Share:
Home Search Share Link My Likes