You should never have more than a day’s work sitting unintegrated in your local repository

– Martin Fowler, Branching Patterns

In interviews, I throw out a gimme: “Can you explain continuous integration?”

Folks tend to ignore the easy answer: Continuous integration is when folks merge code to main as soon as it’s healthy—often a couple of times a day.

Some code review systems afford continuous integration.

But GitHub doesn’t. Instead, pull requests afford integration pain.

How pull requests can slow you down

If folks finish a feature before merging to main, that’s called a feature branch.

GitHub Flow is focused on feature branches—fork a repo, make changes to a branch, and create a pull request. This is how most people learn to share code.

But this is a trap.

Feature branches slow you down in two ways

  1. Hard to review – They’re large, slowing down review.
  2. Painful to integrate – They contain many changes, creating more opportunities for merge conflicts.

Feature branches encourage big diffs

Code review decision fatigue is real—big changes make for slow reviews.1

Imagine a feature branch with a three commits:

  • Refactor some code
  • Add a model class
  • Add a database migration

Push that branch as a pull request and you get this:

Feature branch flow: a local feature branch becomes a review with three commits

But this is an anti-pattern—you’ve created a monster pull request that some poor sap has to wade through.

Compare the same local branch pushed up as a stack of three reviews:

Stacked patches flow: a local feature branch becomes three reviews with one commit each

Now, each commit gets reviewed on its own. And that makes each review small.

The small delta means less code for the reviewer to understand, making each change easier to review and faster to merge.

Feature branches create integration pain

It’s common for two developers to create conflicting code changes.

Feature branches make integration painful by hiding conflicts until merge time:

Feature branches resolve conflicts long after they’re created, resulting in lots of rework

Continuous integration makes it easier to catch the same situation earlier:

Continuous integration catches conflicts when they happen, resulting in less rework

Now, instead of waiting until their feature is complete, developers push and resolve their conflicts along the way.

Less painful integration means you’ll do it more and move faster.

Stacked patches afford continuous integration

It’s possible to make small pull requests.

But GitHub isn’t making it easy.

Developers hacking away at ginormous feature branches is the root of all the problems I outline above.

And that’s precisely where “GitHub Flow” steers you.

Stacked patch systems—like Gerrit and Phorge—afford continuous integration. Each commit makes a patchset. And each patchset can merge with main.

This is one of the reasons big software projects2 have opted for stacked patch systems.

  1. “Smaller patches undergo fewer rounds of revisions, while larger changes have more re-work done before they successfully land into the project’s version control repository.” via Investigating technical and non-technical factors influencing modern code review↩︎

  2. Google, Facebook, Twitter, Android all use either Gerrit or Phabricator/Phorge for code review (or did at some point)↩︎