Merge Conflicts — Understand & Resolve Them
Why Conflicts Are Not Scary
Merge conflicts are the thing that makes new Git users most anxious. They see a file full of <<<<<<< markers and feel like they have broken something. They have not.
A merge conflict is not an error. It is Git saying: "Two branches both changed the same part of the same file in different ways. I cannot automatically decide which change to keep, so I need you to decide."
That is it. The conflict markers are not damage — they are Git's way of presenting the decision to you clearly. Once you understand what they mean and how to resolve them, conflicts become a routine part of collaborative development, not a source of dread.
This lesson teaches you to resolve conflicts confidently, every time.
Why Conflicts Happen
Git is remarkably good at automatically merging changes. Most of the time, when two branches change different files (or different parts of the same file), Git merges them without any conflict. The magic works because Git tracks the common ancestor.
A conflict occurs when:
- Two branches modify the same lines of the same file — Git cannot determine which version to keep
- One branch modifies a file that the other branch deletes — Git cannot determine what to do with the file
- Two branches rename the same file differently — Git cannot determine which name to use
The most common case by far is case 1.
Setting Up a Conflict
Let's deliberately create a merge conflict to work through:
Git will stop and report:
Reading Conflict Markers
Open taskr.sh and find the conflict. It looks like this:
Let's parse each section:
<<<<<<< HEAD — This marker starts the conflict block. Everything between this line and the ======= line is the version from your current branch (HEAD, which is main).
======= — This divider separates the two conflicting versions.
>>>>>>> feature/better-help — This marker ends the conflict block. Everything between ======= and this line is the version from the branch being merged in (feature/better-help).
So reading the conflict:
mainchanged the line to:echo "Usage: taskr <command> <args>"feature/better-helpchanged the line to:echo "Usage: taskr COMMAND [OPTIONS]"
Git presents both versions and asks: which one do you want? Or do you want a combination of both?
The Ancestor (Base) Version
Sometimes it helps to know what the line looked like before either branch changed it. You can configure Git to show a three-way view that includes the common ancestor:
With diff3, the conflict looks like:
The middle section (between ||||||| and =======) shows the original line before either branch touched it. This context is often invaluable for understanding what both sides were trying to change.
The diff3 style is highly recommended. Enable it globally.
git status During a Conflict
Key information:
- "You have unmerged paths" — a merge is in progress
both modified: taskr.sh— this file has conflicts that need resolution- Instructions: fix the conflicts, then
git addthe resolved files, thengit commit - Or:
git merge --abortto cancel the merge entirely
Resolving Conflicts Manually
Resolving a conflict means editing the file to contain exactly what you want — neither the raw <<<<<<< markers nor both versions unchanged, but your considered decision about the correct final content.
The resolution options:
Option A: Keep HEAD's version
Option B: Keep the feature branch's version
Option C: Combine both (create a new version)
Option D: Write something completely different
After editing, the file should contain no conflict markers at all. Just the correct final content.
Step-by-Step Resolution
Git will open your editor with a pre-filled merge commit message:
You can edit this message or keep it as-is. Save and close to complete the merge.
Multiple Conflicts in One Merge
Real merges often have more than one conflict. The workflow is the same, applied to each file:
The --ours and --theirs flags on git checkout (or git restore --source) let you wholesale accept one side:
After using --ours or --theirs, you still need to git add the file to mark it as resolved.
Using a Merge Tool: git mergetool
Visual merge tools show conflicts in a three-pane or four-pane interface: the original (base), your version (ours), their version (theirs), and the output. They are much more comfortable than editing raw conflict markers for complex conflicts.
Configure a Merge Tool
Running the Merge Tool
Git opens each conflicted file in the configured tool. You resolve each conflict, save, and close the tool. Git marks the file as resolved and moves to the next.
Merge tools often create .orig backup files. Clean them up:
VS Code's Built-in Conflict Resolution
VS Code has excellent built-in conflict resolution. When you open a file with conflicts, VS Code shows color-coded inline buttons above each conflict:
- Accept Current Change — keep HEAD's version
- Accept Incoming Change — keep the merging branch's version
- Accept Both Changes — insert both versions sequentially
- Compare Changes — open a side-by-side diff
These are convenient for simple conflicts. For complex ones, the full merge tool view is better.
git merge --abort
If you get into a conflict and decide you are not ready to resolve it right now, abort the merge:
This returns your working directory and staging area to the state they were in before you ran git merge. All conflict markers are removed, your files are restored to their pre-merge state, and you are back on your branch as if the merge never happened.
Use this when:
- You want to prepare better before attempting the merge
- You realize you need more information about the intent of the conflicting changes
- You want to take a different approach (e.g., rebase instead of merge)
Conflict Prevention Strategies
The best conflict is one that never happens. These practices reduce conflict frequency and severity:
1. Keep Feature Branches Short-Lived
The longer a branch lives, the more it diverges from main, and the bigger the eventual merge conflict. Aim to merge feature branches within a day or two. Break large features into smaller, independently merge-able slices.
2. Sync Your Branch Frequently
Regularly merge or rebase main into your feature branch:
By pulling changes from main into your branch often, you resolve small conflicts early (when the context is fresh) rather than one huge conflict at merge time.
3. Communicate With Your Team
Before refactoring a file, changing a core API, or moving code around, announce it in your team's communication channel. "I'm about to rename all the auth functions — anyone touching auth right now?" A five-second Slack message can save an hour of conflict resolution.
4. Small, Focused Commits
Commits that change one thing at a time are easier to merge automatically. A commit that touches 15 files across the codebase is much more likely to conflict than a commit that changes 3 closely related files.
5. Use .gitattributes for Line Endings
Line ending differences (CRLF vs LF) can cause false conflicts. Add a .gitattributes file to normalize line endings:
6. Structure Code to Minimize Coupling
High coupling between files means more simultaneous edits. Good software architecture — clear module boundaries, separation of concerns, dependency injection — naturally reduces merge conflicts because developers work in separate, well-defined areas of the codebase.
Understanding Common Conflict Scenarios
Scenario 1: Both Branches Edited the Same Line
The most common case, shown throughout this lesson. The resolution is always a human decision: which version is correct, or what combination of both?
Scenario 2: One Branch Deleted a File, the Other Modified It
You must decide: keep the file (by git add config.json) or confirm the deletion (by git rm config.json). Look at the modification to understand if it was important.
Scenario 3: Both Branches Added the Same File
Both branches created a file with the same name but different content. Open the file, resolve the conflict markers as usual.
Scenario 4: Rename Conflicts
One branch renamed auth.js to authentication.js; another renamed it to auth-service.js. Git reports:
You need to delete one of the renamed files and keep the one with the name you want:
Practical Exercises
Exercise 1: Create and Resolve a Text Conflict
Exercise 2: Use diff3 Style
Exercise 3: Practice --ours and --theirs
Exercise 4: Abort and Retry
Challenge: Three-Branch Merge Conflict
Create a scenario where three branches all modify the same file:
feature/achanges lines 10-15 of taskr.shfeature/bchanges lines 12-18 of taskr.sh (overlapping range)- Merge
feature/ainto main - Merge
feature/binto main (conflict!) - Resolve carefully so the final version is correct and logical
Summary
- Merge conflicts happen when two branches change the same lines of the same file. Git cannot automatically decide which change wins, so it presents the conflict to you.
- Conflict markers:
<<<<<<< HEADbegins the current branch's version;=======separates the two versions;>>>>>>> branch-nameends the incoming branch's version. - Configure
merge.conflictstyle diff3to also see the common ancestor version in conflicts — this provides valuable context. - Resolution: edit the file to contain exactly the correct final content (no markers), then
git addthe file, andgit committo complete the merge. git mergetoolopens a visual merge interface; VS Code has built-in conflict resolution.git merge --abortcancels a merge in progress and returns you to the pre-merge state.- Prevention: keep branches short-lived, sync often with the base branch, communicate with teammates about major refactoring, and use
.gitattributesto handle line endings.
The next lesson covers rebase and cherry-pick — two powerful operations for rewriting and reorganizing history.