A stack is a linear chain of local jj changes. Use jj to write and rearrange those changes,
then run jj-stack submit to create or update one GitHub pull request per change.
One change, one pull request
Suppose you refactor a model in A, build an API on it in B, and add a UI in C. Submitting that chain produces three pull requests in the same order:
---
config:
block:
padding: 32
---
block-beta
columns 3
L["Local jj"] space G["GitHub"]
C["C: UI"] space P3["PR #3"]
B["B: API"] space P2["PR #2"]
A["A: model"] space P1["PR #1"]
T["trunk()"] space M["main"]
T --> A
A --> B
B --> C
M --> P1
P1 --> P2
P2 --> P3
A -.-> P1
B -.-> P2
C -.-> P3
style L fill:none,stroke:none
style G fill:none,stroke:noneThe bottom PR targets trunk, usually main, and each PR above it targets the PR branch below.
This means PR #2’s diff shows just the API changes added in B, since the refactor in A is
already in its base. Reviewers can consider each change separately while you keep working on
the changes that depend on it.
GitHub needs a branch for every PR. jj-stack creates and updates those PR branches for you,
and they normally stay out of local bookmark output. When you submit two or more changes,
jj-stack also groups their PRs into a GitHub stack. A single change gets an ordinary PR.
Select a stack by its head
The head is the top change in your stack. When you select C, jj-stack follows its parents
back to trunk to find the whole chain, A → B → C. Trunk is the base of the stack and is not
included. You don’t need bookmarks to define the stack, and adding one to B won’t divide it.
If you don’t specify a head, jj-stack starts from your working copy (@), provided it has
both a description and changes. Otherwise, it uses the parent (@-). With an empty working
copy above C, for example, jj-stack submit submits the chain ending at C.
If you edit B directly, select C explicitly to update the whole stack:
jj edit <B-change-id>
# edit files
jj-stack submit <C-change-id>
The change you’re editing is B, but the stack still ends at C. Passing C’s ID to submit
selects the whole A → B → C stack. You can inspect it with jj-stack view <C-change-id>.
Use jj-stack list to find the heads of stacks you’ve already submitted.
See bookmarks and selection for selection rules,
or multiple stacks to submit dependent work
separately with --base.
Editing a change keeps its pull request
A jj change keeps its change ID when you edit or reorder it, even though rewriting it gives
it a new Git commit ID. jj-stack uses the change ID to find the existing PR, so submitting
again preserves the PR number, discussion, and review history.
flowchart TB Before["change puvuntsm<br/>commit 2b9f83a1<br/>PR #2"] Edit["edit with jj<br/>then submit"] After["same change puvuntsm<br/>new commit 761b55c9<br/>same PR #2"] Before --> Edit --> After
When you edit B, jj also rebases C onto the new B. Submitting the stack updates both PR
branches. See edit and rearrange for splitting, squashing, or abandoning
submitted changes.
Submit, review, merge, and sync
flowchart TB Local["Work with jj"] -->|submit| Review["Review on GitHub"] Review -->|feedback| Local Review -->|merge| Landed["Work reaches trunk"] Landed -->|sync| Continue["Continue with remaining changes"]
After you publish your changes with jj-stack submit, review takes place on GitHub as usual.
When you’re ready to land the stack, jj-stack merge asks GitHub to merge it from the bottom.
Use --pull-request <pr> to stop at an earlier PR. GitHub decides whether its checks and
review rules allow the requested group to merge.
jj-stack merge waits for GitHub, including its merge queue, then rebases your remaining
changes onto the updated trunk, updates their PRs to match, and removes PR branches that are no
longer needed. Run jj-stack sync <head-change-id> yourself only after --no-wait, an
interrupted wait, a merge made through GitHub, or GitHub’s Rebase stack action, once GitHub
has finished.
To rebase onto newer trunk changes at any other time, use jj rebase, then publish the result
with jj-stack submit. See merge and sync for partial merges and
queues.
When jj-stack is unsure, it stops
Before updating a PR, jj-stack checks that it can safely match it to the intended local change.
If more than one PR could match, or a branch has moved unexpectedly, it stops and explains what
to do next. See working on GitHub for which edits you can make
there and how they interact with your local work.