Let’s look at how to resolve two Git branches that have diverged by creating a Git merge commit (your other option in this situation is to perform a git rebase).
As discussed in Visualizing Git Branching with Blocks, we can’t do a fast-forward merge when the most recent commit on the receiving branch (e.g. commit C
on main
) does not appear in the branch we are merging in (e.g. branch feat/d
).
(main*) (feat/d)
C D
B B
A A
A Commit with Two Parents
As discussed in What is a Git Commit?, a commit includes a bunch of information including the commit hash that came directly before it (the parent
commit).
Typically, a commit has exactly one parent. In the case of a merge commit, there are multiple parents.
After running git merge feat/d
to merge feat/d
into main
, Git will create commit (MERGE
) which has both C and D as parents).
(main*) (feat/d)
MERGE
C D D
B B
A A
This allows us to merge these branches that have diverged without rewriting either of their history.
Git Log Display Merge Commits
To get a clearer picture of the situation we add --graph
to our git log --oneline
command
git log --oneline --graph
* 154382e (HEAD -> main) Merge branch 'feat/d' into main
|\
| * 163fe29 (feat/d) D
* | 5d408f9 (feat/c) C
|/
* 2b3a38b B
* ec6a2c7 A
Here we can see our merge commit (154382e
) has two parents 5d408f9
(from feat/c
) and 163fe29
(from feat/d
).
Tracing back through this graph we can see the branches diverged after commit B
(2b4a38b
).
Spacers in the Git Log Graph
Even though commits C
and D
are adjacent to each other in Git, they are not displayed that way with git log --oneline --graph
. Spacers (|
) are included in the output while the commit is noted with an asterisk (*
). While visualizing this with blocks, I like to think of transparent blocks for the spacers.
View the Parent Commits
We can see the under the hood details of our merge commit (154382e
) with
git cat-file -p 154382e
tree 62feee95ffe3c1d6a738d35dac3da1d27a68abe9
parent 5d408f929e4c596d007532725abfd592b293dfb3
parent 163fe29d91b2ff43d4c724934836617ca619fa25
author Sal Ferrarello <sal@example.com> 1613264290 -0500
committer Sal Ferrarello <sal@example.com> 1613264290 -0500
Merge branch 'feat/d' into main
In the above output, we can see there are two parent
commits. The first is 5d408f9
and the second 163fe29
. These values match those that we see when we run git log --oneline --graph
.
Merge Conflicts
Since our branches have diverged, it is possible we will have a merge conflict when we create our merge commit. To resolve our merge conflict we find the locations in our code with merge conflict markers (<<<<<<<
and >>>>>>>
) and correct this code.
After the code is corrected, we stage our changes with git add
and then complete the merge with
git merge --continue
History is not Rewritten
The biggest advantage of using a merge commit (over a rebase) is it does not rewrite your history. You can now push your changes to a server without overwriting history.
Leave a Reply