A merge conflict means Git tried to combine two branches and found that both of them changed the same lines of the same file in different ways. Git cannot guess which version you want, so it stops and asks you to decide. The fix is always the same shape: open the conflicted file, edit it to the version you actually want, then run git add on it and git commit. That is the whole job.
The scary part is the wall of <<<<<<< and ======= and >>>>>>> symbols Git writes into your file. Those are just signposts. Once you can read them, conflicts stop being frightening and become a two-minute chore. If you are still finding your footing with branches, the beginner-friendly walkthrough of Git branching is worth reading first, because conflicts only happen when branches diverge.
When does a merge conflict actually happen?
Conflicts do not happen on every merge. Git is good at combining changes automatically when they touch different parts of a file, or different files entirely. A conflict only appears when Git sees two edits it cannot reconcile on its own.
The classic case: you and a teammate both edited line 12 of config.js. You changed the port to 3000, they changed it to 8080. When one branch merges into the other, Git has no rule for "which port wins," so it hands the decision to you.
You will usually meet a conflict in one of these moments:
- Running
git merge feature-branchinto your current branch. - Running
git pull, which is a fetch plus a merge under the hood (see the difference between git pull and git fetch); pull-driven conflicts are also where you meet the prompt about how a git pull reconciles divergent branches. - Running
git rebase, which replays your commits and can hit the same competing-edit problem.
When it happens, Git tells you plainly:
Auto-merging config.js
CONFLICT (content): Merge conflict in config.js
Automatic merge failed; fix conflicts and then commit the result.
Your repository is now in a paused, "merging" state. Nothing is broken. Git is waiting for you.
Anatomy of a conflict: reading the markers
Open the conflicted file and you will see a block like this where Git could not decide:
const port = 3000;
<<<<<<< HEAD
const host = "localhost";
const timeout = 5000;
=======
const host = "0.0.0.0";
const timeout = 30000;
>>>>>>> feature-branch
const debug = false;Read it top to bottom:
- Everything between
<<<<<<< HEADand=======is the version from your current branch (the one you are merging into, labelledHEAD). - Everything between
=======and>>>>>>> feature-branchis the version from the branch you are merging in (here,feature-branch). - The lines above and below the markers (
const port = 3000;andconst debug = false;) were not in conflict. Git already merged those cleanly and left them alone.
So the marker block is saying: "Your side wants localhost and a timeout of 5000; their side wants 0.0.0.0 and a timeout of 30000. Pick."
Resolving a conflict by hand
To resolve it, you edit the file so it contains exactly the code you want, and you delete all three marker lines (<<<<<<<, =======, >>>>>>>). Those markers are not magic; they are plain text Git inserted, and they have to be gone before the file is valid again.
Say you want their host but your timeout. You edit the block down to:
const port = 3000;
const host = "0.0.0.0";
const timeout = 5000;
const debug = false;No markers, no leftover lines, just the final code. That is the resolution. The conflict was a question; this is your answer.
A few habits that save pain:
- Search for the markers before you commit. Run
git diffor grep for<<<<<<<across the repo so you do not accidentally commit a marker into your code. A stray=======in a file compiles in almost no language. - Resolve one file at a time.
git statuslists every conflicted file under "Unmerged paths." Work through them, not all at once in your head. - Do not just keep both sides unless that is genuinely correct. The point is to produce the code that should run, not to mash the two versions together.
Once a file is fixed, stage it. Staging a conflicted file is how you tell Git "this one is resolved." If you are hazy on what staging means, the Git staging area explained covers it.
git add config.jsRepeat for every conflicted file. When git status shows no more unmerged paths, finish the merge with a commit:
git commitGit pre-fills a sensible merge commit message for you; you can accept it as-is. The merge is now complete and your branches are joined.
Resolving with a mergetool
For a small conflict, editing the file by hand is fastest. For a big, gnarly one with overlapping changes across many lines, a visual three-way merge tool makes the two sides much easier to compare side by side. Git ships a launcher for this:
git mergetoolThis opens whichever diff tool you have configured (VS Code, Meld, KDiff3, Beyond Compare, vimdiff, and others are all supported), showing your side, their side, and often the common ancestor in separate panes. You click to take a line from the left or right, or hand-edit the result, and save. Git then marks that file resolved automatically, so you do not need a manual git add for files you finished in the tool.
To set VS Code as your mergetool, for example:
git config --global merge.tool vscode
git config --global mergetool.vscode.cmd 'code --wait $MERGED'After git mergetool finishes every file, you still finish the merge the same way: confirm with git status, then git commit. Older Git versions left .orig backup files behind; if you see them, delete them, or set git config --global mergetool.keepBackup false to stop them being created.
Bailing out: how to abort a merge
Sometimes you start a merge, see the conflicts, and decide you do not want to deal with it right now, or you realise you merged the wrong branch. You can throw the whole thing away and go back to exactly where you were before you ran git merge:
git merge --abortThis resets your working tree to the state it was in before the merge began. The conflict markers vanish, your files return to their pre-merge content, and the repository leaves the merging state. It is completely safe as long as you had a clean working tree before the merge (commit or stash any work in progress first).
If you were resolving a rebase rather than a merge, the equivalent escape hatch is git rebase --abort.
Merge vs abort vs reset: which command undoes what
People mix these up under pressure. Here is what each one actually does to a conflicted merge:
| Command | What it does | When to reach for it |
|---|---|---|
git merge --abort | Cancels the in-progress merge, restores pre-merge state | You want out of this merge entirely, cleanly |
git checkout --theirs <file> | Takes the incoming branch's version of one file wholesale | You know one whole file should be their version |
git checkout --ours <file> | Keeps your current branch's version of one file wholesale | You know one whole file should stay yours |
git reset --hard | Discards all changes back to the last commit | A blunt last resort; loses uncommitted work |
For most conflicts you will never need reset. git merge --abort is the polite undo; manual editing plus git add is the resolution. If you only need to walk back a single bad merge commit rather than nuke everything, how to undo the last commit is the softer move. And if you do reach for the heavier undo tools, the differences between git reset and git revert are worth understanding so you do not lose work you wanted to keep, and there is also a focused guide on discarding local changes safely. Even after a git reset --hard or a misjudged abort, committed work is usually not gone for good: recovering them with git reflog is the safety net.
A complete example, start to finish
Here is the whole flow in one place. You are on main, merging in feature-branch:
git checkout main
git merge feature-branchGit reports a conflict in config.js. You check what is unmerged:
git statusYou open config.js, find the marker block, edit it to the final code you want, and remove every marker line. Then you stage the resolved file and commit:
git add config.js
git commitDone. If at any point you wanted to back out instead, git merge --abort would have returned you to a clean main.
If you regularly hit conflicts because your branch has drifted far from main, the deeper fix is workflow, not commands. Smaller, more frequent merges create smaller conflicts. The comparison of merge versus rebase and the rundown of team Git workflows both speak to keeping branches close enough that conflicts stay trivial.
Where this fits in the bigger picture
Merge conflicts are a normal, expected part of collaborating in Git, not a sign you did something wrong. The more comfortable you get reading the markers, the less they slow you down. If you are still building up the fundamentals, start at the Git for beginners hub and work outward through branching, staging, and stashing. Each of those makes conflicts easier to avoid and easier to fix when they do show up.
Sources
Authoritative references this article was fact-checked against.
- git-merge: Git official documentationgit-scm.com
- Pro Git: Basic Branching and Merginggit-scm.com
- git-mergetool: Git official documentationgit-scm.com





