TT Lab
Get started
Learn Learning paths Courses

Git in Practice

Solving the Same Conflict a Third Time Means Something Is Missing

Continue in TT Lab

One-line summary

rerere records how you resolved a conflict and applies it as it is when the same conflict comes up again. merge.conflictStyle=zdiff3 also shows the common ancestor in the conflict markers so that you do not have to guess "who changed what". -X ours and -s ours have similar names but completely different results.

Why this is needed

If you keep rebasing a long-lived feature branch onto main, the same conflict occurs at the same place every time. If you rebase ten commits, you resolve that conflict ten times. If you --abort by mistake and start over, it is another ten. When you get tired, your hand slips, and a slipped resolution is committed silently.

And the conflict markers themselves lack information. The default marking looks like this.

<<<<<<< HEAD
b by main
=======
b by topic
>>>>>>> topic

There is one thing you cannot tell here — what it originally was. You cannot distinguish whether one side deleted it, whether both sides changed it differently, or whether one side left it alone and only the other changed it. So you end up choosing "the side that looks safe", and if that judgment is wrong, someone else's fix disappears.

How it works

zdiff3 — shows the common ancestor too

git config merge.conflictStyle zdiff3

Then the conflict markers get one more column in the middle.

<<<<<<< HEAD
b by main
||||||| 75364cd
b
=======
b by topic
>>>>>>> topic

Below ||||||| is the content of the common ancestor. Now "it was originally b and both sides changed it differently" is visible at a glance. The old setting value diff3 does the same thing, but zdiff3 (git 2.35 or later) moves the lines that both sides left in common outside the conflict, so the marking is shorter.

rerere — remembers the resolution

git config rerere.enabled true

If you turn it on, when a conflict occurs git records the pre-conflict appearance (preimage) in .git/rr-cache, and when you commit, it attaches the resolved result (postimage) in the same place. The next time the same preimage appears, it says this.

Resolved 'handler.txt' using previous resolution.

There are three things to watch out for. First, rerere only applies the resolution and does not stage it. Looking at the result and running git add is up to a person (if you turn on rerere.autoUpdate, it is staged automatically, but then it passes without confirmation, so we do not recommend it). Second, the record is inside .git, so it remains only on your machine. Third, it remembers a wrongly resolved resolution just the same — in that case you must erase that record with git rerere forget <경로>.

-X ours and -s ours are completely different

Because the names are similar, they are often confused.

Knob What it is Result
-X ours An option given to the default merge strategy Chooses ours only at the conflicting spots, and the other side's non-conflicting changes come in as they are
-s ours The strategy itself Throws away the other side's tree wholesale. The result tree is not one character different from our pre-merge tree

-s ours is used to leave in the history the fact that "we decided not to merge this branch". Because the other branch is attached as an ancestor, from then on that branch drops out of the merge targets. If you use it with the intention of bringing in content, you end up in a state where the history says it was merged but nothing came into the code — and later nobody suspects it.

What it looks like in the field

What you will do in the next lab

You make two branches that modified the same place differently and look at the default marking and the zdiff3 marking side by side. You turn on rerere, resolve once, then undo the merge and merge again, and confirm that git applies the same resolution by itself. Finally you use -X ours and -s ours once each and check by content what remains and what disappears in the result file.

The official documentation is git-rerere, git-merge and git-config.