Jujutsu (jj) Version Control Guide
Jujutsu is a modern, Git-compatible version control system. This project uses jj colocated with git.
Key Differences from Git
| Concept | Git | Jujutsu |
|---|---|---|
| Staging area | Explicit git add | None - working copy IS a commit |
| Branches | Named refs | Bookmarks (auto-follow rewrites) |
| Stash | Separate stash stack | Not needed - just use commits |
| Amend | git commit --amend | Just edit files, or use jj squash |
| Identity | Commit ID only | Change ID (stable) + Commit ID |
Essential Commands
| Task | Command |
|---|---|
| Status | jj status or jj st |
| Diff | jj diff |
| Log | jj log |
| Commit & continue | jj commit -m "message" |
| Update message | jj describe -m "message" |
| New empty commit | jj new |
| Squash into parent | jj squash |
| Undo last operation | jj undo |
| Fetch from remote | jj git fetch |
| Push to remote | jj git push |
| Create & push bookmark | jj git push --named name=@ |
| Push existing bookmark | jj git push --bookmark name |
Working Copy Model
The working copy (@) is always a commit. File changes are automatically tracked - no staging required.
parent commit
↓
@ (working copy) ← your edits go here automatically
Quick Git-to-Jujutsu Translation
| Git | Jujutsu |
|---|---|
git status | jj st |
git diff | jj diff |
git log | jj log |
git add . && git commit -m "msg" | jj commit -m "msg" |
git push | jj git push |
git pull | jj git fetch then jj rebase -d main@origin |
git checkout -b branch | jj new main then jj bookmark set branch |
git branch | jj bookmark list |
git stash | jj new (just start new commit) |
git blame | jj file annotate |
Jujutsu Workflows
Common development workflows and patterns for jj.
Understanding the Working Copy Model
In jj, there's no staging area. The working copy IS a commit that updates automatically:
main │ ├── feature-commit (parent) │ │ │ └── @ (working copy - your current edits)
When you edit files, they're immediately part of the working copy commit. This is fundamentally different from git.
Basic Development Workflow
Starting New Work
# Make sure you're up to date jj git fetch jj rebase -d main@origin # Start a new feature from main jj new main -m "Add new feature" # Edit files... changes are tracked automatically # When done, describe your work jj describe -m "Implement feature X" # Create new commit for next task jj new
The Staging Commit Pattern (Recommended)
Work with two commits: a "staging" commit for completed work and the working copy for in-progress changes.
# Start with a staging commit jj new main -m "Feature: user authentication" # Create working copy on top jj new # Make changes... edit files # When a piece is ready, squash into staging jj squash # Continue working on next piece # (working copy is now empty again) # Repeat: edit, squash, edit, squash... # When feature is complete, the staging commit has all changes # Push it jj git push --bookmark feature-branch
Quick Single Commit
For simple changes that don't need the staging pattern:
jj new main # Make your changes jj commit -m "Fix typo in README" # This describes the commit AND creates a new empty one
Modifying History
Editing a Previous Commit
# Find the commit you want to edit jj log # Switch to editing it (use change ID, not commit ID) jj edit <change-id> # Make your changes... files update automatically # Go back to where you were jj new <where-you-were> # Or just: jj new
All descendant commits are automatically rebased.
Squashing Commits Together
# Squash current commit into parent jj squash # Squash specific commit into its parent jj squash -r <change-id> # Squash current into a specific ancestor jj squash --into <change-id> # Interactive squash (select what to move) jj squash -i
Splitting a Commit
# Split current commit jj split # Opens editor to select which changes go in first commit # Remaining changes stay in second commit
Reordering Commits
# Move current commit to be based on different parent jj rebase -d <new-parent> # Move a specific commit jj rebase -r <change-id> -d <new-parent> # Move commit and all its descendants jj rebase -s <change-id> -d <new-parent>
Working with Bookmarks (Branches)
Creating a Feature Branch
# Start new work jj new main # Create bookmark pointing here jj bookmark set feature/my-feature # Work on your feature... jj commit -m "Part 1" jj commit -m "Part 2" # Move bookmark to current commit jj bookmark move feature/my-feature --to @-
Pushing to Remote
# Create bookmark and push in one command (recommended for new bookmarks) jj git push --named feature/my-feature=@ # Subsequent pushes jj git push --bookmark feature/my-feature # Or push all tracked bookmarks jj git push
Creating Pull Requests
Full PR Workflow (Recommended)
# 1. Ensure your changes are committed jj log # Review your commits # 2. Create bookmark and push in one command jj git push --named feature/my-feature=@ # 3. Create the PR using GitHub CLI gh pr create --title "My feature" --body "Description" --base main --head feature/my-feature
Alternative: Create Bookmark First
# Create bookmark separately jj bookmark create -r @ feature/my-feature # Track and push (required for new bookmarks) jj bookmark track feature/my-feature@origin jj git push --bookmark feature/my-feature
Key Points
- •--named flag: Creates bookmark, tracks it, and pushes in one step (recommended)
- •Multiple commits: The bookmark points to the tip; all ancestor commits up to main are included
- •Subsequent pushes: Just use
jj git push --bookmark name
Updating a PR
# Make changes to your commits (edit, squash, etc.) jj edit <change-id> # ... make changes ... # Push the updated bookmark jj git push --bookmark feature/my-feature
Updating from Main
# Fetch latest jj git fetch # Rebase your work onto updated main jj rebase -d main@origin
Handling Conflicts
Conflicts in jj are first-class citizens - they're stored in commits and don't block operations.
When Conflicts Occur
# After a rebase that causes conflicts, jj shows them jj status # Shows: Conflicted files: src/main.rs # Resolve interactively jj resolve # Or resolve specific file jj resolve src/main.rs # Accept one side entirely jj resolve --tool=:ours jj resolve --tool=:theirs
Viewing Conflict Markers
Conflict markers in files look like:
<<<<<<< Conflict 1 of 1 %%%%%%% Changes from base to side #1 -old line +modified in first branch +++++++ Contents of side #2 modified in second branch >>>>>>>
Undo and Recovery
Undoing Operations
# Undo the last operation jj undo # See operation history jj op log # Restore to a specific point jj op restore <operation-id>
Recovering Abandoned Commits
# Even abandoned commits are recoverable via op log jj op log # Find the operation before the abandon jj op restore <operation-id>
Stacked Changes / Multiple PRs
Creating a Stack
# First change jj new main -m "Refactor: extract helper function" jj bookmark set refactor-helper # Make changes... jj new # Second change (builds on first) jj describe -m "Feature: use helper for new feature" jj bookmark set feature-new # Make changes... jj new # Third change jj describe -m "Docs: document new feature" jj bookmark set docs-feature
Pushing Stacked PRs
# Push each bookmark separately jj git push --bookmark refactor-helper --allow-new jj git push --bookmark feature-new --allow-new jj git push --bookmark docs-feature --allow-new
Updating a Stack After Review
# Edit the first commit jj edit <refactor-helper-change-id> # Make changes... # All descendants automatically rebase! # Push updates jj git push --bookmark refactor-helper jj git push --bookmark feature-new jj git push --bookmark docs-feature
Colocated Repository Tips
When using jj colocated with git (.jj and .git both exist):
# Git operations still work git status # Shows same state git log # Shows commits # But prefer jj commands for changes jj commit # Instead of git commit # Sync if git and jj get out of sync jj git import jj git export
Common Patterns Summary
| Goal | Commands |
|---|---|
| Start feature | jj new main -m "Feature" |
| Save progress | jj commit -m "WIP" or jj squash |
| Fix old commit | jj edit <id>, fix, jj new |
| Update from main | jj git fetch && jj rebase -d main@origin |
| Push feature | jj bookmark set X && jj git push --bookmark X |
| Undo mistake | jj undo |
Additional References
- •commands-reference.md - Complete command reference
- •revsets.md - Revision selection syntax