Rebase: How does it work?

Do you find re­bas­ing in Git con­fus­ing? I do.

Photo by Samuel SianiparUnsplash

If you’re like me, you learned Git by look­ing at ex­am­ples and mem­o­riz­ing a hand­ful of Git com­mands. Most of the time the com­mands do what I want, but I don’t al­ways un­der­stand how and why they work.

I de­cided to fig­ure out how re­base works by do­ing it by hand.

Refresher: what does git rebase do?

The re­base com­mand is in­voked like this:

$ git rebase <upstream> <branch>

which means:

Tack <branch> onto the end of <upstream>. If you don’t spec­ify <branch>, rebase uses the cur­rent 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 run­ning the re­base com­mand:

$ 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 re­base the cur­rent branch.

    $ git checkout topic
    Switched to branch 'topic'
    
    Details
  2. Find the af­fected com­mits 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 re­base to the branch to re­base onto. That means set­ting topic (which is also the cur­rent branch) to the same com­mit as master.

    $ git reset --hard master
    
    Details
  4. Apply the com­mits from step 2 to the cur­rent 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 ex­actly the same as if we had used git rebase.

  1. Another way of writ­ing

    $ 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 your­self. ↩︎

  3. You won’t ac­tu­ally see ORIG_HEAD in the git log out­put. I put that in to make what’s go­ing on clearer. ↩︎

  4. You might no­tice the text Applied patch to 'file1' with conflicts. and then the text Resolved 'file1' using previous resolution. There is a con­flict in this patch that I had re­solved ear­lier. I have rerere enabled, so Git re­mem­bers how I re­solved the con­flict the first time and does it again. ↩︎