Jujutsu Cheatsheet


jj Cheatsheet

Config (~/.config/jj/config.toml)

[aliases]
tug = ["bookmark", "move", "--from", "heads(::@- & bookmarks())", "--to", "@-"]
sync = ["rebase", "-b", "@", "-d", "master@origin"]

[revset-aliases]
'mine' = 'trunk()::@'

Daily Workflow

jj log -r mine          # view your chain (no coworker noise)
jj commit -m "message"  # commit current work
jj git fetch && jj sync # sync chain onto updated master
jj tug                  # move nearest bookmark up to follow you

Editing Older Commits

# Option 1: jump to it (note your current commit id first!)
jj edit <commit_id>
# make changes, then jump back
jj edit <original_commit_id>

# Option 2: stay in place, squash fix into older commit
jj squash --into @-----

Splitting Changes (git add -p equivalent)

Since jj has no staging area, use jj split to separate the current commit into two:

jj split          # interactive hunk picker — select what stays, the rest goes to a new child commit
jj split <paths>  # limit the split to specific files

Rebasing

jj rebase -d <destination> with one of three flags:

FlagWhat movesUse when
-rOnly the specified commitYou want to pluck a single commit out and move it elsewhere. Its children stay behind (re-parented to its parent).
-sThe specified commit + all descendantsYou want to move a subtree. Everything from that commit downward moves together.
-bThe whole branch (all ancestors back to the common ancestor with destination)You want to rebase an entire line of work onto a new base.
Given:  A - B - C - D    (you are at D, destination is X)

-r C :  moves C only → B's children become [D], C goes onto X
-s C :  moves C-D    → the C-D subtree lands on X
-b D :  moves A-B-C-D (everything not already on X)
jj rebase -r @ -d master@origin                 # move only current commit
jj rebase -s <commit> -d <destination>           # move commit + its descendants
jj rebase -b @ -d master@origin                  # move entire branch (used by `jj sync`)
jj rebase -b <bookmark> -d <destination>         # rebase a bookmark onto another

Inserting Commits Without Breaking the Graph

# Insert a new commit before a revision (descendants auto-rebase on top)
jj new --insert-before <revision>

# Insert a new commit after a revision (descendants auto-rebase on top)
jj new --insert-after <revision>

Use these instead of jj new + manual rebase when you need to add a commit in the middle of a chain — descendants stay connected automatically.

Key Concepts

  • Working copy is the commit — no staging area, all changes are automatically part of the current commit.
  • Bookmarks are jj’s equivalent of git branches — movable pointers to commits.

Creating a New Commit with a Bookmark

# make your changes...
jj commit -m "your commit message"  # snapshot current changes into a commit and start a new one
jj bookmark set my-bookmark -r @-   # point a bookmark at the just-created commit (@- = parent)

Bookmarks

jj bookmark set <name>              # set bookmark at current commit
jj bookmark set <name> -r <rev>     # set bookmark at a specific revision
jj git push --bookmark <name>       # push bookmark to remote

Handling a Merged Branch in a Stack (GitLab squash merge)

When a branch in the middle of your stack gets merged into its parent via GitLab:

# 1. Fetch latest remote state
jj git fetch

# 2. Update the parent bookmark to match origin (it now contains the merged branch)
jj bookmark set <parent-bookmark> -r <parent-bookmark>@origin

# 3. Rebase the merged branch (and everything above it) onto the updated parent
jj rebase -s <merged-bookmark> -d <parent-bookmark>

# 4. Abandon the now-empty commits from the merged branch
jj abandon <merged-bookmark>

Updating local bookmarks after fetch

jj git fetch updates @origin bookmarks but does NOT move local ones. If master falls behind master@origin, your mine revset will break.

jj bookmark set master -r master@origin

Cleaning up divergent commits

After rebasing, old pre-rebase commits may linger as divergent copies.

# Find all divergent commits
jj log -r 'divergent()'

# Abandon the old versions (/2 = old, /0 = rebased)
jj abandon <change_id>/2

Resolving bookmark conflicts after rebase

After rebasing, bookmarks that had @origin tracking will be conflicted (divergent). Each conflicted bookmark will have two versions: /0 (rebased) and /2 (old). You want /0 — the rebased version in your current stack.

# Check which bookmarks are conflicted
jj bookmark list <bookmark-name>

# Fix each conflicted bookmark (use the /0 version)
jj bookmark set <bookmark-name> -r <change_id>/0

Pushing rebased bookmarks

jj push already does --force-with-lease automatically — no --force flag needed:

jj git push -b <bookmark1> -b <bookmark2> -b <bookmark3>

Safety

All jj operations are non-destructive and reversible:

jj undo              # revert the last operation
jj op log            # see all operations history
jj op restore <id>   # restore to any previous state

Shell Aliases (~/.zshrc)

alias jjsync='jj git fetch && jj sync'