Solving the Same Conflict a Third Time Means Something Is Missing
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
- When you regularly merge a release branch back into
main, the same conflict occurs on the version number line every time. If you keep rerere on, from the second time you pass by just looking at the markers. - rerere is most valuable for people who often abort a rebase and start again. Because they meet the same conflict repeatedly.
- If generated files (lock files, bundle outputs) conflict every time, rather than having it remember the resolution, it is better to specify a merge method in
.gitattributesor not track them in the first place. - More and more places recommend
merge.conflictStyle=zdiff3as a team rule. Because with one line of configuration you no longer have to guess "what it originally was".
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.