// Jujutsu (jj) version control system - a Git-compatible VCS with novel features. Use when working with jj repositories, managing stacked/dependent commits, needing automatic rebasing with first-class conflict handling, using revsets to select commits, or wanting enhanced Git workflows. Triggers on mentions of 'jj', 'jujutsu', change IDs, operation log, or jj-specific commands.
| name | jj |
| description | Jujutsu (jj) version control system - a Git-compatible VCS with novel features. Use when working with jj repositories, managing stacked/dependent commits, needing automatic rebasing with first-class conflict handling, using revsets to select commits, or wanting enhanced Git workflows. Triggers on mentions of 'jj', 'jujutsu', change IDs, operation log, or jj-specific commands. |
Jujutsu is a powerful Git-compatible version control system that combines ideas from Git, Mercurial, Darcs, and adds novel features. It uses Git repositories as a storage backend, making it fully interoperable with existing Git tooling.
Key differentiators from Git:
In jj, the working copy is always a commit. Changes are automatically snapshotted:
# No need for 'git add' - changes are tracked automatically
jj status # Shows working copy state
jj diff # Shows changes in working copy commit
kntqzsqt)5d39e19d)Always prefer change IDs when referring to commits in commands.
Instead of staging, use these patterns:
jj split - Split working copy into multiple commitsjj squash -i - Interactively move changes to parentjj diffeditConflicts are recorded in commits, not blocking operations:
jj rebase -s X -d Y # Succeeds even with conflicts
jj log # Shows conflicted commits with ×
jj new <conflicted> # Work on top of conflict
# Edit files to resolve, then:
jj squash # Move resolution into parent
Every operation is recorded and can be undone:
jj op log # View operation history
jj undo # Undo last operation
jj op restore <op-id> # Restore to specific operation
| Command | Description | Git Equivalent |
|---|---|---|
jj git clone <url> | Clone a Git repository | git clone |
jj git init | Initialize new repo | git init |
jj status / jj st | Show working copy status | git status |
jj log | Show commit history | git log --graph |
jj diff | Show changes | git diff |
jj new | Create new empty commit | - |
jj describe / jj desc | Edit commit message | git commit --amend (msg only) |
jj edit <rev> | Edit existing commit | git checkout + amend |
jj squash | Move changes to parent | git commit --amend |
jj split | Split commit in two | git add -p + multiple commits |
jj rebase | Move commits | git rebase |
jj bookmark / jj b | Manage bookmarks | git branch |
jj git fetch | Fetch from remote | git fetch |
jj git push | Push to remote | git push |
jj undo | Undo last operation | git reflog + reset |
jj file annotate | Show line origins | git blame |
# Working copy changes are auto-committed
# When ready to start fresh work:
jj new # Create new commit on top
jj describe -m "message" # Set description
# Or combine:
jj new -m "Start feature X"
# Option 1: Edit in place
jj edit <change-id> # Make working copy edit that commit
# Make changes, they're auto-committed
jj new # Return to working on new changes
# Option 2: Squash changes into parent
jj squash # Move all changes to parent
jj squash -i # Interactively select changes
jj squash <file> # Move specific file
# Rebase current branch onto main
jj rebase -d main
# Rebase specific revision and descendants
jj rebase -s <rev> -d <destination>
# Rebase only specific revisions (not descendants)
jj rebase -r <rev> -d <destination>
# Insert commit between others
jj rebase -r X -A Y # Insert X after Y
jj rebase -r X -B Y # Insert X before Y
jj bookmark list # List bookmarks
jj bookmark create <name> # Create at current commit
jj bookmark set <name> # Move bookmark to current commit
jj bookmark delete <name> # Delete bookmark
jj bookmark track <name>@<remote> # Track remote bookmark
# Push specific bookmark
jj git push --bookmark <name>
# Push change by creating auto-named bookmark
jj git push --change <change-id>
# Push all bookmarks
jj git push --all
# After a rebase creates conflicts:
jj log # Find conflicted commit (marked with ×)
jj new <conflicted> # Create commit on top
# Edit files to resolve conflicts
jj squash # Move resolution into conflicted commit
# Or use external merge tool:
jj resolve # Opens merge tool for each conflict
jj undo # Undo last operation
jj op log # View operation history
jj op restore <op-id> # Restore to specific state
# View repo at past operation
jj --at-op=<op-id> log
Revsets select commits using a functional language:
| Expression | Description |
|---|---|
@ | Working copy commit |
@- | Parent of working copy |
x- | Parents of x |
x+ | Children of x |
::x | Ancestors of x (inclusive) |
x:: | Descendants of x (inclusive) |
x..y | Ancestors of y not in ancestors of x |
x::y | Commits between x and y (DAG path) |
bookmarks() | All bookmark targets |
trunk() | Main branch (main/master) |
mine() | Commits by current user |
conflicts() | Commits with conflicts |
description(text) | Commits with matching description |
Examples:
jj log -r '@::' # Working copy and descendants
jj log -r 'trunk()..@' # Commits between trunk and working copy
jj log -r 'mine() & ::@' # My commits in working copy ancestry
jj rebase -s 'roots(trunk()..@)' -d trunk() # Rebase branch onto trunk
By default, jj git clone and jj git init create colocated repos where both jj and git commands work:
jj git clone <url> # Creates colocated repo (default)
jj git clone --no-colocate <url> # Non-colocated (jj only)
In colocated repos, Git changes are auto-imported. For non-colocated:
jj git import # Import changes from Git
jj git export # Export changes to Git
cd existing-git-repo
jj git init --colocate # Add jj to existing Git repo
Edit config with jj config edit --user:
[user]
name = "Your Name"
email = "your@email.com"
[ui]
default-command = "log" # Run 'jj log' when no command given
diff-editor = ":builtin" # Or "meld", "kdiff3", etc.
[revset-aliases]
'wip' = 'description(exact:"") & mine()' # Custom revset alias
For comprehensive documentation, see:
"Working copy is dirty" - Never happens in jj! Working copy is always a commit.
Conflicts after rebase - Normal in jj. Conflicts are recorded, resolve when convenient.
Lost commits - Use jj op log to find when commits existed, then jj op restore.
Divergent changes - Same change ID, different commits. Usually from concurrent edits:
jj log # Shows divergent commits
jj abandon <unwanted> # Remove one version
Immutable commit error - Can't modify trunk/tagged commits by default:
jj --ignore-immutable <command> # Override protection