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:
| Flag | What moves | Use when |
|---|---|---|
-r | Only the specified commit | You want to pluck a single commit out and move it elsewhere. Its children stay behind (re-parented to its parent). |
-s | The specified commit + all descendants | You want to move a subtree. Everything from that commit downward moves together. |
-b | The 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'