How to effectively use git rebase --onto?

martinbelev

Martin Belev

Posted on June 20, 2020

How to effectively use git rebase --onto?

This post was originally published on https://belev.dev/how-to-effectively-use-git-rebase-onto

We are going to get a step further and explore the git rebase --onto command. What is it, how to use it to fix Git branches after rebase, how to change a branch's base, and a bit more? We will learn those things by exploring real-world scenarios in which git rebase --onto is really useful.

Let's get started!


Quick git rebase recap

Visiting the docs, we will see the following explanation:

Reapply commits on top of another base tip

The most important bit here is what base means/refers to? This is the previous/parent commit. Let's see an example so that it can become clearer:

a--b--c--d--e  master
       \
        f--g--h  feature

h's previous commit is g. This means that h's base is g.

  1. g's base is f
  2. f's base is c
  3. d's base is c as well. This shows us that one commit can be the base of multiple commits.

The current feature's base is c. By checking out the feature branch and executing git rebase master, we are going to change the feature branch's base to e.

a--b--c--d--e  master
             \
              f'--g'--h'  feature

Mentioned in a previous blog post this is creating new commits and the Git history is changed. With the ' after f, g, h we are showing that those are different commits. They contain the same set of changes though.

When we have only two branches which we want to keep in sync one on top of the other, git rebase is working perfectly. However, this is not enough when we need to make our changes based on the old base. Here git rebase --onto comes in help.

What is git rebase --onto?

We can say that this is a more precise way of changing a parent branch. It accepts three arguments - <new-base>, <old-base>, <end-inclusive> where the third one is optional. This gives us complete control over what and where we want to rebase.

Let's take the previous example and achieve the same result using --onto.

a--b--c--d--e  master
       \
        f--g--h  feature

We want feature to be rebased off of master. So we know what our new base is, it is master. The old base is the base of our first commit in the branch which means it is going to be c. Now we know both the new and old base, and we can run the command: git rebase --onto master c.

This will lead us to:

a--b--c--d--e  master
             \
              f'--g'--h'  feature

The result is the same as using git rebase master. This shows us that they are doing the same thing. git rebase master is just a shorthand syntax for git rebase --onto master <old-base>.

How to rebase with skipping not needed commits?

We have already seen the easiest case of using --onto. Let's take the previous example again but say that we don't want commit f to be present after the rebase because it is not relevant anymore.

a--b--c--d--e  master
       \
        f--g--h  feature

Running git rebase --onto master f gives us:

a--b--c--d--e  master
             \
              g--h  feature

Be careful with the value provided for <old-base>. It is a common mistake to think that it is inclusive.

It is important to remember/understand that we are working with bases, not a certain commit. g's base is f so when we say <old-base> is f this is going to be changed by the provided <new-base> and we are going to loose commit f this way.

How to fix branch from a branch after rebase

Working with multiple people or in a large codebase, it is almost certain that at some point you will need to create a branch from a branch. For example, you need to start a new feature but it depends on another one so you need to start your branch from your previous branch. Or you have automation QAs which are creating a branch from your feature branch.

Let's see a visual representation of the problem:

a--b--c--d--e  master
       \
        f--g--h  feature-1
               \
                i--j--k--l  feature-2

Being in this situation, you would like to move the entire tree to be based on master. Meaning feature-1 is going to be rebased on top of master and feature-2 on top of feature-1.

Let's checkout feature-1 and run git rebase master. This will lead to:

a--b--c--d--e  master
      |      \
      |       f'--g'--h'  feature-1
       \
        f--g--h--i--j--k--l  feature-2

If you have opened PR from feature-2 to feature-1 branch, you are going to see the commits from feature-1 in your PR. There is no need to worry because as we can see from the visual representation above and the image below this is expected.

Rebase feature-1 result

We can now checkout feature-2 and run git rebase --onto h' h. This gives us:

a--b--c--d--e  master
             \
              f'--g'--h'  feature-1
                       \
                        i'--j'--k'--l'  feature-2

Keep in mind that sometimes simple git rebase feature-1 is going to work if f/g/h is the same as f'/g'/h'. Git is smart enough and when we use rebase, it is going to/can remove the commits that are with the same set of changes. However, it is hard and annoying to check if everything is the same in another branch, and using --onto is the better option here because otherwise, it can lead to a branch with commits that don't belong in it.

You should give --onto a try. However, if you are not feeling ready or you don't want to use it and end up in the described situation above, there is a solution with an interactive rebase. Rebase feature-2 from feature-1. Check if there are commits that don't belong in the branch and drop those commits by doing an interactive rebase. If you are not familiar with how to do that, have a look at the previous blog post Some of the most used Git interactive rebase options.

This was a problem that we had for a few months. People were surprised by the changes that they have been seeing in open PRs. After all, you are not doing anything and all of a sudden you see some commits in your PR which don't belong there. It seems strange but hopefully, with this article, this will be cleared out and it won't be strange anymore.

How to change Git branch's base?

From time to time we need to change the base of a branch. Two examples would be:

  • we haven't started our branch from the right branch
  • we started working on something from master but a colleague made some changes in his/her branch that are going to make our life easier. Then maybe we would like to change master as a base and use the colleague's branch changes.

Let's have a visual representation for a better understanding:

a--b--c--d--e  master
             \
              f--g  feature-1
                  \
                   h--i  feature-2

We started our branch feature-2 from feature-1 but we did that by mistake. The features turned out to be not connected at all and we would like our feature-2 branch to start from master.

It is not a surprise that we are going to use --onto. So we have git rebase --onto <new-base> <old-base>. Our old base is feature-1, we started our branch from there. Our new base should be master because we want our branch to start from it. We are going to execute git rebase --onto master feature-1.

This gives us:

a--b--c--d--e  master
            | \
            |  f--g  feature-1
             \
              h--i  feature-2

We are using Git flow at work and changing the base of a branch comes up from time to. In our case, it is mostly when the hotfix branch is not created from the proper branch. For example, the hotfix branch is opened, but we started our branch with a fix from the release branch. Then we would need to change the base from release to hotfix.

How to use the third argument of git rebase --onto?

git rebase --onto takes up a third argument which is used as an end stop inclusive.

Let's have the following example:

a--b--c--d--e  master
       \
        f--g--h--i--j--k--l  feature

We want to rebase feature from master but take only certain commits - from h to j. We have seen how to skip redundant commits by using --onto. If we only want to do that, we would need to execute git rebase --onto master g.

Now we want a specific commit for the last one. The third argument of --onto is exactly what we want. So we would need only to say which is the last commit.

Note: it is taken inclusively.

Run git rebase --onto master g j and this gives us:

a--b--c--d--e  master
       \
        h--i--j  feature

Conclusion

We dived deeper into git rebase --onto. It is one of the more advanced commands with which we can easily solve certain Git problems with our branches. Even if we don't know about it, we can solve our problems with a combination of commands - normal and interactive rebase, cherry-pick, .etc. As developers, we should try to automate our work as most as possible, so using less number of commands is a sufficient condition to learn about --onto.

We covered a lot of things so let's quickly summarize:

  • A quick recap about git rebase, what it base and what does rebase do
  • What does git rebase --onto give us by using it
  • How to use --onto to rebase a branch and skip not needed commits
  • How to fix our branches after rebase when we have a deeper branch structure - branch from a branch from a branch
  • How to change Git branch's base and why would it be useful
  • Use the complete form of git rebase --onto with 3 arguments which allows us to take only certain commits from start to end provided

Thank you for reading this to the end. I hope you enjoyed it and learned something new. If so, please follow me on Twitter where I will share other tips, new articles, and things I learn. If you would like to learn more, have a chat about software development, or give me some feedback, don't be shy and drop me a DM.

💖 💪 🙅 🚩
martinbelev
Martin Belev

Posted on June 20, 2020

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related