When performing a Git rebase, I often find myself in the situation where I have one or more merge conflicts. This is how I resolve these merge conflicts.
Note: When resolving a git rebase merge conflict, Git will open your text editor to modify your commit message. By default, Git uses Vim which is not the most user friendly editor. I suggest you set Git to use your preferred editor (see how to set your Git commit message editor).
What is a Merge Conflict
In Git Rebase with Blocks, we looked at how rebasing:
- finds the most recent common commit between the two branches
- temporarily removes any newer commit(s) from our branch
- updates our branch to match the branch the other branch
- then re-applies our temporarily removed commit(s)
When we re-apply our temporarily removed commits, they are being re-applied to code that may have changed.
Imagine if the commit being re-applied renamed a function but due to the rebase that function no longer exists. Git can not automatically determine how to apply this commit, so a merge conflict is created.
Example of a Merge Conflict
We create a text file (letters.txt
) with letters in alphabetical order. We add the letter “A” in a commit and then we add the letter “B” in another commit.
(main)
B
A
We decide to create a new branch (called feat/d
) in order to introduce our new feature (the letter “D”).
(main) (feat/d*)
D
B B
A A
Based on this we could do a fast-forward merge of feat/d
into main
(because main
is an ancestor of feat/d
).
Unfortunately, someone has already added another commit to main
while we were working on our branch.
(main) (feat/d*)
C D
B B
A A
Rebasing our Feature Branch
Now we can’t do a fast-forward merge (because the latest commit on main, D
does not appear in our feat/d
branch), so we run git rebase main
on our feat/d
branch to bring it up to date.
(main) (feat/d*)
D
C C
B B
A A
but when we try to re-apply our D
commit, Git doesn’t know if the letter “D” goes before or after “C”.
CONFLICT (content): Merge conflict in letters.txt
error: could not apply 163fe29... D
Resolve all conflicts manually, mark them as resolved with
"git add/rm <conflicted_files>", then run "git rebase --continue".
You can instead skip this commit: run "git rebase --skip".
To abort and get back to the state before "git rebase", run "git rebase --abort".
Could not apply 163fe29... D
Now if we look inside letters.txt
, we’ll see a section that looks like this
<<<<<<< HEAD
C
=======
D
>>>>>>> 163fe29... D
Note: Some browsers (e.g. Visual Studio Code) provide additional UI options to assist with resolving merge conflicts.
Branches During our Merge Conflict
Our merge conflict occurred at this moment where we’ve added commit C
to our feat/d
branch but we’ve not yet re-applied our commit D
.
(main) (feat/d*)
D
C C
B B
A A
Looking again at the merge conflict in our file, we can see our two different versions. In the top half (labeled HEAD
) we can see how this code appears in commit the branch thus far (at the moment of commit C
).
In the bottom half, we see how the code appears in our temporarily removed commit D
(which has a commit hash of 163fe29
).
<<<<<<< HEAD
C
=======
D
>>>>>>> 163fe29... D
Fixing Our Code
Using these two different versions of the code, it is our job to modify the code as needed. Sometimes this involves keeping one block and discarding the other block. In our case, we need the information from both blocks put in the correct order (and removing the Git marker lines).
C
D
Resolving Our Merge Conflict
Stage Our Changes
Once our code is fixed, we add it to our staged changes with
git add letters.txt
Review Our Changes
We can review our changes with
git diff --staged
Remember we are trying to re-apply our commit C
but it didn’t apply cleanly (Git couldn’t figure out how to apply it). Now we’ve made changes to D
, we are previewing this new version of our commit D
.
Note: A single commit can have multiple merge conflicts and they all must be resolved before we move on. Previewing the commit (with git diff --staged
) allows us to verify there are no unresolved merge conflicts.
Apply Our Updated Commit
We apply our updated commit with
git rebase --continue
Now our feat/d
branch has our updated D'
commit, which contains the code after our merge conflict was resolved.
(main) (feat/D*)
D'
C C
B B
A A
Multiple Commits with Merge Conflicts
If our branch has multiple commits that need to be temporarily removed and then re-applied during the rebase, we may have to resolve multiple merge conflicts (one each time a commit is re-applied). The secret to rebasing multiple commits is viewing the current commit message during the rebase conflict.
One advantage a merge commit has over performing a rebase is you only have to resolve the merge commit one time (rather than once for each commit).
Update Remote
By default, the git push
command only adds new commits to a remote branch (e.g. on GitHub).
Because we have rewritten history, we need to overwrite the remote branch (rather than just add commits). To do this we add --force-with-lease
to our git push
.
git push --force-with-lease
Leave a Reply