Both branches changed the same lines in the same file, so Git cannot decide which version is correct. It marks the file, stops, and waits for you.
This is normal. It is not a failure state.
Find the conflicted files
git diff --name-only --diff-filter=U
src/config.ts
src/api/client.ts
--diff-filter=U means unmerged, and this is the authoritative list. git status shows it too, buried among other information.
Read the markers
<<<<<<< HEAD
const TIMEOUT = 5000
=======
const TIMEOUT = 30000
>>>>>>> feature/retry
Between <<<<<<< and ======= is your current branch. Between ======= and >>>>>>> is what you are merging in. The labels tell you which is which, and they matter during a rebase where the sides are swapped from what you might expect.
Add the common ancestor, which usually makes the decision obvious:
git checkout --conflict=diff3 -- src/config.ts
<<<<<<< HEAD
const TIMEOUT = 5000
||||||| merged common ancestor
const TIMEOUT = 10000
=======
const TIMEOUT = 30000
>>>>>>> feature/retry
Now you can see the base was 10000: one side lowered it, the other raised it. Without the ancestor you are guessing which side actually changed.
Make it the default:
git config --global merge.conflictStyle zdiff3
zdiff3 is the improved version, which also removes lines common to both sides from the conflict region, leaving a smaller thing to read.
Resolve
Edit the file, remove all three marker lines, keep what is correct. It is frequently neither side verbatim.
Then stage it:
git add src/config.ts
Staging a conflicted file is how you tell Git it is resolved. Repeat for each file, then:
git commit # merge: the message is pre-filled
git rebase --continue # rebase
Before committing, check you did not leave a marker behind:
git diff --check
grep -rn '^<<<<<<<\|^>>>>>>>' src/
Committed conflict markers are a genuinely common and embarrassing mistake, and a CI grep for them is worth adding.
Taking one side wholesale
When one side is simply correct, for a lockfile or generated output:
git checkout --ours package-lock.json # current branch
git checkout --theirs package-lock.json # incoming branch
git add package-lock.json
During a rebase, --ours and --theirs are reversed from what you expect. A rebase replays your commits on top of the upstream branch, so the upstream is checked out and becomes "ours", while your commit being replayed is "theirs". This catches almost everyone once.
For a lockfile the better answer is usually to regenerate it rather than pick a side:
git checkout --theirs package-lock.json
npm install
git add package-lock.json
Other conflict types
modify/delete
CONFLICT (modify/delete): src/old.ts deleted in HEAD and modified in feature/new.
There are no markers because there is no content to interleave. Decide:
git rm src/old.ts # accept the deletion
git add src/old.ts # keep the modified version
add/add. Both branches created the same path independently. Resolved like a content conflict.
rename/rename. Both renamed the same file differently. Git leaves all the variants in the tree; pick one and git rm the rest.
Abort
At any point:
git merge --abort
git rebase --abort
git cherry-pick --abort
Each restores the state before the operation started. Nothing is lost, and there is no reason to hesitate: a half-finished merge you no longer understand is worth abandoning and restarting with a clearer head.
git merge --abort fails if you had uncommitted changes when you started the merge, which is the argument for a clean working tree before merging.
Reducing how often this happens
Merge the target branch into yours regularly. A feature branch that tracks main weekly resolves conflicts in small pieces rather than one enormous one at the end.
Turn on rerere. Git records how you resolved a conflict and replays it automatically next time it sees the same one:
git config --global rerere.enabled true
This pays for itself during a long rebase where the same conflict reappears in commit after commit.
Use a merge tool for large conflicts:
git mergetool --tool=vimdiff
VS Code, IntelliJ and the rest all have three-way merge editors, and for a conflict spanning many hunks they are considerably faster than editing markers by hand.
A checklist
git diff --name-only --diff-filter=Ufor the real list.- Set
merge.conflictStyle = zdiff3so you can see the common ancestor. - Edit, remove all markers, keep what is correct. Often a blend of both.
git add <file>to mark each one resolved.git diff --checkbefore committing, to catch leftover markers.--oursand--theirsare reversed during a rebase.- Lockfiles: regenerate rather than choosing a side.
- Lost?
git merge --abortorgit rebase --abort. Nothing is lost.
Frequently Asked Questions
What do the conflict markers mean?
<<<<<<< to ======= is the version on your current branch, and ======= to >>>>>>> is the incoming version, with the branch names on the marker lines. With merge.conflictStyle set to diff3 or zdiff3 you also get the common ancestor between ||||||| and =======, which is much more useful because it shows what both sides started from. Seeing the base usually makes it immediately clear which side made the meaningful change, rather than leaving you to guess from two similar-looking versions.
Why are --ours and --theirs backwards during a rebase?
Because a rebase works by checking out the upstream branch and replaying your commits on top of it, one at a time. From Git's point of view the checked-out upstream is "ours" and the commit being applied, which is yours, is "theirs". It is the opposite of a merge, where your branch is checked out and the incoming one is theirs. The reliable habit is to read the branch names on the conflict marker lines rather than relying on which flag feels right.
How do I undo a merge that is going badly?
git merge --abort restores the state exactly as it was before the merge began, and the equivalents are git rebase --abort and git cherry-pick --abort. Nothing is lost and there is no penalty for using them, so abandoning a confusing resolution and starting again is often the fastest path. The one limitation is that git merge --abort can fail if you had uncommitted changes when you started, which is a good reason to begin any merge with a clean working tree.
What should I do with a conflicted lockfile?
Regenerate it rather than resolving it by hand. A package-lock.json or yarn.lock is generated output, and hand-merging two versions reliably produces something that does not correspond to any real dependency resolution. Take either side with git checkout --theirs package-lock.json, then run npm install to regenerate it from the merged package.json, and stage the result. The same logic applies to any generated file: resolve the source, then regenerate.
How can I avoid resolving the same conflict repeatedly?
Enable rerere with git config --global rerere.enabled true. Git records your resolution of each conflict and replays it automatically whenever it encounters an identical one, which is exactly what happens during a long rebase where the same hunk conflicts in commit after commit. The broader habit that helps more is merging the target branch into your feature branch regularly, so conflicts arrive in small, comprehensible pieces instead of as one large one at the end.