Resolving Merge Conflicts with Git

For any developer working with a version control system like Git, at some point in your day-to-day workflow, you are going to run into merge conflicts.

This can be quite frustrating. But easily resolved, so have no fear...

When you are working in teams and each team member has a complete working copy of the repository and are pushing and pulling updates to your workflow branches, chances are you will run into a scenario where you are having to resolve merge conflicts.

Git is a distributed VCS. Each team member has all of the repository history, branch history, log history and merge history. This is how Git tracks the current pointer and the tip of the current branch.

Merging is a basic practice of Git and Feature Branch Workflow. Merging is a way of rebuilding a forked history. Git merge allows the developer to take parts of different branches and put them back together again into a single branch.

The current branch is updated to reflect the changes brought about by the merge, but the merged branch remains completely unaffected. Git merge is often used with Git checkout and Git branch.

Usage

Depending on your repository, Git has two primary ways it uses to accomplish a merge. A fast forward merge and a 3 way merge. Let's take a quick look at both of these methods.

Fast Forward

A fast-forward merge can happen when there is a linera rpath from the current branch to the tip of the target branch. Instead of actually merging the branches, Git integrates the histories by moving the tip of the current pointer to the tip of the current branch. This combines the two histories since all of the commits are reachable form the target branch, which are now also available through the current branch.

Git merge

A fast forward merge is not possible if the branches have diverged. When Git can not determine a linear path to the target branch, Git has to combine them in a 3 way merge. 3 way merges use a dedicated commit to tie together the two divergent histories. Git uses 3 commits to generate the merge commit and the two branch tips to their common ancestor.

Git merge

While it is quite common for developers to use either of these two merge strategies, many of us like to use fast forward merges (i.e. rebasing) for small feature branches or bug fixes.

This essentially reserves 3 way merging for integration of longer running, more complex features or updates. The result is a combination of the joining of the two branches in a symbolic commit in the repository history.

Resolving Conflicts

If you have two branches that you are trying to merge and the same part of the same file contains different content, Git will not be able to figure out which version of the file to use. When this occurs, Git stops the merge before the merge commit and prompts you to fix the conflict manually before committing.

Like everything in Git, the merge process is the same as your traditional workflow. You run similar commands to resolve merge conflicts. When you run into a merge conflict, Git shows you which file the conflict resides and executes a git status to show you exactly where the conflict is occuring.

In our example, both branches modified the same section of our forms.py file.

# On branch development
# Unmerged paths
#(use 'git/add/rm...' as appropriate to mark resolution)
#
# both modified forms.py

When you open the forms.py file, Git has edited the file and marked the sections that are in conflict with each other.

These sections can be identified with ======> and <========.

When you enter the edit mode, Git allows you to fix the changes to you are satisified. Then, once you are ready to finalize the merge, all you have to do is run git add on the conflicted files and inform Git that this merge conflict us resolved. Then run a normal commit to generate the merge commit.

Special note: merge conflicts only occur during a 3 way merge and are not possible in a fast forward merge.

Fast Forward

In the below example, we execute a simple fast-forward merge.

# git co -b new-feature-branch
#
#<edit some files>
# git add .
# git commit -m "New features"
#
# <edit some more files>
# git add .
# git commit -m "Changes to new features"
#
# git checkout development
# git merge new-feature-branch
# git branch -d new-feature-branch

This is the most common of all Git topic branches and is used by most developers. Git will not complain when you delete the new-feature-branch because it is now a part of the development branch.

3-Way Merge

This example shows the 3 way merge strategy.

# git co -b new-feature-branch
# <edit some files>
# git add .
# git commit -m "New features"
#
# <edit some more files>
# git add .
# git commit -m "Change some more features"
#
# git checkout development
# <edit some files>
# git add .
# git commit -m "Changes to the dev branch"
# git merge new-feature-branch
# git branch -d new-feature-branch

A fast-forward merge is not possible now because we have diverged the histories of the development branch.

We have changes in the new-feature-branch and the development branch. Git can not just move the pointer to point to the tip of the new-feature-branch.

Git completes the merge by backtracking the history and applies the commits to the new feature branch, then applying those changes into the development branch.

Craig Derington

Veteran full stack web dev focused on deploying high-performance, responsive, modern web applications using Python, NodeJS, Django, Flask, MongoDB and MySQL.

comments powered by Disqus