Jujutsu uses "changes" instead of commits. This guide covers the complete change workflow.
Understanding Changes
Change vs Commit
| Aspect | Git Commit | Jujutsu Change |
|---|---|---|
| Identifier | SHA hash (mutable on rewrite) | Stable Change ID |
| Mutability | Immutable (amend = new commit) | Mutable (amend keeps ID) |
| Working Copy | Staged/unstaged files | Always a change |
| Branches | Named references | Optional (anonymous allowed) |
Change IDs
Every change has a unique, stable identifier:
bash
jj log
# @ mnxpqkpv main git_head()
# │ My change description
# ◉ mzvwuvkv
# Initial commit
# ^ stable IDThe ID mnxpqkpv stays the same even after:
- Rebasing
- Amending
- Reordering
Creating Changes
From Scratch
bash
# Create new change from main
jj new main -m "Add authentication"
# Edit files...
echo "auth code" > auth.js
# View result
jj logFrom Existing Work
bash
# Start where you are
jj new -m "WIP: feature"
# Or abandon and restart
jj abandon
jj new main -m "Fresh start"Modifying Changes
Amend
Update the current change:
bash
# Edit more files
echo "more code" > auth.js
# Amend (keeps same Change ID!)
jj describe -m "Add authentication with OAuth"
jj log
# Same Change ID, updated descriptionDescribe
Change only the message:
bash
jj describe -m "Better description"
# Or open editor
jj describeOrganizing Changes
Parallel Work
Create multiple independent changes:
bash
# Change A
jj new main -m "Add auth"
# ... work ...
# Change B (also from main)
jj new main -m "Fix bug"
# ... work ...
jj log
# Shows both changes diverging from mainStacking Changes
Build dependent changes:
bash
jj new main -m "Add database layer"
# ... work ...
jj new -m "Add API endpoints"
# Depends on database layer
# ... work ...
jj new -m "Add frontend"
# Depends on API endpoints
# ... work ...Moving Changes
Rebase to reorder:
bash
# Move current change onto different parent
jj rebase -d other-change
# Or use source/destination
jj rebase -s change-a -d change-bCombining Changes
Squash
Merge into parent:
bash
# Current change → parent
jj squash
# Specific change → specific target
jj squash --from feature-x --into mainSplit
Divide a change:
bash
# Interactively split current change
jj split
# Each selected hunk becomes new changeReviewing Changes
Diff
bash
# Current change
jj diff
# Specific change
jj diff -r abc123
# Against another change
jj diff -r abc123 -r def456
# Show names only
jj diff --statShow
bash
# Detailed change info
jj show abc123Publishing Changes
Push
bash
# Push current change
jj git push
# Push specific change
jj git push -c abc123
# Push to specific remote/branch
jj git push origin mainCreate Pull Request
Via web UI or API after pushing:
bash
# Push first
jj git push
# Then create PR via API
curl -X POST /api/v1/repos/org/repo/pulls \
-d '{"title": "Add auth", "head": "abc123", "base": "main"}'Cleaning Up
Abandon
Remove changes:
bash
# Abandon current change
jj abandon
# Abandon specific change
jj abandon abc123
# Abandon with descendants
jj abandon -s abc123Restore
Recover abandoned changes:
bash
# Via operation log
jj op log
jj op restore operation-idBest Practices
- One logical change per Change ID — Squash related work
- Use descriptive messages — Future you will thank you
- Rebase before push — Cleaner history
- Anonymous branches for experiments — No naming overhead
- Split large changes — Easier review
Common Patterns
Feature Development
bash
jj new main -m "Add user profile page"
# ... work ...
jj git push
# Create PRBug Fix
bash
jj new main -m "Fix login redirect"
# ... one-line fix ...
jj squash # Combine if needed
jj git pushCode Review Updates
bash
# Review feedback received
jj describe -m "Address review comments"
# ... make changes ...
jj git push
# Same PR, updatedNext Steps
- Conflicts & Resolution — Handle merge conflicts
- Operation Log — Undo and time travel