DEV Community

Cover image for Git Rebase and the golden rule explained
Pierre
Pierre

Posted on • Edited on • Originally published at daolf.com

Git Rebase and the golden rule explained

This post is a follow up to this one where we explore the .git directory.

The base of the rebase

This what you might have in mind when you think about what is a rebase in git:

You could say that when you rebase you “unplug” the branch you want to rebase, and “replug” it on the tip of another branch. It is not very far from the truth but it worths digging a little deeper. If you look at the doc this is what is written about the rebase:

“git-rebase: Forward-port local commits to the updated upstream head”— git doc

Not very helpful isn’t it? An approximate translation could be:

git-rebase: Reapply all the commit from your branch to the tip of another branch.

The most important word here is “reapply” because a rebase is not simply a ctrl-x/ctrl-v of a branch to another. A rebase will sequentially take all the commit from the branch you’re in, and reapply them to the destination. This behavior has 2 main implications:

  1. By reapplying commits git creates new ones. Those new commits, even if they bring the same set of change will be treated as completely different and independent by git.

  2. Git rebase reapplies commits, and does not destroy the old ones. It means that even after a rebase, your old commits will still be in the /objects folder in your .git directory. If you are not really familiar with how git consider and stores commit you could learn some interesting things here.

So this could be a more accurate representation of what actually happens during a rebase:

As you can see, the feature branch has completely new commits. As said before, same set of changes, but completely different objects from the git point of view. And you can also see that previous commits are not destroyed. They are simply not directly accessible. If you remember, a branch is only a pointer to a commit. Therefore if neither branches nor tags are pointing to a commit it becomes almost impossible to reach, but the commit still exists.

Let’s now talk about this famous golden rule.

The golden rule of rebase

“No one shall rebase a shared branch” — Everyone about rebase

You have probably came across that rule, maybe phrased differently. For those who haven’t, this rule is quite simple. Never, NEVER, **NEVER, **rebase a shared branch. By shared branch I mean a branch that exists on the distant repository and that other people on your team could pull.

Too often this rule is thrown as a divine truth and I think understanding it could be a good thing if you want to improve your understanding of git.

To do that, let’s imagine a situation where a dev breaks the rule and see what happens.

Let’s say Bob and Anna are both working on the same project. Here is an overview of both Bob’s, Anna’s repos and the remote on GitHub:

Everybody is sync with the remote (GitHub)Everybody is sync with the remote (GitHub)

Now Bob, innocently breaks the golden rule of rebase, in the mean time Anna decides to work on the feature and creates a new commit:

Do you see what’s coming ?Do you see what’s coming ?

Bob tries to push now, he got rejected and receives that kind of message:

[Oh My Zsh](https://github.com/robbyrussell/oh-my-zsh) with agnoster theme for those who caresOh My Zsh with agnoster theme for those who cares

Here git is not happy because it doesn’t know how to merge the Bob feature branch with the GitHub feature branch. Usually when you push your branch on the remote, git merges the branch you’re trying to push with the branch currently on the remote. In fact git tries to fast-forward your branch, we’ll talk more about that in a future post. What you have to remember is that the distant repo can’t, in a simple way, handle the rebased branch Bob is trying to push.

One solution for Bob would be to do a git push — force, basically it tells the remote repository:
“Don’t try to merge or do whatever work between what I push and what you already have. Erase your version of the feature branch, what I push is now the new feature branch”
And this is what we end up with:

Had Anna known what’s coming, she wouldn’t have come to work this morning.Had Anna known what’s coming, she wouldn’t have come to work this morning.

Now Anna wants to push her change:

This is normal, git just told Anna that she does not have a sync version of the feature branch, i.e., her version of the branch and the GitHub version of the branch are different. So naturally, Anna pulls. The same way git tries to merge your local branch with what is in the distant repo when you push, git tries to merge what is in the distant repos with what is in your local branch when you pull.

Before the pull those are the commits in the distant and local feature branch:

A--B--C--D'   origin/feature // GitHub
A--B--D--E    feature        // Anna
Enter fullscreen mode Exit fullscreen mode

When you pull, git has to do a merge to resolve this issue. And this is what happens:

The commit M represent the merge commit. The commit where Anna’s and GitHub’s feature branch were finally reunited. Anna is finally relieved, she managed to resolve all the merge conflicts and can now push her work. Bob decides to pull, and everyone is now synced.

Looking at the mess should be enough to convince you of the validity of the golden rule. You have to keep in mind that you are in front of a mess created only by one person, on a branch shared by only two. Imagine doing that with a team of 10 peoples. One of the numerous reasons people use git is for being able to easily go back in time, and the more messy you history is the more complex it becomes.

You can also notice that there are duplicated commit on the remote, D and D’ have the same set of changes. Basically the number of duplicated commits can be as big as the number of commits inside your rebased branch.

If you’re still not convinced, try to imagine Emma, the third dev. She worked on the feature branch before Bob messed up everything and now wants to push. Note that she pushes after our previous little scenario.

damn it Bob!damn it Bob!

update: As some redditor mentioned it, this post might let you think that rebase can only be used to rebase a branch on the top of another branch. This is not the case, you can rebase on the same branch, but this is another story.

Thank you for reading:

I hope you learn something valuable reading this post and that it will make your use of git easier.

You can read the part 3 here.

If you like JS, I've just published something you might like:

Please tell me in the comments your last Git confusement, don't be shy 🙂 and, if you liked this post, do not forget to subscribe to my newsletter you will also get the first chapter of my next ebook about git for free (coming very soon).

Top comments (9)

Collapse
 
thebouv profile image
Anthony Bouvier

I'm going to admit something: I've never rebased.

I've never seen a reason to.

My team commits early and often, and we push to GitHub constantly. This includes our feature branches. If someone was working on a feature over two days and didn't push at the end of each day, I'd be mad. Shit happens, laptops break, and code would go away. So push your branch to GitHub.

And since it is on a remote like GitHub, and the golden rule as you say is to never rebase a branch that someone else could have pulled down, then this is always the case.

So, we never rebase. We pull master. Create a new branch. Work on it. Push it to GH. When complete, make a PR. Merge into master. Deploy. Repeat.

Collapse
 
superhawk610 profile image
Aaron Ross

I've never seen a reason to.

The biggest reason to rebase is to keep your commit history clean. If you are working on a project with many active contributors, you'll almost certainly need to merge recent changes to master into your feature branch to prevent merge conflicts. This creates a merge commit which does nothing but gum up your commit history on master when you merge it.

feat: foo
fix: bar
merge branch master into branch feature
merge branch feature into branch master

it makes grokking your commit history much easier if you rebase PRs instead of merging since it gets rid of that extra commit and makes your commit history more declarative.

Collapse
 
thebouv profile image
Anthony Bouvier

Except to do that my team has to break another rule -- don't rebase if you pushed your branch remotely because someone else could pick it up.

In fact, since making a PR requires having pushed to GitHub, shouldn't you never rebase if a PR is involved?

Thread Thread
 
superhawk610 profile image
Aaron Ross

As long as all team members are aware ahead of time, I don't think it should be a problem, just make sure to pull in the remote before doing additional work (git pull -r or similar). How often are multiple team members working on the same PR at the same time? If you ever find that's the case it signals to me that your PRs should be more focused.

Thread Thread
 
thebouv profile image
Anthony Bouvier

Rarely honestly.

Currently though the way we do things just doesn't bother me. I don't mind the extra commit of the merge and other than "slightly cleaner history", I don't see a benefit of rebase right now that would make us change.

Every time I read up on it, and I do occasionally to see if there are ways to improve our flow, after all the back and forth opinions on whether rebase is good or bad or neutral or helpful or destructive, I end up back at "Well, we're doing good now and I can't see a real good reason to do rebase yet".

Then I check again when inevitably another one of these articles comes up about it. I'm not vehemently opposed to it, I just don't get an overwhelming sense of it being "right" compared to what we do.

Thread Thread
 
superhawk610 profile image
Aaron Ross

Totally fair. At the end of the day you should favor whatever works best for your team, and I 100% agree, if it's not broken, don't try to fix it.

Collapse
 
coatsnmore profile image
Nick Coats

I have also never used rebase. You are not alone!

Collapse
 
justalever profile image
Andy Leverenz

Nice article. Rebase is actually part of my teams day-to-day. Yes, we force push after a rebase but the commit history is clean, especially if commits are squashed. You also don't see merge commits in the history which is nice. If everyone's on the same page it can work quite nicely combined with CI and slack bots. I'm probably the odd man out here though 😂

Collapse
 
judegibbons profile image
Jude Gibbons

Ditto: we're a small team (3 devs, all in the same location) and often rebase if more than one of us has been working on the same code. With small commits it's easy to sort the occasional conflict, but you have to agree to this method as a team.