Posts Tagged “git”

Git Workflow: rebase and merge

I am a fan of a clean git history. It should be easy to spot which commits belong to which feature, which feature was merged when, and what changes were made by whom. I’ve seen several git histories that look more like a pile of spaghetti and it takes a lot of effort to understand the history. I will explain how you can keep things neat and clean by using rebase and how to avoid the pitfalls I ran into.

Git History with Pull Merges
Git History with Pull Merges

If your git history looks like this and this bothers you this article is for you. With a better understanding of git and it’s features you can make your history look like this.

Git History with Rebase
Git History with Rebase

If you want to know how read more!

Understand merging with --ff or --no-ff

In git there are two strategies to merge branches. Fast-Forward merges and Non-Fast-Forward merges. Let’s take a look at those two strategies, when you can use which strategy and how the result differs.

Fast-forward merges are possible if you have a situation like this:

          D---E---F topic
         /
A---B---C master

The topic branch is ahead of master. But the master branch does not have any additional commits.

If you merge topic with --ff to master

git checkout master
git merge --ff topic

…you will have the following result:

A---B---C---D---E---F topic/master

Now master and topic are identical. If you delete the topic branch nobody can tell anymore that there was a topic branch once.

Let’s take a look what the result looks like if you merge with the --no-ff strategy:

git checkout master
git merge --no-ff topic

Git will create an extra commit for the merge itself:

          D---E---F topic
         /         \
A---B---C---------- G master

This way it is transparent for everyone what commits belonged to the feature branch and what was merged. It also makes it easier to un-merge the branch. Because you only need to revert the merge commit and you are done.

So my recommendation is if you merge towards the main line use --no-ff. This means merges from topic to develop or develop to master should always be done with --no-ff.

Merges away from the main line could be done using --ff but continue reading to learn about an even cleaner approach.

Understand rebasing

With rebase you can move commits. Strictly speaking you do not move the commits, but you reapply the commits somewhere else. New commits (with new commit ids) are created, your old commits are removed. This can be used to “catch up” on the changes of master or any other branch.

Let’s assume the following situation:

      A---B---C topic
     /
D---E---F---G master

Now you want to update your topic branch to include the latest changes from master. You could merge masterto topic but this would create a useless merge commit (--no-ff is not possible because both branches have additional commits). A cleaner solution is to rebase your topic branch onto the current master.

git checkout topic
git rebase master

The result will be:

              A'--B'--C' topic
             /
D---E---F---G master

Your old commits A, B and C have been reapplied on top of master. They have new commit ids and your topic branch contains now the latest changes from master. If your commits don’t apply cleanly on master you have to resolve your conflicts while rebasing. Afterwards you can merge your rebased topic branch conflict free with --no-ff to master. Your merge will cause no conflicts and you don’t have to fix several errors in a big, complicated, bloated merge commit. This will make the history cleaner and better to read.

My second recommendation is to rebase your topic branches regularly to stay up to date and solve conflicts as soon as possible. The fresher the changes are the more likely it is that you still remember why you introduced a change and this will make resolving rebase conflicts easier.

Working with a shared repository

When you work with a shared repository and you start to rebase branches you need to communicate some extra knowledge to your team. Otherwise you clutter you git history with a lot of useless merge commits and it will blow up in your face. That’s basically the reason why people tell you “don’t rebase pushed branches”. Let’s narrow this principle down to the first rule: “don’t rebase your pushed main branches”. It depends on your branching model what your main branches are called but commonly these are master and develop.

By default, when pulling from a remote branch and both, your local and remote branch have additional commits, git performs a --no-ff- merge. This creates an additional merge commit as you learned earlier. To avoid this you can use rebasing while pulling:

git checkout topic
git pull --rebase origin topic

Remembering to add --rebase for every pull is annoying so you can set the default strategy for the topic branch to rebase:

git config branch.topic.rebase true

You can even tell git to set this automatically for every new branch you create:

git config --global branch.autosetuprebase = always

After rebasing your branch locally you need to push it back to the remote repository. By default git will reject the push as “diverged”. But you can force the push with --force-with-lease1:

git push --force-with-lease origin topic

This is a potentially destructive operation because it will overwrite the remote branch and you will loose any changes not included in your local copy of the branch. So always be careful which branch you push.

Common Pitfalls

Make sure you do not rebase your main branches. If you do this it will almost certainly cause unwanted results. Like inlined merges (like with --ff). So make sure rebase is disabled for your main branches:

git config branch.master.rebase false
git config branch.develop.rebase false

If you rebase on master (or any other main branch) make sure your master is up to date. So usually you run the following command sequence:

git checkout master
git pull origin master
git checkout topic
git rebase master

TL;DR

  1. Merge topic branches with git merge --no-ff topic towards the mainline.
  2. Use git rebase master topic to catch up with the latest changes from master.
  3. Update your topic branch from remote with git pull --rebase origin topic.
  4. Push your topic branches with git push --force-with-lease origin topic.

Read the TL;DR version of this post at http://stevenharman.net/git-pull-with-automatic-rebase

Further reading

If you want to dive deeper into git here are some recommendations:

Provide Feedback!

This workflow is what works well for me and my teams and after some initial explaining is well accepted. What is your workflow? How do you deal with feature branches and master diverging? Do you care about the transparency of your git history?

  1. Learn more about --force-with-lease, how it works, and why you should use it at https://developer.atlassian.com/blog/2015/04/force-with-lease/ 

Read More