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 called like-cilantro, one called dislike-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 and git 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 and git 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.

  1. Create two branches before making any modifications.

  2. Again modify some ingredient on both branches.

  3. Merge one, merge the other and observe a conflict, resolve the conflict and commit the merge.

  4. What happens if you apply the same modification on both branches?

  5. If you create a branch like-avocados, commit a change, then from this branch create another banch dislike-avocados, commit again, and try to merge both branches into main you will not see a conflict. Can you explain, why it is different this time?

Optional exercises with conflict resolution

(optional) Conflict-2: Resolve a conflict when rebasing a branch

  1. Create two branches where you anticipate a conflict.

  2. Try to merge them and observe that indeed they conflict.

  3. Abort the merge with git merge --abort.

  4. What do you expect will happen if you rebase one branch on top of the other? Do you anticipate a conflict? Try it out.

(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
    
    Conflict resolution using 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 using git 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.