Merging is a pain in svn. A real chore. So much so that it is a disincentive to branch at all. How many times have you thought, ‘sod it, I’ll just commit it straight to trunk’? We’ve all been there! Merging in git is as effortless as branching is, which means the two go together remarkably well, incentivising you to branch out and try new things.
So how does it work? Well if we consider the previous example we have changes in two separate branches, so let’s imagine that we’ve fixed a load of bugs in the bugfix1 branch and want to merge the changes back into master and release them. You can ask git to merge the bugfix1 branch into the master branch. Git looks back through the history and see that the two branches have a common ancestor (commit 456def), so it has to take all the commits in both branches since then and merge them together. It will calculate what the repo will look like with everything merged together and then creates a new commit to represent the new state, so you end up with this:
The branch pointer for master is moved to point to the new commit. A commit like this that is the result of a merge is called a merge commit. If you recall that when we created the bugfix1 branch we had commit 456def with 2 different descendants. The merge commit is similar in that it has 2 parent commits, and this is essentially the definition of a merge commit; 2 parent commits.
Because branches are just pointers it is now very simple and lightweight to remove the old bugfix1 branch now that it is merged into master, it is just a case of deleting the pointer, no files or directories to remove at all!
The merge commit is the simplest way to conceptualise merging in git but there is actually another way to perform a branch merge under special circumstances that is trickier conceptually but much neater and simpler in implementation. This is called a fast-forward merge. To explain this concept consider a similar example to our previous one except now we have 2 commits on the bugfix2 branch and nothing has been added to master since bugfix2 was branched from it:
Using the same technique as before we would create a new commit that joined bugfix2 and master together and move the master branch pointer to the new commit, like so:
While is it possible to get git to do this it could actually use a fast-forward merge in this case. Git sees that nothing has happened in the master branch during the life of the bugfix2 branch and realises that the history is actually linear. So all git does is move the master branch pointer to the same commit as the bugfix2 pointer (i.e. commit 987tsr), like so:
No merge took place, it just moved a pointer. Obviously this is a much simpler implementation but can be confusing when you see it happen. The basic rule is that if your branch is up to date with master then when you come to merge, it will resolve as a fast-forward. If you find this confusing you can use the
--no-ff flag to the
git merge command to force it to create a merge commit.
The commands to execute merges in git are also much simpler than svn. To merge a branch into master as in the examples above all you need to do is:
git merge bugfix1
Thats it. No need to look through logs to find revision numbers of branch starts and merges, just the name of the branch you want to merge. It is so flexible it is just as easy to merge feature branches into each other using the same syntax. The basic rule is that when you run
git merge it will merge the branch that you specify into the branch that you have checked out. So if you wanted to merge master into bugfix1 you would do this:
git checkout bugfix1 git merge master
If you already had bugfix1 checked out you wouldn’t even need the first line although it helps as a sanity check.
The graph below is typical of a representation of git commits, branches and merges and shows the following things occurring:
- 2 feature branches derived from the same commit on master
- commits in parallel on master, feature1 and feature2
- master merged into feature1 branch
- feature1 and feature2 branches merged together to form a release branch
- release branch merged into master.
It is nice to be able to see these kind of graphs when operating git and fortunately they are readily available. On your machine you can try:
Both installable via your package manager. Or on github there is the excellent ‘Network’ view for each repo.