Stop cherry-picking, start merging, Part 1: The merge conflict - The Old New Thing
Skip to main content
Dev Blogs
AI
All .NET posts
.NET MAUI<br>ASP.NET Core<br>Blazor<br>Entity Framework
C++<br>C#<br>F#<br>TypeScript
NuGet<br>Servicing<br>.NET Blog in Chinese
Microsoft for Developers<br>Agent Framework<br>Develop from the cloud<br>Xcode<br>ISE Developer<br>TypeScript<br>PowerShell<br>Python<br>Java<br>Java Blog in Chinese<br>Go<br>Microsoft Edge Dev<br>Microsoft 365 Developer<br>Microsoft Entra Identity Developer<br>Microsoft Entra PowerShell
Visual Studio<br>Visual Studio Code<br>Aspire
All things Azure<br>Azure SDK<br>Azure VM Runtime Team<br>Microsoft Azure<br>Azure Cosmos DB<br>Azure DocumentDB<br>Azure Data Studio<br>Azure SQL<br>DevOps<br>DirectX<br>Microsoft Foundry<br>Power Platform
OData<br>Unified Data Model (IDEAs)
Windows Command Line<br>#ifdef Windows<br>Inside MSIX<br>MIDI and music<br>React Native<br>The Old New Thing<br>Windows Developer
Raymond Chen
Cherry-picking is a common operation in git, and it’s not a good idea. Sometimes it’s a neutral idea, but I haven’t yet found a case where it’s actually good.
This is the start of a series that will begin by explaining why cherry-picking is bad, continue by explaining why cherry-picking is worse, then try to talk you down from the ledge by showing how to get the same effect as a cherry-pick but with merging, showing how to apply that technique to the case where you need to do a retroactive merge, and wrap up by showing how to apply that technique to the case where you already made the mistake of cherry-picking and want to fix it before something bad or worse happens.
It’s a tall order, but I’ve been meaning to write this up for a while, and what’s gotta get done gotta get done.
In order to cherry-pick, you need two branches, one to be the donor and one to be the recipient. Let’s call them the master branch and the feature branch. And for simplicity’s sake, let’s say that the commit being cherry-picked is a one-line change to a single file. Each commit will be annotated with the contents of that one line.
apple
apple
berry
M1<br>← ← ←<br>M2
master
↖︎
F1<br>F2
feature
apple
berry
For the purpose of illustration, I’m using a dotted line to denote cherry-picks. This dotted line doesn’t really exist in the repo, but I’m drawing it to help express the chronology. (Eventually, I’ll stop drawing dotted lines, too.)
You have some common ancestor A, and in the commit, the line in question is the word apple. From that common ancestor, the two branches diverge: Commit F1 happens on the feature branch, and commit M1 happens on the master branch. These changes don’t affect the line in question, so it still says apple. You then make some commit F2 in the feature branch that changes the line in question from apple to berry, and you cherry-pick commit F2 into the master branch as M2.
So far, nothing exciting is happening.
Time passes, more commits occur, and your commit graph looks like this:
apple
apple
berry
berry
M1<br>← ← ←<br>M2<br>M3
master
↖︎
F1<br>F2<br>← ← ←<br>F3
feature
apple
berry
berry
You made another commit M3 to the master branch and another commit F3 to the feature branch. Neither of these commits affected the line in question, so the line is still the word berry.
It’s time to merge back, and since the line in question is the same in both branches, the merge is trivial, and the result in the final merged result is berry.
apple
apple
berry
berry
berry
M1<br>← ← ←<br>M2<br>M3<br>M4
master
↖︎
↙︎
F1<br>F2<br>← ← ←<br>F3
feature
apple
berry
berry
This is the ideal case.
It is also relatively uncommon in an active code base.
Consider this alternate timeline: After the cherry-pick, additional commits M3 to the master branch and F3 to the feature branch are made, but this time commit F3 changes the line in question to cherry. This could be because the person who made the original commit F2 found an improvement (cherries are on sale right now), or maybe they made a larger change that happened to require switching from berries to cherries.
Whatever the reason, the commit graph now looks like this:
apple
apple
berry
berry
M1<br>← ← ←<br>M2<br>M3<br>💥
master
↖︎
↙︎
F1<br>F2<br>← ← ←<br>F3
feature
apple
berry
cherry
This time, when it’s time to merge the feature branch back into the master branch, there is a merge conflict. The base of the three-way merge contains apple, the incoming feature branch has cherry and the existing master branch has berry.
>>>>>> feature
The conflict occurred because the cherry-picked changes were subsequently changed again by one of the branches. We’ve been using dotted lines in our diagrams to emphasize that the cherry-pick relationship is all in our heads, and not actually recorded anywhere in the commit graph.
You have to hope that whoever resolves this merge conflict remembers the history of this line, or can access the team’s knowledge of this line of code to understand that the correct resolution it to accept the changes in the feature branch rather than the one in the master branch.
In this case, there haven’t been many changes,...