Jujutsu (jj) Quick Reference

A concise cheat sheet for daily Jujutsu VCS operations.


🚀 Getting Started

Initial Setup

jj git init                    # Initialize a new repo (Git-backed)
jj git clone <url>             # Clone a Git repository

# Example: Clone and start working
jj git clone https://github.com/user/repo.git
cd repo
jj log                         # View commit history
jj new main                    # Start new work from main

First Commits

# Create your first change
jj new main                    # Start from main
jj describe -m "Add README"    # Set description
echo "# My Project" > README.md
# Changes automatically tracked!

jj status                      # See what changed
jj diff                        # See exact changes

# Create another commit
jj new                         # New commit on top of current
jj describe -m "Add config"
echo "{}" > config.json

# View your work
jj log                         # See commit history

Basic Status Checks

jj status                      # Show working copy status
jj log                         # View commit graph
jj show                        # Show current commit details
jj diff                        # Show uncommitted changes

# Example: Check your work
jj status                      # See modified files
jj diff src/app.ts             # See changes in specific file
jj log -r "@~::"               # See last few commits

📝 Daily Workflow

Creating Changes

jj new                         # Create new empty change on top of current
jj new <commit>                # Create new change on top of specific commit
jj new main                    # Start work from main bookmark
jj describe                    # Edit current change description
jj describe -m "message"       # Set description directly

# Example: Start new feature
jj new main
jj describe -m "EX-123: Add user authentication"
# Make your changes...

Working with Files

jj diff                        # Show uncommitted changes
jj diff -r <commit>            # Show changes in a specific commit
jj diff --from main            # Show changes since main
jj diff --from main --to @     # Same as above (explicit)

jj restore <file>              # Discard changes to file
jj restore .                   # Discard all changes
jj restore --from <commit> <file>  # Restore file from specific commit

# Examples:
jj restore package.json        # Undo changes to package.json
jj restore 'src/**/*.test.ts'  # Restore all test files
jj restore --from main src/    # Reset src/ to main's version

Viewing History

jj log                         # Show commit graph
jj log -r <commit>             # Show specific commit
jj log -r "main..@"            # Commits between main and current
jj log --limit 10              # Show last 10 commits
jj log -r "mine()"             # Show only your commits

jj show <commit>               # Show commit details and diff
jj show                        # Show current commit (same as jj show @)

# Examples:
jj log -r "description(bug)"   # Find commits with "bug" in description
jj log -r "@---::"             # Last 3 commits and current
jj log --summary               # Show file changes summary

Quick commit operations

# Amend current commit (just make changes and they're included)
vim src/file.ts
# Changes automatically tracked in current commit

# Create commit with specific files only
jj new
vim file1.ts file2.ts
jj squash file1.ts             # Only file1 goes to previous commit
# file2.ts stays in current commit

# Undo last change
jj undo                        # Undo most recent jj operation

🔀 Managing Commits

Splitting Changes

Split current commit into multiple commits:

# Method 1: Split using paths (recommended)
# You have changes to fileA.ts, fileB.ts, fileC.ts all in one commit
jj split fileA.ts fileB.ts     # First commit gets fileA + fileB
                                # Remaining fileC.ts stays in second commit

# Method 2: Interactive split (opens diff editor)
jj split                       # Interactively choose hunks for first commit

# Example: Split UI and data changes
jj split src/components/ src/ui/  # UI in first commit
                                   # Everything else in second commit

# Example: Create empty first commit, move files selectively
jj split --                    # Creates empty first commit
jj squash fileA.ts fileB.ts    # Move specific files to that commit

Copying Changes Between Commits

Copy specific files from one commit to another:

# Copy file from commit X to current working copy
jj restore --from <commit> <file>

# Example: Copy config.ts from commit abc123
jj restore --from abc123 src/config.ts

# Copy multiple files
jj restore --from abc123 src/config.ts src/utils.ts

# Copy entire directory
jj restore --from abc123 src/components/

Move changes from current commit to parent:

# Move specific files to parent commit
jj squash <file>

# Example: Move only config.ts to parent
jj squash config.ts
# Other files remain in current commit

# Move multiple files
jj squash config.ts utils.ts

# Move all matching pattern
jj squash 'src/**/*.test.ts'

Move changes from parent to current:

# Get specific files from parent
jj restore --from @- <file>

# Example: Pull database.ts from parent into current
jj restore --from @- src/database.ts

Copy changes from any commit to another (not current):

# Create new commit from destination
jj new <destination-commit>
# Copy files from source
jj restore --from <source-commit> <files>
jj describe -m "Copied changes from <source>"

Squashing Commits

Squash current commit into parent:

jj squash                      # Move ALL changes to parent, abandon current
jj squash -m "New message"     # Squash and update parent description

# Example workflow:
# Before: parent(EX-851) -> current(fix typo)
jj squash
# After: parent(EX-851 + typo fix) -> new empty commit

Squash specific commit into its parent:

jj squash -r <commit>          # Squash specific commit

# Example: Squash commit abc123 into its parent
jj squash -r abc123

Squash only specific files:

jj squash <file1> <file2>      # Only move these files to parent

# Example: Only move the README changes
jj squash README.md
# Other changes stay in current commit

Rebasing Commits

Rebase current commit to new parent:

jj rebase -d <destination>     # Move current commit to new parent

# Example: Move current commit onto main bookmark
jj rebase -d main

# Example: Move current commit onto specific commit
jj rebase -d abc123

Rebase a specific commit and its descendants:

jj rebase -s <source> -d <destination>

# Example: Move feature branch (xyz789) onto updated main
jj rebase -s xyz789 -d main
# xyz789 and all commits on top of it move to main

Rebase only specific commits (no descendants):

jj rebase -r <commit> -d <destination>

# Example: Move just commit abc123 (not its children)
jj rebase -r abc123 -d main

Rebase onto parent's parent (skip one commit):

# Useful when you want to remove the parent commit
jj rebase -d @--               # Rebase onto grandparent

Resolving Conflicts

Check for conflicts:

jj status                      # Shows if there are conflicts
jj log -r 'conflicts()'        # List all commits with conflicts
jj diff                        # View conflicted files with markers

Resolve conflicts manually:

# 1. View conflicted files
jj status

# 2. Edit files and remove conflict markers manually:
#    <<<<<<< Side #1 (Conflict 1 of 1)
#    your changes
#    |||||||
#    base version
#    =======
#    their changes
#    >>>>>>> Side #2 (Conflict 1 of 1 ends)

# 3. Changes are auto-tracked - just save the file

# 4. Verify resolution
jj diff                        # Check your changes
jj status                      # Should show no conflicts

Resolve by choosing one side entirely:

# Take "their" version (destination/target of rebase)
jj restore --from <destination-commit> <file>

# Take "our" version (source of rebase)  
jj restore --from <source-commit> <file>

# Example: During rebase onto main, take main's package.json
jj restore --from main package.json

# Example: Keep your version of config.ts
jj restore --from @- config.ts

Resolve conflicts in specific files:

# For package.json conflicts, often use theirs
jj restore --from main package.json yarn.lock

# For code files, manually merge then verify
vim src/conflicted-file.ts     # Edit and resolve
jj diff                        # Verify resolution

# For binary files, choose one version
jj restore --from main assets/logo.png

View conflict details:

jj show                        # Show current commit with conflict markers
jj show @                      # Same as above
jj diff -r @                   # See all changes including conflicts
jj diff --from <commit>        # Compare with specific commit

Abort and start over:

jj undo                        # Undo the operation that caused conflict
jj op log                      # See what you can undo

Continue after resolving:

# No special command needed!
# Once conflicts are resolved (jj status shows no conflicts)
# Continue with your normal workflow:
jj new                         # Create next commit
jj describe                    # Update description
# Or just make more changes to current commit

Undoing & Editing

jj undo                        # Undo last operation
jj undo --op <operation>       # Undo specific operation (see jj op log)
jj abandon <commit>            # Abandon a commit (remove from history)
jj edit <commit>               # Start editing a past commit

🏷️ Bookmarks & Branches

jj bookmark list               # List all bookmarks
jj bookmark create <name>      # Create bookmark at current commit
jj bookmark set <name> -r <commit>  # Move bookmark to specific commit
jj bookmark delete <name>      # Delete a bookmark
jj new <bookmark>              # Create new change on top of bookmark

🔄 Git Integration

Syncing with Git

jj git fetch                   # Fetch from Git remote
jj git push                    # Push to Git remote
jj git push --bookmark <name>  # Push specific bookmark
jj git push --change <commit>  # Push specific change (creates branch)

Working with Pull Requests

# Create PR branch from your changes
jj bookmark create feature-xyz
jj git push --bookmark feature-xyz

# Update PR after review feedback
# Make changes in your commits (split, squash, edit, etc.)
jj git push --bookmark feature-xyz --force

# Sync with updated main
jj git fetch
jj rebase -s <your-feature> -d main
jj git push --bookmark feature-xyz --force

Remote Branch Workflow

# Fetch colleague's branch
jj git fetch --branch their-feature

# Create local bookmark to track it
jj bookmark create their-feature -r <their-commit>

# Make changes on top
jj new their-feature
# Do your work
jj bookmark create my-additions

# Push your additions
jj git push --bookmark my-additions

Git Remotes

jj git remote list             # List Git remotes
jj git remote add <n> <url> # Add a Git remote

# Work with multiple remotes
jj git fetch --remote origin
jj git fetch --remote upstream
jj rebase -d origin/main       # Rebase onto origin's main

🎯 Common Patterns

Split mixed changes into clean commits

# Starting state: One commit with UI and API changes mixed
jj split 'src/components/**' 'src/ui/**'  # UI in first commit
                                           # API changes in second commit

# Or using multiple splits:
jj split src/api/                    # API changes in first commit
jj split src/components/             # UI in second commit
                                     # Everything else in third commit

Reorganize commits - move specific changes

# Move database.ts from commit C to commit A
# Before: A -> B -> C(has database.ts) -> @

jj new A                           # Go to commit A
jj restore --from C src/database.ts # Copy file from C
jj squash                          # Add it to A
jj new C                           # Go to commit C
jj restore --from @- src/database.ts  # Remove file from C

Work on feature while main moves forward

# Your feature branch is behind main
jj rebase -s <your-feature-commit> -d main

# If you have multiple commits in feature:
jj rebase -s <first-feature-commit> -d main
# All descendants automatically come along

# Example with bookmark:
jj rebase -s feature-branch -d main

Cherry-pick specific changes from another branch

# Copy specific files from commit abc123 onto current branch
jj restore --from abc123 src/feature.ts src/utils.ts
jj describe -m "Cherry-pick feature from abc123"

# Copy entire commit:
jj new                               # Create new commit
jj restore --from abc123 .           # Copy all changes
jj describe -m "Cherry-pick abc123"

Fix a mistake in a past commit

jj edit <commit-to-fix>              # Edit the past commit
# Make your changes to files
jj describe -m "Fix: corrected typo" # Update description
jj new <where-you-were>              # Jump back to where you were

# Example: Fix commit 3 commits back
jj edit @---                         # Edit grandparent's parent
vim src/bug.ts                       # Fix the bug
jj new @                             # Jump back to current

Amend/add to previous commit without editing it

# Add forgotten file to parent commit
jj restore --from @- forgotten.ts    # Get file to current commit
# Edit forgotten.ts
jj squash forgotten.ts               # Move it to parent

# Or move everything:
# Make all your fixes
jj squash                            # Move all changes to parent

Create clean commit history for PR

# You have: main -> WIP1 -> WIP2 -> fix -> WIP3 -> @

# 1. Squash all WIP commits
jj squash -r WIP1                    # Squash WIP1 into main
jj squash -r WIP2                    # Squash WIP2 into main
jj squash -r WIP3                    # Squash WIP3 into main

# 2. Now you have: main -> fix -> @
# Split if needed:
jj edit fix
jj split src/feature-a/              # Feature A
jj split src/feature-b/              # Feature B
                                     # Rest stays

Extract feature into separate branch

# Current: A -> B -> C -> D -> E (@)
# Want: Branch from B containing D's changes

jj new B                             # Create new commit from B
jj bookmark create feature-x         # Create bookmark
jj restore --from D .                # Copy changes from D
jj describe -m "Feature X"

# Now you have:
# A -> B -> C -> D -> E (@)
#       \-> feature-x

Revert a commit (create opposite changes)

# Revert commit abc123
jj new                               # Create new commit
jj restore --from abc123~ .          # Restore to before abc123
jj describe -m "Revert abc123"

# Or manually:
jj new                               # Create new commit
jj restore --from @- file.ts         # Get old version
jj describe -m "Revert changes to file.ts"

🔍 Useful Tips

Operation Log (Your Safety Net)

jj op log                      # View all operations you've performed
jj op log --limit 10           # Last 10 operations
jj undo                        # Undo last operation
jj undo --op <operation-id>    # Undo specific operation

# Example: You accidentally abandoned commits
jj op log                      # Find the operation
jj undo --op abc123            # Restore everything

Revset Examples

jj log -r "main..@"            # Commits between main and current
jj log -r "description(fix)"   # Commits with "fix" in description
jj log -r "@-"                 # Parent commit
jj log -r "all()"              # All commits
jj log -r "mine()"             # Your commits
jj log -r "conflicts()"        # Commits with conflicts

Working with Multiple Changes

# View your current stack
jj log -r "@::@---"            # Current + 3 parents

# Create parallel branches
jj new main                    # Branch A from main
jj new main                    # Branch B from main (separate)

# Work on specific change without losing current
jj new <other-commit>          # Jump to work on other commit
jj new <original-commit>       # Jump back when done

🔧 Troubleshooting

"Uncommitted changes would be lost"

# You're trying to switch commits but have changes
# Option 1: Create new commit with changes
jj new
jj describe -m "WIP: save my work"

# Option 2: Abandon changes
jj restore .

"The given commits are not in a linear sequence"

# You're trying to squash non-adjacent commits
# Use rebase to make them adjacent first
jj rebase -r <commit-to-squash> -d <target-parent>
jj squash -r <commit-to-squash>

"No repo found"

# Make sure you're in a jj repository
jj status
# If not initialized:
jj git init

Accidentally abandoned important commit

# View operation log
jj op log
# Undo the abandon operation
jj undo --op <operation-id>

Push rejected (branch diverged)

# Fetch latest changes
jj git fetch
# Rebase your changes on top
jj rebase -d <remote-bookmark>
# Push again
jj git push

💡 Real-World Scenarios

"I made changes to the wrong commit"

# You edited files but they should be in the parent commit
jj squash                      # Move all changes to parent

# Or move specific files only
jj squash src/config.ts        # Move just this file to parent

"I want to combine these three commits"

# Combine commits A -> B -> C into A
jj squash -r B                 # B merges into A
jj squash -r C                 # C merges into A (now parent)

"I need to split this commit into two separate commits"

# Split by files/directories
jj split src/api/ src/models/  # API/models in first, rest in second

# Split by creating empty commit first
jj split --                    # Empty first commit
jj squash src/api/             # Move API to first
# Rest stays in second commit

"I want my feature on the latest main"

jj git fetch                   # Get latest main
jj rebase -s my-feature -d main  # Rebase feature onto main
# Or if you're on the feature:
jj rebase -d main              # Rebase current commit to main

"I need to update my PR with review changes"

# Make changes to address review
vim src/file.ts
jj describe -m "Address review feedback"

# Or amend existing commit:
vim src/file.ts
jj squash                      # Add to previous commit

# Push updated branch
jj git push --bookmark feature-branch --force

"I committed debugging code by mistake"

# Remove file from current commit
jj restore src/debug.ts
rm src/debug.ts

# Remove file from previous commit
jj edit @-                     # Edit parent
jj restore src/debug.ts        # Remove it
rm src/debug.ts
jj new @                       # Go back to where you were

"I need to extract one feature into a separate PR"

# Current: main -> A -> B(feature) -> C(other) -> @
jj new A                       # Branch from A
jj bookmark create feature-branch
jj restore --from B .          # Copy feature changes
jj describe -m "Feature X"
jj git push --bookmark feature-branch

"I accidentally deleted important commits"

jj op log                      # Find the operation
jj undo --op <operation-id>    # Restore everything
# Or just:
jj undo                        # Undo last operation

"I want to try something without affecting my current work"

# Save current work
jj new                         # New commit (current work saved in @-)
jj describe -m "WIP: experiment"

# Try things...

# Discard experiment:
jj abandon @                   # Remove experiment commit
jj new @-                      # Go back to before experiment

# Keep experiment:
jj describe -m "Successful experiment"

"Merge conflict while rebasing"

# Conflict appears during rebase
jj status                      # See conflicted files

# Option 1: Fix manually
vim src/conflicted.ts          # Edit and resolve
jj status                      # Verify resolved

# Option 2: Take one side
jj restore --from main src/conflicted.ts  # Take theirs
# or
jj restore --from @- src/conflicted.ts    # Keep yours

# Continue working (no special command needed)

⚙️ Configuration

# Set your identity
jj config set --user user.name "Your Name"
jj config set --user user.email "you@example.com"

# Set editor
jj config set --user ui.editor "code --wait"

📚 Quick Command Reference

Command Description
jj new Create new change
jj new <commit> Create change from specific commit
jj describe Edit description
jj diff Show changes
jj log View history
jj split Split change interactively
jj squash Move changes to parent
jj squash <file> Move specific files to parent
jj squash -r <commit> Squash specific commit
jj rebase -d <dest> Rebase current to destination
jj rebase -s <src> -d <dest> Rebase source + descendants
jj restore --from <commit> <file> Copy file from commit
jj undo Undo last operation
jj edit <commit> Edit a past commit
jj abandon <commit> Remove commit from history
jj bookmark Manage bookmarks
jj git fetch/push Sync with Git
jj status Show status (including conflicts)

Special Symbols

Symbol Meaning
@ Current working copy commit
@- Parent of working copy
@-- Grandparent of working copy
<commit>~ Parent of specified commit
<commit>~2 Grandparent of specified commit

Learn more: jj help or jj help <command>