More Advanced Git Merge Cases
More Advanced Git Merge Cases κ΄λ ¨
What would happen if John and Paul were to coordinate a new song, and work on it together?
In this case, John creates the first version of this song in the main
branch:
git checkout main
nano everyone.md
By the way, this text is indeed taken from the version that John Lennon recorded for a demo in 1968. But this isn't an article about the Beatles, so if you're curious about the process the Beatles underwent while writing this song, you can follow the links in the appendix below.
git add everyone.md
git commit -m "Commit 10"
Now John and Paul split. Paul creates a new verse in the beginning:
git checkout -b paul_branch_3
nano everyone.md
Also, while talking to John, they decided to change the word "feet" to "foot", so Paul adds this change as well.
And Paul adds and commits his changes to the repo:
git add everyone.md
git commit -m "Commit 11"
You can observe Paul's changes, by comparing this branch's state to the state of branch main
:
git diff main
Store this diff in a patch file:
git diff main > paul_3.patch
Now back to main
...
git checkout main
John decides to make another change, in his own new branch:
git checkout -b john_branch_3
And he replaces the line "Everyone had the boot in" with the line "Everyone had a wet dream". In addition, John changed the word "feet" to "foot", following his talk with Paul.
Observe the diff:
git diff main
Store this output as well:
git diff main > john_3.patch
Now, stage and commit:
git add everyone.md git commit -m "Commit 12"
This is our current history:
Paul told John he added a new verse, so John would like to merge Paul's changes.
Can John simply apply Paul's patch?
Consider the patch again:
git diff main paul_branch_3
As you can see, this diff relies on the line "Everyone had the boot in", but this line no longer exists on John's branch. As a result, you could expect applying the patch to fail. Go on, give it a try:
git apply paul_3.patch
Indeed, you can see that it failed.
But should it really fail? π€
As explained earlier, git merge
uses a 3-way merge algorithm, and this can come in handy here. What would be the first step of this algorithm?
Well, first, Git would find the merge base β that is, the common ancestor of Paul's branch and John's branch. Consider the history:
So the common ancestor of "Commit 11" and "Commit 12" is "Commit 10". We can verify this by running the command:
git merge-base john_branch_3 paul_branch_3
Now we can take the patches we generated from the diffs on both branches, and apply them to main
. Would that work?
First, try to apply John's patch, and then Paul's patch.
Consider the diff:
git diff main john_branch_3
We can store it in a file:
git diff main john_branch_3 > john_3.patch
And I want to apply this patch on main
, so:
git checkout main git apply john_3.patch
Let's consider the result:
nano everyone.md
The line changed as expected. Nice π
Now, can Git apply Paul's patch? To remind you, this is the patch:
Well, Git cannot apply this patch, because this patch assumes that the line "Everyone had the boot in" exists. Trying to apply is liable to fail:
git apply -v paul_3.branch
What you tried to do now, applying Paul's patch on main
branch after applying John's patch, is the same as being on john_branch_3
, and attempting to apply the patch, that is:
git checkout john_branch_3
git apply paul_3.patch
What would happen if we tried the other way around?
First, clean up the state:
git reset --hard
And start from Paul's branch:
git checkout paul_branch_3
Can we apply John's patch? As a reminder, this is the status of everyone.md
on this branch:
And this is John's patch:
Would applying John's patch work? π€ Try to answer yourself before reading on.
You can try:
git apply john_3.patch
Well, no! Again, if you are not sure what happened, you can always ask git apply
to be a bit more verbose:
git apply john_3.patch -v
Git is looking for "Everyone put the feet down", but Paul has already changed this line so it now consists of the word "foot" instead of "feet". As a result, applying this patch fails.
Notice that changing the number of context lines here (that is, using git apply
with the -C
flag, as discussed in a previous post) is irrelevant β Git is unable to locate the actual line that the patch is trying to erase.
But actually, Git can make this work, if you just add a flag to apply
, telling it to perform a 3-way merge under the hood:
git apply -3 john_3.patch
And let's consider the result:
Exactly what we wanted! You have Paul's verse (marked in the image above), and both of John's changes!
So, how was Git able to accomplish that?
Well, as I mentioned, Git really did a 3-way merge, and with this example, it will be a good time to dive into what this actually means.