As soon as you begin working on a software project that exceeds a few lines of code, managing changes rapidly becomes trickier. To counter this, developers use version control systems to track changes to folders and files.
These systems typically work by tracking changes to files as individual “commits” of work, which then allow you to build up a file’s current state by looking at its initial state, then the series of changes (or commits) that have affected it since then.
Which version control system?
Here at import.io we use Git, a distributed and open-source version control system.
The primary reason for this is it has a more robust design than alternatives such as CVS and SVN. Where those repositories maintain a “master” repository on a server somewhere, and all of your developers have a “client” to that repository, Git allows every developer to have their own repository which is then synchronised to other servers as and when required.
This distributed model allows for more flexibility, better backups, and higher availability of code. It can be more difficult to learn due to this added layer of complexity over other options, and can throw up some situations which need manual intervention, one of which I want to talk about here.
What is a merge conflict?
Due to Git’s distributed nature, it can be possible (and happens reasonably frequently) that your repository has a merge conflict. A merge conflict arises when two or more people have made changes to the same file or files in a git repository that Git’s algorithms could not resolve themselves.
This means that Git is unable to figure out what the final version of the file should look like once everyone’s changes have been taken into account. In order to resolve this conflict – i.e. determine what the file should look like – human interaction is required.
What causes a merge conflict?
A merge conflict is caused by edits being made to the same line of a file by two separate branches, when a file deleted in one branch but not the other, or when a file is edited in one branch but not the other. Team environments are often the catalysts of merge conflicts. Luckily, an understanding of merge conflicts and the help of a tool can fix this issue.
Merge conflicts without code
Take for example this git repository, conveniently hosted on Github. To reduce the problem to its essence, I haven’t used code as the example files – instead I have used some English prose. There are two branches – the master branch which is the main branch for this repository (the convention in Git) and a “changes” branch.
The first commit in the repository established a “myfile.txt” file, which contains some placeholder text. After I had created the file, I created the “changes” branch, and made a commit which changed a couple of words in the first sentence.
In a normal Git process – if nobody else had changed the file – merging the “changes” branch into master would be a simple process. This is because the file was changed in one branch and not in the other, so all of the changes can be taken without issue.
However, in this repository, I made two subsequent commits to master. The first of these completely rephrased the first sentence, and the second changed some wording at the end of the passage.
If I were to try and merge the “changes” branch into the master branch now, it would result in a merge conflict. In fact, Github has shown us this automatically when I opened a Pull Request asking to do exactly that:
Github has instead asked us to resolve the merge conflict on the command line – i.e. manually.
Resolving merge conflicts
If you like, you can follow along with this resolution of the merge conflict on your own machine by checking out the Git repository using one of the options that Github provides on the repository – just make sure to avoid the ZIP download, as this will not give you the version history!
Now, using the command line, you can checkout the master branch, and attempt to merge in the “changes” branch. As you can see from this output, you will get a merge conflict when you try to do this:
We now need to use a tool to help us resolve this conflict. It is possible to do it using solely the command line, and many purists will say this is the way to go. However, when it comes to big merges, we tend to find that it is all to easy to make a mistake – and we would rather avoid that!
On Linux, we use the excellent Meld. You can install it on Ubuntu using “sudo apt-get install meld”. There are also unofficial OS X and Windows ports available, although we don’t tend to use those. In the past we have also used kdiff3 on OS X.
Whichever tool you use, ensure it supports a three way merge. Most tools support a two way merge; this is when you are shown the current version on your branch and the version that is currently on the remote branch (in this case, “changes”) in order to compare them.
This approach is all well and good, but Meld and other applications support a three way merge. In addition to the current version on your branch, and the version from the remote (“changes”) branch, it adds the most common ancestor of the two different files in the middle.
To use Meld for your comparison, run the command “git mergetool” after the merge failure message, then type “meld” at the prompt and press enter. This will generate a screen like shown below:
On the left is your local file, i.e. the one from master. On the right, is the last version from the “changes” branch. In the middle is the most common ancestor, which is the one that was added in the first commit in the repository.
The final file we will use is the one in the middle, so our job now is to resolve the merge conflict by putting the middle file into the state that we want.
The last two changes were made in master, and we want to keep them. We can do this by clicking the right-facing arrow next to them. This merges the changes into the middle file:
Our final job is to resolve the conflict on the first line of the file. Now in this example, let’s say I want to use the version from the “changes” branch, on the right hand side. I simply click the left-facing arrow next to that change, and it is merged into the middle file:
Note that at any point it is possible to make free edits to these files. So once I have done these auto-merges, I could change a few of the words if I wished. Finally, save the middle file, and exit Meld. Your final output should look like this:
Now you have finished your merge, you can simply commit and push the changes to your repository.
Do you have a better process or another tool for doing merges? Let us know on Twitter!