TechEarl

How to Resolve Merge Conflicts in Git

A merge conflict means Git found two changes to the same lines and needs you to pick. Here is how to read the conflict markers, fix them by hand or with a mergetool, and commit.

Ishan Karunaratne⏱️ 6 min readUpdated
Share thisCopied
How to resolve merge conflicts in Git by editing conflict markers and committing the result

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:

When it happens, Git tells you plainly:

text
Auto-merging config.js
CONFLICT (content): Merge conflict in config.js
Automatic merge failed; fix conflicts and then commit the result.
A real Git merge conflict and its conflict markers shown in the terminal
A genuine merge conflict: the markers Git writes into the conflicted file.

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:

text
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 <<<<<<< HEAD and ======= is the version from your current branch (the one you are merging into, labelled HEAD).
  • Everything between ======= and >>>>>>> feature-branch is the version from the branch you are merging in (here, feature-branch).
  • The lines above and below the markers (const port = 3000; and const 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:

text
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 diff or 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 status lists 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.

bash
git add config.js

Repeat for every conflicted file. When git status shows no more unmerged paths, finish the merge with a commit:

bash
git commit

Git 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:

bash
git mergetool

This 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:

bash
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:

bash
git merge --abort

This 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:

CommandWhat it doesWhen to reach for it
git merge --abortCancels the in-progress merge, restores pre-merge stateYou want out of this merge entirely, cleanly
git checkout --theirs <file>Takes the incoming branch's version of one file wholesaleYou know one whole file should be their version
git checkout --ours <file>Keeps your current branch's version of one file wholesaleYou know one whole file should stay yours
git reset --hardDiscards all changes back to the last commitA 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:

bash
git checkout main
git merge feature-branch

Git reports a conflict in config.js. You check what is unmerged:

bash
git status

You 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:

bash
git add config.js
git commit

Done. 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.

Tagsresolve merge conflictsGitVersion Controlgit mergemergetool

Found this useful? Pass it on.

Copied

Ishan Karunaratne

Tech Architect · Software Engineer · AI/DevOps

Tech architect and software engineer with 20+ years building software, Linux systems, and DevOps infrastructure, and lately working AI into the stack. Currently Chief Technology Officer at a healthcare tech startup, which is where most of these field notes come from.

Keep reading

Related posts

How to Remove a Secret from Git History

Committed an API key or password? Deleting it in a new commit does not remove it. Rotate the credential first, then scrub it from all history with git filter-repo or BFG.

How to Discard Local Changes in Git

Discard local changes in Git with restore, checkout, and clean. How to throw away edits to tracked files, delete untracked files, and reset to the last commit.