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:
- Keep the real work in separate commits or branches.
- Create a local merge that combines them.
- Work on top of that combined view.
- 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.