DESCRIPTION
Merge conflicts can happen during a git merge, git rebase, git
cherry-pick, git pull, git revert, or any other command that does
a merge. All of those commands use the same merge algorithm, and the
process for resolving a merge conflict is always very similar.
There are 3 ways to handle a merge conflict:
-
Resolve the conflict. (see HOW TO RESOLVE A MERGE CONFLICT below for details)
-
Stop the operation and return your branch to its original state with the appropriate
--abortcommand, for examplegitmerge--abortorgitrebase--abort. See EXAMPLE: GIT STATUS OUTPUT below for how to find the command to run. -
Or (if you’re in a multi-step operation like a rebase) skip the problematic commit with the appropriate
--skipcommand.
WHAT IS A MERGE CONFLICT?
When Git merges two commits together, it looks at the changes that
each side has made and combines those changes. For example, if one side
edited lines 1-5 of hello.py and the other side edited lines 20-25 of
the same file, then it can easily combine them since there’s no overlap.
But if both sides edited overlapping lines of the same file (for example one side edited lines 1-5 and the other edited lines 3-6), Git will not try to guess how to combine those changes. This is called a "merge conflict".
When this happens, Git shows you both sides' edits and asks you to pick how to resolve them. It:
-
Stages all of the files which were successfully merged
-
For the files with conflicts, it marks them as conflicted, puts both sides' edits in the file, and leaves merge conflict markers that you need to resolve.
|
Note
|
Even if Git successfully merges both sides with no conflicts, it’s possible to end up with code that doesn’t work. For example, this can happen if one side renames a function, and the other side adds a call to the old function. This is called a "semantic merge conflict". |
MERGE CONFLICT MARKERS
When there’s a merge conflict, Git will update the conflicted file
to include merge conflict markers <<<<<<<, =======, and >>>>>>>.
For example, here’s a merge conflict where both sides edited a list of
fruits in different ways — one side has "cherry" and the other side
has "banana":
FRUITS = [
"apple",
<<<<<<< HEAD
"cherry",
=======
"banana",
>>>>>>> add-fruit
"mango",
"orange",
]
The lines from one side of the merge conflict is between <<<<<<< and
=======, and the lines from the other side is between ======= and
>>>>>>>. See "OURS" AND "THEIRS" below for a full explanation
of which side is which.
HOW TO RESOLVE A MERGE CONFLICT
The process for resolving a merge conflict is:
-
Run
gitstatusto get a list of files with merge conflicts. -
For each one, find the conflict markers (the <<<<<<<,
=======, >>>>>>>) and edit the file to fix the conflict. -
Run
gitaddFILENAMEfor each file to mark the conflict as resolved. -
Run the appropriate
--continuecommand to continue the operation that was interrupted by the conflict, for examplegitmerge--continueorgitrebase--continue. See EXAMPLE: GIT STATUS OUTPUT below for how to find the command to run.Note: During a
gitmerge,gitcommitandgitmerge--continuedo the same thing.
EXAMPLE OF RESOLVING A MERGE CONFLICT
If you see this in your code during a merge conflict:
FRUITS = [
"apple",
<<<<<<< HEAD
"cherry",
=======
"banana",
>>>>>>> add-fruit
"mango",
"orange",
]
Then you might edit that part of the code like this, which includes the fruits from both sides of the conflict:
FRUITS = [
"apple",
"banana",
"cherry",
"mango",
"orange",
]
TOOLS FOR HANDLING MERGE CONFLICTS
Here are some ways to get extra context while handling a merge conflict:
-
There are many graphical "merge tools" for Git, which will normally show you the different versions of the code side by side. If you have a mergetool configured,
gitmergetoolwill launch it. See alsomerge.toolin git-config(1) for a list of the mergetools Git supports. -
You can set the configuration variable
merge.conflictstyle=diff3. See DIFF3 below for more. -
gitlog--merge--patch<filename> will list all commits which caused the merge conflict for <filename>, and the diff of how they changed the file. -
Look at the original files.
gitshow:1:filenameshows the common ancestor,gitshow:2:filenameshows the "ours" version, andgitshow:3:filenameshows the "theirs" version. See "OURS" AND "THEIRS" below for more on "ours" and "theirs".
Here are some ways to track your progress while handling a conflict:
-
Use
gitstatusto get a list of files with conflicts -
Use
gitdiff--checkto make sure you haven’t left any merge conflict markers in a file by accident. It will print "leftover conflict marker" if it finds any. -
Use
gitdiffAUTO_MERGEto show what changes you’ve made so far to resolve the conflicts. See EXAMPLE OF USINGAUTO_MERGEbelow for more.
EXAMPLE: GIT STATUS OUTPUT
When you’re in a merge conflict, you can find out what commands to run
to handle the conflict by running git status.
For example, this git status output tells you that:
-
gitrebase--abortwill safely bring your branch back to its original state -
you should run
gitrebase--continuewhen you’re done resolving all the conflicts -
the conflicts in
fruits.pyhaven’t been marked as resolved
$ git status
You are currently rebasing branch 'main' on '58a9fcc'.
(fix conflicts and then run "git rebase --continue")
(use "git rebase --skip" to skip this patch)
(use "git rebase --abort" to check out the original branch)
Unmerged paths:
(use "git restore --staged <file>..." to unstage)
(use "git add <file>..." to mark resolution)
both modified: fruits.py
DIFF3
By default, Git doesn’t include the original code when formatting
a merge conflict. To include the original code, you can set the
configuration option merge.conflictstyle to diff3.
This extra context can make it much easier to understand what’s
happening in a merge conflict.
Here’s an example of what a merge conflict would look like when using
diff3. There’s an extra section (between ||||||| and =======)
with the original code.
FRUITS = [
"apple",
<<<<<<< HEAD
"cherry",
"mango",
||||||| 1c22e48
"banana",
"cherry",
"mangoooo",
=======
"banana",
"mango",
>>>>>>> add-fruit
"orange",
]
With this view, you can see that both sides fixed a spelling
mistake in "mango", and each removed one fruit from the list.
This makes it more clear how to resolve the merge conflict: since one
side removed banana and one side removed cherry, it would probably
make sense to remove both from the list, like this:
FRUITS = [
"apple",
"mango",
"orange",
]
|
Note
|
zdiff3 is an alternative to diff3 that makes the merge conflict
shorter by making it more ambiguous. See git-config(1) for more.
|
"OURS" AND "THEIRS"
Sometimes during a merge conflict, Git will use the terms "ours" and
"theirs" (or "us" and "them"). For example, git status might say that
a file was deleted by us.
"Ours" and "theirs" are both commits: "ours" is the current
HEAD commit, and "theirs" is the other side being merged.
The first part of a merge conflict (between <<<<<<< and =======) is
from the "ours" side, and the second part (between ======= and
>>>>>>>) is from the "theirs" side.
FRUITS = [
"apple",
<<<<<<< HEAD
"cherry", <- ours
=======
"banana", <- theirs
>>>>>>> add-fruit
"mango",
"orange",
]
During a rebase, it can seem "upside down" because the "ours" commit is
from the branch you’re rebasing on (for instance main in git rebase
main).
These terms in Git all mean the same thing when dealing with a merge conflict:
-
"common ancestor" and "base". The files from this commit are "in stage 1".
-
"ours", "us", and
HEAD. The files from this commit are "in stage 2". -
"theirs", "them". The files from this commit are "in stage 3".
If you’re confused about what something like "deleted by us" means, it’s often easiest to use some of the tools from TOOLS FOR HANDLING MERGE CONFLICTS above to get more context. Finding the commit that deleted the file and seeing why is usually more helpful than trying to abstractly reason through what "us" means.
EXAMPLE OF USING AUTO_MERGE
git diff AUTO_MERGE will show what changes you’ve made so far to
resolve conflicts. AUTO_MERGE is a reference that Git creates during a
merge. It contains the result of running the merge algorithm.
For example, if we resolved the conflict by adding both "banana" and "cherry" in order, the diff would look like this:
FRUITS = [
"apple",
-<<<<<<< HEAD
- "cherry",
-=======
"banana",
->>>>>>> add-fruit
+ "cherry",
"mango",
"orange",
]
|
Note
|
AUTO_MERGE is only set if you’re using the default Git merge algorithm.
|
GIT
Part of the git(1) suite