---
title: Adventures with git Rebasing
teaser: 'git rebase --onto is a useful tool for rebasing your branch off of another
  branch that has itself been rebased or had its history rewritten.

  '
tags: git
author: Stephen Hanson
published_on: 2020-02-10
---

Every now and then I run into a situation where I’m branched off of a topic
branch and the topic branch gets rebased off of main or squashes commits. How
do I cleanly rebase my branch off the topic branch? This situation looks like
this:

```
a b c          main
  |  \
  |    d' e'   branch-a (after being rebased off main)
  |
  d e          branch-a-outdated (before rebasing main)
      \
       f g     branch-b (based off outdated branch-a)
```

In the above, `branch-b` had branched off of `branch-a`, then `branch-a`
rebased off of main. For the sake of this discussion, let's also assume there
were merge conflicts from main that were resolved during this rebase.
The goal is to now rebase `branch-b` so its two commits, `f` and `g`, branch
off of `e'` from `branch-a`. This might appear trivial at first, but is trickier
than it seems.

## Let's try a regular rebase

Many times, I have attempted to solve this situation with a regular rebase,
only to be met with a confusing situation:

```
branch-b % git rebase branch-a
First, rewinding head to replay your work on top of it...
Applying: d
Using index info to reconstruct a base tree...
M       b.txt
Falling back to patching base and 3-way merge...
Auto-merging b.txt
CONFLICT (content): Merge conflict in b.txt
error: Failed to merge in the changes.
Patch failed at 0001 d
Use 'git am --show-current-patch' to see the failed patch

Resolve all conflicts manually, mark them as resolved with...
```

"Applying: d"?? Our branch started at commit `f`, so why is git trying to apply
`d`?

The short answer is that git doesn't know that `d` is already in `branch-a` in
this case because when `branch-a` was rebased off of main, the `d` commit
changed. This can be due to a merge conflict or because it was squashed or
changed for any other reason.

When we look at the merge conflict, it's for a file that wasn't
even touched in `branch-b`. **We are forced to resolve the same conflicts that
we already resolved when rebasing `branch-a` off of `main`!** This inserts
room for error, especially if we aren't familiar with the changes from
`branch-a`.

If you've ever rebased and wondered why you are having to resolve conflicts for
files not in your current branch, there's a chance this is what was occurring --
the branch you are rebasing off of had rewritten history in some way that
modified the content of its commits, and git no longer recognizes that the
commit in your branch is from the branch off of which you are rebasing.

## How about a cherry-pick?

Before we go on with the proposed solution, here is the commit diagram again so
you don't have to scroll up:

```
a b c          main
  |  \
  |    d' e'   branch-a (after being rebased off main)
  |
  d e          branch-a-outdated (before rebasing main)
      \
       f g     branch-b (based off outdated branch-a)
```

Remember that our goal is to have `f` and `g` branched off of `e'` from
`branch-a`. Since we just saw that `git rebase branch-a` doesn't work as we'd
like, what else could we try? One solution that I used for years before I knew
about the technique I'm about to show was to use `git cherry-pick` to play `f`
and `g` directly on top of `branch-a`. This absolutely works but requires some
confusing branch shuffling:

```sh
branch-b % git log
ABC123 g (HEAD -> branch-b)
XYZ321 f
...
branch-b % git branch -m branch-b-bak       # not necessary but nice if you're scared of git reflog like me
branch-b-bak % git checkout -b branch-b branch-a # check out new branch-b from branch-a
branch-b % git cherry-pick XYZ321^..ABC123  # copy the range of commits we want from the late branch-b
branch-b % git branch -D branch-b-bak       # delete that backup
```

The above works just fine, and I'm sure there are some improvements that could
be made to the process, but there is still a much easier way to get the exact
same results. Read on!

## `git rebase --onto` for the win!

git, in its wisdom, knew that this issue would come up, so it provides a special
kind of rebase where we can explicitly specify which commit we want the rebase
to start from. **That means that from `branch-b`, we can effectively say "rebase
off of branch-a but only play my commits starting after commit X"**. Here's a
simplified look at the syntax:

```sh
git rebase --onto branch-i-want-to-be-based-off
branch-or-hash-that-my-changes-are-currently-based-off branch-im-rebasing
```

So in our case, from `branch-b`, we would do:

```sh
% git log
ABC123 g (HEAD -> branch-b)
XYZ321 f
QWE123 e (branch-a-outdated)
...
% git rebase --onto branch-a QWE123 branch-b
First, rewinding head to replay your work on top of it...
Applying: f
Applying: g
```

This performed the same thing as `git rebase branch-a`, but we were able to
specify which commit to start rebasing from and only apply `f` and `g`. Notice
that the `QWE123` SHA we specified is the commit directly **before** the two
commits we want from `branch-b`. We could also have achieved the same results
with:

```
% git rebase --onto branch-a branch-a-outdated branch-b
```

or:

```
% git rebase --onto branch-a HEAD~2 branch-b
```

Now, here are the results. Just what we wanted, and with one command.

```
% git log
g  (HEAD -> branch-b)
f
e' (branch-a)
d'
c  (main)
b
a
```

## A Little Syntactic Sugar

I find it redundant to have to specify the current branch as the last parameter
to `git rebase --onto`, since I always run it from the branch I'm rebasing, so
I have an alias and function I've set up in my dotfiles:

```
alias gcurrent="git rev-parse --abbrev-ref HEAD"
function gro() { git rebase --onto $1 $2 $(gcurrent) }
```

This allows me to accomplish the above with just: `gro branch-a QWE123`. Simple!

Be sure to [check out (pun) the git rebase docs][rebase] for more info on all
this.

[rebase]: https://git-scm.com/docs/git-rebase#_description
