loading...
Cover image for Regain control of branches with git rebase --onto
Headway

Regain control of branches with git rebase --onto

drews256 profile image Andrew Stuntz Updated on ・5 min read

This article originally appeared on Headway's blog

Rebasing branches with git is a process that all developers work through. It can be challenging at times to get all your commits exactly where you're hoping that they will end up. While git isn't perfect, git rebase --onto can help tame your branches when you have to work with non standard rebasing. git rebase --onto let's us be a little more choosy about where and which commits we want to more easily handle complicated rebases.

How a traditional rebase works

Traditional rebases (those without the --onto flag) are pretty easy to understand. Commits get applied to the head of the branch you originally created your working brach off of this works for the majority of cases but sometimes the head of your base branch is faulty, or your doing some testing and want to move your feature branch one commit at a time.

Most of the following examples are pretty contrived, but I think git rebase --onto is applicable to a number of situations. Testing large feature branches, or moving a feature branch from one base to another base. Reasons to use git rebase --onto include fixing mistakes, or moving a feature off of a feature branch and onto a base branch of some kind. git rebase --onto can be your friend when you need a scalpel for your git rebase.

To easily reproduce a bunch of commits in a repo and make it easy to work with, here is a quick bash script that will generate a ruby file and commit a bunch of times to make our "git world" more consistent.

  #!/bin/bash

  function create_basic_git_repo()
  {
    echo 'git rebase is great'
    mkdir gitexample
    cd gitexample
    git init
    mkdir lib
    touch lib/my_thing.rb
    echo "class MyThing; def my_method; 1 + 1 == 2; end; end" | tee lib/my_thing.rb
    git add lib/my_thing.rb
    git commit -m 'Commit 1'
    echo "class MyThing; def my_method; 1 + 2 == 3; end; end" | tee lib/my_thing.rb
    git add lib/my_thing.rb
    git commit -m 'Commit 2'
    echo "class MyThing; def my_method; 1 + 3 == 4; end; end" | tee lib/my_thing.rb
    git add lib/my_thing.rb
    git commit -m 'Commit 3'
    git checkout -B My_Feature_Branch
    echo "class MyThing; def my_method; 1 + 4 == 5; end; end" | tee lib/my_thing.rb
    git add lib/my_thing.rb
    git commit -m 'My Feature Commit 1'
    echo "class MyThing; def my_method; 1 + 5 == 6; end; end" | tee lib/my_thing.rb
    git add lib/my_thing.rb
    git commit -m 'My Feature Commit 2'
    git checkout master
    echo "class MyThing; def my_method; 1 + 6 == 7; end; end" | tee lib/my_thing.rb
    git add lib/my_thing.rb
    git commit -m 'Commit 4'
    echo "class MyThing; def my_method; 1 + 7 == 8; end; end" | tee lib/my_thing.rb
    git add lib/my_thing.rb
    git commit -m 'Commit 5'
  }

  create_basic_git_repo

In this repo you should see a lib directory as well as a file called my_thing.rb with some commits on. 5 commits on the master branch and 2 commits on a feature branch that are based off of the third commit of the master branch. The SHA's between your repo and my repo will be different, but at least the commit messages will be the same to easily follow what we are doing below.

Object Identifiers

As a quick aside, let's talk about object identifiers in git for a second.

An object identifier usually identifies a commit, we often call them SHA's but we can also be talking about a branch. When we refer to set a of work by the branch name we are just talking about the collection of commits that make up and we reference the head of that collection of commits by the name of the branch.

Any object identifier is a valid argument with a rebase. We can rebase on a branch name, a SHA, a tag, as long as it identifies a commit, it is a valid argument to rebase.

Git rebase —onto

There are two versions of git rebase --onto , the binary and ternary functions. Also written as git rebase --onto/2 and git rebase --onto/3.

Git rebase —onto/2

The two argument version of git rebase onto is used to move a set of commits from one one object identifier to any arbitrary object identifier.

In words I want to move my set of commits from one commit to any other arbitrary commit.

For the most part, this is helpful if you don't want to move your commits to the top of a branch or for some reason you can't have your feature branch at the top of master . But it can also be useful if you have a release branch and need to maintain your work across multiple branches.

I like to think about git rebase --onto/2 git rebase "where I want to go" from "where I was", with "where I want to go" being the object identifier of the place you want all your commits to go to and "where I was" to be the object identifier of where your branch is currently sitting off at.

For example in this case I did git rebase --onto 2b4c572 b45626d

I want my work to be on top of commit 4 with SHA 2b4c572 and it's currently on commit 3 with SHA b45626d. Not too bad or crazy complicated. For these cases I took the changes from My Feature Commit 1 and My Feature Commit 2 to make sure the commits applied to see the example.


Git rebase --onto/3

The three argument version of git rebase --onto is a little more powerful, but not as useful always. I tend to only use it when I have really messed something up or have a pretty nifty reason to use it. In fact, I am not certain I have ever used it to do something entirely useful in git repository. Either way, it could be useful at some point.

For this one, we want to move my set of commits from one object identifier to another object identifier, but I want to only select a certain amount of commits from my branch.

Note that we masterfully and easily cut out commit G from our feature branch. This makes it pretty easy to do this.

We want to git rebase --onto "where I want to go" from "where we were" up to "my chosen commit"

For example here we could do git rebase --onto c5d6535 42fa4e5 22d71a4

We are at commit 42fa4e5 and we want just commit 22d71a4 on top of commit c5d6536.

Pretty easy if you ask me. One thing to note is that git rebase --onto/3 leaves HEAD in a detached state and to save this state, you need to name the branch something.

All of these examples were generated using a quick bash script that generates the same commits each time. Though the commit SHA's (object identifiers) will be different (thanks Git!).

Feel free to check out the bash script and play around with git commit rebase --onto it can take some getting used to but its a powerful tool that has really helped me minimize some git pain in the past.

Have any questions? Let me know below.

Learn more about Git rebasing

Git Rebase Documentation

Git Rebase —onto Tutorial

Posted on May 15 '19 by:

drews256 profile

Andrew Stuntz

@drews256

I'm a full stack developer working on building web applications for all sorts of sized companies using a wide variety of web technologies. Mostly working in Ruby and JavaScript.

Headway

Helping startups and corporations grow into their next phase of business through product strategy, design, and development.

Discussion

markdown guide
 

I'm pretty sure all these use cases are covered by rebase and --interactive. My use of --onto was for unrelated history.

In your first example just pass the first hash to rebase it will select the same second argument because that is the first common ancestor.

Your second can us -i and then drop the commits you don't want, reorder, edit...

 

Yeah, you're not wrong. Maybe the examples should be moving to a completely different base?? Seemed like an easy to show how it worked.

 

I do think that is important, but then you'd have to pull in an unrelated but similar repo.