Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Working ahead with merge points

Sometimes I am waiting on approval, but I still need to keep moving.

Or my work depends on someone else’s branch.

Or I have two streams of my own work that should stay reviewable separately, but I need to know whether they work together.

This is where a local merge point can be useful.

Definition: merge point

A merge point is a local change with multiple parents. I use it as an integration surface for branches or review commits that are still separate pieces of work.

The idea is simple:

  1. Keep the real work in separate commits or branches.
  2. Create a local merge that combines them.
  3. Work on top of that combined view.
  4. Move finished pieces back into the right parent when needed.

What the graph looks like

Imagine I have one PR waiting for approval and another branch I depend on:

main
  feature: auth flow

main
  refactor: client setup

I want to keep both reviewable on their own, but I also want to continue as if both existed together.

I create a merge change with both tips as parents:

jj new <auth-flow-tip> <client-setup-tip> -m "private: integrate auth flow and client setup"

Then I create a normal working change on top:

jj new -m "wip: continue on integrated work"

The graph is roughly:

main
  feature: auth flow ----.
                         \
                          private: merge auth flow and client setup
                            \
                             wip: continue on integrated work
                         /
  refactor: client setup-'

A merge commit is just another change with more than one parent.

Definition: merge change

In jj, jj new A B creates a new change with both A and B as parents. That is how I create a local merge surface.

Why I like it

The useful part is mundane: I can run the code as if both PRs had landed.

If the auth branch and client refactor fight each other, I find out now, while both are still easy to change. The merge point is not for review; it is my local test bench.

Local integration surface

I usually treat the merge point as local scaffolding. The separate commits are the things I want reviewed. The merge point is for testing whether they work together.

Moving work back

While working on top of the merge point, I might make a change that actually belongs in one of the parent branches.

If everything in the current working change belongs in the auth flow branch:

jj squash --into <auth-flow-tip>

If only part of it belongs there:

jj squash -i --into <auth-flow-tip>

Then I review what happened:

jj log -r main..@
jj diff -r <auth-flow-tip>

With my config, the first command is:

jj stack

This is the same shaping loop as before, but the merge lets me shape work across several active streams.

When an agent is helping

This is a very natural place to use agents.

I can create the merge point, ask an agent to make the combined state pass tests, then decide where each part belongs:

jj diff
jj split
jj squash --into <target-change>

The agent does not need to understand my review plan perfectly. I can use jj afterwards to split the result into good commits.

Push only the real branches

After moving work back into the actual review branches, I push those branches again:

jj git push -b 'push-*'

With my config:

jj ps

or, for one new change:

jj git push -c @-

Do not accidentally review the scaffolding

The merge point is often not the PR. It is a local test commit. Before pushing, I check which bookmarks I am moving and which commits I actually want other people to review.

The escape hatch is still simple:

jj undo

That is why I am comfortable trying this. The graph can be creative locally, and I can still keep the review surface clean.