Bernd H., Senior Software Engineer
Integrating Your Work With git (Part 1) – Merge
When you’ve built fixes or features, you want to integrate them with the main line of development, generally the “master” branch. Git offers multiple ways you can go about that task. Let’s take a look at your options using a little sample repository.
This is, in a way, the “default” way of integrating changes. The command is even called merge!
The process is pretty easy: You start with the branch you want to integrate your changes with already checked out. Then you call
git merge other-branch for each of the other branches you want to integrate. We begin:
Of course, many times there will be conflicts, so the sample makes sure we’ll have conflicts to resolve, too! I just ask git to start my configured merge tool for all files with conflicts:
git tells me that it starts merging with
main.txt. “Local”, in this context, means the branch we are on; “remote” is the branch that we want to merge. After these messages, git starts the
opendiff program for me, or whatever three-way-merge tool you have have configured:
Once we’re done merging, we save the results and quit the merge tool. Git helpfully warns us that the result seems unchanged, and whether we want to restore the state before the tool was run, if the result is unchanged. It will also ask whether we even want to attempt to merge other conflicts in that case.
I just performed a sample merge and git drops me back to a shell prompt. Let’s take a look at git status:
We can see that shows us that the merge has not been concluded, but the conflicts are already resolved. There’s also a
.orig File with the unmerged contents:
This is nice for a final comparison if we’re unsure about our merge, but in a real project we might have a lot of resolved conflicts scattered over a large directory tree. Let’s get rid of this untracked file and do what git says to finish the merge:
git prefills the commit message with a note what we merged, with some helpful comments about what we did, what we’re about to do and the affected files added below:
When we just save & quit, git performs the merge commit and we get a short confirmation message:
After we repeat the process with the other branches to merge, we get the following history structure:
Merging like this is fairly easy for most users, and it has definitive benefits:
- each merge is explicitly marked, it is easy to see what was merged and when it happened
- if conflicts had to be resolved, the required changes have their own easy-to-see commit
These benefits have several drawbacks following from them, however:
- The history gets awfully busy, and quickly – it’s not awful when one person merges two branches, but when a whole team integrates many branches, you quickly end up with a diagram that looks like a major railway station, with many branches fanning out at the start of a sprint and lots of overlapping branches coming together in the last week or so.
- Merge forces you to integrate all the commits of one branch with all the commits of another branch in one go. If you have a bigger project and multiple concerns you had to touch, this can make merges very complicated. You wouldn’t define or implement a big story as one task, so why perform all merge tasks at once?
- Merge commits often provide very little value-added: Is putting calls for one feature in a method below calls for another feature in the same method always its own “thing” that you want to see later?
Sub-case: Octopus Merge
If changes can be merged without any conflict, they can be octopus merged. If we didn’t have any conflicts in our branches, we could e.g. do
git merge feature-1 feature-2 feature-3 quick-fix-1.
This would cut down on the number of merge commits & save some time. Since it’s only applicable when there are no conflicts, it is rarely applicable, alas – something of an edge case.