Merge or Rebase, that’s the question

Among my previous companies, I see a common pattern about Git. A lot of Git users stick on one command to update their branches: merge or rebase. Sometimes they are using one and change their mind to use the other one because “something weird happened”™. The reality is, we should use both.

The merge command is used to add the content of one branch to another one.

For example, if we want to add the content from my-feature into the master branch, we should merge our feature into master.

The rebase command do the same thing that merge but it also rewrites the history. That means, if we rebase our commits, our Git history will look the same (i.e: same message, same content,..) but will be different (different SHA).

We have to avoid this command when we are working on a shared branch like master, develop, … Otherwise, our commits will not be the same as the commits of our coworkers.

Hopefully, Git does not authorize us to push this kind of things. We will have to force the push

protips: if we have to force push, we are currently doing something really bad.

When rebasing our branch?

Shared branch

I know... I just said we should avoid this command on a shared branch. It’s not totally true.

We should use this command instead of merge when we have some commits in our branch that are not yet pushed and Git rejects our push because another coworker already pushed some code.

For example, we are on the branch my-feature, we did one commit to add tests on this feature and, in the meantime, our teammate did a commit to fix some broken css.

If we try to push, Git will display:

Updates were rejected because the tip of your current branch is behind its remote counterpart. Merge the remote changes.

In this case it means some commits are present on our remote Git repository and we have different commits on our local Git repository.

If we merge the remote in our local, it will work but it will generate a clunky history like the following one:

As we can see the history is difficult to read.

In this case Richard did one commit the 9th at 15:04. Andrew was working on this version too.

The 10th, each developer did a commit. One at 10:01 and the other one at 10:18.

Richard pushed his commit before Andrew.

So when Andrew tried to push, Git rejected his commit. So, Andrew merged his branch with itself.

It did not cause any Git trouble, but it made history harder to read by adding an useless commit (#330a695) and an useless branching.

Instead, in this case, we should rebase we branch. In this case, it’s not dangerous.

The only history we will rewrite is our own history. So we can’t break our whole Git. In this case weshould run git pull --rebase. This command will:

  • generate patches for our commits
  • “remove” our commits
  • apply the remote commits on our branches
  • re-apply our commit patches over the branch.

Because Git needs to re-apply our commits, our SHA will change.

In this case we would have an history like:

Much more readable, right?

And, as you can see, the commits that come from the remote stayed the same.

Andrew’s commit is the only one which changed. The SHA was #a24d2c5 and is now #a24d2c5, because Git re-applied his commit.

At the end of our feature

I don’t know how you work but my way to work is: each time I’ve something working, I commit.

It leads to a lot of commits at the end of my features.

Example:

It’s nice in our development process because we can rollback to a previous step easily.

Eventually, it can also help our coworkers to follow our mind (if possible) when they have to read our Pull Request.

Moreover, even if we are the best developers ever, we will have, sometimes, to change our code after some discussions in a Pull Request.

All these commits are normal in a development process but they shouldn’t appear in our final Git history. They don’t add any useful informations when come the time to search something back in our history.

Once the Pull Request is finished and we are sure nobody will push anything on this branch, we should rewrite our history to make it cleaner, instead of just clicking the “Merge pull request” button on GitHub.

Rewriting our history consists of squashing commits together, removing some commits, rewriting some commit messages, reordering the commits,… For example, the previous example could be rewritten like:

When our commits are rebased, and the history is clean, we can push our changes to the remote.

In this case, we will have the same error message than previously. It’s the only case we should allow yourself to force a push.

Once we finished to rewrite the history, we can merge our branch on our development branch as usual.

When merging our branch?

In all other cases, we should merge. It’s the safest option for our repository.


Originally published at kevin.disneur.me on December 11, 2014.

One clap, two clap, three clap, forty?

By clapping more or less, you can signal to us which stories really stand out.