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