Browse documentation
Repositories and Changes
Submit and stacking
How vex submit turns work into a reviewable Change, and how to stack dependent changes from a Vex-native checkout or a plain Git clone.
On this page
vex submit turns local work into a reviewable Change. It never rewrites the
target branch itself — landing does that, separately, once review and CI gates pass. A
stack is just what you get when one submitted Change is built directly on another:
Vex tracks the relation and lands the stack in dependency order, and where the repository
is GitHub-synced, projects each Change to its own pull request with the parent's branch
as the base rather than one flat diff against trunk.
There are two ways to reach vex submit, and this page covers both:
- A Vex-native checkout — cloned with
vex clone, working against the JJ change graph directly. This is the deep, first-class path: stacking is implicit in how you build commits. - A plain Git clone — no native Vex workspace at all, just
git cloneand an ordinary branch.vex submitstill works here, and branches can still stack, but the relationship has to be named explicitly.
The submit model
Regardless of which mode you're in, vex submit does the same three things: import the
exact content you built, create or update the Vex Change and its patchset, and (where the
repository is GitHub-synced) project a matching pull request. Re-submitting the same work
appends a patchset to the existing Change rather than creating a new one — vex submit is
safe to run again after a small fix, a rebase, or a retried network call.
Stacking from a Vex-native checkout
In a Vex-native checkout there is no separate "stack" command to create one. A stack is
just the ordinary JJ change graph: vex new starts the next change on top of wherever
you are, so a chain of vex new calls is already a stack before you submit anything.
vex new main -m "feat: add the storage layer"
# ...edit files...
vex new -m "feat: use the storage layer in the API"
# ...edit files...
vex submit -r 'main..@' --target main
vex submit -r 'main..@' submits every change in that range as its own Change, each one
still carrying its place in the graph. Vex numbers parents before children, so the change
numbers themselves read in dependency order. Submitting a single change at a time works
the same way — vex submit --target main from @ submits just the current change, and
Vex still detects its parent from the JJ graph if that parent was submitted too.
Inspect and manage a stack's identity with vex stack:
vex stack list # active graph-derived stacks in this repository
vex stack show '#42' # the stack a given Change belongs to
vex stack name my-feature # give a stack a stable bookmark alias
If the target branch moves while your stack is still open, vex sync fetches it and
restacks your submitted changes on top, then resubmits — pass --no-restack to pull and
resubmit without rewriting anything locally, and --restack to say so explicitly. A
GitHub-synced repository projects each Change in the stack to its own pull request, base
branch chained to the parent's branch automatically.
Submitting (and stacking) from a plain Git clone
vex submit also works from an ordinary git clone — no .jj directory, no native Vex
workspace. Push a branch, then submit it:
git clone https://github.com/<org>/<repo>.git
git checkout -b feature/widget
# ...commit work...
git push origin feature/widget
vex submit --target main
Vex re-fetches the exact branch head itself (it does not trust a locally-computed diff),
imports the commits between the target and your branch, and creates the Change and
pull request. Push again and re-run vex submit to append a patchset — the branch is
adopted, never rewritten out from under you.
Stacking is explicit in this mode. There is no local graph for Vex to read a parent
out of, so name the parent branch with --target:
git checkout -b feature/widget-styling feature/widget
# ...commit work built on top of feature/widget...
git push origin feature/widget-styling
vex submit --target feature/widget
Vex resolves feature/widget to the Change that already adopted it, and the new Change
inherits that Change's landing target rather than trying to land onto feature/widget
itself — the pull request Vex creates uses feature/widget as its base, so it renders as
a genuinely stacked PR, not a flat diff against main.
Two things this mode requires that a Vex-native checkout does not:
- Submit bottom-up. The parent branch has to already be submitted (and pushed) before you submit a branch stacked on it — Vex needs to find an existing Change for the parent to attach to.
- Rebase forward, don't fall behind. A stacked branch has to be built on its parent's
current pushed head. If the parent branch has moved since you branched from it,
vex submitrejects the child with astack_parent_not_at_tiperror rather than importing a stale diff — rebase your branch onto the parent's latest commit, push, and submit again.
There is no local restack for this mode: when a parent branch changes, git rebase the
child branch yourself and push, the same as you would with any other Git remote.
Commands, side by side
| Task | Vex-native checkout | Plain Git clone |
|---|---|---|
| Start the next change in a stack | vex new | git checkout -b <branch> <parent-branch> |
| Submit one change | vex submit --target main | vex submit --target main |
| Submit a whole stack at once | vex submit -r 'main..@' --target main | not available — submit each branch after its parent |
| Stack on another submitted change | automatic (JJ parent) | vex submit --target <parent-branch> |
| Update after a small fix | edit, then vex submit again | commit, git push, then vex submit again |
| Pull the target and reconcile | vex sync --target main | git pull on the target, then resubmit |
| Inspect a stack | vex stack show '#42' | vex change show '#42' (walk parent_change) |
| Land | vex land '#42' | vex land '#42' |
Landing a stack
Landing is identical once a Change exists, in either mode: vex land '#N' requests
server-verified landing for that Change and its submitted-together closure — every open
ancestor it depends on lands first, in order. vex land '#N' --dry-run prints the
computed closure and gate state without requesting anything; --when-ready queues the
change and lets the server land it once CI and approvals clear instead of refusing while
they're outstanding.
A child cannot land ahead of its parent. If the parent is still open, landing the child lands the whole stack up to that point in one queued request — you do not need to land each Change in a stack by hand.