Conflict resolution
Objectives
Understand merge conflicts sufficiently well to be able to fix them.
Instructor note
20 min teaching/type-along
20 min exercise
Conflicts in Git and why they are good
Imagine we start with the following text file:
1 tbsp cilantro
2 avocados
1 chili
1 lime
1 tsp salt
1/2 onion
On branch A somebody modifies:
2 tbsp cilantro
2 avocados
1 chili
2 lime
1 tsp salt
1/2 onion
On branch B somebody else modifies:
1/2 tbsp cilantro
2 avocados
1 chili
1 lime
1 tsp salt
1 onion
When we try to merge Git will figure out that we 2 limes and an entire onion but does not know whether to reduce or increase the amount of cilantro:
?????????????????
2 avocados
1 chili
2 lime
1 tsp salt
1 onion
Git is very good at resolving modifications when merging branches and
in most cases a git merge
runs smooth and automatic.
Then a merge commit appears (unless fast-forward; see Optional exercises with branches) without you even noticing.
But sometimes the same portion of the code/text is modified on two branches in two different ways 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:
It is good that Git conflicts exist: Git will not silently overwrite one of two differing modifications.
Conflicts may look scary, but are not that bad after a little bit of practice. Also they are luckily rare.
Don’t be afraid of Git because of conflicts. You may not meet some conflicts using other systems because you simply can’t do the kinds of things you do in Git.
You can take human measures to reduce them.
The human side of conflicts
What does it mean if two people do the same thing in two different ways?
What if you work on the same file but do two different things in the different sections?
What if you do something, don’t tell someone from 6 months, and then try to combine it with other people’s work?
How are conflicts avoided in other work? (Only one person working at once? Declaring what you are doing before you start, if there is any chance someone else might do the same thing, helps.)
Minor conflicts (two people revise spelling) vs semantic (two people rewrite a function to add two different new features). How did Git solve these in branching/merging easily?
Now we can go to show how Git controls when there is actually a conflict.
Preparing a conflict
Instructor note
We do the following together as type-along/demo.
If you got stuck previously or joined later
If you got stuck previously or joined later, you can apply the commands below. But skip this box if you managed to create branches.
$ cd .. # step out of the current directory
$ git clone https://github.com/coderefinery/recipe-before-merge.git
$ cd recipe-before-merge
$ git remote remove origin
$ git graph
Or call a helper to un-stuck it for you.
We will make two branches, make two conflicting changes (both increase and decrease the amount of cilantro), and later we will try to merge them together.
Create two branches from
main
: one calledlike-cilantro
, one calleddislike-cilantro
:$ git branch like-cilantro main $ git branch dislike-cilantro main
On the two branches make different modifications to the amount of the same ingredient:
On the branch
like-cilantro
we have the following change:$ git diff main like-cilantro
diff --git a/ingredients.txt b/ingredients.txt index e83294b..6cacd50 100644 --- a/ingredients.txt +++ b/ingredients.txt @@ -1,4 +1,4 @@ -* 1 tbsp cilantro +* 2 tbsp cilantro * 2 avocados * 1 chili * 1 lime
And on the branch
dislike-cilantro
we have the following change:$ git diff main dislike-cilantro
diff --git a/ingredients.txt b/ingredients.txt index e83294b..6484462 100644 --- a/ingredients.txt +++ b/ingredients.txt @@ -1,4 +1,4 @@ -* 1 tbsp cilantro +* 1/2 tbsp cilantro * 2 avocados * 1 chili * 1 lime
Merging conflicting changes
What do you expect will happen when we try to merge these two branches into main?
Note
In case git switch
does not work, your Git version might be older than from 2019.
On older Git it is git checkout
instead of git switch
.
The first merge will work:
$ git switch main
$ git status
$ git merge like-cilantro
Updating 4e03d4b..3caa632
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
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")
Git won’t decide which to take and we need to decide. 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
=======
* 1/2 tbsp cilantro
>>>>>>> dislike-cilantro
* 2 avocados
* 1 chili
* 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 6cacd50,6484462..0000000
--- a/ingredients.txt
+++ b/ingredients.txt
@@@ -1,4 -1,4 +1,10 @@@
++<<<<<<< HEAD
+* 2 tbsp cilantro
++=======
+ * 1/2 tbsp cilantro
++>>>>>>> dislike-cilantro
* 2 avocados
* 1 chili
* 1 lime
git diff
now only shows the conflicting part, nothing else.
Conflict resolution
<<<<<<< HEAD
* 2 tbsp cilantro
=======
* 1/2 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.
Steps to resolve a conflict
Check status with
git status
andgit diff
.Decide what you keep (the one, the other, or both or something else). Edit the file to do this.
Remove the resolution markers, if not already done.
The file(s) should now look exactly how you want them.
Check status with
git status
andgit diff
.Tell Git that you have resolved the conflict with
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).Verify the result with
git status
.Finally commit the merge with only
git commit
. Everything is pre-filled.
Exercise: Create and resolve a conflict
Conflict-1: Create another conflict and resolve
In this exercise, we repeat almost exactly what we did above with a different ingredient.
Create two branches before making any modifications.
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?
If you create a branch
like-avocados
, commit a change, then from this branch create another banchdislike-avocados
, commit again, and try to merge both branches intomain
you will not see a conflict. Can you explain, why it is different this time?
Solution
4: No conflict in this case if the change is the same.
5: No conflict in this case since in Git history one change happened after the other. The two changes are related and linked by Git history and one is a Git ancestor of the other. Git will assume that since we applied one change after the other, we meant this. There is nothing to resolve.
Optional exercises with conflict resolution
(optional) Conflict-2: Resolve a conflict when rebasing a branch
Create two branches where you anticipate a conflict.
Try to merge them and observe that indeed they conflict.
Abort the merge with
git merge --abort
.What do you expect will happen if you rebase one branch on top of the other? Do you anticipate a conflict? Try it out.
Solution
Yes, this will conflict. If it conflicts during a merge, it will also conflict
during rebase but the conflict resolution looks slightly different:
You still need to look for conflict markers but you tell Git that you resolved
a conflict with git add
and then you continue with git rebase --continue
.
Follow instructions that you get from the Git command line.
(optional) Conflict-3: Resolve a conflict 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
Using “ours” or “theirs” strategy
Sometimes you know that you want to keep “ours” version (version on the branch you are on) or “theirs” (version on the merged branch).
Then you do not have to resolve conflicts manually.
See merge strategies.
Example (merge and in doubt take the changes from current branch):
$ git merge -s recursive -Xours less-avocados
Or (merge and in doubt take the changes from less-avocados branch):
$ git merge -s recursive -Xtheirs less-avocados
Aborting a conflicting merge
Sometimes you get a merge conflict but realize that you can’t solve it without talking to a colleague (who created the other change) first. What to do?
You can abort the merge and postponing conflict resolution by resetting the
repository to HEAD
(last committed state):
$ git merge --abort
The repository looks then exactly as it was before the merge.
Avoiding conflicts
Human measures
Think and plan to which branch you will commit to.
Do not put unrelated changes on the same branch.
Collaboration measures
Open an issue and discuss with collaborators before starting a long-living branch.
Project layout measures
Modifying global data often causes conflicts.
Modular programming reduces this risk.
Technical measures
Share your changes early and often - this is one of the happy, rare circumstances when everyone doing the selfish thing (e.g.
git push
as early as practical) results in best case for everyone!Pull/rebase often to keep up to date with upstream.
Resolve conflicts early.
Discussion
Discuss how Git handles conflicts compared to services like Google Drive.
Keypoints
Conflicts often appear because of not enough communication or not optimal branching strategy.