Skip to content

The index decides which marker blocks git actually wrote - #54

Merged
mindaugaskasp merged 1 commit into
mainfrom
fix/merge-marker-shaped-prose
Aug 11, 2026
Merged

The index decides which marker blocks git actually wrote#54
mindaugaskasp merged 1 commit into
mainfrom
fix/merge-marker-shaped-prose

Conversation

@mindaugaskasp

Copy link
Copy Markdown
Owner

The finding left open at the end of the v0.4.30 audit, now closed.

The bug

A document about merge conflicts carries marker-shaped lines of its own:

# how a conflict looks

<<<<<<< HEAD
the left side
=======
the right side
>>>>>>> other

setting = base

Change setting on both branches and git conflicts that line only. But the parser reads $MERGED and finds two marker blocks, so the view offered the document's own example as a decision — and answering it dropped one of its two sides. Silently: the result pane deliberately shows no markers, so there was nothing on screen to notice by. It also said "2 conflicts left" where git had made one.

Why the reference tools don't have it

JetBrains' resolve-conflicts dialog never reads $MERGED. It loads the three revisions from the VCS and works from those, so marker-shaped prose is just text — identical on both sides, hence not a conflict.

We already read the same three stages. Look at what git puts in the index for the file above:

$ git show :2:doc.md          # ours, pristine
# how a conflict looks

<<<<<<< HEAD
the left side
=======
the right side
>>>>>>> other

setting = ours

The example is ordinary text there. git never writes markers into the index — that is the whole test, and it is exact rather than heuristic: a block appearing verbatim in a pristine side was already in the file, so it goes back as stable text.

What I did not do, and why

JetBrains literally recomputes the conflict regions from the three revisions. I deliberately did not.

Doing so replaces git's own auto-merge with another algorithm's output on the one file this app overwrites — every stable line between conflicts would become ours rather than git's, with different answers on whitespace, renames and the rest. It also buys nothing for this bug: git has already decided where the conflicts are, and the only defect was that we invented extra ones. Using the index to validate keeps git's result byte-for-byte and fixes exactly what was broken.

Worth having on record if the full three-way model is ever wanted for its own sake — it would additionally handle a $MERGED someone had hand-edited.

Guarded

  • e2e, the exact reproduction, verified red→green: without the fix it reports 2 conflicts left; with it, one decision and the document lands on disk byte-identical, markers and both sides intact.
  • unit, four cases including the one that matters most — with no index available nothing is demoted, because guessing there would drop a real conflict.
  • The panes now take their seed ranges from the store instead of re-parsing rawContent. That is what kept the corrected regions and the seeded decorations from disagreeing, and it drops a second parse of the file.

npm run check exit 0. 20/20 merge e2e green.

🤖 Generated with Claude Code

A document ABOUT merge conflicts carries marker-shaped lines of its own. The
parser could not tell them from git's, so the view offered the documented
example as a decision — and answering it dropped one side of it. Silently: the
result pane shows no markers, so there was nothing on screen to notice by. It
also claimed two conflicts where git had made one.

This is the reference tools' whole reason for not reading $MERGED: JetBrains
resolves from the three VCS revisions, so marker-shaped prose is just text on
every side. The same source of truth settles it here without replacing git's
merge — git never writes markers into the index, so a block that appears
verbatim in a pristine side was already in the file, and is put back as stable
text. With no index there is nothing to check against and nothing is demoted:
guessing there would drop a real conflict.

Deliberately NOT recomputing the regions from the three stages, which is what
JetBrains does literally. That would replace git's own auto-merge with another
algorithm's on the one file this app overwrites, and buys nothing here — git has
already decided where the conflicts are, and this only stops us inventing extra
ones.

The panes now take their seed ranges from the store rather than re-parsing
`rawContent`, which is what kept the corrected regions and the seeded
decorations from disagreeing — and drops a second parse of the file.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@mindaugaskasp
mindaugaskasp merged commit 56b67cf into main Aug 11, 2026
6 checks passed
@mindaugaskasp
mindaugaskasp deleted the fix/merge-marker-shaped-prose branch August 11, 2026 07:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant