Jujutsu (jj) Version Control System
This skill helps you work with Jujutsu, a Git-compatible VCS with mutable commits and automatic rebasing.
Tested with jj v0.37.0 - Commands may differ in other versions.
Important: Automated/Agent Environment
When running as an agent:
- Always use flags to provide messages inline rather than relying on editor prompts:
bash
# Always use -m to avoid editor prompts
jj desc -m "message" # NOT: jj desc
jj squash -m "message" # NOT: jj squash (which opens editor)
Editor-based commands will fail in non-interactive environments.
- Verify operations with after mutations (, , , ) to confirm the operation succeeded.
Core Concepts
The Working Copy is a Commit
In jj, your working directory is always a commit (referenced as
). Changes are automatically snapshotted when you run any jj command. There is no staging area.
There is no need to run
.
Commits Are Mutable
CRITICAL: Unlike git, jj commits can be freely modified. This enables a high-quality commit workflow:
- Use to initialize a new, blank commit.
- Describe your intended changes first with
- Make your changes
- When complete, use to initialize a new, blank commit and begin the process again.
You may refine the commit using
or
as needed
Change IDs vs Commit IDs
- Change ID: A stable identifier (like ) that persists when a commit is rewritten
- Commit ID: A content hash (like ) that changes when commit content changes
Prefer using Change IDs when referencing commits in commands.
Essential Workflow
Starting Work: Describe First, Then Code
Always create your commit message before writing code:
bash
# First, describe what you intend to do
jj desc -m "Add user authentication to login endpoint"
# Then make your changes - they automatically become part of this commit
# ... edit files ...
# Check status
jj st
Creating Atomic Commits
Each commit should represent ONE logical change. Use this format for commit messages:
Examples:
- "Add validation to user input forms"
- "Fix null pointer in payment processor"
- "Remove deprecated API endpoints"
- "Update dependencies to latest versions"
Viewing History
bash
# View recent commits
jj log
# View with patches
jj log -p
# View specific commit
jj show <change-id>
# View diff of working copy
jj diff
Moving Between Commits
bash
# Create a new empty commit on top of current
jj new
# Create new commit with message
jj new && jj desc -m "Commit message"
# Edit an existing commit (working copy becomes that commit)
jj edit <change-id>
# Edit the previous commit
jj prev -e
# Edit the next commit
jj next -e
Refining Commits
Squashing Changes
Move changes from current commit into its parent:
bash
# Squash all changes into parent
jj squash
Note:
opens an interactive UI and will hang in agent environments. Avoid it.
Splitting Commits
Warning:
is interactive and will hang in agent environments. To divide a commit, use
to move changes out, then create separate commits manually.
Absorbing Changes
Automatically distribute changes to the commits that last modified those lines:
bash
# Absorb working copy changes into appropriate ancestor commits
jj absorb
Abandoning Commits
Remove a commit entirely (descendants are rebased to its parent):
Undoing Operations
Reverse the last jj operation:
This reverts the repository to its state before the previous command. Useful for recovering from mistakes like accidental
,
, or
.
Restoring Files
Discard changes to specific files or restore files from another revision:
bash
# Discard all uncommitted changes in working copy (restore from parent)
jj restore
# Discard changes to specific files
jj restore path/to/file.txt
# Restore files from a specific revision
jj restore --from <change-id> path/to/file.txt
Working with Bookmarks (Branches)
Bookmarks are jj's equivalent to git branches:
bash
# Create a bookmark at current commit
jj bookmark create my-feature -r@
# Move bookmark to a different commit
jj bookmark move my-feature --to <change-id>
# List bookmarks
jj bookmark list
# Delete a bookmark
jj bookmark delete my-feature
Git Integration
Working with Existing Git Repos
bash
# Clone a git repository
jj git clone <url>
# Initialize jj in an existing git repo
jj git init --colocate
Switching Between jj and git (Colocated Repos)
In a colocated repository (where both
and
exist), you can use both jj and git commands. However, there are important considerations:
Switching to git mode (e.g., for merge workflows):
bash
# First, ensure your jj working copy is clean
jj st
# Then checkout a branch with git
git checkout <branch-name>
Switching back to jj mode:
bash
# Use jj edit to resume working with jj
jj edit <change-id>
Important notes:
- Git may complain about uncommitted changes if jj's working copy differs from the git HEAD
- ALWAYS ensure your work is committed in jj before switching to git
- After git operations, jj will detect and incorporate the changes on next command
Pushing Changes
When the user asks you to push changes:
bash
# Push a specific bookmark to the remote
jj git push -b <bookmark-name>
# Example: push the main bookmark
jj git push -b main
Before pushing, ensure:
- Your bookmark points to the correct commit (bookmarks don't auto-advance like git branches)
- The commits are refined and atomic
- The user has explicitly requested the push
IMPORTANT: Unlike git branches, jj bookmarks do not automatically move when you create new commits. You must manually update them before pushing:
bash
# Move an existing bookmark to the current commit
jj bookmark move my-feature --to @
# Then push it
jj git push -b my-feature
If no bookmark exists for your changes, create one first:
bash
# Create a bookmark at the current commit
jj bookmark create my-feature
# Then push it
jj git push -b my-feature
Handling Conflicts
jj allows committing conflicts — you can resolve them later:
Agent conflict resolution: Do not use
(interactive). Instead, edit the conflicted files directly to remove conflict markers, then run
to verify resolution.
Preserving Commit Quality
IMPORTANT: Because commits are mutable, always refine them:
- Review your commit: or
- Is it atomic? One logical change per commit
- Is the message clear? Use imperative verb phrase in sentence case format with no full stop: "Verb object"
- Are there unrelated changes? Use to move changes out, then create separate commits
- Should changes be elsewhere? Use or
Quick Reference
| Action | Command |
|---|
| Describe commit | |
| View status | |
| View log | |
| View diff | |
| New commit | jj new && jj desc -m "message"
|
| Edit commit | |
| Squash to parent | |
| Auto-distribute | |
| Abandon commit | |
| Undo last operation | |
| Restore files | |
| Create bookmark | jj bookmark create <name>
|
| Push bookmark | |
Best Practices Summary
- Describe first: Set the commit message before coding
- One change per commit: Keep commits atomic and focused
- Use change IDs: They're stable across rewrites
- Refine commits: Leverage mutability for clean history
- Embrace the workflow: No staging area, no stashing - just commits