Rebase: How does it work?

Do you find rebasing in Git confusing? I do.

If you’re like me, you learned Git by looking at examples and memorizing a handful of Git commands. Most of the time the commands do what I want, but I don’t always understand how and why they work.

I decided to figure out how rebase works by doing it by hand.

Refresher: what does git rebase do?

The rebase command is invoked like this:

$ git rebase <upstream> <branch>

which means:

Tack <branch> onto the end of <upstream>. If you don’t specify <branch>, rebase uses the current branch.

What we want to do

Assume our repo looks like this:

             HEAD
               |
            master (branch to rebase onto)
               |
C1---C2---C5---C6
      \
       C3---C4
            |
          topic (branch to rebase)

After running the rebase command:

$ git rebase master topic

the repo will look like this:

            master
               |
C1---C2---C5---C6
                \
                 C3'---C4'
                       |
                     topic
                       |
                     HEAD

Step by step

According to its man page, rebase does its work in four steps:

  1. Make the branch to rebase the current branch.

    $ git checkout topic
    Switched to branch 'topic'
    
    Details
  2. Find the affected commits and save them:

    $ git log --oneline master..HEAD
    ff2d8db C4
    c409bd6 C3
    $ git show -p c409bd6 > c3.patch
    $ git show -p ff2d8db > c4.patch
    
    Details
  3. Reset the branch to rebase to the branch to rebase onto. That means setting topic (which is also the current branch) to the same commit as master.

    $ git reset --hard master
    
    Details
  4. Apply the commits from step 2 to the current branch

    $ git apply -3 c3.patch
    $ git commit -m "C3"
    [topic 4cb79ee] C3
    1 file changed, 1 insertion(+)
    
    $ git apply -3 c4.patch
    $ git commit -m "C4'"
    [topic 029ede0] C4
    1 file changed, 1 insertion(+)
    create mode 100644 file2
    
    Details

This is how things end up:

$ git log --oneline --graph --decorate --all
* 703d475 (HEAD -> topic) C4'
* 3ffdcab C3'
* 6adbbb9 (master) C6
* 88f76a2 C5
| * ff2d8db (ORIG_HEAD) C4
| * c409bd6 C3
|/
* 1c74366 C2
* f50bc06 C1

Or in ASCII art:

            master
              |
C1---C2---C5---C6
    \          \
      C3---C4    C3'---C4'
          |           |
      ORIG_HEAD     topic
                      |
                    HEAD

Which is exactly the same as if we had used git rebase.

Photo by Samuel Sianipar on Unsplash

  1. Another way of writing

    $ git log --oneline master..topic
    

    is like this, which I find clearer:

    $ git log --oneline ^master topic
    
    ↩︎
  2. Git sets ORIG_HEAD whenever HEAD changes. You don’t have to set ORIG_HEAD yourself. ↩︎

  3. You won’t actually see ORIG_HEAD in the git log output. I put that in to make what’s going on clearer. ↩︎

  4. You might notice the text Applied patch to 'file1' with conflicts. and then the text Resolved 'file1' using previous resolution. There is a conflict in this patch that I had resolved earlier. I have rerere enabled, so Git remembers how I resolved the conflict the first time and does it again. ↩︎