Exercise notebook: Git merge conflicts

Offered at: Otto-Friedrich-UniversitΓ€t Bamberg License: CC BY 4.0

Edit The notebook builds on our peer-reviewed pedagogical foundations.

We Edit your feedback and suggestions on this notebook!


Transfer Challenge: Git merges occur when two branches are integrated. Merge conflicts require you to resolve competing file changes.


With this notebook, you can practice merging and resolving merge conflicts.

Practice Label Time (min)
1 A simple merge conflict 20
2 Optional: A more realistic merge conflict 30
3 Understand how to prevent merge conflicts 8
4 Wrap-up 2
Β  Overall 40

Edit We are here to help if errors or questions come up!



Important: Make sure to copy the commands and enter them in the shell as shown in the screenshot. It is not possible to run the cells in this notebook.

Git is highly efficient in creating branches and merging them. This is a useful property for software development projects in which programmers implement different features, fix bugs, and refactor the codebase. These changes are typically implemented in separate branches, which are not affected by coding activities in other parts of the codebase (other branches). When developers decide that the work from their branch should be integrated into a target branch (often the main branch), the git merge other_branch_name command starts the merge operation. In this operation, Git uses heuristics to determine whether the changes in both branches are compatible. When developers have changed unrelated parts of the codebase, the branches are merged automatically.

However, if the branches introduce changes to the same parts of the codebase, there is no simple rule to decide which change is kept and which is discarded. In this case, Git creates a merge conflict, indicating that the user has to indicate which changes should be retained.

Part 1: Resolving a simple merge conflict

To create and resolve a merge conflict in a simple hello-world project, run the following commands in the (GitHub Codespace) shell below:

Info The last command will reopen the codespace window and add the new project to the explorer sidebar. You will have to navigate to this notebook again.
cd /workspaces
mkdir project
cd project
git init
echo "print('hello world')" > app.py
git add app.py
git commit -m 'first commit'
code -a /workspaces/project

At this point, the Python script printing a "hello world" statement should be saved in the app.py file and included in the first commit.

We will proceed to create and resolve a merge conflict. Two developers will help us: Linda and Reynold. Each modifies the file to print something different. Let's start with Reynold.

cd /workspaces/project
git branch bugfix
git switch bugfix
echo "print('hello world. good luck with this shity day and all the rain.')" > app.py
git add app.py
git commit -m 'use a more accurate greeting'

Next, let's see Linda's contribution.

git switch main
echo "print('hello world. what a beautiful day')" > app.py
git add app.py
git commit -m 'include a nice greeting'

Given that both have modified the same part of the codebase, we expect a merge conflict.

git merge bugfix

Git should now print the following:

Auto-merging app.py
CONFLICT (add/add): Merge conflict in app.py
Automatic merge failed; fix conflicts and then commit the result.

To analyze and resolve the conflict, open the app.py file (VisualStudio highlights files with merge conflicts in red). It should highlight the conflicting part as follows:

Conflict

Note that there are two areas between the <<<<<, the ===== and the >>>>>>> (the conflict markers).

The first section shows the content on your current branch (selected by HEAD). The second section shows the content from the other branch (in this case, bugfix).

Task: To resolve the conflict, select the code parts that should be retained. Remove the surrounding conflict markers. Once completed, complete the merge.

git add app.py
git commit

To complete the commit, simply close the commit message.

Check the Git history and verify whether the merge commit was created.

πŸŽ‰πŸŽˆ With these commands, you have solved the first merge conflict! πŸŽˆπŸŽ‰

Part 2: Resolving a more realistic merge conflict

For this task, we focus on a project where Lisa and Ted work on a Python module for a command-line application. Lisa works on a separate branch and changes print() statements to logging.info(). In parallel, Ted introduces changes to the COLORS variable on main. Both changes affect the same module and even the same lines of code. Let's see how the conflict unfolds and how we can resolve it.

# Run these commands if you do not have the colrev project.
# git clone https://github.com/CoLRev-Environment/colrev
# As you already know, the following will restart your Codespace
# code -a /workspaces/colrev
# code /workspaces/practice-git/notebooks/git_merge_conflicts.ipynb
cd /workspaces/colrev
# Next, we make sure that we always start from the same commit
git reset --hard d4b15c1783b48c6a8df04fec165d828709d69b9e

# Apply Lisa's changes
git branch logger
git switch logger
git apply /workspaces/practice-git/notebooks/use-logger.patch

Tasks:

  • Analyze the changes using the Git GUI
  • Commit the changes using an informative commit message
# We go back to the main branch and apply another patch
git switch main
# Apply Ted's changes
git apply /workspaces/practice-git/notebooks/replace-color-import.patch

Tasks:

  • Analyze the changes using the Git GUI
  • Commit the changes
  • Merge the logger branch and resolve the conflict, making sure that the changes of Lisa and Ted are retained correctly
git merge logger
# Resolve merge conflict
git status
# Once resolved, create a merge commit as advised in the git status

To validate your solution, run the following command to retrieve our solution:

cp -f /workspaces/practice-git/cli.py colrev/ui_cli/cli.py
git status

Note: for simplicity, we worked in the same repository. But the merge conflict would be identical if the branches were shared in a remote repository. In fact, if GitHub encounters a merge conflict, it suggests that users download the branches and resolve the merge conflict locally (like we just did).

Part 3: Understand how to prevent merge conflicts

Although merge conflicts are not necessarily a bad thing, it is recommended to avoid complex merge conflicts. To achieve this, different strategies can be combined:

  • Understand whether changes are likely to create merge conflicts
  • Communicate with other contributors to understand who is working on which parts of the codebase or read the codebase if there is no personal contact with the other developers
  • Avoid problematic changes, such as code formatting and restructuring that affects the whole codebase. Coordinate development if they are necessary.
  • Avoid long-running branches by merging often
  • Create atomic commits

When learning Git, this is surprising for many: A branch gives you a completely independent version of the codebase, allowing you to test changes and "do your thing". But to use branches in the most effective way possible, you have to anticipate how your changes will integrate with the work of others.

Task: To understand whether commits are likely to create merge conflicts, examine the following commits:

Commit 1

<summary {style='color:green;font-weight:bold'}>Check</summary>

Solution: Over 500 files changed. But mostly in the docs directory and mostly whitespace changes. It would be better to maintain consistent formatting from the start, but this commit is not very likely to create merge conflicts.

Commit 2

<summary {style='color:green;font-weight:bold'}>Check</summary>

Solution: Many changes throughout the codebase. This may raise merge conflicts when attempting to integrate changes from another branch.

Commit 3

<summary {style='color:green;font-weight:bold'}>Check</summary>

Solution: Many files changed, but mostly "dead code". This is unlikely to raise merge conflicts.

Commit 4

<summary {style='color:green;font-weight:bold'}>Check</summary>

Solution: Many changes (+- 6,000 lines) in many central parts of the codebase. This has a very high probability of creating merge conflicts. Before introducing such fundamental changes, it makes sense to complete and merge all parallel development in branches. A maintainer who adds such a substantial change without notifying other contributors would be reckless. If a contributor suggested to merge such a change, it would probably be rejected.

Commit 5

<summary {style='color:green;font-weight:bold'}>Check</summary>

Solution: Single line change in a config file. Very unlikely to create a merge conflict

Commit 6

<summary {style='color:green;font-weight:bold'}>Check</summary>

Solution: Changes in a lock file that was generated by some algorithm. In this case, it could be problematic to resolve merge conflicts manually. It would be necessary to understand what generates the file and check whether it the process can be rerun when merging branches.

Warning: Misleading Assumption!

Working on a separate Git branch can create the illusion that you can radically reorganize the codebase without any consequences. While it's true that changes made in a branch are isolated, remember that these changes will eventually be merged into the main codebase. If you drastically reorganize files or the structure, it could create significant merge conflicts, and disrupt team workflows.

Catch yourself if you're thinking: "I'm on my own branch, so I'll just reorganize the codebase before creating my feature."

Always consider how your changes will affect the project once merged, and coordinate with your team before undertaking major refactoring.


Wrap-up

πŸŽ‰πŸŽˆ You have completed the Git merging notebook - good work! πŸŽˆπŸŽ‰

In this notebook, we have learned to

  • Prevent merge conflicts from happening
  • Resolve merge conflicts once they occur

Remember to delete your codespace here (see instructions).