Skip to content

Lab: Resolve a Merge Conflict

Lesson 2 of 4Beginner3 min readHands-On Git & GitHub Labs · Workflow Labs
Time15 minutes
LevelBeginner
You needLocal Git only — no GitHub account needed

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.

Create a genuine conflict, read the markers correctly, resolve it, and know how to abort when a resolution has gone wrong.

  • Git installed
  • Comfortable with git commit and git switch
  • Git Merge provides the model this lab exercises
Terminal window
mkdir -p /tmp/lab-conflict && cd /tmp/lab-conflict
git 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",
};
EOF
git add . && git commit -q -m "Add config"
# Branch A: the performance work
git switch -qc perf/timeouts
sed -i 's/timeout: 3000/timeout: 500/' config.js
git commit -qam "Reduce timeout for faster failure"
# Branch B: the reliability work, on the same line
git switch -q main
sed -i 's/timeout: 3000/timeout: 10000/' config.js
git commit -qam "Raise timeout for slow networks"
git log --oneline --all --graph

Two branches. Both changed timeout. Both are defensible. Git will not choose.

  1. Trigger the conflict:

    Terminal window
    git merge perf/timeouts

    Expected: CONFLICT (content): Merge conflict in config.js and Automatic merge failed; fix conflicts and then commit the result.

  2. Inspect the state. Run git status. Note that config.js is listed under “Unmerged paths” — it is neither staged nor unstaged, but in a third state.

  3. Read the file:

    Terminal window
    cat config.js

    Identify which side is which. HEAD is the branch you are onmain, with the 10000 value. The other side is what you are merging in.

  4. Resolve it. Edit config.js so it contains exactly one timeout line and no conflict markers. For this lab choose 10000, the value that handles slow networks.

  5. Verify no markers remain:

    Terminal window
    grep -nE '^(<<<<<<<|=======|>>>>>>>)' config.js && echo "MARKERS REMAIN" || echo "clean"
  6. Stage and commit:

    Terminal window
    git add config.js
    git commit --no-edit
  7. Now practise aborting. Reset and redo the merge, then abandon it:

    Terminal window
    git reset --hard HEAD~1
    git merge perf/timeouts
    git merge --abort
    git status

    Confirm 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/timeouts

Resolution 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:

Terminal window
git checkout --ours config.js # keep current branch's version
git checkout --theirs config.js # take the incoming version
Terminal window
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 # 0
git add config.js
git commit --no-edit
git log --oneline --graph

Aborting instead:

Terminal window
git merge --abort

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.

Terminal window
cd /tmp && rm -rf lab-conflict

Repair a broken interactive rebase — the same conflict skills, under the harder conditions rebase creates.

Choose a learning pathA sequenced route through the curriculum for wherever you are now.