name: jj-git-interactive-rebase-to-jj description: Complete reference for translating git interactive rebase operations to Jujutsu equivalents. disable-model-invocation: true
Complete Reference: Git Interactive Rebase to Jujutsu
A comprehensive guide for Git users transitioning to Jujutsu, mapping all interactive rebase operations to their jj equivalents and beyond.
Diamond / development-join applicability. The single-chain examples below treat
@as a freely-mutable content commit. That model does not apply inside a development join (the diamond workflow). Inside a join,@is a shared, always-empty[wip]commit sitting directly on a multi-parent join, and that[wip]is the stable coordination surface that makes N concurrent editors safe by construction. In this repo a pushedwipdeploy bookmark additionally tracks@, so machines rebuild from it. Inside a join neverjj describe @, neverjj rebase -r @/--revisions @, and never barejj squash -r @(the form at the squashing example below advances@through a chain and consumes the[wip]). Route changes downward while leaving@empty in place withjj squash --from @ --insert-after <target> -m "..." --keep-emptied [-- <paths>](or--insert-before),jj absorb(documented below as the blame-routed down-route), orjj splitkeeping the wip. Canonical rule, the single-shared-[wip]invariant, and rationale live in~/.claude/skills/jj-version-control/diamond-workflow.md; join mechanics in~/.claude/skills/jj-version-control/SKILL.md; non-interactive command execution in~/.claude/skills/jj-workflow/SKILL.md.
Table of Contents
- Core Concept: The Paradigm Shift
- Complete Operation Mapping
- Visual Comparison Workflows
- Beyond Git: Jujutsu's Advanced Capabilities
- Practical Examples
- Quick Reference Tables
Core Concept: The Paradigm Shift
Git's Interactive Rebase Model
graph TD
A[Start Interactive Rebase] --> B[Edit Todo List]
B --> C[Process Each Commit]
C --> D{Conflict?}
D -->|Yes| E[Stop, Resolve, Continue]
D -->|No| F[Next Commit]
E --> F
F --> G{More Commits?}
G -->|Yes| C
G -->|No| H[Complete Rebase]
In Git, interactive rebase is a batch operation where you:
- Enter a special mode (
git rebase -i) - Edit a todo list in an editor
- Execute all operations sequentially
- Handle interruptions with
--continue,--abort, or--skip - Cannot make additional changes without restarting
Jujutsu's Direct Editing Model
graph TD
A[Identify Target Commit] --> B[Execute Direct Command]
B --> C[Descendants Auto-Rebase]
C --> D[Operation Complete]
D --> E{Want to Undo?}
E -->|Yes| F[jj undo]
E -->|No| G[Continue Working]
F --> A
In Jujutsu, there is no special mode:
- Commands operate directly on any commit in the history
- Descendants are automatically rebased when ancestors change
- Every operation is atomic and immediately undoable
- Conflicts are committed and can be resolved later
- You never leave the normal working state
The fundamental insight: In jj, every commit operation is non-interactive by default, making "interactive rebase" as a concept obsolete.
Complete Operation Mapping
Basic Operations
| Git Interactive Rebase | What It Does | Jujutsu Command | Key Differences |
|---|---|---|---|
pick <commit> |
Keep commit as-is | (automatic) | All commits are "picked" by default; use jj new <commit> to build on top |
reword <commit> |
Edit commit message | jj describe -r <commit> -m "message" |
Can reword any commit directly without entering special mode |
edit <commit> |
Stop to modify commit | jj edit <commit> |
Checks out commit immediately; changes automatically amend it |
squash <commit> |
Merge into parent (keep both messages) | jj squash -r <commit> |
Moves changes from commit into parent; descendants auto-rebase |
fixup <commit> |
Merge into parent (discard message) | jj squash -r <commit> |
jj doesn't distinguish fixup from squash; both discard the source message by default |
fixup -c <commit> |
Merge, use fixup message | jj squash -r <commit> -m "message" |
Specify message explicitly with -m |
fixup -C <commit> |
Merge, use fixup message, no edit | jj squash -r <commit> |
Default behavior when commit has a description |
drop <commit> |
Remove commit entirely | jj abandon <commit> |
Abandons commit; descendants rebased onto grandparent(s) |
exec <command> |
Run shell command at this point | (no direct equivalent) | Use jj log + shell script, or test after each change and jj undo if needed |
break |
Pause to make changes | (not needed) | All operations are atomic; never in "rebasing mode" |
label <name> |
Mark a point in history | jj bookmark create <name> -r <commit> |
Creates a bookmark (jj's term for Git branches) |
reset <label> |
Move to labeled point | jj new <bookmark> or jj edit <bookmark> |
new creates new commit on top; edit checks out bookmark |
merge <branches> |
Create merge commit | jj new <parent1> <parent2> ... |
Multiple parents create a merge; supports n-way merges |
Advanced Operations
| Operation | Git Interactive Rebase | Jujutsu Command | Notes |
|---|---|---|---|
| Reorder commits | Reorder lines in todo list | jj rebase -r <commit> -A <target> or -B <target> |
-A = insert after; -B = insert before; descendants follow |
| Split commit | Mark "edit", reset, stage, commit | jj split -r <commit> -- <paths> -m "msg" |
Non-interactive: pass explicit paths and -m. Bare jj split (no paths) opens a diff TUI and hangs non-interactive Bash; jj split <paths> opens the editor twice (extracted commit plus remainder), so pre-stage the remainder via jj describe -r <commit> -m "<remainder>" first. See jj-workflow "Non-interactive command execution". |
| Partial squash | Stage subset, commit --amend | jj squash -i -r <commit> |
Interactive diff editor to select hunks to move |
| Move to specific ancestor | fixup + reorder + exec | jj squash --from <commit> --into <ancestor> |
Can target any ancestor, not just parent |
| Move between any commits | (not possible) | jj squash --from <src> --into <dest> |
Works on any two commits in history |
| Edit without checkout | (not possible) | jj diffedit -r <commit> |
Edit commit's diff directly in editor without checking out |
| Auto-absorb changes | (not possible) | jj absorb |
Automatically distributes working copy changes to appropriate ancestors |
| Keep empty after squash | (not possible) | jj squash --keep-emptied |
Preserves source commit after moving changes |
Conflict Resolution
| Git Interactive Rebase | Jujutsu Equivalent |
|---|---|
| Rebase stops with conflict markers | Conflict is committed; marked as "conflict" in log |
git add <file> |
(automatic - jj tracks all files) |
git rebase --continue |
(not needed - never in special mode) |
git rebase --abort |
jj undo or jj op restore <operation> |
git rebase --skip |
jj abandon <commit> (to skip applying a commit) |
| Resolve before continuing | jj new <conflicted>, resolve, jj squash into conflicted commit |
git mergetool |
jj resolve (launches merge tool for each conflict) |
Visual Comparison Workflows
Workflow 1: Reordering Commits
Git Interactive Rebase:
sequenceDiagram
participant U as User
participant G as Git
participant E as Editor
U->>G: git rebase -i HEAD~5
G->>E: Open todo list editor
E->>U: Show:<br/>pick A<br/>pick B<br/>pick C
U->>E: Edit to:<br/>pick A<br/>pick C<br/>pick B
E->>G: Save and close
G->>G: Execute rebase
G->>U: Complete (or stop on conflict)
Jujutsu Direct Command:
sequenceDiagram
participant U as User
participant J as Jujutsu
U->>J: jj rebase -r C -B B
J->>J: Move C before B
J->>J: Auto-rebase descendants
J->>U: Complete immediately
Workflow 2: Squashing Multiple Commits
Git:
A - B - C - D - E (HEAD)
Goal: Combine C, D, E into one commit
git rebase -i HEAD~5
# Change todo list:
pick A
pick B
pick C
squash D
squash E
Jujutsu:
A - B - C - D - E (@)
Goal: Same
jj squash -r @ # E squashed into D, @ now points to D
jj squash -r @ # D squashed into C, @ now points to C
# Result: A - B - C (@)
Single-chain cleanup only. In a development join
@is the shared empty[wip]sitting on the multi-parent join, sojj squash -r @would consume that[wip]into its parent and re-point@— emptying the editing surface concurrent actors write to and, in this repo, dragging the pushedwipdeploy bookmark. There, route changes downward withjj squash --from @ --insert-before <target> --keep-emptied -m "..."(or--insert-after <target>), which leaves@empty and in place, orjj absorb. See~/.claude/skills/jj-version-control/diamond-workflow.md.
Or more directly:
# From E, squash everything into C
jj squash --from @ --into C
jj squash --from @- --into C
Beyond Git: Jujutsu's Advanced Capabilities
These features have no equivalent in Git's interactive rebase and represent jj's advanced capabilities.
1. Operation Log - Complete History Tracking
What Git has:
- Per-ref reflog (
git reflog) - Limited to tracking HEAD and branch movements
- Cannot undo complex multi-ref operations atomically
What Jujutsu has:
jj op log # See all operations performed on repository
jj op show <id> # Show what an operation changed
jj undo # Undo last operation (any operation!)
jj op restore <id> # Restore repo to any previous state
Example:
$ jj log -r @
@ abc123 feat: add feature X
$ jj describe -m "feat: add feature Y" # Oops, wrong message
$ jj squash # Oops, didn't mean to squash
$ jj rebase -d main # Oops, wrong base
$ jj op log
@ def456 ... rebase
○ ghi789 ... squash
○ jkl012 ... describe
○ mno345 ... [original state]
$ jj undo # Undoes rebase
$ jj undo # Undoes squash
$ jj undo # Undoes describe - back to original!
2. Parallel History Editing
Git limitation: Can only edit one commit at a time in interactive rebase mode.
Jujutsu capability: Edit multiple commits in parallel:
# Reword multiple commits simultaneously
jj describe -r A -m "Fix typo in A"
jj describe -r B -m "Fix typo in B"
jj describe -r C -m "Fix typo in C"
# Split multiple commits (non-interactive: pre-stage the remainder, then split by paths)
jj describe -r X -m "<remainder>"
jj split -r X -- <paths> -m "<extracted>"
jj describe -r Y -m "<remainder>"
jj split -r Y -- <paths> -m "<extracted>"
# Note: `-i` and parameterless `jj split` open an interactive TUI and hang non-interactive shells.
# Diamond note: splitting while keeping the empty `[wip]` at `@` is a valid down-route, but never `jj describe @` and never `jj rebase -r @`.
# All operations execute immediately and independently
3. Absorb - Intelligent Change Distribution
# Make changes to multiple files
echo "fix" >> file1.txt
echo "improvement" >> file2.txt
echo "refactor" >> file3.txt
# Automatically move each change to the commit that last touched those lines
jj absorb
# jj analyzes blame information and distributes changes appropriately
Visual representation:
graph LR
A[Working Copy<br/>Changes to<br/>file1, file2, file3] -->|jj absorb| B[Changes<br/>distributed<br/>automatically]
B --> C[Commit X<br/>gets file1 changes]
B --> D[Commit Y<br/>gets file2 changes]
B --> E[Commit Z<br/>gets file3 changes]
4. Working Copy Commit
The working copy is always a commit:
jj log
@ work-in-progress # Working copy is always a commit
○ finished-feature
○ main
# No "git add" needed - all changes are tracked
# No "git commit --amend" needed - changes automatically amend @
# No "git stash" needed - just create new commit with jj new
5. Conflicted Commits
# After a rebase that would conflict in Git:
jj log
@ abc123 (conflict) attempted merge
○ def456 feature branch
○ ghi789 main
# Work continues normally - conflict is just marked
jj new # Create new commit on top of conflict
jj new other-commit # Or work on something else
jj edit abc123 # Come back to resolve conflict later
6. Flexible Targeting
# Move changes between ANY two commits (not just parent/child)
jj squash --from <commit-A> --into <commit-B>
# Insert commit anywhere in history
jj rebase -r X -A Y -B Z # Insert X after Y and before Z
# Duplicate commits to multiple locations
jj duplicate <commit> -d <dest1>
jj duplicate <commit> -d <dest2>
7. Revsets - Powerful Commit Selection
# Select multiple commits with expressions
jj rebase -s 'author("alice")' -d main # All commits by Alice
jj abandon 'empty()' # All empty commits
jj squash -r 'description(glob:"WIP:*")' # All WIP commits
jj log -r 'mine() & ~bookmarks()' # My commits not on bookmarks
8. Evolution Log
Track how a single change evolved:
jj evolog -r <commit> # See all versions of this commit
jj evolog -p # With diffs showing what changed each time
# Shows history of amendments, rebases, descriptions, etc.
Practical Examples
Example 1: Clean Up a Feature Branch
Scenario: You have 10 commits with several "WIP" commits, typo fixes, and changes that should be reordered.
Git approach:
git rebase -i main
# Manually edit todo list:
# - Reorder commits
# - Mark several commits as "fixup"
# - Mark one as "edit" to split
# Deal with conflicts at each step
git rebase --continue (multiple times)
Jujutsu approach:
# Reword WIP commits
jj describe -r <commit1> -m "proper message"
jj describe -r <commit2> -m "another proper message"
# Squash fixup commits
jj squash -r <fixup-commit1>
jj squash -r <fixup-commit2>
# Reorder if needed
jj rebase -r X -B Y
# Split a commit (non-interactive: pre-stage the remainder, then split by paths)
jj describe -r <large-commit> -m "<remainder>"
jj split -r <large-commit> -- <paths> -m "<extracted>"
# All done! Each command executes immediately
# Use jj undo at any point if you make a mistake
Example 2: Fix a Bug in an Earlier Commit
Git approach:
# Make fix
git add <files>
git commit --fixup=<commit-to-fix>
git rebase -i --autosquash <base>
# Interactive rebase executes
Jujutsu approach:
# Approach 1: Direct edit
jj edit <commit-to-fix>
# Make changes
# Changes automatically amend the commit
jj new @- # Return to where you were
# Approach 2: From working copy
# Make changes in working copy
jj squash --into <commit-to-fix>
# Approach 3: Interactive selection
jj squash -i --into <commit-to-fix>
Example 3: Rebase Complex Branch Structure
Scenario:
C - D - E (feature-v2)
/
B (feature-v1)
/
A (main)
Want to rebase feature-v2 directly onto A
Git approach:
git rebase --onto A B feature-v2
# OR
git rebase -i A
# Manually drop commits from feature-v1
Jujutsu approach:
# Approach 1: Direct rebase
jj rebase -s C -d A
# Approach 2: Using revset
jj rebase -s 'B..feature-v2' -d A
# Result:
# A - C' - D' - E' (feature-v2)
# \
# B (feature-v1)
Example 4: Recover from Mistakes
Scenario: You ran several operations and realized the third operation was wrong.
Git approach:
# Very difficult without reflog expertise
git reflog
git reset --hard HEAD@{5} # Hope you count correctly
# Lost all subsequent work
Jujutsu approach:
jj op log
# @ op4 ... latest operation
# ○ op3 ... the mistaken operation
# ○ op2 ...
# ○ op1 ...
jj undo # Undoes op4
jj undo # Undoes op3 (the mistake)
jj op restore op4 # Restores op4
# Now have: op1, op2, op4 (op3 was removed)
# Or restore to exact state:
jj op restore <op-before-mistake>
Example 5: Split Commit by File
Git approach:
git rebase -i <commit>^
# Mark commit as "edit"
git reset HEAD^
git add file1.txt
git commit -m "Part 1"
git add file2.txt
git commit -m "Part 2"
git rebase --continue
Jujutsu approach:
# By path specification (non-interactive: pre-stage the remainder, then split by paths):
jj describe -r <commit> -m "Part 2"
jj split -r <commit> -- file1.txt -m "Part 1"
# file1.txt goes to the first commit; file2.txt stays in the second commit
# Note: `-i` and parameterless `jj split` open an interactive TUI and hang non-interactive shells; always split by explicit paths with `-m`, pre-staging the remainder description.
Quick Reference Tables
Operation Categories
Message Editing
| Task | Jujutsu Command |
|---|---|
| Change commit message | jj describe -r <commit> -m "message" |
| Open editor for message | jj describe -r <commit> |
| Change author | jj describe -r <commit> --author "Name <email>" |
| Reset author to self | jj describe -r <commit> --reset-author |
Commit Combination
| Task | Jujutsu Command |
|---|---|
| Squash into parent | jj squash -r <commit> |
| Squash partial into parent | jj squash -i -r <commit> |
| Squash into specific ancestor | jj squash --from <commit> --into <ancestor> |
| Squash between any commits | jj squash --from <src> --into <dest> |
| Keep empty after squash | jj squash -r <commit> --keep-emptied |
| Auto-squash to right places | jj absorb |
Commit Division
| Task | Jujutsu Command |
|---|---|
| Split commit (non-interactive) | jj split -r <commit> -- <paths> -m "msg" (pre-stage remainder with jj describe -r <commit> -m; bare jj split / -i open a TUI and hang non-interactive shells) |
| Split by paths | jj split -r <commit> <paths> |
| Create new commit on top | jj new <commit> |
| Move working copy changes to new commit | jj commit |
| Move specific paths to new commit | jj commit <paths> |
History Rewriting
| Task | Jujutsu Command |
|---|---|
| Remove commit | jj abandon <commit> |
| Edit commit directly | jj edit <commit> |
| Edit commit without checkout | jj diffedit -r <commit> |
| Duplicate commit | jj duplicate <commit> |
| Duplicate to specific location | jj duplicate <commit> -d <dest> |
Inside a development join: keep @ as the empty [wip] |
Do not describe @ / rebase -r @ / bare squash -r @; route down with jj squash --from @ --insert-after <target> -m "..." --keep-emptied, jj absorb, or jj split (keep wip). See ~/.claude/skills/jj-version-control/diamond-workflow.md. |
Commit Reordering & Moving
| Task | Jujutsu Command |
|---|---|
| Move commit after target | jj rebase -r <commit> -A <target> |
| Move commit before target | jj rebase -r <commit> -B <target> |
| Move commit to new parent | jj rebase -r <commit> -d <parent> |
| Move commit tree | jj rebase -s <commit> -d <dest> |
| Move branch to new base | jj rebase -b <branch> -d <dest> |
Conflict Management
| Task | Jujutsu Command |
|---|---|
| See conflicted files | jj status or jj log -r 'conflict()' |
| Start resolving conflicts | jj new <conflicted-commit> |
| Resolve with merge tool | jj resolve |
| List conflicts | jj resolve --list |
| Move resolution into conflicted commit | jj squash |
Undo Operations
| Task | Jujutsu Command |
|---|---|
| Undo last operation | jj undo |
| See operation history | jj op log |
| Restore to specific operation | jj op restore <operation> |
| View changes in operation | jj op show <operation> |
| Create reverse commit (git revert) | jj revert -r <commit> -d <target> |
Common Patterns
"I want to edit commit X"
jj edit X # Checkout X, changes amend it
# ... make changes ...
# Changes automatically amend X
"I want to add these changes to commit X"
# Make changes in working copy
jj squash --into X
"I want to split commit X by paths"
# Non-interactive: pre-stage the remainder, then split by paths.
jj describe -r X -m "<remainder>"
jj split -r X -- <paths> -m "<extracted>"
# `-i` and parameterless `jj split` open an interactive TUI and hang non-interactive shells.
"I want to combine commits A, B, and C"
jj squash -r C # C → B
jj squash -r B # B → A
# Result: One commit at A with all changes
"I want to reorder A-B-C to A-C-B"
jj rebase -r C -B B
"I want to move commit X to a different branch"
jj rebase -r X -d <other-branch>
"I messed up, undo everything"
jj undo # Undo last operation
jj undo # Undo one more
jj op restore <operation-id> # Go back to specific state
Command Flag Reference
jj rebase flags
| Flag | Meaning | Example |
|---|---|---|
-r <revset> |
Rebase only specified commits | jj rebase -r X -d Y |
-s <revset> |
Rebase commit and descendants | jj rebase -s X -d Y |
-b <revset> |
Rebase branch (revs from base) | jj rebase -b X -d Y |
-d <revset> |
Destination (new parent) | jj rebase -r X -d Y |
-A <revset> |
Insert after (and rebase children) | jj rebase -r X -A Y |
-B <revset> |
Insert before (and rebase children) | jj rebase -r X -B Y |
jj squash flags
| Flag | Meaning | Example |
|---|---|---|
-r <revset> |
Squash this commit into parent | jj squash -r X |
--from <revset> |
Source of changes | jj squash --from X --into Y |
--into <revset> |
Destination of changes | jj squash --into Y |
-i |
Interactive (select hunks) | jj squash -i -r X |
-m <message> |
Set description | jj squash -r X -m "msg" |
--keep-emptied |
Keep empty source commit | jj squash -r X --keep-emptied |
<paths> |
Squash only these paths | jj squash file1.txt file2.txt |
jj new flags
| Flag | Meaning | Example |
|---|---|---|
<revset> |
Parent(s) of new commit | jj new X or jj new X Y (merge) |
-m <message> |
Set description | jj new X -m "Start feature" |
-A <revset> |
Insert after | jj new -A X |
-B <revset> |
Insert before | jj new -B X |
--no-edit |
Don't edit new commit | jj new X --no-edit |
Summary: Why Jujutsu Doesn't Need Interactive Rebase
Git's interactive rebase exists because:
- The working copy is not a commit
- You need a staging area to prepare commits
- History editing requires entering a special mode
- Operations are batched and executed sequentially
- Conflicts pause the entire process
Jujutsu eliminates these needs by:
- Making the working copy a commit (
@) - Eliminating the staging area
- Making every command operate directly on history
- Executing each operation immediately and atomically
- Committing conflicts and allowing later resolution
- Auto-rebasing descendants of changed commits
- Providing operation-level undo for any mistake
The result: Every operation you'd use interactive rebase for in Git is a direct, immediate command in jj. You never enter a special mode, never need to continue or abort, and can undo anything instantly.
This document serves as a complete reference for transitioning from Git's interactive rebase to Jujutsu's direct commit editing model.