Scenario
Section titled “Scenario”Two people edited the same lines of the same file on different branches. Merging one into the other, Git stops and reports a conflict.
A conflict is not an error. It is Git declining to guess which change is correct — because it cannot know.
Objective
Section titled “Objective”Create a genuine conflict, read the markers correctly, resolve it, and know how to abort when a resolution has gone wrong.
Prerequisites
Section titled “Prerequisites”- Git installed
- Comfortable with
git commitandgit switch - Git Merge provides the model this lab exercises
Starting state
Section titled “Starting state”mkdir -p /tmp/lab-conflict && cd /tmp/lab-conflictgit init -q .git config user.email "lab@example.com"git config user.name "Lab User"
cat > config.js <<'EOF'export const config = { timeout: 3000, retries: 3, endpoint: "https://api.example.com",};EOFgit add . && git commit -q -m "Add config"
# Branch A: the performance workgit switch -qc perf/timeoutssed -i 's/timeout: 3000/timeout: 500/' config.jsgit commit -qam "Reduce timeout for faster failure"
# Branch B: the reliability work, on the same linegit switch -q mainsed -i 's/timeout: 3000/timeout: 10000/' config.jsgit commit -qam "Raise timeout for slow networks"
git log --oneline --all --graphTwo branches. Both changed timeout. Both are defensible. Git will not choose.
-
Trigger the conflict:
Terminal window git merge perf/timeoutsExpected:
CONFLICT (content): Merge conflict in config.jsandAutomatic merge failed; fix conflicts and then commit the result. -
Inspect the state. Run
git status. Note thatconfig.jsis listed under “Unmerged paths” — it is neither staged nor unstaged, but in a third state. -
Read the file:
Terminal window cat config.jsIdentify which side is which.
HEADis the branch you are on —main, with the 10000 value. The other side is what you are merging in. -
Resolve it. Edit
config.jsso it contains exactly onetimeoutline and no conflict markers. For this lab choose 10000, the value that handles slow networks. -
Verify no markers remain:
Terminal window grep -nE '^(<<<<<<<|=======|>>>>>>>)' config.js && echo "MARKERS REMAIN" || echo "clean" -
Stage and commit:
Terminal window git add config.jsgit commit --no-edit -
Now practise aborting. Reset and redo the merge, then abandon it:
Terminal window git reset --hard HEAD~1git merge perf/timeoutsgit merge --abortgit statusConfirm you are back to a clean tree with no merge in progress.
What do the markers mean?
<<<<<<< HEAD timeout: 10000, ← your current branch======= timeout: 500, ← the branch being merged in>>>>>>> perf/timeoutsResolution means producing the correct final content. It does not have to be either side — it can be both, neither, or something new. Git only requires that the markers are gone.
git diff during a conflict shows a combined diff against both parents, which reads oddly
the first time. git diff --ours and git diff --theirs are usually clearer.
Taking one side wholesale:
git checkout --ours config.js # keep current branch's versiongit checkout --theirs config.js # take the incoming versionSolution
Section titled “Solution”git merge perf/timeouts# CONFLICT (content): Merge conflict in config.js
cat > config.js <<'EOF'export const config = { timeout: 10000, retries: 3, endpoint: "https://api.example.com",};EOF
grep -c '<<<<<<<' config.js # 0git add config.jsgit commit --no-editgit log --oneline --graphAborting instead:
git merge --abortExplanation
Section titled “Explanation”Git merges by three-way comparison. It looks at the merge base (the last common commit) and both tips. Where only one side changed a region, that change is taken automatically. Where both sides changed the same region differently, there is no correct automatic answer — so Git writes both versions into the file and stops.
Conflict markers are a working state, not corruption. The file is deliberately invalid so that committing it accidentally is hard.
The index tracks the conflict. During a conflict the index holds up to three versions of the
file — base, ours, theirs. git add collapses them to your resolution, which is why staging is
what marks a conflict resolved.
git merge --abort is always available while unresolved. It restores the pre-merge state
exactly. After the merge is committed you need git revert -m 1 <merge-sha> instead.
Clean up
Section titled “Clean up”cd /tmp && rm -rf lab-conflictRelated lessons
Section titled “Related lessons”Next lab
Section titled “Next lab”Repair a broken interactive rebase — the same conflict skills, under the harder conditions rebase creates.