Learning Objectives
By the end of this lesson you will be able to:
- Identify each of the three conflict markers and what it separates
- Say with certainty which side of a conflict is yours and which is incoming
- Find every conflicted file in a repository
- Recognise a conflict marker that has been accidentally committed
What Git Writes Into the File
When Git cannot merge two versions of a region, it does not pick one and it does not stop halfway. It writes both versions into the file, separated by markers, and leaves the file for you.
timeout: 30
<<<<<<< HEAD
retries: 2
parallel: true
=======
retries: 5
parallel: false
>>>>>>> dev
base_url: https://staging.example.com
Three markers, each seven characters long:
| Marker | Meaning |
|---|---|
<<<<<<< HEAD |
Start of your version — the branch you are standing on |
======= |
The divider between the two versions |
>>>>>>> dev |
End of the incoming version, from the branch named after it |
Everything between <<<<<<< and ======= is what your current branch has. Everything between ======= and >>>>>>> is what the branch you are merging has. Everything outside the markers merged cleanly and is not in dispute.
Which Side Is Mine?
This trips up almost everyone at first, so here is the reliable rule:
The top half — labelled HEAD — is always the branch you are currently on. The bottom half is always what is coming in.
During git merge dev while standing on main:
- Top (
HEAD) =main - Bottom (
dev) =dev
During a rebase, the labels invert, because a rebase replays your commits onto the other branch — so HEAD is the branch you are replaying onto, and the bottom is your own commit. If a conflict during a rebase feels backwards, that is why. When in doubt, don't reason about it: read the branch names Git printed after the markers.
Finding Every Conflicted File
git status
On branch main
You have unmerged paths.
(fix conflicts and run "git commit")
(use "git merge --abort" to abort the merge)
Unmerged paths:
(use "git add <file>..." to mark resolution)
both modified: config/test-env.yaml
both modified: tests/auth/login.spec.js
git status is the authoritative list. Anything under Unmerged paths still needs a decision.
For a bare list, useful for scripting or for grepping:
git diff --name-only --diff-filter=U
config/test-env.yaml
tests/auth/login.spec.js
Most editors also highlight conflict regions and offer "Accept Current" / "Accept Incoming" buttons. Those buttons are just editing the text between the markers — exactly what you would do by hand.
Seeing More Context
The default markers show two versions. You can ask Git for three:
git config --global merge.conflictStyle zdiff3
After which conflicts look like this:
<<<<<<< HEAD
retries: 2
||||||| merge base
retries: 3
=======
retries: 5
>>>>>>> dev
The middle section is the original — what the line looked like at the merge base. This is genuinely valuable: seeing that the base was 3, yours is 2 and theirs is 5 tells you both sides made a deliberate change, which is a different situation from one side simply not having the update.
This setting is worth turning on permanently.
Committed Markers Are a Real Bug
If a <<<<<<< line reaches production, the file is corrupt. A YAML config fails to parse, a JavaScript file throws a syntax error, and a Markdown document displays gibberish to a customer.
This happens more than you would expect, usually when someone resolves the top half of a large conflict and misses a second conflicted region further down the same file.
Before staging anything, check:
git diff --check
config/test-env.yaml:4: leftover conflict marker
Or search the working tree directly:
grep -rn "^<<<<<<< \|^=======$\|^>>>>>>> " .
Pro Tip: This is one of the highest-value QA checks you can add to a CI pipeline: fail the build if any tracked file contains a line starting with
<<<<<<<. It takes one line of shell script and catches a whole class of embarrassing production defects.
Key Takeaways
- Git writes both versions into the file, wrapped in three seven-character markers
<<<<<<< HEADstarts your side,=======divides,>>>>>>> branchends the incoming side- Top is always the branch you are on — but the labels invert during a rebase, so read the names
git statuslists every unmerged path;git diff --name-only --diff-filter=Ugives the bare listmerge.conflictStyle = zdiff3adds the original merge-base version and is worth enabling- A committed conflict marker is a genuine defect;
git diff --checkcatches it before you commit