When a git merge conflict lands on your screen, most developers' first instinct is to panic — but a conflict is really Git's way of helping you. Git notices that the same lines were changed differently on two branches and can't decide which version is correct, so it stops and hands the decision back to you. This guide walks through why conflicts happen, how to read the markers in your terminal, and how to clean up the file safely, step by step.
What exactly is a merge conflict?
Git merges changes line by line. When two branches touch different regions of the same file, Git blends them with no fuss. A conflict only appears when the same lines (or lines very close together) were edited differently on both branches, or when one side deleted what the other side modified. In that case Git asks: "there are two possible truths here, which one do you want?"
Conflicts can surface in almost all of these commands: git merge, git rebase, git pull (which runs a merge or rebase internally), git cherry-pick, and git stash pop. The mechanism is identical everywhere, so once you learn to resolve one, you can resolve them all.
Reading the conflict: what the markers mean
When you open a conflicted file, you'll see three markers Git inserted:
<<<<<<< HEAD
const timeout = 5000;
=======
const timeout = 10000;
>>>>>>> feature/login
- Between
<<<<<<< HEADand=======: your side (the branch you're on, "ours"). - Between
=======and>>>>>>>: the incoming side, the branch you're merging in ("theirs"). - The trailing label (
feature/login) tells you where the conflicting change came from.
One important detail: during a rebase, the meaning of "ours" and "theirs" is reversed, because rebase replays your commits on top of the target branch. So HEAD represents the target branch and the lower block holds your change. Keep this in mind if you resolve from the command line.
Step-by-step resolution
Here's a safe, repeatable flow:
- See the state. Run
git status; it lists conflicted files under the "Unmerged paths" heading. - Open each conflicted file. Find every marker block. You have three options: keep your side, keep the incoming side, or merge both by hand.
- Write the correct result. You must delete the three marker lines (
<<<<<<<,=======,>>>>>>>) so that only the final code you want remains. - Mark it as resolved. Stage the file with
git add <file>. - Finish the operation. Continue with
git commitfor a merge,git rebase --continuefor a rebase, orgit cherry-pick --continuefor a cherry-pick.
Editor support makes this much easier. VS Code highlights conflicting blocks and shows "Accept Current Change / Accept Incoming Change / Accept Both Changes" buttons. Still, before clicking, check that the result actually makes sense — automatic acceptance doesn't always produce the correct code.
Quick decisions from the command line
Sometimes you know you want one entire side of a file from top to bottom. Instead of editing by hand:
# keep my branch's version of this file entirely
git checkout --ours config/app.php
# keep the incoming branch's version of this file entirely
git checkout --theirs config/app.php
# then stage it
git add config/app.php
If you want to back out completely to the point before the conflict, git merge --abort (or git rebase --abort for a rebase) returns you to the starting state. It's the cleanest way to start over without breaking anything.
Reducing conflicts in the first place
The best conflict is the one that never happens. A few habits noticeably lower the risk:
- Commit small and often, integrate frequently. Keeping a branch away from main for weeks is an invitation for conflicts to pile up. Stay current with
git pull --rebase. - Keep files small and focused. A modular structure, rather than huge single files, reduces the odds that two people touch the same line.
- Watch out for generated files. Lock files like
composer.lockandpackage-lock.jsonconflict often; regenerating them (e.g.composer install) is safer than editing by hand. - Standardize formatting. A shared formatter across the team (Prettier, PHP-CS-Fixer) eliminates fake conflicts caused only by indentation or whitespace.
Verifying your resolution
After closing a conflict, checking "does it compile" isn't enough. Make sure both sides of the merged change kept their intent: run the tests, open the app, and manually exercise the behavior tied to the conflicted region. Reviewing the final state once more with git diff before committing is a fast way to catch a stray >>>>>>> line left behind or old code that should have been deleted.
Frequently Asked Questions
I kept the wrong side while resolving — can I undo it?
If you haven't committed yet, git merge --abort cancels the whole merge so you can start over. If you already committed, use git reflog to find the previous HEAD and git reset --hard <hash> to return there. That's exactly why you shouldn't push before verifying your resolution.
I keep resolving the same conflicts over and over — is there a fix?
Yes. Enable rerere (reuse recorded resolution) with git config --global rerere.enabled true. Git remembers how you resolved a conflict and reapplies it automatically if the same one reappears — extremely handy during long rebases.
Why are "ours" and "theirs" different between merge and rebase?
In a merge, the branch you're on is "ours." In a rebase, Git takes the target branch as the base first and replays your commits on top of it, so the roles flip: HEAD represents the target branch, and "theirs" is your replayed change. Check which command you're in before deciding.
Are conflicts slowing your project down? We can streamline your Git workflow, branching strategy, and deployment process together. If you'd like help on your project, get in touch with me.