Overview
Teaching: 15 min
Exercises: 15 minQuestionsObjectives
- How can we resolve conflicts?
- How can we avoid conflicts?
- Understand merge conflicts sufficiently well to be able to fix them.
In most cases a git merge
runs smooth and automatic.
Then a merge commit appears (unless fast-forward) without you even noticing.
Git is very good at resolving modifications when merging branches.
But sometimes the same line or portion of the code/text is modified on two branches and Git issues a conflict. Then you need to tell Git which version to keep (resolve it).
There are several ways to do that as we will see.
Please remember:
We will make two branches, make two conflicting changes (both increase and decrease the amount of cilantro), and then try to merge them together. Git won’t decide which to take for you, so will present it to you for deciding. We do that and commit again to resolve the conflict.
master
: one called like-cilantro
, one called dislike-cilantro
:$ git graph
* 4b3e3cc (HEAD -> master, like-cilantro, dislike-cilantro) Merge branch 'less-salt'
|\
| * bf59be6 reduce amount of salt
* | 80351a9 Merge branch 'experiment'
|\ \
| * | 6feb49d maybe little bit less cilantro
| * | 7cf6d8c let us try with some cilantro
| |/
* | 40fbb90 draft a readme
|/
* dd4472c we should not forget to enjoy
* 2bb9bb4 add half an onion
* 2d79e7e adding ingredients and instructions
$ git graph
* eee4b85 (dislike-cilantro) reduce cilantro to 0.5
| * 55d1ce2 (like-cilantro) please more cilantro
|/
* 4b3e3cc (HEAD -> master) Merge branch 'less-salt'
|\
| * bf59be6 reduce amount of salt
* | 80351a9 Merge branch 'experiment'
|\ \
| * | 6feb49d maybe little bit less cilantro
| * | 7cf6d8c let us try with some cilantro
| |/
* | 40fbb90 draft a readme
|/
* dd4472c we should not forget to enjoy
* 2bb9bb4 add half an onion
* 2d79e7e adding ingredients and instructions
On the branch like-cilantro
we have the following change:
$ git diff master like-cilantro
diff --git a/ingredients.txt b/ingredients.txt
index a83af39..83f2f94 100644
--- a/ingredients.txt
+++ b/ingredients.txt
@@ -1,4 +1,4 @@
-* 1 tbsp cilantro
+* 2 tbsp cilantro
* 2 avocados
* 1 lime
* 1 tsp salt
And on the branch dislike-cilantro
we have the following change:
$ git diff master dislike-cilantro
diff --git a/ingredients.txt b/ingredients.txt
index a83af39..2f60e23 100644
--- a/ingredients.txt
+++ b/ingredients.txt
@@ -1,4 +1,4 @@
-* 1 tbsp cilantro
+* 0.5 tbsp cilantro
* 2 avocados
* 1 lime
* 1 tsp salt
The first merge will work:
$ git checkout master
$ git status
$ git merge like-cilantro
Updating 4b3e3cc..55d1ce2
Fast-forward
ingredients.txt | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
But the second will fail:
$ git merge dislike-cilantro
Auto-merging ingredients.txt
CONFLICT (content): Merge conflict in ingredients.txt
Automatic merge failed; fix conflicts and then commit the result.
Without conflict Git would have automatically created a merge commit, but since there is a conflict, Git did not commit:
$ git status
On branch master
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: ingredients.txt
no changes added to commit (use "git add" and/or "git commit -a")
Observe how Git gives us clear instructions on how to move forward.
Let us inspect the conflicting file:
$ cat ingredients.txt
<<<<<<< HEAD
* 2 tbsp cilantro
=======
* 0.5 tbsp cilantro
>>>>>>> dislike-cilantro
* 2 avocados
* 1 lime
* 1 tsp salt
* 1/2 onion
Git inserted resolution markers (the <<<<<<<
, >>>>>>>
, and =======
).
Try also git diff
:
$ git diff
diff --cc ingredients.txt
index 83f2f94,2f60e23..0000000
--- a/ingredients.txt
+++ b/ingredients.txt
@@@ -1,4 -1,4 +1,8 @@@
++<<<<<<< HEAD
+* 2 tbsp cilantro
++=======
+ * 0.5 tbsp cilantro
++>>>>>>> dislike-cilantro
* 2 avocados
* 1 lime
* 1 tsp salt
git diff
now only shows the conflicting part, nothing else.
We have to resolve the conflict. We will discuss 3 different ways to do this.
<<<<<<< HEAD
* 2 tbsp cilantro
=======
* 0.5 tbsp cilantro
>>>>>>> dislike-cilantro
We have to edit the code/text between the resolution markers. You only have to care about what git shows you: Git stages all files without conflicts and leaves the files with conflicts unstaged.
Simple steps:
git status
and git diff
.git status
and git diff
.git add ingredients.txt
(if you use the Emacs editor with a certain plugin the editor may stage the
change for you after you have removed the conflict markers).git status
.git commit
- everything is pre-filled.Exercise: Create another conflict and resolve
- After you have merged
like-cilantro
anddislike-cilantro
create again two branches.- Again modify some ingredient on both branches.
- Merge one, merge the other and observe a conflict, resolve the conflict and commit the merge.
- What happens if you apply the same modification on both branches?
(Optional) Exercise: Conflicts and rebase
- Create two branches where you anticipate a conflict.
- Try to merge them and observe that indeed they conflict.
- Abort the merge.
- What you if you rebase one branch on top of the other? Do you anticipate a conflict? Try it out.
(Optional) Resolution using mergetool
- Again create a conflict (for instance disagree on the number of avocados).
- Stop at this stage:
Auto-merging ingredients.txt CONFLICT (content): Merge conflict in ingredients.txt Automatic merge failed; fix conflicts and then commit the result.
- Instead of resolving the conflict manually, use a visual tool (requires installing one of the visual diff tools):
$ git mergetool
- Your current branch is left, the branch you merge is right, result is in the middle.
- After you are done, close and commit,
git add
is not needed when usinggit mergetool
.If you have not instructed Git to avoid creating backups when using mergetool, then to be on the safe side there will be additional temporary files created. To remove those you can do a git clean after the merging.
To view what will be removed:
$ git clean -n
To remove:
$ git clean -f
To configure Git to avoid creating backups at all:
$ git config --global mergetool.keepBackup false
Example:
$ git merge -s recursive -Xours less-avocados # merge and in doubt take the changes from current branch
Or:
$ git merge -s recursive -Xtheirs less-avocados # merge and in doubt take the changes from less-avocados branch
What to do?
HEAD
(last committed state).$ git merge --abort
The repository looks then exactly as it was before the merge.
Discuss how Git handles conflicts compared to the Google Drive.
Key Points
Conflicts often appear because of not enough communication or not optimal branching strategy.