How I stack PRs
This is the loop I use most: turn one idea into a stack of small PRs, push them before they are polished, and keep coding while review catches up.
The example uses this book as the work item. Imagine I want to build it as three small pull requests:
- Create the mdBook skeleton.
- Add the mental model and aliases.
- Add common workflow examples.
The stack will look like this:
@ empty working-copy change
docs: add common jj workflow examples
docs: explain mental model and aliases
docs: create mdbook skeleton
main
Stack
A stack is a line of small commits where each commit builds on the one below it. In this guide, I usually turn each commit into its own small PR.
Trunk
In this book, main is trunk: the shared branch I keep my work based on.
For this example I start from a clean repo with main fetched. The files are from this book, but the command loop is the part to copy.
Start from main
I fetch remote changes and create a new empty change on top of main:
jj git fetch
jj new main
This creates an empty working-copy change. In jj, the current working-copy change is @.
@
@ is the current working-copy change. It exists in the jj graph even before I commit it.
Before editing, I like to check that I am starting clean:
jj status
First PR: create the book
First I make the smallest useful piece: the book exists and has a table of contents.
book.toml
src/SUMMARY.md
src/introduction.md
This is the small loop I repeat all day: look, commit, look again, push.
jj diff
jj commit -m "docs: create mdbook skeleton"
jj diff -r @-
jj git push -c @-
jj diff -r @- is the check I care about here. I want to review the change I just committed before I push it.
@-
@- means the parent of @. Right after jj commit, that is usually the change I just committed.
jj creates a temporary bookmark with a generated name like push-abc.... That is the branch name GitHub sees.
Bookmark
A bookmark is the jj name that maps to a branch. For review, the important part is that GitHub has a branch name to point at.
I do not need to name it perfectly. I just need a small PR my team can review.
I usually look at the graph after pushing:
jj log
I expect to see the new push-* bookmark on the commit I just pushed and an empty @ above it.
Second PR: explain the model
After jj commit, @ is already an empty child of the first PR. I can keep writing, and the next commit naturally stacks on top.
I edit the next logical unit:
src/mental-model.md
src/config-and-aliases.md
Then I repeat the same loop:
jj diff
jj commit -m "docs: explain mental model and aliases"
jj diff -r @-
jj git push -c @-
Now I have two push-* bookmarks, one for each PR in the stack.
Each PR has one job. Reviewers can look at the book skeleton first, then the model, then the examples.
Third PR: add common workflows
I edit the workflow examples:
src/common-things.md
src/wip-private-commits.md
src/conflict-resolution.md
src/reviewing-teammate-work.md
Then I repeat the loop again:
jj diff
jj commit -m "docs: add common jj workflow examples"
jj diff -r @-
jj git push -c @-
The stack has three pushed PR branches now.
At this point the work is visible. It does not have to be final.
Inspect the stack
I check the local stack:
jj log -r main..@
I check the pushed temporary branch bookmarks:
jj log -r 'bookmarks("push-*")'
The shape should be roughly:
@ empty working-copy change
push-c... docs: add common jj workflow examples
push-b... docs: explain mental model and aliases
push-a... docs: create mdbook skeleton
main
bookmarks("push-*") is my quick way to see the review branches jj created for this stack.
Then I keep moving
At this point the interesting thing has already happened: I split one idea into reviewable pieces, pushed them early, and kept the local graph easy to read.
Once review starts, the loop is the same shape: fetch, rebase the pushed stack when needed, edit the change that needs feedback, and push the push-* bookmarks again.
The next chapters add my small config layer and then show how I keep the open PRs moving.