How to rebase a Github pull request

I find it surprisingly confusing to work with Github pull requests while I'm primarily used to contribute to drupal.org and its own contribution mechanisms. So I thought I'd write a step-by-step for rebasing a PR and stop looking it up.

When do you need to rebase a PR?

Active projects can get tens of PRs everyday. This means your PR can go "stale" quickly. In other words, it means it's no longer up-to-date with the main line of development and needs to be updated before it can safely be reviewed/merged into the project.

This is similar to applying a patch if you will. If several PRs attempt to modify the same exact line(s) in the same file and one of them gets merged, then all other - unmerged - PRs will now have a merge conflict to resolve. This is not the only reason a merge can fail, but this is for sure the most common scenario you'll be confronted to.

Github merge conflict

Prepare your fork for rebasing

First, make sure you have two remotes for your repo: upstream and origin (your fork).

$ git remote -v
origin    [email protected]:anavarre/repo.git (fetch)  
origin    [email protected]:anavarre/repo.git (push)  
upstream  [email protected]:vendor/repo.git (fetch)  
upstream  [email protected]:vendor/repo.git (push)  

Next, you need to ensure your fork is synchronized with the upstream repo. This is very well detailed at https://help.github.com/articles/syncing-a-fork/

Here's a practical example:

$ git fetch upstream
remote: Counting objects: 168, done.  
remote: Compressing objects: 100% (2/2), done.  
remote: Total 168 (delta 121), reused 121 (delta 121), pack-reused 45  
Receiving objects: 100% (168/168), 23.25 KiB | 0 bytes/s, done.  
Resolving deltas: 100% (122/122), completed with 62 local objects.  
From github.com:vendor/repo  
   4db2376..8ece95f  ID.60       -> upstream/ID.60
   95bae36..6945d98  ID.60.1      -> upstream/ID.60.1
   c5f9db4..072416c  fix-memcache   -> upstream/fix-memcache
 * [new branch]      cm-split  -> upstream/cm-split
   74ba779..8b02709  master     -> upstream/master
$ git checkout master
Switched to branch 'master'  
Your branch is up-to-date with 'origin/master'.  
$ git merge upstream/master
Updating 74ba779..8b02709  
Fast-forward  
 docroot/src/Endpoint/NewSite.php                        |  21 +-------
 docroot/src/Entity/Sitename.php                         |   5 --
 docroot/src/Repository/Stack.php                        |  27 +++++++---
 docroot/src/CustomService.php                           |  14 +++++-
$  git push
Counting objects: 123, done.  
Delta compression using up to 4 threads.  
Compressing objects: 100% (80/80), done.  
Writing objects: 100% (123/123), 15.49 KiB | 0 bytes/s, done.  
Total 123 (delta 90), reused 69 (delta 42)  
remote: Resolving deltas: 100% (90/90), completed with 48 local objects.  
To [email protected]:anavarre/repo.git    74ba779..8b02709  master -> master  

Rebase your pull request

So far, so good. Now, let's get to the rebase. First things first, we need to checkout the branch from which we originally created the PR.

$ git checkout ID-1234

If we no longer have it locally, we can fetch it from our fork:

$ git fetch
$ git checkout ID-1234

Now, we can run the rebase on top of our development branch.

$ git rebase upstream/master
First, rewinding head to replay your work on top of it...  
Applying: ID-1234 | Improve code quality  
Using index info to reconstruct a base tree...  
M    src/modules/version/D8.mustache  
M    src/modules/version/WebConfig.php  
tests/modules/D8-settings.inc  
tests/stack/orchestration.inc  
Falling back to patching base and 3-way merge...  
Auto-merging src/modules/version/WebConfig.php  
Auto-merging src/modules/version/D8.mustache  

The last remaining step is to force push the changes. This will ensure your rebase does replace the outdated PR.

$ git push -f

From the Github interface, you will now notice a new reference to the rebase.

Successful Github rebase

If you have a testbot, it'll get triggered as if you were creating a new PR. The merge conflict message will go away if all is well.

All good!

What if there are changes required in your PR?

A best practice is to have only one commit in your PR. You can absolutely make several commits, but be sure to squash your changes.

There are several ways to do this and the one that I like to use is when squashing the last N consecutive commits into a single commit. This works best when you work with feature branches and can't commit to master. Here's an example:

$ git reset --soft HEAD~5
$ git commit -m "ID-1234 | Improve code quality"

And, since we've pushed changes to the remote previously, we can simply do:

$ git push origin +ID-1234

The + signs basically acts as a force push to rewrite history.

Aurelien Navarre

Read more posts by this author.

Lyon, France