Git Merging: How to Resolve Conflicts, Choose Strategies, and Keep Clean History
The dreaded CONFLICT (content): Merge conflict in file.txt. For many developers, seeing this message triggers an immediate spike in heart rate. It feels like you broke something.
Take a deep breath. You didn't break anything. A merge conflict is simply Git throwing its hands up and saying, "Hey, two people changed the exact same line of code, and I am not smart enough to guess which one is right. I need a human."
Here is how to read the Matrix, resolve the conflict, and get on with your day without losing your mind.
The Anatomy of a Conflictโ
When a conflict happens, Git physically injects "Conflict Markers" straight into your code. To fix the conflict, you literally just delete the markers and leave the code you want to keep.
Here is what a raw conflict looks like inside your file:
<<<<<<< HEAD
const serverPort = 8080;
=======
const serverPort = 3000;
>>>>>>> feature-new-server
Decoding the Markersโ
<<<<<<< HEAD: This is the start of your current changes (the branch you are currently standing on).=======: This is the divider. It separates your changes from the incoming changes.>>>>>>> branch-name: This is the end of the incoming changes (the branch you are trying to merge in).
The 3-Step Resolution Processโ
When your terminal turns red with conflicts, follow this exact checklist.
Step 1: Find the Battlegroundsโ
Git will usually tell you which files are conflicted, but if you clear your terminal and lose the message, just type:
git status
Look for the files listed under "Unmerged paths". These are the files containing the conflict markers.
Step 2: Make the Choice (The Edit)โ
Open the conflicted files in your code editor. You have three choices for every conflict:
- Accept Current (HEAD): Keep your code, delete their code.
- Accept Incoming: Keep their code, delete your code.
- Accept Both / Combine: Rewrite the block of code to include elements of both.
To resolve it manually, you just delete the weird Git symbols and leave the correct code.
Before:
<<<<<<< HEAD
const serverPort = 8080;
=======
const serverPort = 3000;
>>>>>>> feature-new-server
After (Decided to keep 3000):
const serverPort = 3000;
Step 3: Seal the Dealโ
Once you have removed all the <====> markers from all the files, you need to tell Git you are finished.
# 1. Stage the resolved files
git add .
# 2. Complete the merge (Git will auto-generate a merge message for you)
git commit
Pro-Tips for Keeping Your Sanityโ
1. Use a Visual IDE (Stop Doing This in Notepad)โ
If you are using a modern editor like VS Code, IntelliJ, or WebStorm, you don't even have to manually delete the markers.
When you open a conflicted file, the editor will highlight the block in neon colors and give you clickable buttons that say:
[Accept Current Change] | [Accept Incoming Change] | [Accept Both Changes]
Clicking one of those buttons automatically cleans up the markers for you.
2. The Panic Buttonโ
If you start trying to resolve a massive conflict, get confused, accidentally delete half the file, and feel like you are going to cry, stop. You can always rewind time and abort the merge entirely to try again later.
git merge --abort
(This puts your repository exactly back to how it was 5 seconds before you typed git merge.)
Conflict Resolution Cheat Sheetโ
| Command / Action | What it does |
|---|---|
git status | Lists all files currently containing conflict markers. |
<<<<<<< HEAD | Shows the code you currently have. |
>>>>>>> branch | Shows the code someone else wrote. |
git add <file> | Tells Git: "I have manually fixed the markers in this file." |
git merge --abort | The emergency undo switch. |
Sourcesโ
- [1.1] Git SCM Docs: Basic Merge Conflicts - The official documentation on reading markers.
- [2.1] VS Code Docs: Merge Conflicts - How to use Microsoft's built-in 3-way merge editor.
Merge Strategies: Squash, True Merge, and Fast-Forwardโ
Before you merge, you need to decide how you want your history to look. There are three main ways to combine a feature branch into main.
The Squash Merge (Industry Standard)โ
Instead of bringing over all of your tiny "wip" and "fix typo" commits, a squash merge crushes them all into one single, clean commit on the main branch.
- Best for: Feature branches and bug fixes.
- Why it works: It keeps the
mainbranch incredibly clean. Every commit onmainrepresents one complete, working feature.
The True Merge (--no-ff)โ
Creates a dedicated "Merge Commit" that links the two branches together, preserving every single individual commit.
- Best for: Massive, multi-developer epics where the historical context matters.
- Why it's risky: It creates the classic "Git Spaghetti" graph if overused.
The Fast-Forward (--ff-only)โ
If nothing has changed on main since you started your branch, Git just moves the main pointer forward.
- Best for: Small, local, single-person projects.
How to Merge Locally (CLI)โ
# 1. Update your local main
git checkout main
git pull origin main
# 2. (Optional) Rebase your feature branch first
git checkout your-feature-branch
git rebase main
# 3. Switch back to main
git checkout main
# 4. Execute the Merge (Choose ONE)
# Option A: The Squash Merge (Recommended)
git merge --squash your-feature-branch
git commit -m "feat: added new login dashboard"
# Option B: The True Merge
git merge --no-ff your-feature-branch
# 5. Push
git push origin main
| Strategy | Command | Result on main History | When to use it? |
|---|---|---|---|
| Squash | merge --squash | 1 Clean Commit | 90% of your daily Pull Requests. |
| True Merge | merge --no-ff | All Commits + 1 Merge Commit | Large collaborative feature branches. |
| Fast-Forward | merge --ff-only | All Commits (Linear) | Quick local updates. |
Rebase vs. Merge: When to Choose Eachโ
Neither rebase nor merge is inherently "better"; they are different tools for different purposes.
The Case for rebase: The Clean Historyโ
git rebase maintains a clean, linear, and easy-to-read commit history. It makes it look as if a feature was developed directly on top of the main branch.
- Avoids Merge Commits: Creates a linear history that is easier to navigate with
git logandgit bisect. - Use With: Local, private feature branches that have not been shared.
The Case for merge: The Accurate Historyโ
git merge preserves an accurate and chronological record. It maintains the true history of when and how branches diverged and came back together.
- Non-Destructive: Doesn't rewrite history; simply adds a new commit.
- Use With: Public, shared, or long-running branches.
| Feature | git rebase | git merge |
|---|---|---|
| History | Clean, linear, and rewritten | Accurate, chronological, preserves merge points |
| Safety | Risky on shared branches | Safe for all branches |
| Use Case | Local, private feature branches | Public, shared, or long-running branches |
| Commit Graph | Flat, no merge commits | Can be cluttered with merge commits |
Additional Sourcesโ
- [3.1] GitHub Blog: Squash your commits - Why GitHub introduced the "Squash and Merge" button.
- [4.1] Atlassian Git: Merging vs. Rebasing - The conceptual difference.
- [5.1] Git SCM Docs: git-merge - Official documentation on merge strategies.
- **[6.1] The Golden Rule of Rebasing](https://git-scm.com/book/en/v2/Git-Branching-Rebasing)
