Skip to content

Jujutsu uses "changes" instead of commits. This guide covers the complete change workflow.

Understanding Changes ​

Change vs Commit ​

AspectGit CommitJujutsu Change
IdentifierSHA hash (mutable on rewrite)Stable Change ID
MutabilityImmutable (amend = new commit)Mutable (amend keeps ID)
Working CopyStaged/unstaged filesAlways a change
BranchesNamed referencesOptional (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 ID

The 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 log

From 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 description

Describe ​

Change only the message:

bash
jj describe -m "Better description"

# Or open editor
jj describe

Organizing 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 main

Stacking 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-b

Combining Changes ​

Squash ​

Merge into parent:

bash
# Current change → parent
jj squash

# Specific change → specific target
jj squash --from feature-x --into main

Split ​

Divide a change:

bash
# Interactively split current change
jj split

# Each selected hunk becomes new change

Reviewing 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 --stat

Show ​

bash
# Detailed change info
jj show abc123

Publishing 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 main

Create 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 abc123

Restore ​

Recover abandoned changes:

bash
# Via operation log
jj op log
jj op restore operation-id

Best Practices ​

  1. One logical change per Change ID — Squash related work
  2. Use descriptive messages — Future you will thank you
  3. Rebase before push — Cleaner history
  4. Anonymous branches for experiments — No naming overhead
  5. Split large changes — Easier review

Common Patterns ​

Feature Development ​

bash
jj new main -m "Add user profile page"
# ... work ...
jj git push
# Create PR

Bug Fix ​

bash
jj new main -m "Fix login redirect"
# ... one-line fix ...
jj squash  # Combine if needed
jj git push

Code Review Updates ​

bash
# Review feedback received
jj describe -m "Address review comments"
# ... make changes ...
jj git push
# Same PR, updated

Next Steps ​