Stacks in Jujutsu

This article is part of a series on Stacks in Jujutsu. Other articles in the series:
  1. GitHub Stacks in Jujutsu
  2. Stacks in Jujutsu <== you are here

Here are some helpers to make basic stack operations easier with Jujutsu (jj).

Full configuration

Everything below, distilled into one config. Run jj config edit --user and add the following to get it all:

~/.config/jj/config.toml
[aliases.move-to]
definition = ["util", "exec", "--", "bash", "-c", """
cd "${JJ_WORKSPACE_ROOT:-.}"
revset=$1
shift
edit=$(jj config get ui.movement.edit 2>/dev/null || echo false)
args=()
for a in "$@"; do
case $a in
-e|--edit) edit=true ;;
-n|--no-edit) edit=false ;;
*) args+=("$a") ;;
esac
done
if $edit; then verb=edit; else verb=new; fi
exec jj "$verb" "${args[@]}" "$revset"
""", "jj-move"]
doc = "Move to a revset: new child of it, or edit it with -e"
[aliases.bottom]
definition = ["move-to", "stack_bottom()"]
doc = "Move to the bottom of the current stack, stack_bottom()"
[aliases.top]
definition = ["move-to", "stack_top()"]
doc = "Move to the top of the current stack, stack_top()"
[aliases.sb]
definition = ["stack-bookmarks"]
doc = "Shorthand for stack-bookmarks"
[aliases.stack-bookmarks]
definition = [
"--config",
"revsets.log=substack()",
"log",
"--no-graph",
"--reversed",
"-T",
'if(local_bookmarks, local_bookmarks.map(|b| b.name()).join(" ") ++ " ", "")'
]
doc = "Local bookmark names in substack(@), oldest first, space-separated"
[revset-aliases."stack_heads()"]
definition = "stack_heads(@)"
doc = "Newest mutable commits at or ahead of the working copy"
[revset-aliases."stack_heads(to)"]
definition = "heads(mutable() & to::)"
doc = "Newest mutable commits at or ahead of to"
[revset-aliases."stack_top()"]
definition = "stack_top(@)"
doc = "Newest mutable, single commit at or ahead of the working copy"
[revset-aliases."stack_top(to)"]
definition = "exactly(stack_heads(to), 1)"
doc = "Newest mutable, single commit at or ahead of to"
[revset-aliases."stack_bottom(to)"]
definition = "roots(mutable() & ::to)"
doc = "Oldest mutable commits at or behind to"
[revset-aliases."stack_bottom()"]
definition = "stack_bottom(@)"
doc = "Oldest mutable commits at or behind the working copy"
[revset-aliases."stack(to)"]
definition = "stack_bottom(to)::stack_top(to)"
doc = "Full stack containing to, from stack_bottom(to) to stack_top(to)"
[revset-aliases."stack()"]
definition = "stack(@)"
doc = "Full stack containing the working copy, from its bottom to its top"
[revset-aliases."substack(to)"]
definition = "stack_bottom(to)::to"
doc = "Stack containing to, from stack_bottom(to) through to"
[revset-aliases."substack()"]
definition = "substack(@)"
doc = "Stack from its bottom through the working copy"
[revset-aliases."tree()"]
definition = "tree(@)"
doc = "Full tree (all stacks) containing the working copy"
[revset-aliases."tree(to)"]
definition = "reachable(to, mutable())"
doc = "Full tree (all stacks) containing to"

Revset aliases

Revsets in jj are expressions in “a functional language for selecting a set of revisions.” jj allows you to define revset aliases that can be used anywhere that accepts revsets. The revset aliases that follow make it easier to select revisions in stacks.

stack_heads()

Select the topmost commits. Selects all topmost commits if there is a fork.

~/.config/jj/config.toml
[revset-aliases."stack_heads()"]
definition = "stack_heads(@)"
doc = "Newest mutable commits at or ahead of the working copy"
[revset-aliases."stack_heads(to)"]
definition = "heads(mutable() & to::)"
doc = "Newest mutable commits at or ahead of to"
zz zzzzzz   root() @ zm tlvzsq   main n pqsryyx   feature-1 wl xwlryt   feature-2 x lsmxnzw   feature-3 y rpnwlsw   feature-4 wk sqooso   feature-5
stack_heads()

stack_top()

Select the single, topmost commit. Errors if there are multiple top commits (e.g. there is a fork above the current commit).

~/.config/jj/config.toml
[revset-aliases."stack_top()"]
definition = "stack_top(@)"
doc = "Newest mutable, single commit at or ahead of the working copy"
[revset-aliases."stack_top(to)"]
definition = "exactly(stack_heads(to), 1)"
doc = "Newest mutable, single commit at or ahead of to"
zz zzzzzz   root() zm tlvzsq   main n pqsryyx   feature-1 @ wl xwlryt   feature-2 x lsmxnzw   feature-3 y rpnwlsw   feature-4 wk sqooso   feature-5
stack_top()
zz zzzzzz   root() @ zm tlvzsq   main n pqsryyx   feature-1 wl xwlryt   feature-2 x lsmxnzw   feature-3 y rpnwlsw   feature-4 wk sqooso   feature-5
stack_top() => Error: The revset has more than the expected 1 revisions

stack_bottom()

Select the bottommost commits.

~/.config/jj/config.toml
[revset-aliases."stack_bottom(to)"]
definition = "roots(mutable() & ::to)"
doc = "Oldest mutable commits at or behind to"
[revset-aliases."stack_bottom()"]
definition = "stack_bottom(@)"
doc = "Oldest mutable commits at or behind the working copy"
zz zzzzzz   root() zm tlvzsq   main n pqsryyx   feature-1 @ w lxwlryt   feature-2 x lsmxnzw   feature-3 y rpnwlsw   feature-4
stack_bottom()

stack()

Select the entire stack.

~/.config/jj/config.toml
[revset-aliases."stack(to)"]
definition = "stack_bottom(to)::stack_top(to)"
doc = "Full stack containing to, from stack_bottom(to) to stack_top(to)"
[revset-aliases."stack()"]
definition = "stack(@)"
doc = "Full stack containing the working copy, from its bottom to its top"
zz zzzzzz   root() zm tlvzsq   main n pqsryyx   feature-1 @ w lxwlryt   feature-2 x lsmxnzw   feature-3 y rpnwlsw   feature-4
stack()

substack()

Same as stack(), but the top is the current commit instead of the top of the stack.

~/.config/jj/config.toml
[revset-aliases."substack(to)"]
definition = "stack_bottom(to)::to"
doc = "Stack containing to, from stack_bottom(to) through to"
[revset-aliases."substack()"]
definition = "substack(@)"
doc = "Stack from its bottom through the working copy"
zz zzzzzz   root() zm tlvzsq   main n pqsryyx   feature-1 @ w lxwlryt   feature-2 x lsmxnzw   feature-3 y rpnwlsw   feature-4
substack()

tree()

Same as stack(), but selects all connected branches to the stack as well.

~/.config/jj/config.toml
[revset-aliases."tree()"]
definition = "tree(@)"
doc = "Full tree (all stacks) containing the working copy"
[revset-aliases."tree(to)"]
definition = "reachable(to, mutable())"
doc = "Full tree (all stacks) containing to"
zz zzzzzz   root() @ zm tlvzsq   main n pqsryyx   feature-1 wl xwlryt   feature-2 x lsmxnzw   feature-3 y rpnwlsw   feature-4 wk sqooso   feature-5
tree()
zz zzzzzz   root() zm tlvzsq   main n pqsryyx   feature-1 @ wl xwlryt   feature-2 x lsmxnzw   feature-3 y rpnwlsw   feature-4 wk sqooso   feature-5
tree()

Command examples

Command Explanation
jj edit -r "stack_top()" edit the top commit in your current stack
jj log -r "stack(w)" log all commits in the stack containing w
jj diff -r "stack()" total diff of the entire stack

Aliases

jj allows you to define aliases for commands, including their arguments.

move-to

Add move-to, a bash script alias. This behaves like jj next/jj edit and moves to a specific revision, honoring the ui.movement.edit configuration, which --edit/--no-edit override. In ~/.config/jj/config.toml add:

~/.config/jj/config.toml
[aliases.move-to]
definition = ["util", "exec", "--", "bash", "-c", """
cd "${JJ_WORKSPACE_ROOT:-.}"
revset=$1
shift
edit=$(jj config get ui.movement.edit 2>/dev/null || echo false)
args=()
for a in "$@"; do
case $a in
-e|--edit) edit=true ;;
-n|--no-edit) edit=false ;;
*) args+=("$a") ;;
esac
done
if $edit; then verb=edit; else verb=new; fi
exec jj "$verb" "${args[@]}" "$revset"
""", "jj-move"]
doc = "Move to a revset: new child of it, or edit it with -e"

You can call this directly from the CLI with jj move-to <revset>, but it’s mainly a helper function for other aliases we will call.

Examples:

zz zzzzzz   root() zm tlvzsq   main n pqsryyx   feature-1 w lxwlryt   feature-2 @ x lsmxnzw   feature-3 yr pnwlsw   feature-4
jj move-to w
zz zzzzzz   root() zm tlvzsq   main n pqsryyx   feature-1 @ w lxwlryt   feature-2 x lsmxnzw   feature-3 yr pnwlsw   feature-4 yy uzspzl
By default it runs `jj new ...`, creating a new revision on top of the given revision wherever it sits in the stack
zz zzzzzz   root() zm tlvzsq   main n pqsryyx   feature-1 w lxwlryt   feature-2 @ x lsmxnzw   feature-3 y rpnwlsw   feature-4
jj move-to w --edit
zz zzzzzz   root() zm tlvzsq   main @ n pqsryyx   feature-1 w lxwlryt   feature-2 x lsmxnzw   feature-3 y rpnwlsw   feature-4
Passing `--edit` (or setting `ui.movement.edit = true`) moves `@` to the given revision

top and bottom

jj has built-ins for moving forward/backward: jj next and jj prev. Let’s add jj top and jj bottom to move all the way to the top or bottom of the current stack. In ~/.config/jj/config.toml add:

~/.config/jj/config.toml
[aliases.bottom]
definition = ["move-to", "stack_bottom()"]
doc = "Move to the bottom of the current stack, stack_bottom()"
[aliases.top]
definition = ["move-to", "stack_top()"]
doc = "Move to the top of the current stack, stack_top()"

Examples:

zz zzzzzz   root() zm tlvzsq   main @ n pqsryyx   feature-1 w lxwlryt   feature-2 x lsmxnzw   feature-3 yr pnwlsw   feature-4
jj top
zz zzzzzz   root() zm tlvzsq   main n pqsryyx   feature-1 w lxwlryt   feature-2 x lsmxnzw   feature-3 @ yr pnwlsw   feature-4 yy uzspzl
`jj top` by default adds a new empty revision on top of the stack and leaves `feature-4` where it was
zz zzzzzz   root() zm tlvzsq   main @ n pqsryyx   feature-1 w lxwlryt   feature-2 x lsmxnzw   feature-3 y rpnwlsw   feature-4
jj top --edit
zz zzzzzz   root() zm tlvzsq   main n pqsryyx   feature-1 w lxwlryt   feature-2 @ x lsmxnzw   feature-3 y rpnwlsw   feature-4
Passing `--edit` (or setting `ui.movement.edit = true`) moves `@` to the existing top revision

Alternatively, if you always want jj new functionality and never jj edit, or vice versa, you can omit the move-to alias and just hard-code new:

~/.config/jj/config.toml
[aliases.bottom]
definition = ["new", "stack_bottom()"]
doc = "Move to the bottom of the current stack, stack_bottom()"
[aliases.top]
definition = ["new", "stack_top()"]
doc = "Move to the top of the current stack, stack_top()"

or edit:

~/.config/jj/config.toml
[aliases.bottom]
definition = ["edit", "stack_bottom()"]
doc = "Move to the bottom of the current stack, stack_bottom()"
[aliases.top]
definition = ["edit", "stack_top()"]
doc = "Move to the top of the current stack, stack_top()"

stack-bookmarks (sb)

Next, configure jj stack-bookmarks (alias: jj sb) to give you a bottom-up, space-separated list of local bookmark names in your stack. This is useful for workflows that need the stack bookmarks, e.g. gh stack. In ~/.config/jj/config.toml add:

~/.config/jj/config.toml
[aliases.sb]
definition = ["stack-bookmarks"]
doc = "Shorthand for stack-bookmarks"
[aliases.stack-bookmarks]
definition = [
"--config",
"revsets.log=substack()",
"log",
"--no-graph",
"--reversed",
"-T",
'if(local_bookmarks, local_bookmarks.map(|b| b.name()).join(" ") ++ " ", "")'
]
doc = "Local bookmark names in substack(@), oldest first, space-separated"

Examples:

Either call it with no arguments (the default revset is substack()):

zz zzzzzz   root() zm tlvzsq   main n pqsryyx   feature-1 @ w lxwlryt   feature-2 x lsmxnzw   feature-3 y rpnwlsw   feature-4
jj sb => feature-1 feature-2 feature-3

Or pass it a revset:

zz zzzzzz   root() zm tlvzsq   main n pqsryyx   feature-1 @ w lxwlryt   feature-2 x lsmxnzw   feature-3 y rpnwlsw   feature-4
jj sb -r w::y => feature-2 feature-3 feature-4

Or hand it a revset alias, e.g. to get the whole stack:

zz zzzzzz   root() zm tlvzsq   main n pqsryyx   feature-1 @ w lxwlryt   feature-2 x lsmxnzw   feature-3 y rpnwlsw   feature-4
jj sb -r "stack()" => feature-1 feature-2 feature-3 feature-4

Production Notes

AI Facts
1 serving per container
Serving sizeAbout 1 article
Amount per serving
Machine-Written Words 0%
% Machine*
  • Prose Written by hand. Proof-read by machine. Edits applied by hand. 0%
  • Code & Config 0%
  • Creation All code/configuration written by hand. 0%
  • Validation All code/configuration programmatically verified by Claude Opus 5 for correctness and consistency. Fixes applied by hand. 100%
  • Diagram Content Commits, bookmarks, revsets, etc. 0%
  • Component Code 100%
  • Commit Graph Component The code for the Astro component that draws the jj commit graphs, built on @hpcc-js/wasm-graphviz. 100%
  • AI Facts Component The code (not content) for this Astro component. 100%
  • Human Review 100%

* % Machine tells you how much of this article a machine wrote. Values are yolo'd, not a measurement. This label has not been evaluated by the Food and Drug Administration.