A power on lightbulbA power off lightbulb

About another useless war: git rebase VS merge

published on Ramiel's Creations



Sunday, February 5, 2023

About another useless war: git rebase VS merge

Recently a new (quite useless) debate arose on Twitter. Our fellows developer have been called to decide what they prefer when using git: merge or rebase.

As usual, people are divided between supporters of one or the other, saying bad things about the opposite. Actually, git rebase looks like a misunderstood tool and many people stated that their git repository is usually damaged when they use git rebase. What surprised me a lot is that also very seasoned developers and conference rock stars said that they cannot use git rebase. This confirms one of my greatest biases in our community: people don't know git. Not their fault probably, but this is it. The reason why they usually have no problem with git merge is that usually you can use it without having a clear idea of what's happening and a very complicated mess, easily obtainable with git merge, still results in a working repository.

In this post, I don't want to explain the difference between the two or how git rebase works, for that you have plenty of tutorials and even the official git guide is great. What I'm going to tell you, instead, is the flow I use when working with git. Since it mainly involves git rebase, I hope at the end you'll have a clearer idea of its mechanism.

The goal

When I work on a repository I'd like the history to look in a certain way:

  • Each commit should represent a single feature
  • When a feature branch is closed, it should end up in the main branch as a single commit
  • My history should look like a straight line
  • All the above regardless of how many people work on the project

This is an example

An example of a clean git repository

I know, this above shows the commits of a single person but trust me, it's the same for a repository with tens of developers.

Feature branches

Whenever a new feature has to be developed, we create a new feature branch. This doesn't change if you use git merge or rebase. Eventually, the branch is going to be populated with several commits.

A feature branch with two commits

As you can see from the image, while you're working on it, the main branch may have advanced. This is important to consider

Rebase to integrate recent updates

The first usage for git rebase, is to keep your feature branch up to date with the changes in the main branch. Sometimes you want to see how your feature behaves if it includes all the other changes your team introduced and, also, you don't want to lose all the commits in your branch. The "easiest" solution here is to run git merge main. This is easy, but it's dirty! What you'd prefer is to rewrite your history like you're answering this important question: "How would my work, my feature branch, look like if I'd start to work on it today". In git words, can I rewrite the history like if my feature branch is based on the current main and not on the old main, the one available when started?

To answer this question, you can run git rebase main while in your branch. You may be called to solve several conflicts, after all, the main branch may have touched files you have touched too.

Rebase your feature branch

This is the image many people propose to you when they want to explain git rebase.

To avoid too many conflicts, I can only suggest avoiding long-living branches. Though, there's another way to avoid conflicts and we'll talk about it in a while.

Completing your feature branch

When your feature is over, you want to integrate it into the main branch. At this point what I do is rewrite the history as if my entire branch is composed of only one commit. This is where rebase shines. One of the underestimated powers of rebase is the ability to completely rewrite the history. So, what we have to do is to rewrite history to collapse all our commits into one only. Let's consider this branch.

A branch to rewrite

First of all, we want to collapse the history into one commit. The command we want to use is

git rebase -i 68a2ca7c

Let's see what this means

  • -i mean interactive. Here it's where the magic happens
  • 68a2ca7c is the commit, in the main branch, on which our feature branch is based

If you run this command you'll get an interactive text editor

Interactive rewrite

As you can see this is your branch history. You can change this text to update all of your commits. On top, is the most recent commit, and on the bottom is the oldest. You can collapse one or more commits into another by changing pick with fixup or squash (or the abbreviations f and s). The difference is that with fixup the commit messages are lost, and with squash are kept. I usually use squash and this is the result

All squashed

If you now close the editor, the history will look like this

After rewriting history

Ok, our work is now collapsed in one commit only, great. This will come in handy if we want to revert because it's definitely easier to revert one commit only instead of several.

Now, depending on our case, we may already or not be on top of the main branch. If you're, that's fine. If you're not, like in the example above, we need to move this branch on top of the main branch. You guessed it, we need git rebase!

git rebase main

This is the point where you may have to solve conflicts. Since we collapsed all commits into one, solving conflicts should be relatively easy. Solving conflicts is harder if you have several commits because a conflict may appear at commit 3/10 and the conflict may have been solved already in commits from 4 to 10! We can't ever face this situation because we only have one commit.

After solving your conflict, if any, this is the result.

After rewriting history

Let's close this branch!

Now it's time to close the branch, which means we want this work to be on our main branch. You have two options after you move to your main branch with git checkout main

  • git merge feature-branch
  • git rebase feature-branch

As you can see you can use one of the two. I prefer to use git rebase because like this the history will look like a straight line

After rewriting history

Conclusions

This is just one way of working on your repository and it's not necessarily the better one. I hope though you can see the power of git rebase. Some people prefer to have what's called a "merge commit" and this is totally fine. Even in that case rebase can be a useful tool to move your work on top of another branch.

So, there's not really a war between rebase and merge: they're two different tools, anything else is just a useless debate

Bonus

An interesting topic, when talking about git rebase, is what happens if we rewrite the history of a shared branch. As you may know if you rewrite the history of a branch and the branch is in use by your teammates, an ancient curse will hit you and your red fish. As we saw before though, sometimes we want to rebase our branch on top of our updated main branch.
In this case I suggest to push the rebased branch using

git push --force-with-lease

It's very similar to --force with the difference that, if anybody else pushed to the remote any new commit, the push is rejected. I personally have it aliased as

please = push --force-with-lease

If the push succeed, your teammates have to run git pull in the branch. In any case it's better to coordinate and be very clear about rebasing a common branch, it's also a way to let everybody working with you know about what0s happening!