published on Ramiel's Creations
Sunday, February 5, 2023
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.
When I work on a repository I'd like the history to look in a certain way:
This is an example
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.
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.
As you can see from the image, while you're working on it, the main branch may have advanced. This is important to consider
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.
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.
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.
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
interactive. Here it's where the magic happens
68a2ca7cis 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
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
squash (or the abbreviations
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
If you now close the editor, the history will look like this
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.
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
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
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!