Merge conflicts are often a part of life for working on complex development projects. In this guide we'll look at how you can deal with them, and when they will occur.
When do merge conflicts occur?
A merge conflict in Git will occur if you have made changes to the same file on a the branch you are on, and the one you are trying to merge into. In this case, Git won't know which one should be used - so it will throw a merge conflict error.
Consider the scenario where a change has been made to file.md
in the main
branch and my-branch
. When we checkout main
and try to merge my-branch
into main
, we will run into the following error:
git merge my-branch
Auto-merging file.md
CONFLICT (content): Merge conflict in file.md
Automatic merge failed; fix conflicts and then commit the result.
Git doesn't know what to do here, so it's our job to try and resolve it.
Stabilizing the current branch
Sometimes, when we try to merge a file, the merge will fail completely:
error: Your local changes to the following files would be overwritten by merge:
file.md
Please commit your changes or stash them before you merge.
This happens because we have uncommitted changes on our current branch. We need to close all changes on our current branch before we can merge. To do that, you can run git stash
(I covered git stash in more detail here), or you can simply commit your changes on this branch to stabilize it:
git add -A
git commit -m "My commit message"
Now that our branch is stabilised, we can try to run git merge my-branch
(where my-branch
is your branch) to merge our branch back into main
. If we run into merge conflicts now, we need to resolve them.
How to resolve merge conflicts in git via GUI
If you're using a modern code editor like Visual Studio Code, then merge conflicts will appear in the GUI where you can accept incoming changes or accept the current changes on your branch. This provides a pretty easy way to fix your conflicts. In Visual Studio Code, this is represented in conflicting files via the GUI shown below. You have a number of options:
- "Accept Current Changes" - this will use your current branch's content.
- "Accept Incoming Changes" - this will use the content from the branch you are merging in.
- "Accept Both Changes" - this will keep both, one under the other.
- "Compare Changes" - this will bring up an additional screen to compare changes side by side.
Once you review all conflicts, you can then add your files and commit them as normal:
git add -A
git commit -m "My commit message"
How to resolve merge conflicts in git via Terminal
In some cases, you may not have a GUI to resolve merge conflicts. There are a number of commands you can use.
First, git status
will tell you the files affected:
git status
On branch my-branch
You have unmerged paths.
(fix conflicts and run "git commit")
(use "git merge --abort" to abort the merge)
Unmerged paths:
(use "git add <file>..." to mark resolution)
both modified: file.md
no changes added to commit (use "git add" and/or "git commit -a")
As we can see, only file.md
is affected. Next, we can use git diff
to see the difference between this branch and the one we are merging into:
++<<<<<<< HEAD
+Some a
+Some b
+Some conflict c
+Some conflict d
+Some e
++=======
+ Some conflict 1
+ Some b
+ Some conflict c
+ Some conflict 2
-Some f
++Some f
++>>>>>>> main
Here, everything after ++<<<<<<<
and before ++=======
represents the current branch changes, while anything after ++=======
and before ++>>>>>>> main
represents the branch you are merging in.
We can also see the conflicting commits by running git log --merge
Aborting your merge during a merge conflict
Sometimes you realise you're in a bit too deep on this merge conflict - in which case you can undo it by running git merge --abort
.
git merge --abort
This will bring us back to a non-merge state so we can resolve merge conflicts manually in both branches, before trying to merge again.
Resolving by accepting incoming or current changes
Another easy way to resolve merge conflicts on certain files is to use the --theirs
and --ours
option on git checkout
. Our merge conflict was on file.md
. If I want to accept all incoming changes for file.md
from the branch I am trying to merge, I could write:
git checkout --theirs file.md
This checks out the file.md
from "their" version of code. If I wanted to use the version in my current branch, I'd use --ours
. Both --ours
and --theirs
expects a file as an argument - so this has to be done on a file by file basis.
Easy, right? That's fine for merging in entire files, but what if we have multiple merge conflicts within a file?
Resolving by editing the file itself
Remember, when you create a git merge conflict, we ended up with content that looked like this when we ran git diff
?
++<<<<<<< HEAD
+Some a
+Some b
+Some conflict c
+Some conflict d
+Some e
++=======
+ Some conflict 1
+ Some b
+ Some conflict c
+ Some conflict 2
-Some f
++Some f
++>>>>>>> main
That content also exists in your file where the conflict happens (without the +
and -
, of course). The easiest way to resolve a merge conflict when you want to do it without any help from a GUI is to go to the file in question, and simply remove the section you don't want to accept. That just means deleting the text between <<<<<<< HEAD
and =======
if you want to accept current, or the text between =======
and >>>>>>>
if you want to accept incoming.
Adding and commiting your changes
Once you've resolved your merge conflicts, don't forget to add and commit your changes on the current branch, so they remain saved:
git add -A
git commit -m "Merge message"
Conclusion
We have powerful tools in our code editors to resolve merge conflicts. Having said that, sometimes limitations in environments mean we have to resolve our own git merge errors - or we may just prefer doing this work from terminal. Some specific edge cases may also mean we have to do this work from terminal.
In any case, merge conflicts are a part of development with git, and they can be resolved simply by removing the content we don't want anymore from the files where the conflicts exist. We can also use git checkout --theirs
or git checkout --ours
to resolve issues more quickly.