How I rebase linked or chained pull requests

#git#rebase

Linked pull requests let me split a large change into smaller reviews. The tradeoff appears when an earlier branch changes or merges.

Git provides two options that make this cleanup easier. I use --onto for one branch and --update-refs for a branch chain.

Move one branch with --onto#

Consider the following Git history:

gitGraph
    commit id: "0-19as8su"
    commit id: "1-0a9s8ds"
    branch feature-1
    checkout feature-1
    commit id: "2-a0s9asd"
    commit id: "3-ss91820" type: HIGHLIGHT
    branch feature-2
    checkout feature-2
    commit id: "4-as98dam"
    commit id: "5-as0d9km"
    checkout feature-1
    commit id: "6-0as9d8s"
    commit id: "7-as90dia"
    checkout main
    merge feature-1 id: "8-0as9d8s"

We create feature-1 from main. We then create feature-2 from feature-1 and continue working on it.

Later, we add two commits to feature-1 and merge that branch into main.

At this point, feature-2 still starts from the earlier feature-1 commit. Check out feature-2, then move its commits onto main:

Terminal window
git checkout feature-2
git rebase --onto main 3-ss91820

Here, 3-ss91820 is the commit where feature-2 branched from feature-1. Git takes the later commits and reapplies them onto main.

The new history looks like this:

gitGraph
    commit id: "0-19as8su"
    commit id: "1-0a9s8ds"
    branch feature-1
    checkout feature-1
    commit id: "2-a0s9asd"
    commit id: "3-ss91820" type: HIGHLIGHT
    checkout feature-1
    commit id: "6-0as9d8s"
    commit id: "7-as90dia"
    checkout main
    merge feature-1
    branch feature-2
    checkout feature-2
    commit
    commit

Update a branch chain with --update-refs#

The --update-refs option can update the branches in a chain during one rebase. Consider this history:

%%{init: { 'gitGraph': {'showCommitLabel': false}} }%%
gitGraph
    commit
    commit
    branch feature-1
    checkout feature-1
    commit
    commit
    branch feature-2
    checkout feature-2
    commit
    commit
    checkout feature-2
    branch feature-3
    checkout feature-3
    commit
    commit
    commit
    checkout feature-1
    commit
    commit
    checkout main
    merge feature-1

After we merge feature-1 into main, the remaining work has two parts:

  1. Rebase feature-2 onto main.
  2. Rebase feature-3 onto feature-2.

Check out the last branch in the chain. Then use --update-refs to complete both steps:

Terminal window
# Check out the top branch in the chain.
git checkout feature-3
# Rebase the branch onto main and update every branch in the chain.
git rebase main --update-refs

The new history looks like this:

%%{init: { 'gitGraph': {'showCommitLabel': false}} }%%
gitGraph
    commit
    commit
    branch feature-1
    checkout feature-1
    commit
    commit
    checkout feature-1
    commit
    commit
    checkout main
    merge feature-1
    branch feature-2
    checkout feature-2
    commit
    commit
    checkout feature-2
    branch feature-3
    checkout feature-3
    commit
    commit
    commit

This approach keeps the branch relationships intact without a separate rebase for every branch. I still review the new history before I force-push anything.