Effortless stacked PRs on GitHub with Jujutsu

GitHub now supports stacked pull requests natively: an ordered chain of PRs, each reviewed on its own, merged one at a time from the bottom or all at once.

Jujutsu is the version control system that makes a stack of commits easy to edit: amend any commit in the middle and everything above it rebases.

VisualJJ connects the two. Turn on one switch, and the stack your reviewers see on GitHub is the stack in your commit graph. Reorder or amend locally, push, and GitHub is updated to match.

Stacks are for reviewers

A 2,000-line pull request rarely gets a careful review. Split into a stack of five 400-line PRs, the same change gives the reviewer a table of contents and a diff per layer. They can read the data model today and the UI tomorrow, and approve the first without waiting for the last.

This is how Google and Meta have worked for years: trunk-based development with small, dependent reviews. It is the workflow that holds up on large teams that review carefully and rewrite their changes in response. One difference from Critique or Phabricator is that a layer here is a PR, not a single commit. If a step is naturally three commits, it stays three commits.

The cost has always been maintenance. Change PR 1 and PRs 2 to 5 need rebasing, force-pushing, and re-targeting. GitHub's own gh stack CLI helps with the GitHub side but still wants the order declared by hand, and if you manage branches with another tool you re-link after every push. VisualJJ takes that maintenance over.

Turn it on

VisualJJ reads the stack from commit ancestry. Two changes in a chain, each with a PR, are a stack. There is nothing to declare.

  1. Open Project Info (the gear in the top bar) and, under Stacked Requests (PRs), turn on GitHub native stacked PRs. It's a Pro feature, currently in beta.
  2. Work as usual. Create Request on each layer opens its PR based on the branch below it and adds it to the GitHub stack. Push after an edit moves the branches and then updates the PR bases and the stack grouping to match.

PRs that are part of a GitHub stack show a stack glyph on their badge, in the change tree and in the Reviews view. When a PR is merged, on GitHub or by shipping from VisualJJ, GitHub retargets the ones above it.

Guarding against accidental merges

There is a trap in native stacks that the launch posts don't mention. GitHub merges a pull request the moment its commits become ancestors of its base branch. No diff, no review, no undo. On a stack, that is one reorder away:

Before:  main ← A (pr-1) ← B (pr-2)     PR 2 is based on pr-1
After:   main ← A (pr-2) ← B (pr-1)     you swapped the two layers

Push that, and PR 2's head is now an ancestor of its base. GitHub marks PR 2 merged, with no diff left in it.

VisualJJ takes care of that. It checks every push before any branch moves and updates the PRs in an order that keeps them open. In the few cases it cannot make safe, the push stops with a message saying why, and nothing on GitHub has changed.

Linear stacks, and the other kind

GitHub's stack is a linear chain of PRs in one repository. That covers most stacks, but not all. A feature that forks into two independent PRs off a shared base has no native representation, and neither does a stack pushed to your own fork instead of the main repository.

Another switch in the configuration, Stack info in PR descriptions, covers those. It writes a short block at the top of each PR body with the whole chain, your position in it, and a link to exactly this layer's diff. It is computed from the same commit graph, so it renders diamonds correctly, and it works on repositories where native stacks are not enabled. Use native stacks for linear chains and descriptions for the rest.

Known limitations

This is a beta, and it has a few:

  • Appending rebuilds the stack. Add a PR on top and the GitHub stack is dissolved and re-created around all of them. The PRs themselves are untouched; only the grouping is rebuilt. We plan to append in place.
  • Pushes that would move trunk are refused while the switch is on. main should be updated by shipping, never by pushing to it directly.
  • "Download and retry" means VisualJJ's cached view of GitHub disagreed with GitHub, because someone else moved a branch, a PR closed, or the last download didn't finish. It declines to write on stale information; refresh and push again.
  • If a push fails, not every PR may target the next one in the chain. Refresh and push again.
  • Pushing with jj git push or plain git bypasses all of this. Push from VisualJJ.

Turning the switch off disables the safety checks. With it off, VisualJJ writes nothing to PR bases and GitHub reacts to pushes the way it always has.

Your reviewers need nothing

Only the author needs VisualJJ. Your reviewers see GitHub's stack view and five small diffs instead of one large one. The work you put into shaping a clean stack is work you took off their plate.

Try it

  1. Install VisualJJ and open a repository hosted on GitHub.
  2. Start a 30-day free Pro trial and turn on GitHub native stacked PRs in Project Info.
  3. Take a change you'd normally ship as one PR and split it into three, each on top of the last.
  4. Create Request on each of the three. Open any of them on GitHub and the stack is there.
  5. Fix something in the bottom layer and click Push. The other two rebase, and GitHub's stack updates to match.