All articles
169 articles · updated weekly See our Tools
All articles
Tutorials

How to resolve merge conflicts without panic

A merge conflict isn't an error: it's Git asking for your decision. Understand the markers, follow the calm flow, and learn to abort when you need a restart.

How to resolve merge conflicts without panic
COVER · Tutorials

You run git merge or git pull and the terminal hands you that dreaded line: "Automatic merge failed; fix conflicts and then commit the result." Your pulse jumps, and the urge to close everything and pretend it never happened is real. Relax. A merge conflict is not a Git error, it is not a sign that you broke something, and it does not mean your code is corrupted. It is the exact opposite: Git is being honest with you. It merged everything it could merge on its own and stopped at the spots where two sides changed the same region of the same file, because that decision is not Git's to make — it is yours.

This guide walks through the calm flow for resolving conflicts: understanding why they happen, reading the markers, deciding with intent, cleaning up, and moving on. No panic, no magic commands copied from a forum.

Why conflicts happen (and why they aren't errors)

Git resolves merges automatically in the overwhelming majority of cases. If you touched file A and your teammate touched file B, there is nothing to decide: both changes go in. If you both touched the same file but in distant sections, Git usually still resolves it on its own, lining up the edits by context.

A conflict shows up in a specific scenario: two sides edited the same region of lines, in different ways, and Git has no way to know which version you want. Maybe you renamed a function and your teammate changed its body. Maybe you both adjusted the same configuration line. Git could pick a side arbitrarily, but that would be dangerous — it would silently swallow someone's work. Instead, it stops and asks for a human decision.

That is why a conflict is a design decision, not an accident. The question is never "which side is less work to accept." The question is "what was each side trying to do, and how should the final result behave." People who resolve conflicts by taking the lazy path tend to reintroduce bugs that had already been fixed or delete entire features without noticing.

The anatomy of the markers

When there is a conflict, Git rewrites the affected file by inserting markers around the disputed section. They look scary the first time, but the structure is simple:

<<<<<<< HEAD
const timeout = 3000;
=======
const timeout = 5000;
>>>>>>> feature/increase-timeout

The reading is direct. Everything between <<<<<<< HEAD and ======= is your side — the current state of the branch you are on (the HEAD). Everything between ======= and >>>>>>> is the incoming side — the changes from the branch you are bringing in, identified by the name next to the greater-than signs. The ======= is just the boundary between the two.

Notice something important: the markers are not part of your code. They are temporary annotations from Git. Resolving the conflict means, in the end, leaving the file exactly as it should be and deleting all three marker lines. If you forget one of them, the file is left with syntactic garbage that will break the build.

One detail that helps a lot: enable the conflict style that also shows the common base.

git config --global merge.conflictstyle zdiff3

With zdiff3, Git adds a third block showing how the region looked before either side touched it. That is a game changer, because you start to see each side's intent instead of just comparing two final results with no context. Knowing where both sides started from is what lets you combine the changes correctly instead of guessing.

The calm flow, step by step

When the merge stops, the first thing to do is breathe and look at the big picture. Don't go opening files in the dark.

git status

git status lists the conflicted files under "Unmerged paths." That is your to-do list. Usually it is only a few files — rarely the horror the initial message suggests.

Open each conflicted file and find the markers. For each block, you have three possible decisions: keep your side, keep the incoming side, or combine the two into a third version that respects both intentions. Most of the time the right answer is to combine — after all, both sides were written for a reason.

Before deciding in a large or dense file, it is worth comparing the two versions outside the editor to see clearly what each side actually changed. Pasting the two snippets into a side-by-side difference checker makes it obvious what is identical and what diverges, which lowers the chance of deleting a change without noticing. Once you understand it, you go back to the file, edit the section until it is how it should be, and delete the <<<<<<<, =======, and >>>>>>> lines.

When the file is clean and correct, mark it as resolved:

git add path/to/file.js

Repeat for each file on the list. When all are resolved and added, finish the operation. In a merge, that is just:

git status        # confirm nothing is left under "Unmerged paths"
git commit        # Git fills in the merge message for you

If the conflict showed up during a git rebase or a git cherry-pick, the command to continue is git rebase --continue or git cherry-pick --continue instead of git commit. git status always tells you the next step — read its output instead of guessing.

The escape hatch: abort

Sometimes you start resolving, realize you grabbed the wrong branch, that the merge was premature, or that you are too lost to trust the result. Don't force it. There is a clean exit that returns everything to the previous state, as if the merge had never started:

git merge --abort

git merge --abort undoes the in-progress merge and restores your working tree to how it was before. For rebase, the equivalent is git rebase --abort. Knowing that door exists is half the calm — you are never trapped. You can abort, think, talk to whoever made the other change, and try again without rushing.

How to keep conflicts from becoming a headache

Conflicts are part of working on a team and you cannot eliminate them. But you can keep them small and easy to resolve. Three habits make almost all the difference.

The first is small, frequent commits. The smaller the change, the smaller the collision surface and the easier it is to understand each side when a conflict shows up. A giant commit that touches thirty files is a conflict bomb waiting to go off.

The second is syncing early. The longer your branch stays away from main, the more it diverges and the worse the merge gets at the end. Pulling the base often turns one monstrous, late conflict into several tiny, manageable conflicts along the way.

The third is the least technical and the most underrated: talking to the team about who touches what. A good share of painful conflicts comes from two people refactoring the same module without knowing about each other. A thirty-second heads-up in chat saves hours of merging.

Frequently asked questions

Can I commit with the markers still in the file?

Never do that. The <<<<<<<, =======, and >>>>>>> markers are not valid code in any language — they will break the build, the tests, or the runtime. Committing markers is one of the most embarrassing and most common mistakes. Before any git add, check that the file is clean; many editors even highlight the markers in red to warn you.

How do I know which side is "mine" and which is "theirs"?

The block between <<<<<<< HEAD and ======= is your side — the state of the branch you are on. The block between ======= and >>>>>>> is the incoming one, and the name next to the >>>>>>> signs tells you which branch or commit that change came from. In a git pull, "your side" is your local work and "theirs" is what came from the remote.

What do I do if I resolve everything wrong?

If you haven't made the final commit yet, git merge --abort (or git rebase --abort) returns everything to the previous state and you start over calmly. If you already committed and noticed the mistake afterward, you can undo the merge commit with history-rewriting commands — a subject we cover in git reset, revert, and restore. The key is not to panic: almost nothing in Git is irreversible.

Is zdiff3 really worth it?

Very much so, and it is free. Without it, you see only the two final results and have to guess each side's intent. With merge.conflictstyle=zdiff3, Git also shows the common base — the version both sides started from. That turns "which of these two do I pick" into "what did each one change relative to the original," which is the right question for combining changes without losing anything.

What to take from here

A merge conflict is neither an accident nor a punishment: it is Git outsourcing to you the one thing it cannot do on its own — understanding the intent behind two changes that cross each other. Treat each conflict as a design decision, not as an obstacle to bulldoze past. Read both sides, understand what each one wanted, combine when it makes sense, and only then clean up the markers and move on. Resolving by whichever path is less work is exactly how you reintroduce bugs and erase other people's work. And, please: never commit the markers.

RD
Author
Rafael Duarte
Backend developer with a background in fintech and B2B SaaS — worked on teams that scaled APIs from zero to millions of requests. Carries enough production scars to hold strong opinions on tools, patterns and architecture decisions. Not an academic: he read the UUID RFC when he had to choose between v4 and v7 for a write-heavy table.
View profile