Two branches changed the same line, git merge stopped halfway, and now your file has angle brackets in it. The fix is mechanical: open each conflicted file, cut the marked block down to the code you want, remove the markers, git add the file and commit. If you would rather not deal with it right now, git merge --abort puts everything back.
The part that actually trips people up is the naming. "Ours" and "theirs" trade places during a rebase, and VS Code's "current" and "incoming" trade places along with them. I wrote a longer walkthrough, with every command run on Git 2.51.0, on DevToolLab. This is the short version, using the same toy repo: a checkout.js where main cut the free-shipping minimum from $50 to $35 and a holiday-promo branch cut it to $25.
Reading the Markers
Merging holiday-promo into main fails on that one line:
$ git merge holiday-promo
Auto-merging checkout.js
CONFLICT (content): Merge conflict in checkout.js
Automatic merge failed; fix conflicts and then commit the result.
Git keeps both versions in the file. The half under <<<<<<< HEAD is the branch you have checked out, and the half above >>>>>>> holiday-promo is the one coming in:
export const CURRENCY = "USD"
<<<<<<< HEAD
export const FREE_SHIPPING_MIN = 35
=======
export const FREE_SHIPPING_MIN = 25
>>>>>>> holiday-promo
Anything outside the markers already merged, so the decision is only about this block. On a merge that touches many files, git diff --name-only --diff-filter=U lists just the ones still in conflict.
Finishing the Merge by Hand
Rewrite the block as whatever the result should be: one side, the other, or a combination. In this example the team keeps $35, so five marker lines collapse into one. Staging the file is the signal Git uses to mark the conflict resolved:
$ git add checkout.js
$ git commit --no-edit
[main bec6cfe] Merge branch 'holiday-promo'
$ git log --oneline --graph
* bec6cfe Merge branch 'holiday-promo'
|\
| * 455b2d8 Holiday promo: free shipping over $25
* | 424022f Lower free shipping threshold to $35
|/
* 225abac Add checkout config
Until that commit exists, git merge --abort is still available. In my test it left main sitting on the $35 commit, as if the merge never started.
When a block runs to dozens of lines, paste each half into the Diff Checker to see the word-level differences before choosing. If you would rather click than edit, the Git Conflict Resolver takes the whole conflicted file, lets you pick ours, theirs, both or the base for each block, and gives you the clean file to paste back. Neither one touches your repository, so the git add is still yours to run.
Keeping One Side of a Whole File
git checkout --ours checkout.js and git checkout --theirs checkout.js replace the file with stage 2 or stage 3 of the unmerged path, according to the git-checkout docs. That is the entire file, not only the conflicting lines, so whatever the other branch changed elsewhere in it is thrown away too:
$ git checkout --ours checkout.js # FREE_SHIPPING_MIN = 35, from main
$ git checkout --theirs checkout.js # FREE_SHIPPING_MIN = 25, from holiday-promo
$ git add checkout.js
This is the right move for lockfiles and generated code, where you take one version and regenerate.
Why a Rebase Flips Ours and Theirs
A rebase replays your commits on top of the upstream branch, so from Git's point of view the upstream is already "ours" and your branch is the one being applied. The git-rebase docs say it directly: "the sides are swapped." I ran both operations from holiday-promo to confirm:
on holiday-promo, git merge main:
--ours -> export const FREE_SHIPPING_MIN = 25
--theirs -> export const FREE_SHIPPING_MIN = 35
on holiday-promo, git rebase main:
--ours -> export const FREE_SHIPPING_MIN = 35
--theirs -> export const FREE_SHIPPING_MIN = 25
In practice: mid-rebase, --theirs is how you keep your own change. git pull --rebase follows the same rule. Once a rebase conflict is fixed, git add the file and run git rebase --continue, or git rebase --abort to give up.
zdiff3 Shows Where Both Sides Started
The default markers show how each branch ended, not where the line started. Set merge.conflictStyle to zdiff3 before merging and Git adds a ||||||| section holding the common ancestor:
$ git config merge.conflictStyle zdiff3
$ git merge holiday-promo
$ cat checkout.js
export const CURRENCY = "USD"
<<<<<<< HEAD
export const FREE_SHIPPING_MIN = 35
||||||| e4b05f2
export const FREE_SHIPPING_MIN = 50
=======
export const FREE_SHIPPING_MIN = 25
>>>>>>> holiday-promo
Seeing $50 in the middle tells you both branches were lowering the same number, which is something you can decide on rather than guess at. The git-config docs describe zdiff3 as diff3 that moves matching lines at the edges out of the conflict, so blocks stay shorter. For a conflict that is already in the file, git checkout --conflict=diff3 checkout.js redraws it in that style.
VS Code and GitHub
VS Code shows Accept Current Change, Accept Incoming Change, Accept Both Changes and Compare Changes above each conflict, plus a 3-way merge editor for bigger files. Its docs warn that accepting both does not guarantee valid code. Here it would declare FREE_SHIPPING_MIN twice, which is a syntax error. "Current" is the checked-out branch during a merge but the new base plus already-replayed commits during a rebase, so the same swap applies under different names.
On a pull request, GitHub's Resolve conflicts button opens a web editor with the same markers, but GitHub's docs limit it to simple competing line changes. Anything more involved gets resolved locally. It also merges the whole base branch into your head branch, and if the head branch is a default or protected branch, GitHub may ask you to create a new one.
Errors You Will Hit Mid-Conflict
Each of these came from running a command while checkout.js still had an open conflict. The original post has the full git status output and the reasoning behind each fix.
-
error: Committing is not possible because you have unmerged files.means the fixed file was never staged.git addit, then commit. -
fatal: cannot switch branch while mergingmeansgit switchis blocked until the merge is finished or aborted. -
error: Merging is not possible because you have unmerged files.anderror: Pulling is not possible because you have unmerged files.both mean a second merge, or a pull, ran into the one still open. -
checkout.js:2: leftover conflict markerisgit diff --checkcatching markers you missed. Run it before staging: in my test,git commit -amcommitted the file with all three markers still in it, because-astages the file and staging is what tells Git the conflict is resolved.
When to Stop and Ask
If the other side of the conflict is code you do not understand, abort the merge and ask whoever wrote it. That costs a minute; a wrong hand-merge costs a lot more. Generated files are the other case: take one side and rerun the tool that produced them, because a hand-edited lockfile is less trustworthy than either original.
Wrap-Up
Read the block, make it the code you want, git add the file. The two mistakes that actually cause damage are reaching for --ours or --theirs mid-rebase without remembering the swap, and running commit -a while markers are still in the file. Turn on git config --global merge.conflictStyle zdiff3 once, make git diff --check a habit, and most conflicts take a minute.


Top comments (0)